diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44c3af2..59994e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,4 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
-[Unreleased]: https://github.com/AM-WD/LinkMobility/
+
+### Added
+
+- `(I)LinkMobilityClient` with client options to communicate with the REST API
+- Methods to send SMS messages
+ - `SendTextMessage()` sending a normal text message
+ - `SendBinaryMessage()` sending a binary message as _base64_ encoded message segments
+- `IncomingMessageNotification` to support receiving incoming messages or delivery reports via WebHooks
+- UnitTesting
+
+
+
+[Unreleased]: https://github.com/AM-WD/LinkMobility
diff --git a/README.md b/README.md
index ada0d36..777d9e4 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,20 @@
-# LinkMobility API
+# LINK Mobility REST API for .NET
-This project aims to implement the [LinkMobility API].
+This project aims to implement the [LINK Mobility REST API].
+
+## Overview
+
+Looking for a .NET library to interact with LINK Mobility ended with an [outdated repository] using .NET Framework 3.5.
+
+So I decided to implement the current available API myself with a more modern (and flexible) [.NET Standard 2.0] as target.
---
Published under [MIT License] (see [**tl;dr**Legal])
-[LinkMobility API]: https://developer.linkmobility.eu/
+
+[LINK Mobility REST API]: https://developer.linkmobility.eu/
+[outdated repository]: https://github.com/websms-com/websmscom-csharp
+[.NET Standard 2.0]: https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0
[MIT License]: LICENSE.txt
[**tl;dr**Legal]: https://www.tldrlegal.com/license/mit-license
diff --git a/src/LinkMobility/Enums/SenderAddressType.cs b/src/LinkMobility/Enums/AddressType.cs
similarity index 91%
rename from src/LinkMobility/Enums/SenderAddressType.cs
rename to src/LinkMobility/Enums/AddressType.cs
index 0b65865..465eda5 100644
--- a/src/LinkMobility/Enums/SenderAddressType.cs
+++ b/src/LinkMobility/Enums/AddressType.cs
@@ -7,7 +7,7 @@ namespace AMWD.Net.Api.LinkMobility
/// Specifies the type of sender address.
///
[JsonConverter(typeof(StringEnumConverter))]
- public enum SenderAddressType
+ public enum AddressType
{
///
/// National number.
diff --git a/src/LinkMobility/Enums/DeliveryStatus.cs b/src/LinkMobility/Enums/DeliveryStatus.cs
new file mode 100644
index 0000000..36c4bbf
--- /dev/null
+++ b/src/LinkMobility/Enums/DeliveryStatus.cs
@@ -0,0 +1,48 @@
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Defines the delivery status of a message on a report.
+ ///
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum DeliveryStatus
+ {
+ ///
+ /// Message has been delivered to the recipient.
+ ///
+ [EnumMember(Value = "delivered")]
+ Delivered = 1,
+
+ ///
+ /// Message not delivered and will be re-tried.
+ ///
+ [EnumMember(Value = "undelivered")]
+ Undelivered = 2,
+
+ ///
+ /// Message has expired and will no longer re-tried.
+ ///
+ [EnumMember(Value = "expired")]
+ Expired = 3,
+
+ ///
+ /// Message has been deleted.
+ ///
+ [EnumMember(Value = "deleted")]
+ Deleted = 4,
+
+ ///
+ /// Message has been accepted by the carrier.
+ ///
+ [EnumMember(Value = "accepted")]
+ Accepted = 5,
+
+ ///
+ /// Message has been rejected by the carrier.
+ ///
+ [EnumMember(Value = "rejected")]
+ Rejected = 6
+ }
+}
diff --git a/src/LinkMobility/Enums/DeliveryType.cs b/src/LinkMobility/Enums/DeliveryType.cs
new file mode 100644
index 0000000..84a4b10
--- /dev/null
+++ b/src/LinkMobility/Enums/DeliveryType.cs
@@ -0,0 +1,36 @@
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Defines the types of delivery methods on a report.
+ ///
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum DeliveryType
+ {
+ ///
+ /// Message sent via SMS.
+ ///
+ [EnumMember(Value = "sms")]
+ Sms = 1,
+
+ ///
+ /// Message sent as Push message.
+ ///
+ [EnumMember(Value = "push")]
+ Push = 2,
+
+ ///
+ /// Message sent as failover SMS.
+ ///
+ [EnumMember(Value = "failover-sms")]
+ FailoverSms = 3,
+
+ ///
+ /// Message sent as voice message.
+ ///
+ [EnumMember(Value = "voice")]
+ Voice = 4
+ }
+}
diff --git a/src/LinkMobility/Enums/MessageType.cs b/src/LinkMobility/Enums/MessageType.cs
index 64af266..474c002 100644
--- a/src/LinkMobility/Enums/MessageType.cs
+++ b/src/LinkMobility/Enums/MessageType.cs
@@ -1,18 +1,24 @@
-namespace AMWD.Net.Api.LinkMobility
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.LinkMobility
{
///
/// Specifies the message type.
///
+ [JsonConverter(typeof(StringEnumConverter))]
public enum MessageType
{
///
/// The message is sent as defined in the account settings.
///
+ [EnumMember(Value = "default")]
Default = 1,
///
/// The message is sent as voice call.
///
+ [EnumMember(Value = "voice")]
Voice = 2,
}
}
diff --git a/src/LinkMobility/Enums/StatusCodes.cs b/src/LinkMobility/Enums/StatusCodes.cs
index 9853904..c912d70 100644
--- a/src/LinkMobility/Enums/StatusCodes.cs
+++ b/src/LinkMobility/Enums/StatusCodes.cs
@@ -3,7 +3,7 @@
///
/// Custom status codes as defined by Link Mobility.
///
- public enum StatusCodes
+ public enum StatusCodes : int
{
///
/// Request accepted, Message(s) sent.
diff --git a/src/LinkMobility/ILinkMobilityClient.cs b/src/LinkMobility/ILinkMobilityClient.cs
index 304848f..1d80245 100644
--- a/src/LinkMobility/ILinkMobilityClient.cs
+++ b/src/LinkMobility/ILinkMobilityClient.cs
@@ -1,6 +1,5 @@
using System.Threading;
using System.Threading.Tasks;
-using AMWD.Net.Api.LinkMobility.Requests;
namespace AMWD.Net.Api.LinkMobility
{
@@ -14,6 +13,13 @@ namespace AMWD.Net.Api.LinkMobility
///
/// The request data.
/// A cancellation token to propagate notification that operations should be canceled.
- Task SendTextMessage(SendTextMessageRequest request, CancellationToken cancellationToken = default);
+ Task SendTextMessage(SendTextMessageRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// Sends a binary message to a list of recipients.
+ ///
+ /// The request data.
+ /// A cancellation token to propagate notification that operations should be canceled.
+ Task SendBinaryMessage(SendBinaryMessageRequest request, CancellationToken cancellationToken = default);
}
}
diff --git a/src/LinkMobility/LinkMobility.csproj b/src/LinkMobility/LinkMobility.csproj
index f839917..7ae8d6c 100644
--- a/src/LinkMobility/LinkMobility.csproj
+++ b/src/LinkMobility/LinkMobility.csproj
@@ -5,7 +5,7 @@
enable
AMWD.Net.Api.LinkMobility
- link mobility api
+ link mobility api messaging sms
amwd-linkmobility
AMWD.Net.Api.LinkMobility
@@ -18,8 +18,8 @@
package-icon.png
README.md
- LinkMobility API
- Implementation of the Link Mobility REST API
+ LINK Mobility REST API
+ Implementation of the LINK Mobility REST API using .NET
true
diff --git a/src/LinkMobility/LinkMobilityClient.Messaging.cs b/src/LinkMobility/LinkMobilityClient.Messaging.cs
deleted file mode 100644
index 6586ed2..0000000
--- a/src/LinkMobility/LinkMobilityClient.Messaging.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using AMWD.Net.Api.LinkMobility.Requests;
-
-namespace AMWD.Net.Api.LinkMobility
-{
- public partial class LinkMobilityClient
- {
- ///
- /// Sends a text message to a list of recipients.
- ///
- /// The request.
- /// A cancellation token to propagate notification that operations should be canceled.
- public Task SendTextMessage(SendTextMessageRequest request, CancellationToken cancellationToken = default)
- {
- if (request == null)
- throw new ArgumentNullException(nameof(request));
-
- if (string.IsNullOrWhiteSpace(request.MessageContent))
- throw new ArgumentException("A message must be provided.", nameof(request.MessageContent));
-
- if (request.RecipientAddressList == null || request.RecipientAddressList.Count == 0)
- throw new ArgumentException("At least one recipient must be provided.", nameof(request.RecipientAddressList));
-
- foreach (string recipient in request.RecipientAddressList)
- {
- if (!IsValidMSISDN(recipient))
- throw new ArgumentException($"Recipient address '{recipient}' is not a valid MSISDN format.", nameof(request.RecipientAddressList));
- }
-
- return PostAsync("/smsmessaging/text", request, cancellationToken: cancellationToken);
- }
-
- private static bool IsValidMSISDN(string msisdn)
- {
- if (string.IsNullOrWhiteSpace(msisdn))
- return false;
-
- return Regex.IsMatch(msisdn, @"^[1-9][0-9]{7,14}$", RegexOptions.Compiled);
- }
- }
-}
diff --git a/src/LinkMobility/LinkMobilityClient.Sms.cs b/src/LinkMobility/LinkMobilityClient.Sms.cs
new file mode 100644
index 0000000..d1986c3
--- /dev/null
+++ b/src/LinkMobility/LinkMobilityClient.Sms.cs
@@ -0,0 +1,67 @@
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Implementation of text messaging (SMS). API
+ ///
+ public partial class LinkMobilityClient : ILinkMobilityClient, IDisposable
+ {
+ ///
+ public Task SendTextMessage(SendTextMessageRequest request, CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ throw new ArgumentNullException(nameof(request));
+
+ if (string.IsNullOrWhiteSpace(request.MessageContent))
+ throw new ArgumentException("A message must be provided.", nameof(request.MessageContent));
+
+ if (request.RecipientAddressList == null || request.RecipientAddressList.Count == 0)
+ throw new ArgumentException("At least one recipient must be provided.", nameof(request.RecipientAddressList));
+
+ foreach (string recipient in request.RecipientAddressList)
+ {
+ if (!IsValidMSISDN(recipient))
+ throw new ArgumentException($"Recipient address '{recipient}' is not a valid MSISDN format.", nameof(request.RecipientAddressList));
+ }
+
+ return PostAsync("/smsmessaging/text", request, cancellationToken: cancellationToken);
+ }
+
+ ///
+ public Task SendBinaryMessage(SendBinaryMessageRequest request, CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ throw new ArgumentNullException(nameof(request));
+
+ if (request.MessageContent?.Count > 0)
+ {
+ // Validate that the string is a valid Base64 string
+ // Might throw a ArgumentNullException or FormatException
+ foreach (string str in request.MessageContent)
+ Convert.FromBase64String(str);
+ }
+
+ if (request.RecipientAddressList == null || request.RecipientAddressList.Count == 0)
+ throw new ArgumentException("At least one recipient must be provided.", nameof(request.RecipientAddressList));
+
+ foreach (string recipient in request.RecipientAddressList)
+ {
+ if (!IsValidMSISDN(recipient))
+ throw new ArgumentException($"Recipient address '{recipient}' is not a valid MSISDN format.", nameof(request.RecipientAddressList));
+ }
+
+ return PostAsync("/smsmessaging/binary", request, cancellationToken: cancellationToken);
+ }
+
+ private static bool IsValidMSISDN(string msisdn)
+ {
+ if (string.IsNullOrWhiteSpace(msisdn))
+ return false;
+
+ return Regex.IsMatch(msisdn, @"^[1-9][0-9]{7,14}$", RegexOptions.Compiled);
+ }
+ }
+}
diff --git a/src/LinkMobility/LinkMobilityClient.cs b/src/LinkMobility/LinkMobilityClient.cs
index a6642e2..fea1793 100644
--- a/src/LinkMobility/LinkMobilityClient.cs
+++ b/src/LinkMobility/LinkMobilityClient.cs
@@ -1,5 +1,4 @@
-using System.Globalization;
-using System.Linq;
+using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -16,13 +15,6 @@ namespace AMWD.Net.Api.LinkMobility
///
public partial class LinkMobilityClient : ILinkMobilityClient, IDisposable
{
- private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
- {
- Culture = CultureInfo.InvariantCulture,
- Formatting = Formatting.None,
- NullValueHandling = NullValueHandling.Ignore
- };
-
private readonly ClientOptions _clientOptions;
private readonly HttpClient _httpClient;
@@ -191,7 +183,7 @@ namespace AMWD.Net.Api.LinkMobility
if (request is HttpContent httpContent)
return httpContent;
- string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
+ string json = request.SerializeObject();
return new StringContent(json, Encoding.UTF8, "application/json");
}
@@ -208,7 +200,7 @@ namespace AMWD.Net.Api.LinkMobility
HttpStatusCode.Unauthorized => throw new AuthenticationException($"HTTP auth missing: {httpResponse.StatusCode}"),
HttpStatusCode.Forbidden => throw new AuthenticationException($"HTTP auth missing: {httpResponse.StatusCode}"),
HttpStatusCode.OK =>
- JsonConvert.DeserializeObject(content, _jsonSerializerSettings)
+ content.DeserializeObject()
?? throw new ApplicationException("Response could not be deserialized"),
_ => throw new ApplicationException($"Unknown HTTP response: {httpResponse.StatusCode}"),
};
diff --git a/src/LinkMobility/Models/IncomingMessageNotification.cs b/src/LinkMobility/Models/IncomingMessageNotification.cs
new file mode 100644
index 0000000..df13c52
--- /dev/null
+++ b/src/LinkMobility/Models/IncomingMessageNotification.cs
@@ -0,0 +1,194 @@
+using System.Runtime.Serialization;
+using Newtonsoft.Json.Converters;
+
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Represents a notification for an incoming message or delivery report. (API)
+ ///
+ public class IncomingMessageNotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The notification id.
+ /// The transfer id.
+ public IncomingMessageNotification(string notificationId, string transferId)
+ {
+ NotificationId = notificationId;
+ TransferId = transferId;
+ }
+
+ ///
+ /// Defines the content type of your notification.
+ ///
+ [JsonProperty("messageType")]
+ public Type MessageType { get; set; }
+
+ ///
+ /// 20 digit long identification of your notification.
+ ///
+ [JsonProperty("notificationId")]
+ public string NotificationId { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Unique transfer-id to connect the deliveryReport to the initial message.
+ ///
+ [JsonProperty("transferId")]
+ public string TransferId { get; set; }
+
+ ///
+ /// , :
+ ///
+ /// Indicates whether you received message is a SMS or a flash-SMS.
+ ///
+ [JsonProperty("messageFlashSms")]
+ public bool? MessageFlashSms { get; set; }
+
+ ///
+ /// Originator of the sender.
+ ///
+ [JsonProperty("senderAddress")]
+ public string? SenderAddress { get; set; }
+
+ ///
+ /// , :
+ ///
+ /// – defines the number format of the mobile originated .
+ /// International numbers always includes the country prefix.
+ ///
+ [JsonProperty("senderAddressType")]
+ public AddressType? SenderAddressType { get; set; }
+
+ ///
+ /// Senders address, can either be
+ /// (4366012345678),
+ /// (066012345678) or a
+ /// (1234).
+ ///
+ [JsonProperty("recipientAddress")]
+ public string? RecipientAddress { get; set; }
+
+ ///
+ /// , :
+ ///
+ /// Defines the number format of the mobile originated message.
+ ///
+ [JsonProperty("recipientAddressType")]
+ public AddressType? RecipientAddressType { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Text body of the message encoded in UTF-8.
+ /// In the case of concatenated SMS it will contain the complete content of all segments.
+ ///
+ [JsonProperty("textMessageContent")]
+ public string? TextMessageContent { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Indicates whether a user-data-header is included within a Base64 encoded byte segment.
+ ///
+ [JsonProperty("userDataHeaderPresent")]
+ public bool? UserDataHeaderPresent { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Content of a binary SMS in an array of Base64 strings (URL safe).
+ ///
+ [JsonProperty("binaryMessageContent")]
+ public IReadOnlyCollection? BinaryMessageContent { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Status of the message.
+ ///
+ [JsonProperty("deliveryReportMessageStatus")]
+ public DeliveryStatus? DeliveryReportMessageStatus { get; set; }
+
+ ///
+ /// :
+ ///
+ /// ISO 8601 timestamp. Point of time sending the message to recipients address.
+ ///
+ [JsonProperty("sentOn")]
+ public DateTime? SentOn { get; set; }
+
+ ///
+ /// :
+ ///
+ /// ISO 8601 timestamp. Point of time of submitting the message to the mobile operators network.
+ ///
+ [JsonProperty("deliveredOn")]
+ public DateTime? DeliveredOn { get; set; }
+
+ ///
+ /// :
+ ///
+ /// Type of delivery used to send the message.
+ ///
+ [JsonProperty("deliveredAs")]
+ public DeliveryType? DeliveredAs { get; set; }
+
+ ///
+ /// :
+ ///
+ /// In the case of a delivery report, the contains the optional submitted message id.
+ ///
+ [JsonProperty("clientMessageId")]
+ public string? ClientMessageId { get; set; }
+
+ ///
+ /// Defines the type of notification.
+ ///
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum Type
+ {
+ ///
+ /// Notification of an incoming text message.
+ ///
+ [EnumMember(Value = "text")]
+ Text = 1,
+
+ ///
+ /// Notification of an incoming binary message.
+ ///
+ [EnumMember(Value = "binary")]
+ Binary = 2,
+
+ ///
+ /// Notification of a delivery report.
+ ///
+ [EnumMember(Value = "deliveryReport")]
+ DeliveryReport = 3
+ }
+
+ ///
+ /// Tries to parse the given content as .
+ ///
+ /// The given content (should be the notification json).
+ /// The deserialized notification.
+ ///
+ /// if the content could be parsed; otherwise, .
+ ///
+ public static bool TryParse(string json, out IncomingMessageNotification? notification)
+ {
+ try
+ {
+ notification = json.DeserializeObject();
+ return notification != null;
+ }
+ catch
+ {
+ notification = null;
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/LinkMobility/Models/IncomingMessageNotificationResponse.cs b/src/LinkMobility/Models/IncomingMessageNotificationResponse.cs
new file mode 100644
index 0000000..a1e04ca
--- /dev/null
+++ b/src/LinkMobility/Models/IncomingMessageNotificationResponse.cs
@@ -0,0 +1,27 @@
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Representes the response to an incoming message notification. (API)
+ ///
+ public class IncomingMessageNotificationResponse
+ {
+ ///
+ /// Gets or sets the status code of the response.
+ ///
+ [JsonProperty("statusCode")]
+ public StatusCodes StatusCode { get; set; } = StatusCodes.Ok;
+
+ ///
+ /// Gets or sets the status message of the response.
+ ///
+ [JsonProperty("statusMessage")]
+ public string StatusMessage { get; set; } = "OK";
+
+ ///
+ /// Returns a string representation of the current object in serialized format.
+ ///
+ /// A string containing the serialized form of the object (json).
+ public override string ToString()
+ => this.SerializeObject();
+ }
+}
diff --git a/src/LinkMobility/README.md b/src/LinkMobility/README.md
index e526b30..d0984c7 100644
--- a/src/LinkMobility/README.md
+++ b/src/LinkMobility/README.md
@@ -1,12 +1,12 @@
-# LinkMobility API
+# LINK Mobility REST API
-This project aims to implement the [LinkMobility REST API].
+This project aims to implement the [LINK Mobility REST API].
## Overview
-Link Mobility is a provider for communication with customers via SMS, RCS or WhatsApp.
+LINK Mobility is a provider for communication with customers via SMS, RCS or WhatsApp.
-With this project the REST API of Link Mobility will be implemented.
+In this project the REST API of LINK Mobility will be implemented.
- [SMS API](https://developer.linkmobility.eu/sms-api/rest-api)
@@ -14,5 +14,5 @@ With this project the REST API of Link Mobility will be implemented.
Published under MIT License (see [**tl;dr**Legal])
-[LinkMobility REST API]: https://developer.linkmobility.eu/
+[LINK Mobility REST API]: https://developer.linkmobility.eu/
[**tl;dr**Legal]: https://www.tldrlegal.com/license/mit-license
diff --git a/src/LinkMobility/Requests/SendBinaryMessageRequest.cs b/src/LinkMobility/Requests/SendBinaryMessageRequest.cs
new file mode 100644
index 0000000..b3276b3
--- /dev/null
+++ b/src/LinkMobility/Requests/SendBinaryMessageRequest.cs
@@ -0,0 +1,128 @@
+namespace AMWD.Net.Api.LinkMobility
+{
+ ///
+ /// Request to send a text message to a list of recipients.
+ ///
+ public class SendBinaryMessageRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The recipient list.
+ public SendBinaryMessageRequest(IReadOnlyCollection recipientAddressList)
+ {
+ RecipientAddressList = recipientAddressList;
+ }
+
+ ///
+ /// Optional.
+ /// May contain a freely definable message id.
+ ///
+ [JsonProperty("clientMessageId")]
+ public string? ClientMessageId { get; set; }
+
+ ///
+ /// Optional.
+ /// The content category that is used to categorize the message (used for blacklisting).
+ ///
+ ///
+ /// The following content categories are supported: or .
+ /// If no content category is provided, the default setting is used (may be changed inside the web interface).
+ ///
+ [JsonProperty("contentCategory")]
+ public ContentCategory? ContentCategory { get; set; }
+
+ ///
+ /// Optional.
+ /// Array of Base64 encoded binary data.
+ ///
+ ///
+ /// Every element of the array corresponds to a message segment.
+ /// The binary data is transmitted without being changed (using 8 bit alphabet).
+ ///
+ [JsonProperty("messageContent")]
+ public IReadOnlyCollection? MessageContent { get; set; }
+
+ ///
+ /// Optional.
+ /// When setting a NotificationCallbackUrl all delivery reports are forwarded to this URL.
+ ///
+ [JsonProperty("notificationCallbackUrl")]
+ public string? NotificationCallbackUrl { get; set; }
+
+ ///
+ /// Optional.
+ /// Priority of the message.
+ ///
+ ///
+ /// Must not exceed the value configured for the account used to send the message.
+ /// For more information please contact our customer service.
+ ///
+ [JsonProperty("priority")]
+ public int? Priority { get; set; }
+
+ ///
+ /// List of recipients (E.164 formatted MSISDNs)
+ /// to whom the message should be sent.
+ ///
+ /// The list of recipients may contain a maximum of 1000 entries.
+ ///
+ [JsonProperty("recipientAddressList")]
+ public IReadOnlyCollection RecipientAddressList { get; set; }
+
+ ///
+ /// Optional.
+ ///
+ /// : The message is sent as flash SMS (displayed directly on the screen of the mobile phone).
+ ///
+ /// : The message is sent as standard text SMS (default).
+ ///
+ [JsonProperty("sendAsFlashSms")]
+ public bool? SendAsFlashSms { get; set; }
+
+ ///
+ /// Optional.
+ /// Address of the sender (assigned to the account) from which the message is sent.
+ ///
+ [JsonProperty("senderAddress")]
+ public string? SenderAddress { get; set; }
+
+ ///
+ /// Optional.
+ /// The sender address type.
+ ///
+ [JsonProperty("senderAddressType")]
+ public AddressType? SenderAddressType { get; set; }
+
+ ///
+ /// Optional.
+ ///
+ /// : The transmission is only simulated, no SMS is sent.
+ /// Depending on the number of recipients the status code or is returned.
+ ///
+ /// : No simulation is done. The SMS is sent via the SMS Gateway. (default)
+ ///
+ [JsonProperty("test")]
+ public bool? Test { get; set; }
+
+ ///
+ /// Optional.
+ ///
+ /// : Indicates the presence of a user data header in the property.
+ ///
+ /// : Indicates the absence of a user data header in the property. (default)
+ ///
+ [JsonProperty("userDataHeaderPresent")]
+ public bool? UserDataHeaderPresent { get; set; }
+
+ ///
+ /// Optional.
+ /// Specifies the validity periode (in seconds) in which the message is tried to be delivered to the recipient.
+ ///
+ ///
+ /// A minimum of 1 minute (60 seconds) and a maximum of 3 days (259200 seconds) are allowed.
+ ///
+ [JsonProperty("validityPeriode")]
+ public int? ValidityPeriode { get; set; }
+ }
+}
diff --git a/src/LinkMobility/Requests/SendTextMessageRequest.cs b/src/LinkMobility/Requests/SendTextMessageRequest.cs
index 8215641..c232052 100644
--- a/src/LinkMobility/Requests/SendTextMessageRequest.cs
+++ b/src/LinkMobility/Requests/SendTextMessageRequest.cs
@@ -1,4 +1,4 @@
-namespace AMWD.Net.Api.LinkMobility.Requests
+namespace AMWD.Net.Api.LinkMobility
{
///
/// Request to send a text message to a list of recipients.
@@ -113,7 +113,7 @@
/// The sender address type.
///
[JsonProperty("senderAddressType")]
- public SenderAddressType? SenderAddressType { get; set; }
+ public AddressType? SenderAddressType { get; set; }
///
/// Optional.
diff --git a/src/LinkMobility/Responses/SendTextMessageResponse.cs b/src/LinkMobility/Responses/SendMessageResponse.cs
similarity index 92%
rename from src/LinkMobility/Responses/SendTextMessageResponse.cs
rename to src/LinkMobility/Responses/SendMessageResponse.cs
index a4860ba..b5d5edd 100644
--- a/src/LinkMobility/Responses/SendTextMessageResponse.cs
+++ b/src/LinkMobility/Responses/SendMessageResponse.cs
@@ -3,7 +3,7 @@
///
/// Response of a text message sent to a list of recipients.
///
- public class SendTextMessageResponse
+ public class SendMessageResponse
{
///
/// Contains the message id defined in the request.
diff --git a/src/LinkMobility/Utils/SerializerExtensions.cs b/src/LinkMobility/Utils/SerializerExtensions.cs
new file mode 100644
index 0000000..2fd0596
--- /dev/null
+++ b/src/LinkMobility/Utils/SerializerExtensions.cs
@@ -0,0 +1,20 @@
+using System.Globalization;
+
+namespace AMWD.Net.Api.LinkMobility
+{
+ internal static class SerializerExtensions
+ {
+ private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
+ {
+ Culture = CultureInfo.InvariantCulture,
+ Formatting = Formatting.None,
+ NullValueHandling = NullValueHandling.Ignore
+ };
+
+ public static string SerializeObject(this object? obj)
+ => JsonConvert.SerializeObject(obj, _jsonSerializerSettings);
+
+ public static T? DeserializeObject(this string json)
+ => JsonConvert.DeserializeObject(json, _jsonSerializerSettings);
+ }
+}
diff --git a/test/LinkMobility.Tests/Models/IncomingMessageNotificationTest.cs b/test/LinkMobility.Tests/Models/IncomingMessageNotificationTest.cs
new file mode 100644
index 0000000..966aaf6
--- /dev/null
+++ b/test/LinkMobility.Tests/Models/IncomingMessageNotificationTest.cs
@@ -0,0 +1,90 @@
+using AMWD.Net.Api.LinkMobility;
+
+namespace LinkMobility.Tests.Models
+{
+ [TestClass]
+ public class IncomingMessageNotificationTest
+ {
+ [TestMethod]
+ public void ShouldParseAllPropertiesForTextNotification()
+ {
+ // Arrange
+ string json = @"{
+ ""messageType"": ""text"",
+ ""notificationId"": ""notif-123"",
+ ""transferId"": ""trans-456"",
+ ""messageFlashSms"": true,
+ ""senderAddress"": ""436991234567"",
+ ""senderAddressType"": ""international"",
+ ""recipientAddress"": ""066012345678"",
+ ""recipientAddressType"": ""national"",
+ ""textMessageContent"": ""Hello from user"",
+ ""userDataHeaderPresent"": false,
+ ""binaryMessageContent"": [""SGVsbG8=""],
+ ""deliveryReportMessageStatus"": 2,
+ ""sentOn"": ""2025-12-03T12:34:56Z"",
+ ""deliveredOn"": ""2025-12-03T12:35:30Z"",
+ ""deliveredAs"": 1,
+ ""clientMessageId"": ""client-789""
+ }";
+
+ // Act
+ bool successful = IncomingMessageNotification.TryParse(json, out var notification);
+
+ // Assert
+ Assert.IsTrue(successful, "TryParse should return true for valid json");
+ Assert.IsNotNull(notification);
+
+ Assert.AreEqual(IncomingMessageNotification.Type.Text, notification.MessageType);
+ Assert.AreEqual("notif-123", notification.NotificationId);
+ Assert.AreEqual("trans-456", notification.TransferId);
+
+ Assert.IsTrue(notification.MessageFlashSms.HasValue && notification.MessageFlashSms.Value);
+ Assert.AreEqual("436991234567", notification.SenderAddress);
+ Assert.IsTrue(notification.SenderAddressType.HasValue);
+ Assert.AreEqual(AddressType.International, notification.SenderAddressType.Value);
+
+ Assert.AreEqual("066012345678", notification.RecipientAddress);
+ Assert.IsTrue(notification.RecipientAddressType.HasValue);
+ Assert.AreEqual(AddressType.National, notification.RecipientAddressType.Value);
+
+ Assert.AreEqual("Hello from user", notification.TextMessageContent);
+ Assert.IsTrue(notification.UserDataHeaderPresent.HasValue && !notification.UserDataHeaderPresent.Value);
+
+ Assert.IsNotNull(notification.BinaryMessageContent);
+ CollectionAssert.AreEqual(new List { "SGVsbG8=" }, new List(notification.BinaryMessageContent));
+
+ // delivery status and deliveredAs are numeric in the test json: assert underlying integral values
+ Assert.IsTrue(notification.DeliveryReportMessageStatus.HasValue);
+ Assert.AreEqual(2, (int)notification.DeliveryReportMessageStatus.Value);
+
+ Assert.IsTrue(notification.SentOn.HasValue);
+ Assert.IsTrue(notification.DeliveredOn.HasValue);
+
+ // Compare instants in UTC
+ var expectedSent = DateTime.Parse("2025-12-03T12:34:56Z").ToUniversalTime();
+ var expectedDelivered = DateTime.Parse("2025-12-03T12:35:30Z").ToUniversalTime();
+ Assert.AreEqual(expectedSent, notification.SentOn.Value.ToUniversalTime());
+ Assert.AreEqual(expectedDelivered, notification.DeliveredOn.Value.ToUniversalTime());
+
+ Assert.IsTrue(notification.DeliveredAs.HasValue);
+ Assert.AreEqual(1, (int)notification.DeliveredAs.Value);
+
+ Assert.AreEqual("client-789", notification.ClientMessageId);
+ }
+
+ [TestMethod]
+ public void TryParseShouldReturnFalseOnInvalidJson()
+ {
+ // Arrange
+ string invalid = "this is not json";
+
+ // Act
+ bool successful = IncomingMessageNotification.TryParse(invalid, out var notification);
+
+ // Assert
+ Assert.IsFalse(successful);
+ Assert.IsNull(notification);
+ }
+ }
+}
diff --git a/test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs b/test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs
new file mode 100644
index 0000000..80740de
--- /dev/null
+++ b/test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs
@@ -0,0 +1,264 @@
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.LinkMobility;
+using LinkMobility.Tests.Helpers;
+using Moq.Protected;
+
+namespace LinkMobility.Tests.Sms
+{
+ [TestClass]
+ public class SendBinaryMessageTest
+ {
+ public TestContext TestContext { get; set; }
+
+ private const string BASE_URL = "https://localhost/rest/";
+
+ private Mock _authenticationMock;
+ private Mock _clientOptionsMock;
+ private HttpMessageHandlerMock _httpMessageHandlerMock;
+
+ private SendBinaryMessageRequest _request;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _authenticationMock = new Mock();
+ _clientOptionsMock = new Mock();
+ _httpMessageHandlerMock = new HttpMessageHandlerMock();
+
+ _authenticationMock
+ .Setup(a => a.AddHeader(It.IsAny()))
+ .Callback(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Scheme", "Parameter"));
+
+ _clientOptionsMock.Setup(c => c.BaseUrl).Returns(BASE_URL);
+ _clientOptionsMock.Setup(c => c.Timeout).Returns(TimeSpan.FromSeconds(30));
+ _clientOptionsMock.Setup(c => c.DefaultHeaders).Returns(new Dictionary());
+ _clientOptionsMock.Setup(c => c.DefaultQueryParams).Returns(new Dictionary());
+ _clientOptionsMock.Setup(c => c.AllowRedirects).Returns(true);
+ _clientOptionsMock.Setup(c => c.UseProxy).Returns(false);
+
+ _request = new SendBinaryMessageRequest(["436991234567"])
+ {
+ MessageContent = ["SGVsbG8gV29ybGQ="] // "Hello World" base64
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldSendBinaryMessage()
+ {
+ // Arrange
+ _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(@"{ ""clientMessageId"": ""binId"", ""smsCount"": 1, ""statusCode"": 2000, ""statusMessage"": ""OK"", ""transferId"": ""abc123"" }", Encoding.UTF8, "application/json"),
+ });
+
+ var client = GetClient();
+
+ // Act
+ var response = await client.SendBinaryMessage(_request, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.AreEqual("binId", response.ClientMessageId);
+ Assert.AreEqual(1, response.SmsCount);
+ Assert.AreEqual(StatusCodes.Ok, response.StatusCode);
+ Assert.AreEqual("OK", response.StatusMessage);
+ Assert.AreEqual("abc123", response.TransferId);
+
+ Assert.HasCount(1, _httpMessageHandlerMock.RequestCallbacks);
+
+ var callback = _httpMessageHandlerMock.RequestCallbacks.First();
+ Assert.AreEqual(HttpMethod.Post, callback.HttpMethod);
+ Assert.AreEqual("https://localhost/rest/smsmessaging/binary", callback.Url);
+ Assert.AreEqual(@"{""messageContent"":[""SGVsbG8gV29ybGQ=""],""recipientAddressList"":[""436991234567""]}", callback.Content);
+
+ Assert.HasCount(3, callback.Headers);
+ Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
+ Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
+ Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
+
+ Assert.AreEqual("application/json", callback.Headers["Accept"]);
+ Assert.AreEqual("Scheme Parameter", callback.Headers["Authorization"]);
+ Assert.AreEqual("LinkMobilityClient/1.0.0", callback.Headers["User-Agent"]);
+
+ _httpMessageHandlerMock.Mock
+ .Protected()
+ .Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny());
+
+ _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once);
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public async Task ShouldSendBinaryMessageFullDetails()
+ {
+ // Arrange
+ _request.ClientMessageId = "myCustomId";
+ _request.ContentCategory = ContentCategory.Advertisement;
+ _request.NotificationCallbackUrl = "https://user:pass@example.com/callback/";
+ _request.Priority = 5;
+ _request.SendAsFlashSms = false;
+ _request.SenderAddress = "4369912345678";
+ _request.SenderAddressType = AddressType.International;
+ _request.Test = false;
+ _request.UserDataHeaderPresent = true;
+ _request.ValidityPeriode = 300;
+
+ _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(@"{ ""clientMessageId"": ""myCustomId"", ""smsCount"": 1, ""statusCode"": 2000, ""statusMessage"": ""OK"", ""transferId"": ""abc123"" }", Encoding.UTF8, "application/json"),
+ });
+
+ var client = GetClient();
+
+ // Act
+ var response = await client.SendBinaryMessage(_request, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.AreEqual("myCustomId", response.ClientMessageId);
+ Assert.AreEqual(1, response.SmsCount);
+ Assert.AreEqual(StatusCodes.Ok, response.StatusCode);
+ Assert.AreEqual("OK", response.StatusMessage);
+ Assert.AreEqual("abc123", response.TransferId);
+
+ Assert.HasCount(1, _httpMessageHandlerMock.RequestCallbacks);
+
+ var callback = _httpMessageHandlerMock.RequestCallbacks.First();
+ Assert.AreEqual(HttpMethod.Post, callback.HttpMethod);
+ Assert.AreEqual("https://localhost/rest/smsmessaging/binary", callback.Url);
+ Assert.AreEqual(@"{""clientMessageId"":""myCustomId"",""contentCategory"":""advertisement"",""messageContent"":[""SGVsbG8gV29ybGQ=""],""notificationCallbackUrl"":""https://user:pass@example.com/callback/"",""priority"":5,""recipientAddressList"":[""436991234567""],""sendAsFlashSms"":false,""senderAddress"":""4369912345678"",""senderAddressType"":""international"",""test"":false,""userDataHeaderPresent"":true,""validityPeriode"":300}", callback.Content);
+
+ Assert.HasCount(3, callback.Headers);
+ Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
+ Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
+ Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
+
+ Assert.AreEqual("application/json", callback.Headers["Accept"]);
+ Assert.AreEqual("Scheme Parameter", callback.Headers["Authorization"]);
+ Assert.AreEqual("LinkMobilityClient/1.0.0", callback.Headers["User-Agent"]);
+
+ _httpMessageHandlerMock.Mock
+ .Protected()
+ .Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny());
+
+ _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once);
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public void ShouldThrowOnNullRequest()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act & Assert
+ var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(null, TestContext.CancellationToken));
+
+ Assert.AreEqual("request", ex.ParamName);
+
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public void ShouldThrowOnInvalidMessageEncoding()
+ {
+ // Arrange
+ _request.MessageContent = ["InvalidBase64!!"];
+ var client = GetClient();
+
+ // Act & Assert
+ Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken));
+
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public void ShouldThrowOnNullMessageContent()
+ {
+ // Arrange
+ _request.MessageContent = [null];
+ var client = GetClient();
+
+ // Act & Assert
+ var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken));
+
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ [DataRow(null)]
+ [DataRow("")]
+ public void ShouldThrowOnNoRecipients(string recipients)
+ {
+ // Arrange
+ _request.RecipientAddressList = recipients?.Split(',');
+ var client = GetClient();
+
+ // Act & Assert
+ var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken));
+
+ Assert.AreEqual("RecipientAddressList", ex.ParamName);
+
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ [DataRow(null)]
+ [DataRow("")]
+ [DataRow(" ")]
+ [DataRow("invalid-recipient")]
+ public void ShouldThrowOnInvalidRecipient(string recipient)
+ {
+ // Arrange
+ _request.RecipientAddressList = ["436991234567", recipient];
+ var client = GetClient();
+
+ // Act & Assert
+ var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken));
+
+ Assert.AreEqual("RecipientAddressList", ex.ParamName);
+ Assert.StartsWith($"Recipient address '{recipient}' is not a valid MSISDN format.", ex.Message);
+
+ VerifyNoOtherCalls();
+ }
+
+ private void VerifyNoOtherCalls()
+ {
+ _authenticationMock.VerifyNoOtherCalls();
+ _clientOptionsMock.VerifyNoOtherCalls();
+ _httpMessageHandlerMock.Mock.VerifyNoOtherCalls();
+ }
+
+ private ILinkMobilityClient GetClient()
+ {
+ var client = new LinkMobilityClient(_authenticationMock.Object, _clientOptionsMock.Object);
+
+ var httpClient = new HttpClient(_httpMessageHandlerMock.Mock.Object)
+ {
+ Timeout = _clientOptionsMock.Object.Timeout,
+ BaseAddress = new Uri(_clientOptionsMock.Object.BaseUrl)
+ };
+
+ httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("LinkMobilityClient", "1.0.0"));
+ httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+
+ _authenticationMock.Object.AddHeader(httpClient);
+
+ _authenticationMock.Invocations.Clear();
+ _clientOptionsMock.Invocations.Clear();
+
+ ReflectionHelper.GetPrivateField(client, "_httpClient")?.Dispose();
+ ReflectionHelper.SetPrivateField(client, "_httpClient", httpClient);
+
+ return client;
+ }
+ }
+}
diff --git a/test/LinkMobility.Tests/SendTextMessageTest.cs b/test/LinkMobility.Tests/Sms/SendTextMessageTest.cs
similarity index 59%
rename from test/LinkMobility.Tests/SendTextMessageTest.cs
rename to test/LinkMobility.Tests/Sms/SendTextMessageTest.cs
index 6abdcb5..3ba925b 100644
--- a/test/LinkMobility.Tests/SendTextMessageTest.cs
+++ b/test/LinkMobility.Tests/Sms/SendTextMessageTest.cs
@@ -6,11 +6,10 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AMWD.Net.Api.LinkMobility;
-using AMWD.Net.Api.LinkMobility.Requests;
using LinkMobility.Tests.Helpers;
using Moq.Protected;
-namespace LinkMobility.Tests
+namespace LinkMobility.Tests.Sms
{
[TestClass]
public class SendTextMessageTest
@@ -43,7 +42,7 @@ namespace LinkMobility.Tests
_clientOptionsMock.Setup(c => c.AllowRedirects).Returns(true);
_clientOptionsMock.Setup(c => c.UseProxy).Returns(false);
- _request = new SendTextMessageRequest("Happy Testing", ["4791234567"]);
+ _request = new SendTextMessageRequest("example message content", ["436991234567"]);
}
[TestMethod]
@@ -53,7 +52,7 @@ namespace LinkMobility.Tests
_httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent("{}", Encoding.UTF8, "application/json"),
+ Content = new StringContent(@"{ ""clientMessageId"": ""myUniqueId"", ""smsCount"": 1, ""statusCode"": 2000, ""statusMessage"": ""OK"", ""transferId"": ""0059d0b20100a0a8b803"" }", Encoding.UTF8, "application/json"),
});
var client = GetClient();
@@ -64,12 +63,78 @@ namespace LinkMobility.Tests
// Assert
Assert.IsNotNull(response);
+ Assert.AreEqual("myUniqueId", response.ClientMessageId);
+ Assert.AreEqual(1, response.SmsCount);
+ Assert.AreEqual(StatusCodes.Ok, response.StatusCode);
+ Assert.AreEqual("OK", response.StatusMessage);
+ Assert.AreEqual("0059d0b20100a0a8b803", response.TransferId);
+
Assert.HasCount(1, _httpMessageHandlerMock.RequestCallbacks);
var callback = _httpMessageHandlerMock.RequestCallbacks.First();
Assert.AreEqual(HttpMethod.Post, callback.HttpMethod);
Assert.AreEqual("https://localhost/rest/smsmessaging/text", callback.Url);
- Assert.AreEqual(@"{""messageContent"":""Happy Testing"",""recipientAddressList"":[""4791234567""]}", callback.Content);
+ Assert.AreEqual(@"{""messageContent"":""example message content"",""recipientAddressList"":[""436991234567""]}", callback.Content);
+
+ Assert.HasCount(3, callback.Headers);
+ Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
+ Assert.IsTrue(callback.Headers.ContainsKey("Authorization"));
+ Assert.IsTrue(callback.Headers.ContainsKey("User-Agent"));
+
+ Assert.AreEqual("application/json", callback.Headers["Accept"]);
+ Assert.AreEqual("Scheme Parameter", callback.Headers["Authorization"]);
+ Assert.AreEqual("LinkMobilityClient/1.0.0", callback.Headers["User-Agent"]);
+
+ _httpMessageHandlerMock.Mock
+ .Protected()
+ .Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny());
+
+ _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once);
+ VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public async Task ShouldSendTextMessageFullDetails()
+ {
+ // Arrange
+ _request.ClientMessageId = "myCustomId";
+ _request.ContentCategory = ContentCategory.Informational;
+ _request.MaxSmsPerMessage = 1;
+ _request.MessageType = MessageType.Voice;
+ _request.NotificationCallbackUrl = "https://user:pass@example.com/callback/";
+ _request.Priority = 5;
+ _request.SendAsFlashSms = false;
+ _request.SenderAddress = "4369912345678";
+ _request.SenderAddressType = AddressType.International;
+ _request.Test = false;
+ _request.ValidityPeriode = 300;
+
+ _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(@"{ ""clientMessageId"": ""myCustomId"", ""smsCount"": 1, ""statusCode"": 4035, ""statusMessage"": ""SMS_DISABLED"", ""transferId"": ""0059d0b20100a0a8b803"" }", Encoding.UTF8, "application/json"),
+ });
+
+ var client = GetClient();
+
+ // Act
+ var response = await client.SendTextMessage(_request, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+
+ Assert.AreEqual("myCustomId", response.ClientMessageId);
+ Assert.AreEqual(1, response.SmsCount);
+ Assert.AreEqual(StatusCodes.SmsDisabled, response.StatusCode);
+ Assert.AreEqual("SMS_DISABLED", response.StatusMessage);
+ Assert.AreEqual("0059d0b20100a0a8b803", response.TransferId);
+
+ Assert.HasCount(1, _httpMessageHandlerMock.RequestCallbacks);
+
+ var callback = _httpMessageHandlerMock.RequestCallbacks.First();
+ Assert.AreEqual(HttpMethod.Post, callback.HttpMethod);
+ Assert.AreEqual("https://localhost/rest/smsmessaging/text", callback.Url);
+ Assert.AreEqual(@"{""clientMessageId"":""myCustomId"",""contentCategory"":""informational"",""maxSmsPerMessage"":1,""messageContent"":""example message content"",""messageType"":""voice"",""notificationCallbackUrl"":""https://user:pass@example.com/callback/"",""priority"":5,""recipientAddressList"":[""436991234567""],""sendAsFlashSms"":false,""senderAddress"":""4369912345678"",""senderAddressType"":""international"",""test"":false,""validityPeriode"":300}", callback.Content);
Assert.HasCount(3, callback.Headers);
Assert.IsTrue(callback.Headers.ContainsKey("Accept"));
@@ -172,11 +237,6 @@ namespace LinkMobility.Tests
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("LinkMobilityClient", "1.0.0"));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- if (_clientOptionsMock.Object.DefaultHeaders.Count > 0)
- {
- foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders)
- httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
- }
_authenticationMock.Object.AddHeader(httpClient);
_authenticationMock.Invocations.Clear();