diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0e0fb..efd64c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Channel implementations (SMS, WhatsApp, ...) are extensions to the `ILinkMobilityClient` interface. +- Reorganized namespaces to reflect parts of the API + +### Removed + +- `IQueryParameter` as no usage on API docs found (for now) ## [v0.1.1] - 2026-03-13 diff --git a/README.md b/README.md index 777d9e4..d3012e6 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ So I decided to implement the current available API myself with a more modern (a --- -Published under [MIT License] (see [**tl;dr**Legal]) +Published under [MIT License] (see [choose a license]) [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 +[choose a license]: https://choosealicense.com/licenses/mit/ diff --git a/src/LinkMobility/Enums/StatusCodes.cs b/src/LinkMobility/Enums/StatusCodes.cs index c912d70..cf941a2 100644 --- a/src/LinkMobility/Enums/StatusCodes.cs +++ b/src/LinkMobility/Enums/StatusCodes.cs @@ -1,7 +1,8 @@ namespace AMWD.Net.Api.LinkMobility { /// - /// Custom status codes as defined by Link Mobility. + /// Custom status codes as defined by + /// LINK Mobility. /// public enum StatusCodes : int { diff --git a/src/LinkMobility/ILinkMobilityClient.cs b/src/LinkMobility/ILinkMobilityClient.cs index 22bc798..e152d13 100644 --- a/src/LinkMobility/ILinkMobilityClient.cs +++ b/src/LinkMobility/ILinkMobilityClient.cs @@ -15,8 +15,7 @@ namespace AMWD.Net.Api.LinkMobility /// The type of the request. /// The path of the API endpoint. /// The request data. - /// Optional query parameters. /// A cancellation token to propagate notification that operations should be canceled. - Task PostAsync(string requestPath, TRequest? request, IQueryParameter? queryParams = null, CancellationToken cancellationToken = default); + Task PostAsync(string requestPath, TRequest? request, CancellationToken cancellationToken = default); } } diff --git a/src/LinkMobility/LinkMobilityClient.cs b/src/LinkMobility/LinkMobilityClient.cs index 8a5f673..6dcaef4 100644 --- a/src/LinkMobility/LinkMobilityClient.cs +++ b/src/LinkMobility/LinkMobilityClient.cs @@ -7,6 +7,7 @@ using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; +using AMWD.Net.Api.LinkMobility.Utils; namespace AMWD.Net.Api.LinkMobility { @@ -79,12 +80,12 @@ namespace AMWD.Net.Api.LinkMobility } /// - public async Task PostAsync(string requestPath, TRequest? request, IQueryParameter? queryParams = null, CancellationToken cancellationToken = default) + public async Task PostAsync(string requestPath, TRequest? request, CancellationToken cancellationToken = default) { ThrowIfDisposed(); ValidateRequestPath(requestPath); - string requestUrl = BuildRequestUrl(requestPath, queryParams); + string requestUrl = BuildRequestUrl(requestPath); var httpContent = ConvertRequest(request); var httpRequest = new HttpRequestMessage @@ -99,7 +100,7 @@ namespace AMWD.Net.Api.LinkMobility return response; } - private string BuildRequestUrl(string requestPath, IQueryParameter? queryParams = null) + private string BuildRequestUrl(string requestPath) { string path = requestPath.Trim().TrimStart('/'); var param = new Dictionary(); @@ -110,13 +111,6 @@ namespace AMWD.Net.Api.LinkMobility param[kvp.Key] = kvp.Value; } - var customQueryParams = queryParams?.GetQueryParameters(); - if (customQueryParams?.Count > 0) - { - foreach (var kvp in customQueryParams) - param[kvp.Key] = kvp.Value; - } - if (param.Count == 0) return path; diff --git a/src/LinkMobility/QueryParameters/IQueryParameter.cs b/src/LinkMobility/QueryParameters/IQueryParameter.cs deleted file mode 100644 index d687432..0000000 --- a/src/LinkMobility/QueryParameters/IQueryParameter.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AMWD.Net.Api.LinkMobility -{ - /// - /// Represents options defined via query parameters. - /// - public interface IQueryParameter - { - /// - /// Retrieves the query parameters. - /// - IReadOnlyDictionary GetQueryParameters(); - } -} diff --git a/src/LinkMobility/Enums/AddressType.cs b/src/LinkMobility/Text/AddressType.cs similarity index 90% rename from src/LinkMobility/Enums/AddressType.cs rename to src/LinkMobility/Text/AddressType.cs index 465eda5..a07b285 100644 --- a/src/LinkMobility/Enums/AddressType.cs +++ b/src/LinkMobility/Text/AddressType.cs @@ -1,36 +1,36 @@ -using System.Runtime.Serialization; -using Newtonsoft.Json.Converters; - -namespace AMWD.Net.Api.LinkMobility -{ - /// - /// Specifies the type of sender address. - /// - [JsonConverter(typeof(StringEnumConverter))] - public enum AddressType - { - /// - /// National number. - /// - [EnumMember(Value = "national")] - National = 1, - - /// - /// International number. - /// - [EnumMember(Value = "international")] - International = 2, - - /// - /// Alphanumeric sender ID. - /// - [EnumMember(Value = "alphanumeric")] - Alphanumeric = 3, - - /// - /// Shortcode. - /// - [EnumMember(Value = "shortcode")] - Shortcode = 4, - } -} +using System.Runtime.Serialization; +using Newtonsoft.Json.Converters; + +namespace AMWD.Net.Api.LinkMobility.Text +{ + /// + /// Specifies the type of sender address. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum AddressType + { + /// + /// National number. + /// + [EnumMember(Value = "national")] + National = 1, + + /// + /// International number. + /// + [EnumMember(Value = "international")] + International = 2, + + /// + /// Alphanumeric sender ID. + /// + [EnumMember(Value = "alphanumeric")] + Alphanumeric = 3, + + /// + /// Shortcode. + /// + [EnumMember(Value = "shortcode")] + Shortcode = 4, + } +} diff --git a/src/LinkMobility/Enums/DeliveryType.cs b/src/LinkMobility/Text/DeliveryType.cs similarity index 90% rename from src/LinkMobility/Enums/DeliveryType.cs rename to src/LinkMobility/Text/DeliveryType.cs index 84a4b10..e80bb40 100644 --- a/src/LinkMobility/Enums/DeliveryType.cs +++ b/src/LinkMobility/Text/DeliveryType.cs @@ -1,36 +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 - } -} +using System.Runtime.Serialization; +using Newtonsoft.Json.Converters; + +namespace AMWD.Net.Api.LinkMobility.Text +{ + /// + /// 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/Text/MessageType.cs similarity index 88% rename from src/LinkMobility/Enums/MessageType.cs rename to src/LinkMobility/Text/MessageType.cs index 474c002..8fb2e50 100644 --- a/src/LinkMobility/Enums/MessageType.cs +++ b/src/LinkMobility/Text/MessageType.cs @@ -1,24 +1,24 @@ -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, - } -} +using System.Runtime.Serialization; +using Newtonsoft.Json.Converters; + +namespace AMWD.Net.Api.LinkMobility.Text +{ + /// + /// 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/Requests/SendBinaryMessageRequest.cs b/src/LinkMobility/Text/SendBinaryMessageRequest.cs similarity index 96% rename from src/LinkMobility/Requests/SendBinaryMessageRequest.cs rename to src/LinkMobility/Text/SendBinaryMessageRequest.cs index f1939dd..8063625 100644 --- a/src/LinkMobility/Requests/SendBinaryMessageRequest.cs +++ b/src/LinkMobility/Text/SendBinaryMessageRequest.cs @@ -1,130 +1,130 @@ -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. - /// - /// A binary message as base64 encoded lines. - /// A list of recipient numbers. - public SendBinaryMessageRequest(IReadOnlyCollection messageContent, IReadOnlyCollection recipientAddressList) - { - MessageContent = messageContent; - 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; } - } -} +namespace AMWD.Net.Api.LinkMobility.Text +{ + /// + /// Request to send a text message to a list of recipients. + /// + public class SendBinaryMessageRequest + { + /// + /// Initializes a new instance of the class. + /// + /// A binary message as base64 encoded lines. + /// A list of recipient numbers. + public SendBinaryMessageRequest(IReadOnlyCollection messageContent, IReadOnlyCollection recipientAddressList) + { + MessageContent = messageContent; + 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/Text/SendTextMessageRequest.cs similarity index 96% rename from src/LinkMobility/Requests/SendTextMessageRequest.cs rename to src/LinkMobility/Text/SendTextMessageRequest.cs index c8d07e9..a919be0 100644 --- a/src/LinkMobility/Requests/SendTextMessageRequest.cs +++ b/src/LinkMobility/Text/SendTextMessageRequest.cs @@ -1,139 +1,139 @@ -namespace AMWD.Net.Api.LinkMobility -{ - /// - /// Request to send a text message to a list of recipients. - /// - public class SendTextMessageRequest - { - /// - /// Initializes a new instance of the class. - /// - /// A text message. - /// A list of recipient numbers. - public SendTextMessageRequest(string messageContent, IReadOnlyCollection recipientAddressList) - { - MessageContent = messageContent; - 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. - /// Specifies the maximum number of SMS to be generated. - /// - /// - /// If the system generates more than this number of SMS, the status code is returned. - /// The default value of this parameter is 0. - /// If set to 0, no limitation is applied. - /// - [JsonProperty("maxSmsPerMessage")] - public int? MaxSmsPerMessage { get; set; } - - /// - /// UTF-8 encoded message content. - /// - [JsonProperty("messageContent")] - public string MessageContent { get; set; } - - /// - /// Optional. - /// Specifies the message type. - /// - /// - /// Allowed values are and . - /// When using the message type , the outgoing message type is determined based on account settings. - /// Using the message type triggers a voice call. - /// - [JsonProperty("messageType")] - public MessageType? MessageType { 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. - /// 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; } - } -} +namespace AMWD.Net.Api.LinkMobility.Text +{ + /// + /// Request to send a text message to a list of recipients. + /// + public class SendTextMessageRequest + { + /// + /// Initializes a new instance of the class. + /// + /// A text message. + /// A list of recipient numbers. + public SendTextMessageRequest(string messageContent, IReadOnlyCollection recipientAddressList) + { + MessageContent = messageContent; + 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. + /// Specifies the maximum number of SMS to be generated. + /// + /// + /// If the system generates more than this number of SMS, the status code is returned. + /// The default value of this parameter is 0. + /// If set to 0, no limitation is applied. + /// + [JsonProperty("maxSmsPerMessage")] + public int? MaxSmsPerMessage { get; set; } + + /// + /// UTF-8 encoded message content. + /// + [JsonProperty("messageContent")] + public string MessageContent { get; set; } + + /// + /// Optional. + /// Specifies the message type. + /// + /// + /// Allowed values are and . + /// When using the message type , the outgoing message type is determined based on account settings. + /// Using the message type triggers a voice call. + /// + [JsonProperty("messageType")] + public MessageType? MessageType { 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. + /// 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/TextMessageExtensions.cs b/src/LinkMobility/Text/TextMessageExtensions.cs similarity index 98% rename from src/LinkMobility/TextMessageExtensions.cs rename to src/LinkMobility/Text/TextMessageExtensions.cs index 45dd4c9..43b400f 100644 --- a/src/LinkMobility/TextMessageExtensions.cs +++ b/src/LinkMobility/Text/TextMessageExtensions.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using AMWD.Net.Api.LinkMobility.Utils; -namespace AMWD.Net.Api.LinkMobility +namespace AMWD.Net.Api.LinkMobility.Text { /// /// Implementation of text messaging (SMS). API diff --git a/src/LinkMobility/Utils/SerializerExtensions.cs b/src/LinkMobility/Utils/SerializerExtensions.cs index 2fd0596..3299cc8 100644 --- a/src/LinkMobility/Utils/SerializerExtensions.cs +++ b/src/LinkMobility/Utils/SerializerExtensions.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace AMWD.Net.Api.LinkMobility +namespace AMWD.Net.Api.LinkMobility.Utils { internal static class SerializerExtensions { diff --git a/src/LinkMobility/Models/IncomingMessageNotificationResponse.cs b/src/LinkMobility/Webhook/NotificationResponse.cs similarity index 68% rename from src/LinkMobility/Models/IncomingMessageNotificationResponse.cs rename to src/LinkMobility/Webhook/NotificationResponse.cs index a1e04ca..c9da92d 100644 --- a/src/LinkMobility/Models/IncomingMessageNotificationResponse.cs +++ b/src/LinkMobility/Webhook/NotificationResponse.cs @@ -1,27 +1,33 @@ -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(); - } -} +using AMWD.Net.Api.LinkMobility.Utils; + +namespace AMWD.Net.Api.LinkMobility.Webhook +{ + /// + /// Representes the response to an incoming message notification. + /// (See API) + /// + /// + /// This notification acknowlegement is the same for all webhooks of LINK Mobility. + /// + public class NotificationResponse + { + /// + /// 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/Enums/DeliveryStatus.cs b/src/LinkMobility/Webhook/Text/DeliveryStatus.cs similarity index 92% rename from src/LinkMobility/Enums/DeliveryStatus.cs rename to src/LinkMobility/Webhook/Text/DeliveryStatus.cs index 36c4bbf..4ab2193 100644 --- a/src/LinkMobility/Enums/DeliveryStatus.cs +++ b/src/LinkMobility/Webhook/Text/DeliveryStatus.cs @@ -1,48 +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 - } -} +using System.Runtime.Serialization; +using Newtonsoft.Json.Converters; + +namespace AMWD.Net.Api.LinkMobility.Webhook.Text +{ + /// + /// 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/Webhook/Text/TextMessageType.cs b/src/LinkMobility/Webhook/Text/TextMessageType.cs new file mode 100644 index 0000000..7e7983e --- /dev/null +++ b/src/LinkMobility/Webhook/Text/TextMessageType.cs @@ -0,0 +1,30 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json.Converters; + +namespace AMWD.Net.Api.LinkMobility.Webhook.Text +{ + /// + /// Defines the type of notification. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum TextMessageType + { + /// + /// 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 + } +} diff --git a/src/LinkMobility/Models/IncomingMessageNotification.cs b/src/LinkMobility/Webhook/Text/TextNotification.cs similarity index 67% rename from src/LinkMobility/Models/IncomingMessageNotification.cs rename to src/LinkMobility/Webhook/Text/TextNotification.cs index 552b527..290df9b 100644 --- a/src/LinkMobility/Models/IncomingMessageNotification.cs +++ b/src/LinkMobility/Webhook/Text/TextNotification.cs @@ -1,194 +1,170 @@ -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; - } - } - } -} +using AMWD.Net.Api.LinkMobility.Text; +using AMWD.Net.Api.LinkMobility.Utils; + +namespace AMWD.Net.Api.LinkMobility.Webhook.Text +{ + /// + /// Represents a notification for an incoming text message or delivery report. + /// (API) + /// + public class TextNotification + { + /// + /// Initializes a new instance of the class. + /// + /// The notification id. + /// The transfer id. + public TextNotification(string notificationId, string transferId) + { + NotificationId = notificationId; + TransferId = transferId; + } + + /// + /// Defines the content type of your notification. + /// + [JsonProperty("messageType")] + public TextMessageType 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; } + + /// + /// 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 TextNotification? notification) + { + try + { + notification = json.DeserializeObject(); + return notification != null; + } + catch + { + notification = null; + return false; + } + } + } +} diff --git a/test/LinkMobility.Tests/LinkMobilityClientTest.cs b/test/LinkMobility.Tests/LinkMobilityClientTest.cs index 8e10922..00e82ea 100644 --- a/test/LinkMobility.Tests/LinkMobilityClientTest.cs +++ b/test/LinkMobility.Tests/LinkMobilityClientTest.cs @@ -145,7 +145,7 @@ namespace LinkMobility.Tests var client = GetClient(); // Act - var response = await client.PostAsync("test", _request, null, TestContext.CancellationToken); + var response = await client.PostAsync("test", _request, TestContext.CancellationToken); // Assert Assert.IsNotNull(response); @@ -166,57 +166,12 @@ namespace LinkMobility.Tests 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()); + _httpMessageHandlerMock.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Exactly(2)); VerifyNoOtherCalls(); } - [TestMethod] - public async Task ShouldAddCustomQueryParameters() - { - // Arrange - var queryParams = new TestParams(); - _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(@"{ ""string"": ""some-string"", ""integer"": 123 }", Encoding.UTF8, "application/json"), - }); - - var client = GetClient(); - - // Act - var response = await client.PostAsync("params/path", _request, queryParams, TestContext.CancellationToken); - - // Assert - Assert.IsNotNull(response); - - Assert.HasCount(1, _httpMessageHandlerMock.RequestCallbacks); - - var callback = _httpMessageHandlerMock.RequestCallbacks.First(); - Assert.AreEqual(HttpMethod.Post, callback.HttpMethod); - Assert.AreEqual("https://localhost/rest/params/path?test=query+text", callback.Url); - Assert.AreEqual(@"{""string"":""Happy Testing"",""integer"":54321}", 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 ShouldDisposeHttpClient() { @@ -227,9 +182,7 @@ namespace LinkMobility.Tests client.Dispose(); // Assert - _httpMessageHandlerMock.Mock - .Protected() - .Verify("Dispose", Times.Once(), exactParameterMatch: true, true); + _httpMessageHandlerMock.Protected.Verify("Dispose", Times.Once(), exactParameterMatch: true, true); VerifyNoOtherCalls(); } @@ -245,9 +198,7 @@ namespace LinkMobility.Tests client.Dispose(); // Assert - _httpMessageHandlerMock.Mock - .Protected() - .Verify("Dispose", Times.Once(), exactParameterMatch: true, true); + _httpMessageHandlerMock.Protected.Verify("Dispose", Times.Once(), exactParameterMatch: true, true); VerifyNoOtherCalls(); } @@ -320,7 +271,7 @@ namespace LinkMobility.Tests // Act & Assert await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync("/request/path", _request, null, TestContext.CancellationToken); + await client.PostAsync("/request/path", _request, TestContext.CancellationToken); }); } @@ -336,7 +287,7 @@ namespace LinkMobility.Tests // Act & Assert await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync(path, _request, null, TestContext.CancellationToken); + await client.PostAsync(path, _request, TestContext.CancellationToken); }); } @@ -349,7 +300,7 @@ namespace LinkMobility.Tests // Act & Assert await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync("foo?bar=baz", _request, null, TestContext.CancellationToken); + await client.PostAsync("foo?bar=baz", _request, TestContext.CancellationToken); }); } @@ -366,7 +317,7 @@ namespace LinkMobility.Tests var client = GetClient(); // Act - var response = await client.PostAsync("/request/path", _request, null, TestContext.CancellationToken); + var response = await client.PostAsync("/request/path", _request, TestContext.CancellationToken); // Assert Assert.IsNotNull(response); @@ -389,9 +340,7 @@ namespace LinkMobility.Tests 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()); + _httpMessageHandlerMock.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); VerifyNoOtherCalls(); @@ -411,7 +360,7 @@ namespace LinkMobility.Tests var client = GetClient(); // Act - var response = await client.PostAsync("/request/path", stringContent, null, TestContext.CancellationToken); + var response = await client.PostAsync("/request/path", stringContent, TestContext.CancellationToken); // Assert Assert.IsNotNull(response); @@ -434,9 +383,7 @@ namespace LinkMobility.Tests 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()); + _httpMessageHandlerMock.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); VerifyNoOtherCalls(); @@ -455,7 +402,7 @@ namespace LinkMobility.Tests var client = GetClient(); // Act - var response = await client.PostAsync("posting", null, null, TestContext.CancellationToken); + var response = await client.PostAsync("posting", null, TestContext.CancellationToken); // Assert Assert.IsNotNull(response); @@ -479,9 +426,7 @@ namespace LinkMobility.Tests 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()); + _httpMessageHandlerMock.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); } [TestMethod] @@ -501,7 +446,7 @@ namespace LinkMobility.Tests // Act & Assert var ex = await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync("foo", _request, null, TestContext.CancellationToken); + await client.PostAsync("foo", _request, TestContext.CancellationToken); }); Assert.IsNull(ex.InnerException); Assert.AreEqual($"HTTP auth missing: {statusCode}", ex.Message); @@ -524,7 +469,7 @@ namespace LinkMobility.Tests // Act & Assert var ex = await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync("foo", _request, null, TestContext.CancellationToken); + await client.PostAsync("foo", _request, TestContext.CancellationToken); }); Assert.IsNull(ex.InnerException); Assert.AreEqual($"Unknown HTTP response: {statusCode}", ex.Message); @@ -545,7 +490,7 @@ namespace LinkMobility.Tests // Act & Assert await Assert.ThrowsExactlyAsync(async () => { - await client.PostAsync("some-path", _request, null, TestContext.CancellationToken); + await client.PostAsync("some-path", _request, TestContext.CancellationToken); }); } @@ -563,7 +508,7 @@ namespace LinkMobility.Tests var client = GetClient(); // Act - string response = await client.PostAsync("path", _request, null, TestContext.CancellationToken); + string response = await client.PostAsync("path", _request, TestContext.CancellationToken); // Assert Assert.IsNotNull(response); @@ -586,9 +531,7 @@ namespace LinkMobility.Tests 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()); + _httpMessageHandlerMock.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); VerifyNoOtherCalls(); @@ -638,16 +581,5 @@ namespace LinkMobility.Tests [JsonProperty("integer")] public int Int { get; set; } } - - private class TestParams : IQueryParameter - { - public IReadOnlyDictionary GetQueryParameters() - { - return new Dictionary - { - { "test", "query text" } - }; - } - } } } diff --git a/test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs b/test/LinkMobility.Tests/Text/SendBinaryMessageTest.cs similarity index 96% rename from test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs rename to test/LinkMobility.Tests/Text/SendBinaryMessageTest.cs index 8fc4fec..8f5bcf7 100644 --- a/test/LinkMobility.Tests/Sms/SendBinaryMessageTest.cs +++ b/test/LinkMobility.Tests/Text/SendBinaryMessageTest.cs @@ -1,302 +1,303 @@ -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(["SGVsbG8gV29ybGQ="], ["436991234567"]); // "Hello World" in 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.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - - _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); - VerifyNoOtherCalls(); - } - - [TestMethod] - public void ShouldThrowOnInvalidContentCategoryForBinary() - { - // Arrange - _request.ContentCategory = 0; - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); - Assert.AreEqual("contentCategory", ex.ParamName); - Assert.StartsWith("Content category '0' is not valid.", ex.Message); - - 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.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 ShouldThrowOnNullMessageContentList() - { - // Arrange - _request.MessageContent = null; - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); - - Assert.AreEqual("MessageContent", ex.ParamName); - - VerifyNoOtherCalls(); - } - - [TestMethod] - public void ShouldThrowOnEmptyMessageContentList() - { - // Arrange - _request.MessageContent = []; - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); - - Assert.AreEqual("MessageContent", 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; - } - } -} +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 AMWD.Net.Api.LinkMobility.Text; +using LinkMobility.Tests.Helpers; +using Moq.Protected; + +namespace LinkMobility.Tests.Text +{ + [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(["SGVsbG8gV29ybGQ="], ["436991234567"]); // "Hello World" in 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.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); + + _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); + VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldThrowOnInvalidContentCategoryForBinary() + { + // Arrange + _request.ContentCategory = 0; + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); + Assert.AreEqual("contentCategory", ex.ParamName); + Assert.StartsWith("Content category '0' is not valid.", ex.Message); + + 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.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 ShouldThrowOnNullMessageContentList() + { + // Arrange + _request.MessageContent = null; + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); + + Assert.AreEqual("MessageContent", ex.ParamName); + + VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldThrowOnEmptyMessageContentList() + { + // Arrange + _request.MessageContent = []; + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendBinaryMessage(_request, TestContext.CancellationToken)); + + Assert.AreEqual("MessageContent", 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/Sms/SendTextMessageTest.cs b/test/LinkMobility.Tests/Text/SendTextMessageTest.cs similarity index 97% rename from test/LinkMobility.Tests/Sms/SendTextMessageTest.cs rename to test/LinkMobility.Tests/Text/SendTextMessageTest.cs index 0229f6f..27ec0c0 100644 --- a/test/LinkMobility.Tests/Sms/SendTextMessageTest.cs +++ b/test/LinkMobility.Tests/Text/SendTextMessageTest.cs @@ -1,262 +1,263 @@ -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 SendTextMessageTest - { - public TestContext TestContext { get; set; } - - private const string BASE_URL = "https://localhost/rest/"; - - private Mock _authenticationMock; - private Mock _clientOptionsMock; - private HttpMessageHandlerMock _httpMessageHandlerMock; - - private SendTextMessageRequest _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 SendTextMessageRequest("example message content", ["436991234567"]); - } - - [TestMethod] - public async Task ShouldSendTextMessage() - { - // Arrange - _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(@"{ ""clientMessageId"": ""myUniqueId"", ""smsCount"": 1, ""statusCode"": 2000, ""statusMessage"": ""OK"", ""transferId"": ""0059d0b20100a0a8b803"" }", Encoding.UTF8, "application/json"), - }); - - var client = GetClient(); - - // Act - var response = await client.SendTextMessage(_request, TestContext.CancellationToken); - - // 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"":""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.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - - _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); - VerifyNoOtherCalls(); - } - - [TestMethod] - public void ShouldThrowOnInvalidContentCategory() - { - // Arrange - _request.ContentCategory = 0; - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendTextMessage(_request, TestContext.CancellationToken)); - Assert.AreEqual("contentCategory", ex.ParamName); - Assert.StartsWith("Content category '0' is not valid.", ex.Message); - - 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")); - 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.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.SendTextMessage(null, TestContext.CancellationToken)); - Assert.AreEqual("request", ex.ParamName); - - VerifyNoOtherCalls(); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - public void ShouldThrowOnMissingMessage(string message) - { - // Arrange - var req = new SendTextMessageRequest(message, ["4791234567"]); - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, TestContext.CancellationToken)); - Assert.AreEqual("MessageContent", ex.ParamName); - - VerifyNoOtherCalls(); - } - - [TestMethod] - public void ShouldThrowOnNoRecipients() - { - // Arrange - var req = new SendTextMessageRequest("Hello", []); - var client = GetClient(); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, TestContext.CancellationToken)); - Assert.AreEqual("recipientAddressList", ex.ParamName); - - VerifyNoOtherCalls(); - } - - [TestMethod] - [DataRow(null)] - [DataRow("")] - [DataRow(" ")] - [DataRow("invalid-recipient")] - public void ShouldThrowOnInvalidRecipient(string recipient) - { - // Arrange - var client = GetClient(); - var req = new SendTextMessageRequest("Hello", ["4791234567", recipient]); - - // Act & Assert - var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, 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; - } - } -} +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 AMWD.Net.Api.LinkMobility.Text; +using LinkMobility.Tests.Helpers; +using Moq.Protected; + +namespace LinkMobility.Tests.Text +{ + [TestClass] + public class SendTextMessageTest + { + public TestContext TestContext { get; set; } + + private const string BASE_URL = "https://localhost/rest/"; + + private Mock _authenticationMock; + private Mock _clientOptionsMock; + private HttpMessageHandlerMock _httpMessageHandlerMock; + + private SendTextMessageRequest _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 SendTextMessageRequest("example message content", ["436991234567"]); + } + + [TestMethod] + public async Task ShouldSendTextMessage() + { + // Arrange + _httpMessageHandlerMock.Responses.Enqueue(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@"{ ""clientMessageId"": ""myUniqueId"", ""smsCount"": 1, ""statusCode"": 2000, ""statusMessage"": ""OK"", ""transferId"": ""0059d0b20100a0a8b803"" }", Encoding.UTF8, "application/json"), + }); + + var client = GetClient(); + + // Act + var response = await client.SendTextMessage(_request, TestContext.CancellationToken); + + // 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"":""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.Protected.Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); + + _clientOptionsMock.VerifyGet(o => o.DefaultQueryParams, Times.Once); + VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldThrowOnInvalidContentCategory() + { + // Arrange + _request.ContentCategory = 0; + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendTextMessage(_request, TestContext.CancellationToken)); + Assert.AreEqual("contentCategory", ex.ParamName); + Assert.StartsWith("Content category '0' is not valid.", ex.Message); + + 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")); + 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.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.SendTextMessage(null, TestContext.CancellationToken)); + Assert.AreEqual("request", ex.ParamName); + + VerifyNoOtherCalls(); + } + + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public void ShouldThrowOnMissingMessage(string message) + { + // Arrange + var req = new SendTextMessageRequest(message, ["4791234567"]); + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, TestContext.CancellationToken)); + Assert.AreEqual("MessageContent", ex.ParamName); + + VerifyNoOtherCalls(); + } + + [TestMethod] + public void ShouldThrowOnNoRecipients() + { + // Arrange + var req = new SendTextMessageRequest("Hello", []); + var client = GetClient(); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, TestContext.CancellationToken)); + Assert.AreEqual("recipientAddressList", ex.ParamName); + + VerifyNoOtherCalls(); + } + + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + [DataRow("invalid-recipient")] + public void ShouldThrowOnInvalidRecipient(string recipient) + { + // Arrange + var client = GetClient(); + var req = new SendTextMessageRequest("Hello", ["4791234567", recipient]); + + // Act & Assert + var ex = Assert.ThrowsExactly(() => client.SendTextMessage(req, 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/Models/IncomingMessageNotificationTest.cs b/test/LinkMobility.Tests/Webhook/Text/TextNotificationTest.cs similarity index 86% rename from test/LinkMobility.Tests/Models/IncomingMessageNotificationTest.cs rename to test/LinkMobility.Tests/Webhook/Text/TextNotificationTest.cs index 966aaf6..45c8bf0 100644 --- a/test/LinkMobility.Tests/Models/IncomingMessageNotificationTest.cs +++ b/test/LinkMobility.Tests/Webhook/Text/TextNotificationTest.cs @@ -1,90 +1,91 @@ -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); - } - } -} +using AMWD.Net.Api.LinkMobility.Text; +using AMWD.Net.Api.LinkMobility.Webhook.Text; + +namespace LinkMobility.Tests.Webhook.Text +{ + [TestClass] + public class TextNotificationTest + { + [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 = TextNotification.TryParse(json, out var notification); + + // Assert + Assert.IsTrue(successful, "TryParse should return true for valid json"); + Assert.IsNotNull(notification); + + Assert.AreEqual(TextMessageType.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 = TextNotification.TryParse(invalid, out var notification); + + // Assert + Assert.IsFalse(successful); + Assert.IsNull(notification); + } + } +}