From f0ca2ca47218215f32d467fcf214b827eac9ad3a Mon Sep 17 00:00:00 2001 From: jakublabno Date: Thu, 16 Nov 2023 12:41:31 +0100 Subject: [PATCH 001/142] Profile prices lookup feature --- .../Prices/Fixture/PricesCollectionMother.cs | 58 +++++++++++++++ .../Action/Profile/Prices/GetPricesTest.cs | 73 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs create mode 100644 smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs diff --git a/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs new file mode 100644 index 0000000..f09362d --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; + +namespace smsapiTests.Unit.Action.Profile.Prices.Fixture; + +public static class PricesCollectionMother +{ + public static Dictionary EmptyCollection() + { + return new Dictionary + { + { "collection", new List() } + }; + } + + public static Dictionary SinglePrice( + float amount, + string currency, + string countryName, + int mcc, + string networkName, + int mnc + ) + { + return new Dictionary + { + { + "collection", new List + { + new Dictionary> + { + { + "price", new Dictionary + { + { "amount", amount }, + { "currency", currency } + } + }, + { + "country", new Dictionary + { + { "name", countryName }, + { "mcc", mcc } + } + }, + { + "network", new Dictionary + { + { "name", networkName }, + { "mnc", mnc } + } + } + } + } + }, + { "size", 1 } + }; + } +} diff --git a/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs b/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs new file mode 100644 index 0000000..6e3e565 --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Profile.Prices; +using smsapiTests.Unit.Action.Profile.Prices.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Profile.Prices; + +[TestClass] +public class GetPricesTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = PricesCollectionMother.EmptyCollection(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetPrices().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_prices() + { + var amount = 15.14f; + var currency = "EUR"; + var countryName = "USA"; + var mcc = 310; + var networkName = "Verizon"; + var mnc = 10; + + var response = PricesCollectionMother.SinglePrice( + amount, + currency, + countryName, + mcc, + networkName, + mnc + ); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetPrices().Execute(); + + Assert.AreEqual(1, result.Size); + var firstResult = result.Collection.First(); + Assert.AreEqual(countryName, firstResult.Country.Name); + Assert.AreEqual(mcc, firstResult.Country.MCC); + Assert.AreEqual(networkName, firstResult.Network.Name); + Assert.AreEqual(mnc, firstResult.Network.MNC); + Assert.AreEqual(amount, firstResult.Price.Amount); + Assert.AreEqual(currency, firstResult.Price.Currency); + } + + private GetPrices GetPrices() + { + var action = new GetPrices(); + action.Proxy(_proxyStub); + + return action; + } +} From a158326b1e7a82448da8ee9e62e2618b451ea108 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Thu, 16 Nov 2023 13:39:06 +0100 Subject: [PATCH 002/142] Profile prices lookup feature --- smsapi/Api/Response/Profile/Prices/PriceResponse.cs | 2 ++ .../Action/Profile/Prices/Fixture/PricesCollectionMother.cs | 6 ++++-- smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs index b0bfe42..c60dbc0 100644 --- a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs +++ b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs @@ -10,6 +10,8 @@ public readonly struct PriceResponse [DataMember(Name = "country")] public readonly Country Country; [DataMember(Name = "network")] public readonly Network Network; + + [DataMember(Name = "type")] public readonly string Type; } [DataContract] diff --git a/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs index f09362d..1981910 100644 --- a/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs +++ b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs @@ -18,7 +18,8 @@ public static Dictionary SinglePrice( string countryName, int mcc, string networkName, - int mnc + int mnc, + string type ) { return new Dictionary @@ -26,8 +27,9 @@ int mnc { "collection", new List { - new Dictionary> + new Dictionary { + { "type", type }, { "price", new Dictionary { diff --git a/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs b/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs index 6e3e565..8e87169 100644 --- a/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs +++ b/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs @@ -37,6 +37,7 @@ public void list_prices() var mcc = 310; var networkName = "Verizon"; var mnc = 10; + var type = "hlr"; var response = PricesCollectionMother.SinglePrice( amount, @@ -44,7 +45,8 @@ public void list_prices() countryName, mcc, networkName, - mnc + mnc, + type ); _proxyStub.SyncExecutionResponse = new HttpResponseEntity( response.ToHttpEntityStreamTask(), @@ -61,6 +63,7 @@ public void list_prices() Assert.AreEqual(mnc, firstResult.Network.MNC); Assert.AreEqual(amount, firstResult.Price.Amount); Assert.AreEqual(currency, firstResult.Price.Currency); + Assert.AreEqual(type, firstResult.Type); } private GetPrices GetPrices() From ea0eb1f4c940b417327b0fdfc82f0256c21a9749 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Thu, 16 Nov 2023 14:24:32 +0100 Subject: [PATCH 003/142] Add blacklist feature --- examples/blacklist/List.cs | 17 ++++++++++++++ smsapi/Api/Action/Blacklist/List.cs | 12 ++++++++++ smsapi/Api/BlackListFactory.cs | 30 +++++++++++++++++++++++++ smsapi/Api/Response/BlacklistRecord.cs | 31 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 examples/blacklist/List.cs create mode 100644 smsapi/Api/Action/Blacklist/List.cs create mode 100644 smsapi/Api/BlackListFactory.cs create mode 100644 smsapi/Api/Response/BlacklistRecord.cs diff --git a/examples/blacklist/List.cs b/examples/blacklist/List.cs new file mode 100644 index 0000000..08dea0e --- /dev/null +++ b/examples/blacklist/List.cs @@ -0,0 +1,17 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var result = features.Blacklist() + .List() + .Execute(); + +result.Collection.ForEach(record => + { + Console.WriteLine($"ID: {record.Id}"); + Console.WriteLine($"Phone number: {record.PhoneNumber}"); + Console.WriteLine($"Created at: {record.DateCreated}"); + Console.WriteLine($"Expiring at: {record.DateExpired}"); + } +); diff --git a/smsapi/Api/Action/Blacklist/List.cs b/smsapi/Api/Action/Blacklist/List.cs new file mode 100644 index 0000000..588a0fb --- /dev/null +++ b/smsapi/Api/Action/Blacklist/List.cs @@ -0,0 +1,12 @@ +using SMSApi.Api.Response; + +namespace SMSApi.Api.Action.Blacklist; + +public class List : Action> +{ + protected override RequestMethod Method => RequestMethod.GET; + + protected override string Uri() => "blacklist/phone_numbers"; + + protected override ApiType ApiType() => Action.ApiType.Rest; +} diff --git a/smsapi/Api/BlackListFactory.cs b/smsapi/Api/BlackListFactory.cs new file mode 100644 index 0000000..472ee91 --- /dev/null +++ b/smsapi/Api/BlackListFactory.cs @@ -0,0 +1,30 @@ +using SMSApi.Api.Action.Blacklist; + +namespace SMSApi.Api; + +public class BlackListFactory : Factory +{ + public BlackListFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) : base(client, address) + { + } + + public BlackListFactory(IClient client, Proxy proxy) : base(client, proxy) + { + } + + public List List() + { + var service = new List(); + service.Proxy(proxy); + + return service; + } +} + +public static class BlacklistFeatureRegister +{ + public static BlackListFactory Blacklist(this Features features) + { + return new BlackListFactory(features.Client, features.Proxy); + } +} diff --git a/smsapi/Api/Response/BlacklistRecord.cs b/smsapi/Api/Response/BlacklistRecord.cs new file mode 100644 index 0000000..d098cea --- /dev/null +++ b/smsapi/Api/Response/BlacklistRecord.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.Serialization; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response; + +[DataContract] +public record struct BlacklistRecord : IResponseCodeAwareResolver +{ + [DataMember(Name = "id")] public readonly string Id; + + [DataMember(Name = "phone_number")] public readonly string PhoneNumber; + + public DateTime DateCreated; + + public DateTime? DateExpired; + + [DataMember(Name = "created_at")] + private string DateCreatedDeserializer + { + set => DateCreated = DateTime.Parse(value); + get => default; + } + + [DataMember(Name = "expire_at")] + private string? DateExpiredDeserializer + { + set => DateExpired = value != null ? DateTime.Parse(value) : null; + get => default; + } +} From b77b9f11d8db9e3bc5fd01cec5826c12b3d49a0e Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 10:53:45 +0100 Subject: [PATCH 004/142] Pagination support --- smsapi/Api/Action/Action.cs | 24 ++++- smsapi/Api/Action/ActionPaginationHelper.cs | 22 +++++ smsapi/Api/Action/Blacklist/List.cs | 5 +- smsapi/Api/Action/IPaginable.cs | 8 ++ .../Unit/Action/ActionPaginationTest.cs | 88 +++++++++++++++++++ 5 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 smsapi/Api/Action/ActionPaginationHelper.cs create mode 100644 smsapi/Api/Action/IPaginable.cs create mode 100644 smsapiTests/Unit/Action/ActionPaginationTest.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 2f8b9de..e28e4f9 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Threading; @@ -24,13 +25,13 @@ protected virtual ApiType ApiType() public T Execute() { Validate(); - return ProcessResponse(_proxy.Execute(Uri(), GetValues(), Files(), Method)); + return ProcessResponse(_proxy.Execute(UriWithPagination(), GetValues(), Files(), Method)); } public async Task ExecuteAsync(CancellationToken cancellationToken = default) { Validate(); - return ProcessResponse(await _proxy.ExecuteAsync(Uri(), GetValues(), Files(), Method, cancellationToken)); + return ProcessResponse(await _proxy.ExecuteAsync(UriWithPagination(), GetValues(), Files(), Method, cancellationToken)); } public void Proxy(Proxy proxy) @@ -64,7 +65,7 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of return deserializationResult.Result; } - protected abstract string Uri(); + protected abstract string Uri(); protected virtual void Validate() { @@ -75,6 +76,20 @@ protected virtual NameValueCollection Values() return new NameValueCollection(); } + private string UriWithPagination() + { + if (!typeof(IPaginable).IsAssignableFrom(GetType())) return Uri(); + + var uri = new UriBuilder + { + Path = Uri() + }; + + var action = (IPaginable) this; + + return uri.ToUriWithPagination(action.Limit, action.Offset); + } + private T ProcessResponse(HttpResponseEntity responseEntity) { return ResponseToObject(responseEntity); @@ -83,6 +98,7 @@ private T ProcessResponse(HttpResponseEntity responseEntity) private NameValueCollection GetValues() { var values = Values(); + return values.Count > 0 ? new NameValueCollection { { "format", "json" }, values } : HttpUtility.ParseQueryString(string.Empty); diff --git a/smsapi/Api/Action/ActionPaginationHelper.cs b/smsapi/Api/Action/ActionPaginationHelper.cs new file mode 100644 index 0000000..13babe2 --- /dev/null +++ b/smsapi/Api/Action/ActionPaginationHelper.cs @@ -0,0 +1,22 @@ +using System; +using System.Web; + +namespace SMSApi.Api.Action; + +public static class ActionPaginationHelper +{ + public static string ToUriWithPagination(this UriBuilder uriBuilder, int? limit, int? offset) + { + var query = HttpUtility.ParseQueryString(uriBuilder.Query); + + if (limit != null) + query.Add("limit", limit.ToString()); + + if (offset != null) + query.Add("offset", offset.ToString()); + + uriBuilder.Query = query.ToString(); + + return uriBuilder.Path + uriBuilder.Query; + } +} diff --git a/smsapi/Api/Action/Blacklist/List.cs b/smsapi/Api/Action/Blacklist/List.cs index 588a0fb..86dcd62 100644 --- a/smsapi/Api/Action/Blacklist/List.cs +++ b/smsapi/Api/Action/Blacklist/List.cs @@ -2,11 +2,14 @@ namespace SMSApi.Api.Action.Blacklist; -public class List : Action> +public class List : Action>, IPaginable { protected override RequestMethod Method => RequestMethod.GET; protected override string Uri() => "blacklist/phone_numbers"; protected override ApiType ApiType() => Action.ApiType.Rest; + + public int? Limit { get; set; } + public int? Offset { get; set; } } diff --git a/smsapi/Api/Action/IPaginable.cs b/smsapi/Api/Action/IPaginable.cs new file mode 100644 index 0000000..d36bdd6 --- /dev/null +++ b/smsapi/Api/Action/IPaginable.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Action; + +public interface IPaginable +{ + int? Limit { get; set; } + + int? Offset { get; set; } +} diff --git a/smsapiTests/Unit/Action/ActionPaginationTest.cs b/smsapiTests/Unit/Action/ActionPaginationTest.cs new file mode 100644 index 0000000..9905338 --- /dev/null +++ b/smsapiTests/Unit/Action/ActionPaginationTest.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; + +namespace smsapiTests.Unit.Action; + +[TestClass] +public class ActionPaginationTest +{ + private string Path = "blacklist/phone_numbers"; + + private readonly SpyProxy _spyProxy = new(); + + [TestMethod] + public void raw_uri() + { + var action = GetAction(); + + action.Execute(); + + Assert.AreEqual("blacklist/phone_numbers", _spyProxy.RequestedUri); + } + + [TestMethod] + public void add_limit_to_uri() + { + var limit = 10; + var action = GetAction(); + action.Limit = limit; + + action.Execute(); + + Assert.AreEqual("blacklist/phone_numbers?limit=10", _spyProxy.RequestedUri); + } + + [TestMethod] + public void add_offset_to_uri() + { + var offset = 10; + var action = GetAction(); + action.Offset = offset; + + action.Execute(); + + Assert.AreEqual("blacklist/phone_numbers?offset=10", _spyProxy.RequestedUri); + } + + [TestMethod] + public void add_limit_and_offset_to_uri() + { + var limit = 5; + var offset = 10; + var action = GetAction(); + action.Limit = limit; + action.Offset = offset; + + action.Execute(); + + Assert.AreEqual("blacklist/phone_numbers?limit=5&offset=10", _spyProxy.RequestedUri); + } + + private PaginableAction GetAction() + { + var action = new PaginableAction(Path); + action.Proxy(_spyProxy); + + return action; + } + + private class PaginableAction : Action, IPaginable + { + private readonly string Path; + + public PaginableAction(string path) + { + Path = path; + } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override string Uri() => Path; + + public int? Limit { get; set; } + public int? Offset { get; set; } + } + + private class Response{} +} From cc0a330b1f858a073414a22621ac19b20db354a3 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 11:32:13 +0100 Subject: [PATCH 005/142] Add blacklist feature --- smsapiTests/Unit/Action/Blacklist/ListTest.cs | 69 +++++++++++++++++++ .../Prices/Fixture/PricesCollectionMother.cs | 9 +-- smsapiTests/Unit/Fixture/CollectionMother.cs | 28 ++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 smsapiTests/Unit/Action/Blacklist/ListTest.cs create mode 100644 smsapiTests/Unit/Fixture/CollectionMother.cs diff --git a/smsapiTests/Unit/Action/Blacklist/ListTest.cs b/smsapiTests/Unit/Action/Blacklist/ListTest.cs new file mode 100644 index 0000000..0468781 --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/ListTest.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Blacklist; +using smsapiTests.Unit.Action.Profile.Prices.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class ListTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = PricesCollectionMother.EmptyCollection(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_numbers() + { + var id = "1238f47da26ee45dc41fb987"; + var phoneNumber = "48500000000"; + var createdAt = "2018-11-08T09:36:53+01:00"; + object? expireAt = null; + var response = new Dictionary + { + { "id", id }, + { "phone_number", phoneNumber }, + { "created_at", createdAt }, + { "expire_at", expireAt } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + CollectionMother.WithItems(response).ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(phoneNumber, firstElement.PhoneNumber); + Assert.AreEqual(DateTime.Parse(createdAt), firstElement.DateCreated); + Assert.AreEqual(expireAt, firstElement.DateExpired); + } + + private List GetList() + { + var action = new List(); + action.Proxy(_proxyStub); + + return action; + } +} \ No newline at end of file diff --git a/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs index 1981910..252847c 100644 --- a/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs +++ b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs @@ -1,16 +1,11 @@ using System.Collections.Generic; +using smsapiTests.Unit.Fixture; namespace smsapiTests.Unit.Action.Profile.Prices.Fixture; public static class PricesCollectionMother { - public static Dictionary EmptyCollection() - { - return new Dictionary - { - { "collection", new List() } - }; - } + public static Dictionary EmptyCollection() => CollectionMother.Empty(); public static Dictionary SinglePrice( float amount, diff --git a/smsapiTests/Unit/Fixture/CollectionMother.cs b/smsapiTests/Unit/Fixture/CollectionMother.cs new file mode 100644 index 0000000..bdbb634 --- /dev/null +++ b/smsapiTests/Unit/Fixture/CollectionMother.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace smsapiTests.Unit.Fixture; + +public static class CollectionMother +{ + public static Dictionary Empty() + { + return new Dictionary + { + { "collection", new List() }, + { "size", 0 } + }; + } + + public static Dictionary WithItems(params Dictionary[] items) + { + return new Dictionary + { + { + "collection", items + }, + { + "size", items.Length + } + }; + } +} From e63c095bd084d6ac456e89d095682ab6b5ef83f8 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 12:10:14 +0100 Subject: [PATCH 006/142] Add blacklist feature #add --- examples/blacklist/Add.cs | 28 +++++++++++++++ smsapi/Api/Action/Blacklist/Add.cs | 55 ++++++++++++++++++++++++++++++ smsapi/Api/BlackListFactory.cs | 8 +++++ 3 files changed, 91 insertions(+) create mode 100644 examples/blacklist/Add.cs create mode 100644 smsapi/Api/Action/Blacklist/Add.cs diff --git a/examples/blacklist/Add.cs b/examples/blacklist/Add.cs new file mode 100644 index 0000000..c6e28a2 --- /dev/null +++ b/examples/blacklist/Add.cs @@ -0,0 +1,28 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string phoneNumber = "48500100100"; + +try +{ + var result = features.Blacklist() + .Add(phoneNumber) + .WithExpireAt(DateTimeOffset.Now) //Set expiration date (optional, DateTimeOffset) + .WithExpireAt(DateTimeOffset.Now.ToUnixTimeSeconds()) //Set expiration date (optional, unixtimestamp) + .Execute(); + + Console.WriteLine($"ID: {result.Id}"); + Console.WriteLine($"Phone number: {result.PhoneNumber}"); + Console.WriteLine($"Created at: {result.DateCreated}"); + Console.WriteLine($"Expiring at: {result.DateExpired}"); +} +catch (ValidationException exception) +{ + foreach (var validationErrorsError in exception.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } +} diff --git a/smsapi/Api/Action/Blacklist/Add.cs b/smsapi/Api/Action/Blacklist/Add.cs new file mode 100644 index 0000000..942253d --- /dev/null +++ b/smsapi/Api/Action/Blacklist/Add.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Specialized; +using SMSApi.Api.Response; + +namespace SMSApi.Api.Action.Blacklist; + +public class Add : Action +{ + private readonly string phoneNumber; + private DateTimeOffset? withExpireAt; + + public Add(string phoneNumber) + { + this.phoneNumber = phoneNumber; + } + + protected override RequestMethod Method => RequestMethod.POST; + + public int? Limit { get; set; } + public int? Offset { get; set; } + + public Add WithExpireAt(DateTimeOffset expireAt) + { + withExpireAt = expireAt; + + return this; + } + + public Add WithExpireAt(long expireAtTimestampSeconds) + { + withExpireAt = DateTimeOffset.FromUnixTimeSeconds(expireAtTimestampSeconds); + + return this; + } + + protected override string Uri() + { + return "blacklist/phone_numbers"; + } + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override NameValueCollection Values() + { + var values = new NameValueCollection { { "phone_number", phoneNumber } }; + + if (withExpireAt != null) + values.Add("expire_at", withExpireAt.Value.ToString("O")); + + return values; + } +} diff --git a/smsapi/Api/BlackListFactory.cs b/smsapi/Api/BlackListFactory.cs index 472ee91..c758360 100644 --- a/smsapi/Api/BlackListFactory.cs +++ b/smsapi/Api/BlackListFactory.cs @@ -19,6 +19,14 @@ public List List() return service; } + + public Add Add(string phoneNumber) + { + var service = new Add(phoneNumber); + service.Proxy(proxy); + + return service; + } } public static class BlacklistFeatureRegister From 51a775f5e78b40f42b32423af0ba5496ac5f2c2a Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 13:04:15 +0100 Subject: [PATCH 007/142] Extract proxy assertion --- .../Unit/Action/MFA/CreateMFACodeTest.cs | 40 ++++++------------- .../Unit/Action/MFA/VerifyMFACodeTest.cs | 20 ++++------ smsapiTests/Unit/ProxyAssert.cs | 33 +++++++++++++++ smsapiTests/Unit/SMS/SMSSendTest.cs | 23 +++++------ 4 files changed, 64 insertions(+), 52 deletions(-) create mode 100644 smsapiTests/Unit/ProxyAssert.cs diff --git a/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs b/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs index 1312fee..2d9f23a 100644 --- a/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs +++ b/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api.Action.MFA; @@ -9,6 +7,12 @@ namespace smsapiTests.Unit.Action.MFA; public class CreateMFACodeTest { private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public CreateMFACodeTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } [TestMethod] public void valid_uri() @@ -27,10 +31,10 @@ public void request_contains_only_phone_number() CreateMfaCodeAction(phoneNumber).Execute(); - AssertParametersContain("phone_number", phoneNumber); - AssertParametersDoesNotContain("content"); - AssertParametersDoesNotContain("fast"); - AssertParametersDoesNotContain("from"); + _proxyAssert.AssertParametersContain("phone_number", phoneNumber); + _proxyAssert.AssertParametersDoesNotContain("content"); + _proxyAssert.AssertParametersDoesNotContain("fast"); + _proxyAssert.AssertParametersDoesNotContain("from"); } [TestMethod] @@ -41,7 +45,7 @@ public void create_as_fast() create.Execute(); - AssertParametersContain("fast", "1"); + _proxyAssert.AssertParametersContain("fast", "1"); } [TestMethod] @@ -53,7 +57,7 @@ public void create_with_content() create.Execute(); - AssertParametersContain("content", content); + _proxyAssert.AssertParametersContain("content", content); } [TestMethod] @@ -65,7 +69,7 @@ public void create_with_sendername() create.Execute(); - AssertParametersContain("from", sendername); + _proxyAssert.AssertParametersContain("from", sendername); } private static string GetAnyPhoneNumber() => "48500100100"; @@ -77,22 +81,4 @@ private CreateMFACode CreateMfaCodeAction(string phoneNumber) return action; } - - private void AssertParametersContain(string name, string value) - { - var expectedParameter = new KeyValuePair(name, value); - - Assert.IsTrue( - _spyProxy.Parameters.Contains(expectedParameter), - $"Expected {value}, actual value: {_spyProxy.Parameters[name]}" - ); - } - - private void AssertParametersDoesNotContain(string name) - { - Assert.IsFalse( - _spyProxy.Parameters.ContainsKey(name), - $"Key not expected {name}" - ); - } } diff --git a/smsapiTests/Unit/Action/MFA/VerifyMFACodeTest.cs b/smsapiTests/Unit/Action/MFA/VerifyMFACodeTest.cs index bb96832..4d52a0f 100644 --- a/smsapiTests/Unit/Action/MFA/VerifyMFACodeTest.cs +++ b/smsapiTests/Unit/Action/MFA/VerifyMFACodeTest.cs @@ -9,6 +9,12 @@ namespace smsapiTests.Unit.Action.MFA; public class VerifyMFACodeTest { private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public VerifyMFACodeTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } [TestMethod] public void valid_uri() @@ -26,8 +32,8 @@ public void request_contains_phone_number_and_code() VerifyMfaCodeAction(phoneNumber, code).Execute(); - AssertParametersContain("phone_number", phoneNumber); - AssertParametersContain("code", code); + _proxyAssert.AssertParametersContain("phone_number", phoneNumber); + _proxyAssert.AssertParametersContain("code", code); } private static string GetAnyPhoneNumber() => "48500100100"; @@ -40,14 +46,4 @@ private VerifyMFACode VerifyMfaCodeAction(string phoneNumber, string code) return action; } - - private void AssertParametersContain(string name, string value) - { - var expectedParameter = new KeyValuePair(name, value); - - Assert.IsTrue( - _spyProxy.Parameters.Contains(expectedParameter), - $"Expected {value}, actual value: {_spyProxy.Parameters[name]}" - ); - } } diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs new file mode 100644 index 0000000..dfad771 --- /dev/null +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace smsapiTests.Unit; + +public class ProxyAssert +{ + private readonly SpyProxy _proxy; + + public ProxyAssert(SpyProxy proxy) + { + _proxy = proxy; + } + + public void AssertParametersContain(string name, string value) + { + var expectedParameter = new KeyValuePair(name, value); + + Assert.IsTrue( + _proxy.Parameters.Contains(value: expectedParameter), + $"Expected {value}, actual value: {_proxy.Parameters[name]}" + ); + } + + public void AssertParametersDoesNotContain(string name) + { + Assert.IsFalse( + _proxy.Parameters.ContainsKey(name), + $"Key not expected {name}" + ); + } +} diff --git a/smsapiTests/Unit/SMS/SMSSendTest.cs b/smsapiTests/Unit/SMS/SMSSendTest.cs index 7989f38..3a53a67 100644 --- a/smsapiTests/Unit/SMS/SMSSendTest.cs +++ b/smsapiTests/Unit/SMS/SMSSendTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api.Action; @@ -13,6 +12,14 @@ public class SMSSendTest : UnitTestBase private const string DateFormat = "yyyy-MM-ddTHH:mm:ssK"; private static readonly DateTime DateTime = DateTime.Now; + + private readonly ProxyAssert _proxyAssert; + + public SMSSendTest() + { + _proxyAssert = new ProxyAssert(SpyProxy); + } + [TestMethod] public void action_has_proper_uri() @@ -37,7 +44,7 @@ public void action_has_parameters_set_by_default(string expectedName, string exp Execute(action); - AssertParametersContain(expectedName, expectedValue); + _proxyAssert.AssertParametersContain(expectedName, expectedValue); } [TestMethod] @@ -67,7 +74,7 @@ public void action_has_proper_parameters_binded(string methodName, object[] meth Execute(action); - AssertParametersContain(expectedParameterName, expectedParameterValue); + _proxyAssert.AssertParametersContain(expectedParameterName, expectedParameterValue); } protected override SMSSend CreateAction() @@ -128,14 +135,4 @@ private static IEnumerable SetTo() { "SetTo", new object[] { recipients }, "to", expectedRecipientsString, typeof(string[]) } }; } - - private void AssertParametersContain(string name, string value) - { - var expectedParameter = new KeyValuePair(name, value); - - Assert.IsTrue( - SpyProxy.Parameters.Contains(expectedParameter), - $"Expected {value}, actual value: {SpyProxy.Parameters[name]}" - ); - } } \ No newline at end of file From 718e9def40851125a95dac39387e7052a986dbd8 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 13:04:27 +0100 Subject: [PATCH 008/142] Add blacklist feature #add --- .../Unit/Action/Blacklist/AddRequestTest.cs | 49 ++++++++++++++++++ .../Unit/Action/Blacklist/AddResponseTest.cs | 51 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 smsapiTests/Unit/Action/Blacklist/AddRequestTest.cs create mode 100644 smsapiTests/Unit/Action/Blacklist/AddResponseTest.cs diff --git a/smsapiTests/Unit/Action/Blacklist/AddRequestTest.cs b/smsapiTests/Unit/Action/Blacklist/AddRequestTest.cs new file mode 100644 index 0000000..14bd4a5 --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/AddRequestTest.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action.Blacklist; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class AddRequestTest +{ + private readonly SpyProxy _proxySpy = new(); + private readonly ProxyAssert _proxyAssert; + + public AddRequestTest() + { + _proxyAssert = new ProxyAssert(_proxySpy); + } + + [TestMethod] + public void request_contains_only_phone_number() + { + var phoneNumber = "48500000000"; + var action = AddAction(phoneNumber); + + action.Execute(); + + _proxyAssert.AssertParametersContain("phone_number", phoneNumber); + _proxyAssert.AssertParametersDoesNotContain("expire_at"); + } + + [TestMethod] + public void request_contains_expiration_date() + { + var expirationDate = DateTimeOffset.Now; + var action = AddAction("48500000000") + .WithExpireAt(expirationDate); + + action.Execute(); + + _proxyAssert.AssertParametersContain("expire_at", expirationDate.ToString("O")); + } + + private Add AddAction(string phoneNumber) + { + var action = new Add(phoneNumber); + action.Proxy(_proxySpy); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/Blacklist/AddResponseTest.cs b/smsapiTests/Unit/Action/Blacklist/AddResponseTest.cs new file mode 100644 index 0000000..cce6ec5 --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/AddResponseTest.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Blacklist; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class AddResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void blacklist_response() + { + var id = "1238f47da26ee45dc41fb987"; + var phoneNumber = "48500000000"; + var createdAt = "2018-11-08T09:36:53+01:00"; + object? expireAt = null; + var response = new Dictionary + { + { "id", id }, + { "phone_number", phoneNumber }, + { "created_at", createdAt }, + { "expire_at", expireAt } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + var result = AddAction(phoneNumber).Execute(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(phoneNumber, result.PhoneNumber); + Assert.AreEqual(DateTime.Parse(createdAt), result.DateCreated); + Assert.AreEqual(expireAt, result.DateExpired); + } + + private Add AddAction(string phoneNumber) + { + var action = new Add(phoneNumber); + action.Proxy(_proxyStub); + + return action; + } +} From b9c31554f0ed1250fced3fb43b89357f42f0b92b Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 17 Nov 2023 13:18:55 +0100 Subject: [PATCH 009/142] Add blacklist feature --- smsapi/Api/Action/Blacklist/Add.cs | 1 + smsapi/Api/Action/Blacklist/List.cs | 1 + smsapi/Api/Response/{ => Blacklist}/BlacklistRecord.cs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) rename smsapi/Api/Response/{ => Blacklist}/BlacklistRecord.cs (94%) diff --git a/smsapi/Api/Action/Blacklist/Add.cs b/smsapi/Api/Action/Blacklist/Add.cs index 942253d..d81f522 100644 --- a/smsapi/Api/Action/Blacklist/Add.cs +++ b/smsapi/Api/Action/Blacklist/Add.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using SMSApi.Api.Response; +using smsapi.Api.Response.Blacklist; namespace SMSApi.Api.Action.Blacklist; diff --git a/smsapi/Api/Action/Blacklist/List.cs b/smsapi/Api/Action/Blacklist/List.cs index 86dcd62..fc67137 100644 --- a/smsapi/Api/Action/Blacklist/List.cs +++ b/smsapi/Api/Action/Blacklist/List.cs @@ -1,4 +1,5 @@ using SMSApi.Api.Response; +using smsapi.Api.Response.Blacklist; namespace SMSApi.Api.Action.Blacklist; diff --git a/smsapi/Api/Response/BlacklistRecord.cs b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs similarity index 94% rename from smsapi/Api/Response/BlacklistRecord.cs rename to smsapi/Api/Response/Blacklist/BlacklistRecord.cs index d098cea..9c06330 100644 --- a/smsapi/Api/Response/BlacklistRecord.cs +++ b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs @@ -2,7 +2,7 @@ using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; -namespace SMSApi.Api.Response; +namespace smsapi.Api.Response.Blacklist; [DataContract] public record struct BlacklistRecord : IResponseCodeAwareResolver From 551ad1309ee17d7eef1b0f5e1bd8e6b08eee495e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 17 Nov 2023 17:00:39 +0100 Subject: [PATCH 010/142] MFA feature --- examples/mfa/CreateMFACode.cs | 1 - smsapi/Api/Action/MFA/CreateMFACode.cs | 32 +++++++++---------- .../Unit/Action/MFA/CreateMFACodeTest.cs | 6 ++-- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/examples/mfa/CreateMFACode.cs b/examples/mfa/CreateMFACode.cs index f90bc9b..cd8e91a 100644 --- a/examples/mfa/CreateMFACode.cs +++ b/examples/mfa/CreateMFACode.cs @@ -11,7 +11,6 @@ { var mfaCode = features.MFA() .CreateMfaCode(phoneNumber) - .AsFast() //Send code in fast message (optional) .FromSendername("SMSAPI") //Send code from sendername (optional) .WithContent("Your code is [%code%]") //Send code with custom content (optional) .Execute(); diff --git a/smsapi/Api/Action/MFA/CreateMFACode.cs b/smsapi/Api/Action/MFA/CreateMFACode.cs index e9c8bf4..21fba6e 100644 --- a/smsapi/Api/Action/MFA/CreateMFACode.cs +++ b/smsapi/Api/Action/MFA/CreateMFACode.cs @@ -5,35 +5,35 @@ namespace SMSApi.Api.Action.MFA; public class CreateMFACode : Action { - private readonly string phoneNumber; - private string content; - private bool fast; - private string from; + private readonly string _phoneNumber; + private string _content; + private bool _withoutPriority; + private string _from; public CreateMFACode(string phoneNumber) { - this.phoneNumber = phoneNumber; + this._phoneNumber = phoneNumber; } protected override RequestMethod Method => RequestMethod.POST; - public CreateMFACode AsFast() + public CreateMFACode WithoutPriority() { - fast = true; + _withoutPriority = true; return this; } public CreateMFACode FromSendername(string sendername) { - from = sendername; + _from = sendername; return this; } public CreateMFACode WithContent(string content) { - this.content = content; + this._content = content; return this; } @@ -50,16 +50,16 @@ protected override string Uri() protected override NameValueCollection Values() { - var parameters = new NameValueCollection { { "phone_number", phoneNumber } }; + var parameters = new NameValueCollection { { "phone_number", _phoneNumber } }; - if (content != null) - parameters.Add("content", content); + if (_content != null) + parameters.Add("content", _content); - if (fast) - parameters.Add("fast", "1"); + if (_withoutPriority) + parameters.Add("fast", "0"); - if (from != null) - parameters.Add("from", from); + if (_from != null) + parameters.Add("from", _from); return parameters; } diff --git a/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs b/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs index 2d9f23a..ed2cca2 100644 --- a/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs +++ b/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs @@ -38,14 +38,14 @@ public void request_contains_only_phone_number() } [TestMethod] - public void create_as_fast() + public void create_without_priority() { var create = CreateMfaCodeAction(GetAnyPhoneNumber()) - .AsFast(); + .WithoutPriority(); create.Execute(); - _proxyAssert.AssertParametersContain("fast", "1"); + _proxyAssert.AssertParametersContain("fast", "0"); } [TestMethod] From fb8623ba3dde7aae4ce0d76a1bc1c85b7fed5ffd Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 10:54:54 +0100 Subject: [PATCH 011/142] Add blacklist feature #remove --- examples/blacklist/Remove.cs | 19 +++++++ smsapi/Api/Action/Blacklist/Remove.cs | 19 +++++++ smsapi/Api/BlackListFactory.cs | 8 +++ .../Blacklist/BlacklistRemovalResult.cs | 20 +++++++ .../BlacklistRecordDoesNotExistException.cs | 8 +++ .../Action/Blacklist/RemoveRequestTest.cs | 34 +++++++++++ .../Action/Blacklist/RemoveResponseTest.cs | 57 +++++++++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 5 ++ 8 files changed, 170 insertions(+) create mode 100644 examples/blacklist/Remove.cs create mode 100644 smsapi/Api/Action/Blacklist/Remove.cs create mode 100644 smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs create mode 100644 smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs create mode 100644 smsapiTests/Unit/Action/Blacklist/RemoveRequestTest.cs create mode 100644 smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs diff --git a/examples/blacklist/Remove.cs b/examples/blacklist/Remove.cs new file mode 100644 index 0000000..5990742 --- /dev/null +++ b/examples/blacklist/Remove.cs @@ -0,0 +1,19 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string recordId = "655B26893332330011B0B297"; + +try +{ + features.Blacklist() + .Remove(recordId) + .Execute(); + + //record is deleted at this point +} +catch (BlacklistRecordDoesNotExistException exception) +{ +} diff --git a/smsapi/Api/Action/Blacklist/Remove.cs b/smsapi/Api/Action/Blacklist/Remove.cs new file mode 100644 index 0000000..267d9b1 --- /dev/null +++ b/smsapi/Api/Action/Blacklist/Remove.cs @@ -0,0 +1,19 @@ +using smsapi.Api.Response.Blacklist.Exception; + +namespace SMSApi.Api.Action.Blacklist; + +public class Remove : Action +{ + private readonly string _id; + + public Remove(string id) + { + _id = id; + } + + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override string Uri() => $"blacklist/phone_numbers/{_id}"; + + protected override ApiType ApiType() => Action.ApiType.Rest; +} diff --git a/smsapi/Api/BlackListFactory.cs b/smsapi/Api/BlackListFactory.cs index c758360..caab528 100644 --- a/smsapi/Api/BlackListFactory.cs +++ b/smsapi/Api/BlackListFactory.cs @@ -27,6 +27,14 @@ public Add Add(string phoneNumber) return service; } + + public Remove Remove(string id) + { + var service = new Remove(id); + service.Proxy(proxy); + + return service; + } } public static class BlacklistFeatureRegister diff --git a/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs b/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs new file mode 100644 index 0000000..7e2d8cf --- /dev/null +++ b/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using SMSApi.Api.Response.MFA.Exception; +using SMSApi.Api.Response.ResponseResolver; + +namespace smsapi.Api.Response.Blacklist.Exception; + +[DataContract] +public class BlacklistRemovalResult : IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 404, _ => throw new BlacklistRecordDoesNotExistException() } + }; + } +} diff --git a/smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs b/smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs new file mode 100644 index 0000000..2adaf50 --- /dev/null +++ b/smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Response.MFA.Exception; + +public class BlacklistRecordDoesNotExistException : ClientException +{ + public BlacklistRecordDoesNotExistException() : base("record does not exist", 404) + { + } +} diff --git a/smsapiTests/Unit/Action/Blacklist/RemoveRequestTest.cs b/smsapiTests/Unit/Action/Blacklist/RemoveRequestTest.cs new file mode 100644 index 0000000..cebe71e --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/RemoveRequestTest.cs @@ -0,0 +1,34 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action.Blacklist; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class RemoveRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public RemoveRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_contains_id() + { + var recordId = "5A5359173738303F2F95B7E2"; + + Remove(recordId).Execute(); + + _proxyAssert.AssertUriEquals($"blacklist/phone_numbers/{recordId}"); + } + + private Remove Remove(string id) + { + var action = new Remove(id); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs b/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs new file mode 100644 index 0000000..26839ae --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Blacklist; +using SMSApi.Api.Response.MFA.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class RemoveResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void try_remove_non_existing() + { + //given + var response = new Dictionary(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.NotFound + ); + var nonExistingId = "5A5359173738303F2F95B7E2"; + + //then + var action = () => Remove(nonExistingId).Execute(); + + //when + Assert.ThrowsException(action); + } + + [TestMethod] + public void remove_existing_record() + { + var response = new Dictionary(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.NoContent + ); + var existingId = "5A5359173738303F2F95B7E2"; + + Remove(existingId).Execute(); + + Assert.IsTrue(true); + } + + private Remove Remove(string id) + { + var action = new Remove(id); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index dfad771..1ac8728 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -12,6 +12,11 @@ public ProxyAssert(SpyProxy proxy) { _proxy = proxy; } + + public void AssertUriEquals(string uri) + { + Assert.IsTrue(_proxy.RequestedUri.Equals(uri)); + } public void AssertParametersContain(string name, string value) { From 575e50162a8db690ab3dffdb6739cc01b8753784 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 11:07:18 +0100 Subject: [PATCH 012/142] Pagination support --- smsapi/Api/Action/ActionPaginationHelper.cs | 2 +- smsapi/Api/Action/Blacklist/List.cs | 4 ++-- smsapi/Api/Action/IPaginable.cs | 4 ++-- smsapiTests/Unit/Action/ActionPaginationTest.cs | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/smsapi/Api/Action/ActionPaginationHelper.cs b/smsapi/Api/Action/ActionPaginationHelper.cs index 13babe2..368fda5 100644 --- a/smsapi/Api/Action/ActionPaginationHelper.cs +++ b/smsapi/Api/Action/ActionPaginationHelper.cs @@ -5,7 +5,7 @@ namespace SMSApi.Api.Action; public static class ActionPaginationHelper { - public static string ToUriWithPagination(this UriBuilder uriBuilder, int? limit, int? offset) + public static string ToUriWithPagination(this UriBuilder uriBuilder, uint? limit, uint? offset) { var query = HttpUtility.ParseQueryString(uriBuilder.Query); diff --git a/smsapi/Api/Action/Blacklist/List.cs b/smsapi/Api/Action/Blacklist/List.cs index fc67137..2fcf0d6 100644 --- a/smsapi/Api/Action/Blacklist/List.cs +++ b/smsapi/Api/Action/Blacklist/List.cs @@ -11,6 +11,6 @@ public class List : Action>, IPaginable protected override ApiType ApiType() => Action.ApiType.Rest; - public int? Limit { get; set; } - public int? Offset { get; set; } + public uint? Limit { get; set; } + public uint? Offset { get; set; } } diff --git a/smsapi/Api/Action/IPaginable.cs b/smsapi/Api/Action/IPaginable.cs index d36bdd6..fa8a808 100644 --- a/smsapi/Api/Action/IPaginable.cs +++ b/smsapi/Api/Action/IPaginable.cs @@ -2,7 +2,7 @@ namespace SMSApi.Api.Action; public interface IPaginable { - int? Limit { get; set; } + uint? Limit { get; set; } - int? Offset { get; set; } + uint? Offset { get; set; } } diff --git a/smsapiTests/Unit/Action/ActionPaginationTest.cs b/smsapiTests/Unit/Action/ActionPaginationTest.cs index 9905338..8cdd1e1 100644 --- a/smsapiTests/Unit/Action/ActionPaginationTest.cs +++ b/smsapiTests/Unit/Action/ActionPaginationTest.cs @@ -24,7 +24,7 @@ public void raw_uri() [TestMethod] public void add_limit_to_uri() { - var limit = 10; + var limit = 10u; var action = GetAction(); action.Limit = limit; @@ -36,7 +36,7 @@ public void add_limit_to_uri() [TestMethod] public void add_offset_to_uri() { - var offset = 10; + var offset = 10u; var action = GetAction(); action.Offset = offset; @@ -48,8 +48,8 @@ public void add_offset_to_uri() [TestMethod] public void add_limit_and_offset_to_uri() { - var limit = 5; - var offset = 10; + var limit = 5u; + var offset = 10u; var action = GetAction(); action.Limit = limit; action.Offset = offset; @@ -80,8 +80,8 @@ public PaginableAction(string path) protected override string Uri() => Path; - public int? Limit { get; set; } - public int? Offset { get; set; } + public uint? Limit { get; set; } + public uint? Offset { get; set; } } private class Response{} From 9479ddfa0ee11fcb6e9da47dcdc6c668c057b2ec Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 11:27:27 +0100 Subject: [PATCH 013/142] HLR feature --- examples/hlr/Lookup.cs | 18 ++++++++ smsapi/Api/Action/HLR/CheckNumber.cs | 4 +- smsapi/Api/Action/HLR/Lookup.cs | 25 +++++++++++ smsapi/Api/HLRFactory.cs | 13 +++++- smsapi/Api/Response/HLR/SingleCheckResult.cs | 7 ++++ .../Unit/Action/HLR/LookupRequestTest.cs | 42 +++++++++++++++++++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 examples/hlr/Lookup.cs create mode 100644 smsapi/Api/Action/HLR/Lookup.cs create mode 100644 smsapi/Api/Response/HLR/SingleCheckResult.cs create mode 100644 smsapiTests/Unit/Action/HLR/LookupRequestTest.cs diff --git a/examples/hlr/Lookup.cs b/examples/hlr/Lookup.cs new file mode 100644 index 0000000..643b889 --- /dev/null +++ b/examples/hlr/Lookup.cs @@ -0,0 +1,18 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string phoneNumberToCheck = "48500100100"; + +try +{ + features.HLR() + .Lookup(phoneNumberToCheck) + .Execute(); + + //lookup successfully requested +} +catch (ValidationException) +{ +} diff --git a/smsapi/Api/Action/HLR/CheckNumber.cs b/smsapi/Api/Action/HLR/CheckNumber.cs index 305cee9..1f8f56b 100644 --- a/smsapi/Api/Action/HLR/CheckNumber.cs +++ b/smsapi/Api/Action/HLR/CheckNumber.cs @@ -1,8 +1,10 @@ -using System.Collections.Specialized; +using System; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action { + [Obsolete] public class HLRCheckNumber : Action { private string number; diff --git a/smsapi/Api/Action/HLR/Lookup.cs b/smsapi/Api/Action/HLR/Lookup.cs new file mode 100644 index 0000000..f321e34 --- /dev/null +++ b/smsapi/Api/Action/HLR/Lookup.cs @@ -0,0 +1,25 @@ +using System.Collections.Specialized; +using SMSApi.Api.Response.HLR; + +namespace SMSApi.Api.Action; + +public sealed class Lookup : Action +{ + private readonly string _numberToCheck; + + public Lookup(string numberToCheck) + { + _numberToCheck = numberToCheck; + } + + protected override RequestMethod Method => RequestMethod.POST; + + protected override string Uri() => "hlr/lookups"; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override NameValueCollection Values() + { + return new NameValueCollection { { "phone_number", _numberToCheck } }; + } +} diff --git a/smsapi/Api/HLRFactory.cs b/smsapi/Api/HLRFactory.cs index f25fd3d..4602ee4 100644 --- a/smsapi/Api/HLRFactory.cs +++ b/smsapi/Api/HLRFactory.cs @@ -1,4 +1,5 @@ -using SMSApi.Api; +using System; +using SMSApi.Api; using SMSApi.Api.Action; namespace SMSApi.Api @@ -17,6 +18,7 @@ public HLRFactory(IClient client, Proxy proxy) : base(client, proxy) { } + [Obsolete($"Use {nameof(Lookup)} instead", false)] public HLRCheckNumber ActionCheckNumber(string number = null) { var action = new HLRCheckNumber(); @@ -24,6 +26,15 @@ public HLRCheckNumber ActionCheckNumber(string number = null) action.SetNumber(number); return action; } + + public Lookup Lookup(string number) + { + var action = new Lookup(number); + + action.Proxy(proxy); + + return action; + } } } diff --git a/smsapi/Api/Response/HLR/SingleCheckResult.cs b/smsapi/Api/Response/HLR/SingleCheckResult.cs new file mode 100644 index 0000000..6bc2c7b --- /dev/null +++ b/smsapi/Api/Response/HLR/SingleCheckResult.cs @@ -0,0 +1,7 @@ +using System.Runtime.Serialization; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.HLR; + +[DataContract] +public readonly record struct SingleCheckResult : IResponseCodeAwareResolver; diff --git a/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs b/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs new file mode 100644 index 0000000..1f505c1 --- /dev/null +++ b/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class LookupRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public LookupRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + Lookup("48500100100").Execute(); + + _proxyAssert.AssertUriEquals("hlr/lookups"); + } + + [TestMethod] + public void parameters_contain_phone_number() + { + var phoneNumber = "48500100100"; + + Lookup(phoneNumber).Execute(); + + _proxyAssert.AssertParametersContain("phone_number", phoneNumber); + } + + private Lookup Lookup(string id) + { + var action = new Lookup(id); + action.Proxy(_spyProxy); + + return action; + } +} From 5994e0128e855c6a3476d1ee894f51152bc587e1 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 13:01:17 +0100 Subject: [PATCH 014/142] HLR feature --- examples/hlr/ListLookups.cs | 28 ++++ smsapi/Api/Action/HLR/ListLookups.cs | 14 ++ smsapi/Api/HLRFactory.cs | 9 ++ .../Api/Response/Common/Telephony/Country.cs | 16 +++ .../Api/Response/Common/Telephony/Network.cs | 16 +++ smsapi/Api/Response/HLR/LookupResult.cs | 68 ++++++++++ .../Response/Profile/Prices/PriceResponse.cs | 15 +-- .../HLR/Fixture/LookupsCollectionMother.cs | 73 +++++++++++ .../Action/HLR/ListLookupsResponseTest.cs | 121 ++++++++++++++++++ 9 files changed, 346 insertions(+), 14 deletions(-) create mode 100644 examples/hlr/ListLookups.cs create mode 100644 smsapi/Api/Action/HLR/ListLookups.cs create mode 100644 smsapi/Api/Response/Common/Telephony/Country.cs create mode 100644 smsapi/Api/Response/Common/Telephony/Network.cs create mode 100644 smsapi/Api/Response/HLR/LookupResult.cs create mode 100644 smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs create mode 100644 smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs diff --git a/examples/hlr/ListLookups.cs b/examples/hlr/ListLookups.cs new file mode 100644 index 0000000..1e493ae --- /dev/null +++ b/examples/hlr/ListLookups.cs @@ -0,0 +1,28 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var result = features.HLR() + .ListLookups() + .Execute(); + +result.Collection.ForEach(r => +{ + Console.WriteLine($"ID: {r.Id}"); + Console.WriteLine($"Phone number: {r.PhoneNumber}"); + Console.WriteLine($"Interface: {r.Interface}"); + Console.WriteLine($"Country name: {r.Country?.Name}"); + Console.WriteLine($"MCC: {r.Country?.MCC}"); + Console.WriteLine($"Network name: {r.Network?.Name}"); + Console.WriteLine($"MNC: {r.Network?.MNC}"); + Console.WriteLine($"Cost: {r.Cost.Points}"); + Console.WriteLine($"Sent at: {r.SentAt}"); + Console.WriteLine($"Error code: {r.ErrorCode}"); + + if (r.Ported != null) + foreach (var mcc in r.Ported.Value.PortedFrom) + { + Console.WriteLine(mcc.Mcc); + } +}); diff --git a/smsapi/Api/Action/HLR/ListLookups.cs b/smsapi/Api/Action/HLR/ListLookups.cs new file mode 100644 index 0000000..bc9c18a --- /dev/null +++ b/smsapi/Api/Action/HLR/ListLookups.cs @@ -0,0 +1,14 @@ +using SMSApi.Api.Response; +using SMSApi.Api.Response.HLR; + +namespace SMSApi.Api.Action; + +public class ListLookups : Action>, IPaginable +{ + protected override RequestMethod Method => RequestMethod.GET; + + protected override string Uri() => "hlr/lookups"; + + public uint? Limit { get; set; } + public uint? Offset { get; set; } +} diff --git a/smsapi/Api/HLRFactory.cs b/smsapi/Api/HLRFactory.cs index 4602ee4..d9bc064 100644 --- a/smsapi/Api/HLRFactory.cs +++ b/smsapi/Api/HLRFactory.cs @@ -35,6 +35,15 @@ public Lookup Lookup(string number) return action; } + + public ListLookups ListLookups() + { + var action = new ListLookups(); + + action.Proxy(proxy); + + return action; + } } } diff --git a/smsapi/Api/Response/Common/Telephony/Country.cs b/smsapi/Api/Response/Common/Telephony/Country.cs new file mode 100644 index 0000000..1d44bde --- /dev/null +++ b/smsapi/Api/Response/Common/Telephony/Country.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; + +namespace SMSApi.Api.Response.Common.Telephony; + +[DataContract] +public readonly record struct Country +{ + [DataMember(Name = "name")] public readonly string Name; + [DataMember(Name = "mcc")] public readonly int MCC; + + public Country(string name, int mcc) + { + Name = name; + MCC = mcc; + } +} diff --git a/smsapi/Api/Response/Common/Telephony/Network.cs b/smsapi/Api/Response/Common/Telephony/Network.cs new file mode 100644 index 0000000..c6bc6d6 --- /dev/null +++ b/smsapi/Api/Response/Common/Telephony/Network.cs @@ -0,0 +1,16 @@ +using System.Runtime.Serialization; + +namespace SMSApi.Api.Response.Common.Telephony; + +[DataContract] +public readonly record struct Network +{ + [DataMember(Name = "name")] public readonly string Name; + [DataMember(Name = "mnc")] public readonly int MNC; + + public Network(string name, int mnc) + { + Name = name; + MNC = mnc; + } +} diff --git a/smsapi/Api/Response/HLR/LookupResult.cs b/smsapi/Api/Response/HLR/LookupResult.cs new file mode 100644 index 0000000..668c33e --- /dev/null +++ b/smsapi/Api/Response/HLR/LookupResult.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using SMSApi.Api.Response.Common.Telephony; + +namespace SMSApi.Api.Response.HLR; + +[DataContract] +public class LookupResult +{ + [DataMember(Name = "id")] public readonly string Id; + + [DataMember(Name = "phone_number")] public readonly string PhoneNumber; + + [DataMember(Name = "interface")] public readonly string Interface; + + [DataMember(Name = "country")] public readonly Country? Country; + + [DataMember(Name = "network")] public readonly Network? Network; + + [DataMember(Name = "cost")] public readonly LookupCost Cost; + + [DataMember(Name = "ported")] public readonly Ported? Ported; + + [DataMember(Name = "error_code")] public readonly uint? ErrorCode; + + public DateTime SentAt; + + [DataMember(Name = "sent_at")] + private string? SentAtDeserializer + { + set => SentAt = DateTime.Parse(value); + get => default; + } +} + +[DataContract] +public readonly record struct LookupCost +{ + [DataMember(Name = "points")] public readonly double Points; + + public LookupCost(double points) + { + Points = points; + } +} + +[DataContract] +public readonly record struct Ported +{ + [DataMember(Name = "ported")] public readonly IEnumerable PortedFrom; + + public Ported(IEnumerable portedFrom) + { + PortedFrom = portedFrom; + } +} + +[DataContract] +public readonly record struct MCC +{ + [DataMember(Name = "mcc")] public readonly int Mcc; + + public MCC(int mcc) + { + Mcc = mcc; + } +} diff --git a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs index c60dbc0..acf6c90 100644 --- a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs +++ b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using SMSApi.Api.Response.Common.Telephony; namespace SMSApi.Api.Response.Profile.Prices; @@ -20,17 +21,3 @@ public readonly struct Price [DataMember(Name = "amount")] public readonly float Amount; [DataMember(Name = "currency")] public readonly string Currency; } - -[DataContract] -public readonly struct Country -{ - [DataMember(Name = "name")] public readonly string Name; - [DataMember(Name = "mcc")] public readonly int MCC; -} - -[DataContract] -public readonly struct Network -{ - [DataMember(Name = "name")] public readonly string Name; - [DataMember(Name = "mnc")] public readonly int MNC; -} diff --git a/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs b/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs new file mode 100644 index 0000000..1edc62f --- /dev/null +++ b/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using SMSApi.Api.Response.Common.Telephony; +using SMSApi.Api.Response.HLR; +using smsapiTests.Unit.Fixture; + +namespace smsapiTests.Unit.Action.HLR.Fixture; + +public static class LookupsCollectionMother +{ + public static Dictionary EmptyCollection = CollectionMother.Empty(); + + public static Dictionary Lookups( + string id, + string phoneNumber, + string @interface, + Country? country, + Network? network, + LookupCost cost, + Ported? ported, + uint? errorCode, + DateTime sentAt + ) + { + return new Dictionary + { + { + "collection", new List + { + new Dictionary + { + { "id", id }, + { "phone_number", phoneNumber }, + { + "country", country != null + ? new Dictionary + { + { "name", country.Value.Name }, + { "mcc", country.Value.MCC } + } + : null + }, + { + "network", network != null + ? new Dictionary + { + { "name", network.Value.Name }, + { "mnc", network.Value.MNC } + } + : null + }, + { + "cost", cost + }, + { + "interface", @interface + }, + { + "sent_at", sentAt + }, + { + "ported", ported + }, + { + "error_code", errorCode + } + } + } + }, + { "size", 1 } + }; + } +} \ No newline at end of file diff --git a/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs b/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs new file mode 100644 index 0000000..b685ebf --- /dev/null +++ b/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using SMSApi.Api.Response.Common.Telephony; +using SMSApi.Api.Response.HLR; +using smsapiTests.Unit.Action.HLR.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class ListLookupsResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = LookupsCollectionMother.EmptyCollection; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_lookups_with_result() + { + var id = "655B26893332330011B0B297"; + var phoneNumber = "48500100100"; + var @interface = "api"; + var country = new Country("Poland", 260); + var network = new Network("T-Mobile", 3); + var cost = new LookupCost(1.08); + var sentAt = DateTime.Now; + var response = LookupsCollectionMother.Lookups( + id, + phoneNumber, + @interface, + country, + network, + cost, + null, + null, + sentAt + ); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(phoneNumber, firstElement.PhoneNumber); + Assert.AreEqual(@interface, firstElement.Interface); + Assert.AreEqual(country, firstElement.Country); + Assert.AreEqual(network, firstElement.Network); + Assert.AreEqual(cost, firstElement.Cost); + Assert.AreEqual(null, firstElement.Ported); + Assert.AreEqual(null, firstElement.ErrorCode); + Assert.AreEqual(sentAt, firstElement.SentAt); + } + + [TestMethod] + public void list_lookups_with_error() + { + var id = "655B26893332330011B0B297"; + var phoneNumber = "48500100100"; + var @interface = "api"; + var cost = new LookupCost(1.08); + var sentAt = DateTime.Now; + var response = LookupsCollectionMother.Lookups( + id, + phoneNumber, + @interface, + null, + null, + cost, + null, + 15, + sentAt + ); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(phoneNumber, firstElement.PhoneNumber); + Assert.AreEqual(@interface, firstElement.Interface); + Assert.AreEqual(null, firstElement.Country); + Assert.AreEqual(null, firstElement.Network); + Assert.AreEqual(null, firstElement.Ported); + Assert.AreEqual(cost, firstElement.Cost); + Assert.AreEqual(sentAt, firstElement.SentAt); + Assert.AreEqual(15u, firstElement.ErrorCode); + } + + private ListLookups GetList() + { + var action = new ListLookups(); + action.Proxy(_proxyStub); + + return action; + } +} \ No newline at end of file From 8b63ee05026e24e77455e6a8a9932297cfca9b70 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 13:03:22 +0100 Subject: [PATCH 015/142] HLR feature --- smsapi/Api/Action/HLR/ListLookups.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Action/HLR/ListLookups.cs b/smsapi/Api/Action/HLR/ListLookups.cs index bc9c18a..f5a9eaf 100644 --- a/smsapi/Api/Action/HLR/ListLookups.cs +++ b/smsapi/Api/Action/HLR/ListLookups.cs @@ -1,14 +1,17 @@ using SMSApi.Api.Response; using SMSApi.Api.Response.HLR; +using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Action; -public class ListLookups : Action>, IPaginable +public class ListLookups : Action>, IPaginable, IResponseCodeAwareResolver { protected override RequestMethod Method => RequestMethod.GET; protected override string Uri() => "hlr/lookups"; - + + protected override ApiType ApiType() => Action.ApiType.Rest; + public uint? Limit { get; set; } public uint? Offset { get; set; } } From 4912cae73f9bb5a00fa82c574637ab07541047e1 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 13:11:12 +0100 Subject: [PATCH 016/142] Add .NET8 target --- smsapi/smsapi.csproj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/smsapi/smsapi.csproj b/smsapi/smsapi.csproj index e26208e..23a1d86 100644 --- a/smsapi/smsapi.csproj +++ b/smsapi/smsapi.csproj @@ -6,7 +6,7 @@ netcoreapp3.1;net5.0;net6.0;net7.0 false false - 9.0 + 10 SMSAPI SMSAPI SMSAPI @@ -16,6 +16,7 @@ SMSAPI README.md logo.jpg + enable SMSAPI.pl @@ -26,7 +27,7 @@ SMSAPI Client that allows to send SMS, MMS, VMS and manage your SMSAPI account. SMSAPI Client that allows to send SMS, MMS, VMS and manage your SMSAPI account. smsapi;sms;marketing;shipment;mms;vms;message - net6.0;net7.0;netcoreapp3.1 + net6.0;net7.0;net8.0;netcoreapp3.1 True @@ -55,4 +56,9 @@ + + + ..\..\..\..\.nuget\packages\newtonsoft.json\10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll + + From b191492bf563123e470319a3ab8d75bee1206e23 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 14:26:05 +0100 Subject: [PATCH 017/142] Subusers feature #listing --- examples/subusers/List.cs | 18 +++++ smsapi/Api/Action/Subusers/List.cs | 15 +++++ .../Api/Response/Subusers/SubuserDetails.cs | 25 +++++++ smsapi/Api/SubUsersFactory.cs | 38 +++++++++++ smsapi/Api/UserFactory.cs | 5 +- .../Fixture/SubuersCollectionMother.cs | 36 ++++++++++ smsapiTests/Unit/Action/Subusers/ListTest.cs | 66 +++++++++++++++++++ 7 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 examples/subusers/List.cs create mode 100644 smsapi/Api/Action/Subusers/List.cs create mode 100644 smsapi/Api/Response/Subusers/SubuserDetails.cs create mode 100644 smsapi/Api/SubUsersFactory.cs create mode 100644 smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs create mode 100644 smsapiTests/Unit/Action/Subusers/ListTest.cs diff --git a/examples/subusers/List.cs b/examples/subusers/List.cs new file mode 100644 index 0000000..62fa8c9 --- /dev/null +++ b/examples/subusers/List.cs @@ -0,0 +1,18 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var result = features.Subusers() + .List() + .Execute(); + +result.Collection.ForEach(subuser => +{ + Console.WriteLine($"ID: {subuser.Id}"); + Console.WriteLine($"Username: {subuser.Username}"); + Console.WriteLine($"Active: {subuser.Active}"); + Console.WriteLine($"Description: {subuser.Description}"); + Console.WriteLine($"Points shared with main user: {subuser.Points.FromAccount}"); + Console.WriteLine($"Monthly points' limit: {subuser.Points.PerMonth}"); +}); diff --git a/smsapi/Api/Action/Subusers/List.cs b/smsapi/Api/Action/Subusers/List.cs new file mode 100644 index 0000000..1a099a8 --- /dev/null +++ b/smsapi/Api/Action/Subusers/List.cs @@ -0,0 +1,15 @@ +using SMSApi.Api.Response; +using SMSApi.Api.Response.Subusers; + +namespace SMSApi.Api.Action.Subusers; + +public class List : Action>, IPaginable +{ + protected override RequestMethod Method => RequestMethod.GET; + protected override string Uri() => "subusers"; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + public uint? Limit { get; set; } + public uint? Offset { get; set; } +} diff --git a/smsapi/Api/Response/Subusers/SubuserDetails.cs b/smsapi/Api/Response/Subusers/SubuserDetails.cs new file mode 100644 index 0000000..cb2738a --- /dev/null +++ b/smsapi/Api/Response/Subusers/SubuserDetails.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace SMSApi.Api.Response.Subusers; + +[DataContract] +public readonly record struct SubuserDetails +{ + [DataMember(Name = "active")] public readonly bool Active; + + [DataMember(Name = "description")] public readonly string Description; + + [DataMember(Name = "id")] public readonly string Id; + + [DataMember(Name = "points")] public readonly UserPoints Points; + + [DataMember(Name = "username")] public readonly string Username; +} + +[DataContract] +public readonly record struct UserPoints(double FromAccount, double PerMonth) +{ + [DataMember(Name = "from_account")] public readonly double FromAccount = FromAccount; + + [DataMember(Name = "per_month")] public readonly double PerMonth = PerMonth; +} diff --git a/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs new file mode 100644 index 0000000..f66bd37 --- /dev/null +++ b/smsapi/Api/SubUsersFactory.cs @@ -0,0 +1,38 @@ +using SMSApi.Api.Action.Subusers; + +namespace SMSApi.Api; + +public class SubUsersFactory : Factory +{ + public SubUsersFactory(ProxyAddress address = ProxyAddress.SmsApiIo) + : base(address) + { + } + + public SubUsersFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) + : base(client, address) + { + } + + public SubUsersFactory(IClient client, Proxy proxy) + : base(client, proxy) + { + } + + public List List() + { + var action = new List(); + + action.Proxy(proxy); + + return action; + } +} + +public static class SubusersFeatureRegister +{ + public static SubUsersFactory Subusers(this Features features) + { + return new SubUsersFactory(features.Client, features.Proxy); + } +} diff --git a/smsapi/Api/UserFactory.cs b/smsapi/Api/UserFactory.cs index 77bfb1f..e603a7d 100644 --- a/smsapi/Api/UserFactory.cs +++ b/smsapi/Api/UserFactory.cs @@ -1,8 +1,10 @@ -using SMSApi.Api; +using System; +using SMSApi.Api; using SMSApi.Api.Action; namespace SMSApi.Api { + [Obsolete($"Use {nameof(SubUsersFactory)} instead.")] public class UserFactory : Factory { public UserFactory(ProxyAddress address = ProxyAddress.SmsApiIo) @@ -58,6 +60,7 @@ public UserList ActionList() public static class UserFeatureRegister { + [Obsolete($"Use {nameof(SubusersFeatureRegister)} instead.")] public static UserFactory User(this Features features) { return new UserFactory(features.Client, features.Proxy); diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs new file mode 100644 index 0000000..32ac696 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using SMSApi.Api.Response.Subusers; + +namespace smsapiTests.Unit.Action.Subusers.Fixture; + +public static class SubuersCollectionMother +{ + public static Dictionary Collection( + string id, + string username, + bool active, + string description, + UserPoints userPoints + ) + { + return new Dictionary + { + { + "collection", new List + { + new Dictionary + { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, + { + "points", userPoints + } + } + } + }, + { "size", 1 } + }; + } +} diff --git a/smsapiTests/Unit/Action/Subusers/ListTest.cs b/smsapiTests/Unit/Action/Subusers/ListTest.cs new file mode 100644 index 0000000..ef83b45 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/ListTest.cs @@ -0,0 +1,66 @@ +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers; +using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Action.HLR.Fixture; +using smsapiTests.Unit.Action.Subusers.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class ListTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = LookupsCollectionMother.EmptyCollection; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_subusers() + { + var id = "655B26893332330011B0B297"; + var username = "Fancy name"; + var active = true; + var description = "Description abc"; + var points = new UserPoints(10, 5); + var response = SubuersCollectionMother.Collection(id, username, active, description, points); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(username, firstElement.Username); + Assert.AreEqual(active, firstElement.Active); + Assert.AreEqual(description, firstElement.Description); + Assert.AreEqual(points, firstElement.Points); + } + + private List GetList() + { + var action = new List(); + action.Proxy(_proxyStub); + + return action; + } +} From a06c0be1c25e3015b1a73cc1f56de34931956020 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Mon, 20 Nov 2023 14:28:05 +0100 Subject: [PATCH 018/142] Subusers feature #listing --- .../Fixture/SubuersCollectionMother.cs | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs index 32ac696..9ddd543 100644 --- a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Fixture; namespace smsapiTests.Unit.Action.Subusers.Fixture; @@ -13,24 +14,15 @@ public static Dictionary Collection( UserPoints userPoints ) { - return new Dictionary + return CollectionMother.WithItems(new Dictionary { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, { - "collection", new List - { - new Dictionary - { - { "id", id }, - { "username", username }, - { "active", active }, - { "description", description }, - { - "points", userPoints - } - } - } - }, - { "size", 1 } - }; + "points", userPoints + } + }); } } From 278fe8985e3769cab7233810eeebdc18deceee15 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Wed, 22 Nov 2023 14:43:00 +0100 Subject: [PATCH 019/142] Profile prices lookup feature --- examples/profile/prices/GetPrices.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/profile/prices/GetPrices.cs b/examples/profile/prices/GetPrices.cs index a073874..cc24840 100644 --- a/examples/profile/prices/GetPrices.cs +++ b/examples/profile/prices/GetPrices.cs @@ -11,5 +11,5 @@ .ToList() .ForEach(p => { - Console.WriteLine($"Price for {p.Country.Name} / {p.Network.Name} is {p.Price.Amount} {p.Price.Currency}"); + Console.WriteLine($"Price for {p.Type} in {p.Country.Name} / {p.Network.Name} is {p.Price.Amount} {p.Price.Currency}"); }); From f2e563e2020591afa742fc6780c6487506f3a530 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Fri, 24 Nov 2023 10:52:47 +0100 Subject: [PATCH 020/142] Add exception handling to examples --- examples/blacklist/Add.cs | 4 ++-- examples/blacklist/Remove.cs | 1 + examples/hlr/Lookup.cs | 6 +++++- examples/mfa/CreateMFACode.cs | 5 ++++- examples/mfa/VerifyMFACode.cs | 5 ++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/blacklist/Add.cs b/examples/blacklist/Add.cs index c6e28a2..19a722c 100644 --- a/examples/blacklist/Add.cs +++ b/examples/blacklist/Add.cs @@ -19,9 +19,9 @@ Console.WriteLine($"Created at: {result.DateCreated}"); Console.WriteLine($"Expiring at: {result.DateExpired}"); } -catch (ValidationException exception) +catch (ValidationException ex) { - foreach (var validationErrorsError in exception.ValidationErrors.Errors) + foreach (var validationErrorsError in ex.ValidationErrors.Errors) { Console.WriteLine(validationErrorsError.Message); } diff --git a/examples/blacklist/Remove.cs b/examples/blacklist/Remove.cs index 5990742..9062bc9 100644 --- a/examples/blacklist/Remove.cs +++ b/examples/blacklist/Remove.cs @@ -1,5 +1,6 @@ using SMSApi.Api; using smsapi.Api.Response.REST.Exception; +using SMSApi.Api.Response.MFA.Exception; var client = new ClientOAuth("token"); var features = new Features(client); diff --git a/examples/hlr/Lookup.cs b/examples/hlr/Lookup.cs index 643b889..47b47d0 100644 --- a/examples/hlr/Lookup.cs +++ b/examples/hlr/Lookup.cs @@ -13,6 +13,10 @@ //lookup successfully requested } -catch (ValidationException) +catch (ValidationException ex) { + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } } diff --git a/examples/mfa/CreateMFACode.cs b/examples/mfa/CreateMFACode.cs index cd8e91a..3f1265a 100644 --- a/examples/mfa/CreateMFACode.cs +++ b/examples/mfa/CreateMFACode.cs @@ -22,7 +22,10 @@ } catch (ValidationException ex) { - var errors = ex.ValidationErrors; + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } } catch (TooManyRequestsException) { diff --git a/examples/mfa/VerifyMFACode.cs b/examples/mfa/VerifyMFACode.cs index 916463a..b28e6ee 100644 --- a/examples/mfa/VerifyMFACode.cs +++ b/examples/mfa/VerifyMFACode.cs @@ -19,7 +19,10 @@ } catch (ValidationException ex) { - var errors = ex.ValidationErrors; + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } } catch (InvalidVerificationCodeException) { From 240c5ae0371e4b4479999aa5aa32b345ac1e84d0 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 27 Nov 2023 13:50:47 +0100 Subject: [PATCH 021/142] Blacklist feature --- examples/blacklist/Remove.cs | 4 ++-- smsapi/Api/Action/Blacklist/Remove.cs | 2 +- smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs | 4 ++-- .../Exception/BlacklistRecordDoesNotExistException.cs | 2 +- smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename smsapi/Api/Response/{MFA => Blacklist}/Exception/BlacklistRecordDoesNotExistException.cs (77%) diff --git a/examples/blacklist/Remove.cs b/examples/blacklist/Remove.cs index 9062bc9..b91e287 100644 --- a/examples/blacklist/Remove.cs +++ b/examples/blacklist/Remove.cs @@ -1,6 +1,5 @@ using SMSApi.Api; -using smsapi.Api.Response.REST.Exception; -using SMSApi.Api.Response.MFA.Exception; +using SMSApi.Api.Response.Blacklist.Exception; var client = new ClientOAuth("token"); var features = new Features(client); @@ -17,4 +16,5 @@ } catch (BlacklistRecordDoesNotExistException exception) { + System.Console.WriteLine(e.Message); } diff --git a/smsapi/Api/Action/Blacklist/Remove.cs b/smsapi/Api/Action/Blacklist/Remove.cs index 267d9b1..6a952b2 100644 --- a/smsapi/Api/Action/Blacklist/Remove.cs +++ b/smsapi/Api/Action/Blacklist/Remove.cs @@ -1,4 +1,4 @@ -using smsapi.Api.Response.Blacklist.Exception; +using smsapi.Api.Response.Blacklist; namespace SMSApi.Api.Action.Blacklist; diff --git a/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs b/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs index 7e2d8cf..a97ea42 100644 --- a/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs +++ b/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; -using SMSApi.Api.Response.MFA.Exception; +using SMSApi.Api.Response.Blacklist.Exception; using SMSApi.Api.Response.ResponseResolver; -namespace smsapi.Api.Response.Blacklist.Exception; +namespace smsapi.Api.Response.Blacklist; [DataContract] public class BlacklistRemovalResult : IResponseCodeAwareResolver diff --git a/smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs b/smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs similarity index 77% rename from smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs rename to smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs index 2adaf50..e0da95f 100644 --- a/smsapi/Api/Response/MFA/Exception/BlacklistRecordDoesNotExistException.cs +++ b/smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs @@ -1,4 +1,4 @@ -namespace SMSApi.Api.Response.MFA.Exception; +namespace SMSApi.Api.Response.Blacklist.Exception; public class BlacklistRecordDoesNotExistException : ClientException { diff --git a/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs b/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs index 26839ae..02749f6 100644 --- a/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs +++ b/smsapiTests/Unit/Action/Blacklist/RemoveResponseTest.cs @@ -3,7 +3,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api; using SMSApi.Api.Action.Blacklist; -using SMSApi.Api.Response.MFA.Exception; +using SMSApi.Api.Response.Blacklist.Exception; using smsapiTests.Unit.Fixture; using smsapiTests.Unit.Helper; From 33f4d12370d0487f036fa0253f3e5fa8e5a4bc14 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 27 Nov 2023 13:51:07 +0100 Subject: [PATCH 022/142] Blacklist feature --- examples/blacklist/RemoveAll.cs | 10 +++++ smsapi/Api/Action/Blacklist/RemoveAll.cs | 12 ++++++ .../Action/Blacklist/RemoveAllRequestTest.cs | 40 +++++++++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 20 +++++----- 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 examples/blacklist/RemoveAll.cs create mode 100644 smsapi/Api/Action/Blacklist/RemoveAll.cs create mode 100644 smsapiTests/Unit/Action/Blacklist/RemoveAllRequestTest.cs diff --git a/examples/blacklist/RemoveAll.cs b/examples/blacklist/RemoveAll.cs new file mode 100644 index 0000000..187c444 --- /dev/null +++ b/examples/blacklist/RemoveAll.cs @@ -0,0 +1,10 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +features.Blacklist() + .RemoveAll() + .Execute(); + +//cleaning blacklist has been scheduled at this point diff --git a/smsapi/Api/Action/Blacklist/RemoveAll.cs b/smsapi/Api/Action/Blacklist/RemoveAll.cs new file mode 100644 index 0000000..8718bac --- /dev/null +++ b/smsapi/Api/Action/Blacklist/RemoveAll.cs @@ -0,0 +1,12 @@ +using smsapi.Api.Response.Blacklist; + +namespace SMSApi.Api.Action.Blacklist; + +public sealed class RemoveAll : Action +{ + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override string Uri() => "blacklist/phone_numbers"; + + protected override ApiType ApiType() => Action.ApiType.Rest; +} diff --git a/smsapiTests/Unit/Action/Blacklist/RemoveAllRequestTest.cs b/smsapiTests/Unit/Action/Blacklist/RemoveAllRequestTest.cs new file mode 100644 index 0000000..66df773 --- /dev/null +++ b/smsapiTests/Unit/Action/Blacklist/RemoveAllRequestTest.cs @@ -0,0 +1,40 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action.Blacklist; + +namespace smsapiTests.Unit.Action.Blacklist; + +[TestClass] +public class RemoveAllRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public RemoveAllRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + RemoveAll().Execute(); + + _proxyAssert.AssertUriEquals("blacklist/phone_numbers"); + } + + [TestMethod] + public void request_does_not_contain_body() + { + RemoveAll().Execute(); + + _proxyAssert.AssertNoParameters(); + } + + private RemoveAll RemoveAll() + { + var action = new RemoveAll(); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 1ac8728..6ecd9d1 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -4,18 +4,18 @@ namespace smsapiTests.Unit; -public class ProxyAssert +public class ProxyAssert(SpyProxy proxy) { - private readonly SpyProxy _proxy; - - public ProxyAssert(SpyProxy proxy) + public void AssertUriEquals(string uri) { - _proxy = proxy; + Assert.IsTrue(proxy.RequestedUri.Equals(uri)); } - public void AssertUriEquals(string uri) + public void AssertNoParameters() { - Assert.IsTrue(_proxy.RequestedUri.Equals(uri)); + var parametersCount = proxy.Parameters.Count; + + Assert.IsTrue(parametersCount == 0, $"Parameters expected to be empty, {parametersCount} found"); } public void AssertParametersContain(string name, string value) @@ -23,15 +23,15 @@ public void AssertParametersContain(string name, string value) var expectedParameter = new KeyValuePair(name, value); Assert.IsTrue( - _proxy.Parameters.Contains(value: expectedParameter), - $"Expected {value}, actual value: {_proxy.Parameters[name]}" + proxy.Parameters.Contains(value: expectedParameter), + $"Expected {value}, actual value: {proxy.Parameters[name]}" ); } public void AssertParametersDoesNotContain(string name) { Assert.IsFalse( - _proxy.Parameters.ContainsKey(name), + proxy.Parameters.ContainsKey(name), $"Key not expected {name}" ); } From f02e6ee4f25263aaec33c20d4337537c56dd4fd0 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 27 Nov 2023 13:55:42 +0100 Subject: [PATCH 023/142] Blacklist feature --- smsapi/Api/BlackListFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/smsapi/Api/BlackListFactory.cs b/smsapi/Api/BlackListFactory.cs index caab528..7fc56a2 100644 --- a/smsapi/Api/BlackListFactory.cs +++ b/smsapi/Api/BlackListFactory.cs @@ -35,6 +35,14 @@ public Remove Remove(string id) return service; } + + public RemoveAll RemoveAll() + { + var service = new RemoveAll(); + service.Proxy(proxy); + + return service; + } } public static class BlacklistFeatureRegister From 43854cceb39c56c93594c0bf4b464d3c4d8f58d1 Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 28 Nov 2023 09:47:55 +0100 Subject: [PATCH 024/142] HLR Feature --- examples/hlr/Lookup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/hlr/Lookup.cs b/examples/hlr/Lookup.cs index 47b47d0..bec0970 100644 --- a/examples/hlr/Lookup.cs +++ b/examples/hlr/Lookup.cs @@ -1,4 +1,5 @@ using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; var client = new ClientOAuth("token"); var features = new Features(client); From 3e7e5428f086cb1a05b87469ec6a82ba4f50f963 Mon Sep 17 00:00:00 2001 From: jlabno Date: Wed, 29 Nov 2023 01:07:05 +0100 Subject: [PATCH 025/142] MFA examples --- examples/mfa/CreateMFACode.cs | 4 +++- examples/mfa/VerifyMFACode.cs | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/mfa/CreateMFACode.cs b/examples/mfa/CreateMFACode.cs index 3f1265a..d79dec8 100644 --- a/examples/mfa/CreateMFACode.cs +++ b/examples/mfa/CreateMFACode.cs @@ -27,9 +27,11 @@ Console.WriteLine(validationErrorsError.Message); } } -catch (TooManyRequestsException) +catch (TooManyRequestsException ex) { + Console.WriteLine("Error: " + ex.Message); } catch (ClientException ex) { + Console.WriteLine("Error: " + ex.Message); } diff --git a/examples/mfa/VerifyMFACode.cs b/examples/mfa/VerifyMFACode.cs index b28e6ee..fbab675 100644 --- a/examples/mfa/VerifyMFACode.cs +++ b/examples/mfa/VerifyMFACode.cs @@ -24,9 +24,11 @@ Console.WriteLine(validationErrorsError.Message); } } -catch (InvalidVerificationCodeException) +catch (InvalidVerificationCodeException ex) { + Console.WriteLine("Error: " + ex.Message); } -catch (ExpiredVerificationCodeException) +catch (ExpiredVerificationCodeException ex) { + Console.WriteLine("Error: " + ex.Message); } From 2f4f56ed3ef0e793dc356169d0262979205e09d8 Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 5 Dec 2023 20:46:06 +0100 Subject: [PATCH 026/142] Allow to change request content-type --- smsapi/Api/Action/Action.cs | 10 ++++++---- smsapi/Api/Action/ActionContentType.cs | 7 +++++++ .../Api/Action/Contacts/BindContactToGroup.cs | 4 ++++ smsapi/Api/Action/Contacts/CreateContact.cs | 4 ++++ smsapi/Api/Action/Contacts/CreateField.cs | 4 ++++ smsapi/Api/Action/Contacts/CreateGroup.cs | 4 ++++ .../Action/Contacts/CreateGroupPermission.cs | 4 ++++ smsapi/Api/Action/Contacts/DeleteContact.cs | 2 ++ smsapi/Api/Action/Contacts/DeleteField.cs | 2 ++ smsapi/Api/Action/Contacts/DeleteGroup.cs | 2 ++ .../Action/Contacts/DeleteGroupPermission.cs | 2 ++ smsapi/Api/Action/Contacts/EditContact.cs | 4 ++++ smsapi/Api/Action/Contacts/EditField.cs | 4 ++++ smsapi/Api/Action/Contacts/EditGroup.cs | 4 ++++ .../Api/Action/Contacts/EditGroupPermission.cs | 4 ++++ smsapi/Api/Action/Contacts/GetContact.cs | 2 ++ smsapi/Api/Action/Contacts/GetContactGroup.cs | 2 ++ smsapi/Api/Action/Contacts/GetGroup.cs | 2 ++ .../Api/Action/Contacts/GetGroupPermission.cs | 2 ++ .../Api/Action/Contacts/ListContactGroups.cs | 2 ++ smsapi/Api/Action/Contacts/ListContacts.cs | 2 ++ smsapi/Api/Action/Contacts/ListFieldOptions.cs | 2 ++ smsapi/Api/Action/Contacts/ListFields.cs | 2 ++ .../Action/Contacts/ListGroupPermissions.cs | 2 ++ smsapi/Api/Action/Contacts/ListGroups.cs | 2 ++ .../Action/Contacts/UnbindContactFromGroup.cs | 3 ++- smsapi/Api/Response/Contact.cs | 2 +- .../Deserialization/DeserializationResult.cs | 4 ++-- .../LegacyJsonResponseDeserializer.cs | 6 +++--- .../ResponseResolver/ErrorAwareResponse.cs | 15 +++++++++++++-- smsapi/NativeHttpClientHelper.cs | 15 ++++++++++++++- smsapi/Proxy.cs | 7 +++++++ smsapi/ProxyHTTP.cs | 18 +++++++++++++----- smsapiTests/Unit/Fixture/ProxyStub.cs | 13 +++++++------ smsapiTests/Unit/SpyProxy.cs | 15 ++++++++------- 35 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 smsapi/Api/Action/ActionContentType.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index e28e4f9..74bc0e8 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -17,6 +17,8 @@ public abstract class Action protected abstract RequestMethod Method { get; } + protected virtual ActionContentType ContentType => ActionContentType.Json; + protected virtual ApiType ApiType() { return Action.ApiType.Legacy; @@ -25,18 +27,18 @@ protected virtual ApiType ApiType() public T Execute() { Validate(); - return ProcessResponse(_proxy.Execute(UriWithPagination(), GetValues(), Files(), Method)); + return ProcessResponse(_proxy.Execute(ContentType, UriWithPagination(), GetValues(), Files(), Method)); } public async Task ExecuteAsync(CancellationToken cancellationToken = default) { Validate(); - return ProcessResponse(await _proxy.ExecuteAsync(UriWithPagination(), GetValues(), Files(), Method, cancellationToken)); - } + return ProcessResponse(await _proxy.ExecuteAsync(ContentType, UriWithPagination(), GetValues(), Files(), Method, cancellationToken)); + } public void Proxy(Proxy proxy) { - this._proxy = proxy; + _proxy = proxy; } protected virtual Dictionary Files() diff --git a/smsapi/Api/Action/ActionContentType.cs b/smsapi/Api/Action/ActionContentType.cs new file mode 100644 index 0000000..b16f695 --- /dev/null +++ b/smsapi/Api/Action/ActionContentType.cs @@ -0,0 +1,7 @@ +namespace SMSApi.Api.Action; + +public enum ActionContentType +{ + FormWww, + Json, +} diff --git a/smsapi/Api/Action/Contacts/BindContactToGroup.cs b/smsapi/Api/Action/Contacts/BindContactToGroup.cs index ae2f955..366ab1c 100644 --- a/smsapi/Api/Action/Contacts/BindContactToGroup.cs +++ b/smsapi/Api/Action/Contacts/BindContactToGroup.cs @@ -15,6 +15,10 @@ public BindContactToGroup(string contactId, string groupId) } protected override RequestMethod Method => RequestMethod.PUT; + + // protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/CreateContact.cs b/smsapi/Api/Action/Contacts/CreateContact.cs index 95d69ef..ed8f641 100644 --- a/smsapi/Api/Action/Contacts/CreateContact.cs +++ b/smsapi/Api/Action/Contacts/CreateContact.cs @@ -17,6 +17,10 @@ public class CreateContact : Action private string source; protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public CreateContact SetBirthdayDate(DateTime birthdayDate) { diff --git a/smsapi/Api/Action/Contacts/CreateField.cs b/smsapi/Api/Action/Contacts/CreateField.cs index e1bf428..fb6b489 100644 --- a/smsapi/Api/Action/Contacts/CreateField.cs +++ b/smsapi/Api/Action/Contacts/CreateField.cs @@ -9,6 +9,10 @@ public class CreateField : Action private string type; protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public CreateField SetName(string name) { diff --git a/smsapi/Api/Action/Contacts/CreateGroup.cs b/smsapi/Api/Action/Contacts/CreateGroup.cs index 4cc4fcf..2a43229 100644 --- a/smsapi/Api/Action/Contacts/CreateGroup.cs +++ b/smsapi/Api/Action/Contacts/CreateGroup.cs @@ -10,6 +10,10 @@ public class CreateGroup : Action private string name; protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public CreateGroup SetDescription(string description) { diff --git a/smsapi/Api/Action/Contacts/CreateGroupPermission.cs b/smsapi/Api/Action/Contacts/CreateGroupPermission.cs index 7fb7797..65009a4 100644 --- a/smsapi/Api/Action/Contacts/CreateGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/CreateGroupPermission.cs @@ -13,6 +13,10 @@ public class CreateGroupPermission : Action private bool write; protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public CreateGroupPermission(string groupId) { diff --git a/smsapi/Api/Action/Contacts/DeleteContact.cs b/smsapi/Api/Action/Contacts/DeleteContact.cs index e096a70..f3bc9a0 100644 --- a/smsapi/Api/Action/Contacts/DeleteContact.cs +++ b/smsapi/Api/Action/Contacts/DeleteContact.cs @@ -13,6 +13,8 @@ public DeleteContact(string contactId) } protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/DeleteField.cs b/smsapi/Api/Action/Contacts/DeleteField.cs index c041cb7..0b223fb 100644 --- a/smsapi/Api/Action/Contacts/DeleteField.cs +++ b/smsapi/Api/Action/Contacts/DeleteField.cs @@ -14,6 +14,8 @@ public DeleteField(string fieldId) } protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/DeleteGroup.cs b/smsapi/Api/Action/Contacts/DeleteGroup.cs index d2c27ae..ecf02b1 100644 --- a/smsapi/Api/Action/Contacts/DeleteGroup.cs +++ b/smsapi/Api/Action/Contacts/DeleteGroup.cs @@ -14,6 +14,8 @@ public DeleteGroup(string groupId) } protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/DeleteGroupPermission.cs b/smsapi/Api/Action/Contacts/DeleteGroupPermission.cs index 4c95bc6..58b0057 100644 --- a/smsapi/Api/Action/Contacts/DeleteGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/DeleteGroupPermission.cs @@ -15,6 +15,8 @@ public DeleteGroupPermission(string groupId, string username) } protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/EditContact.cs b/smsapi/Api/Action/Contacts/EditContact.cs index 5df4491..5adb177 100644 --- a/smsapi/Api/Action/Contacts/EditContact.cs +++ b/smsapi/Api/Action/Contacts/EditContact.cs @@ -24,6 +24,10 @@ public EditContact(string contactId) public string ContactId { get; } protected override RequestMethod Method => RequestMethod.PUT; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public EditContact SetBirthdayDate(DateTime birthdayDate) { diff --git a/smsapi/Api/Action/Contacts/EditField.cs b/smsapi/Api/Action/Contacts/EditField.cs index d886403..7ee553a 100644 --- a/smsapi/Api/Action/Contacts/EditField.cs +++ b/smsapi/Api/Action/Contacts/EditField.cs @@ -16,6 +16,10 @@ public EditField(string fieldId) } protected override RequestMethod Method => RequestMethod.PUT; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public EditField SetName(string name) { diff --git a/smsapi/Api/Action/Contacts/EditGroup.cs b/smsapi/Api/Action/Contacts/EditGroup.cs index b0d4125..ee468e6 100644 --- a/smsapi/Api/Action/Contacts/EditGroup.cs +++ b/smsapi/Api/Action/Contacts/EditGroup.cs @@ -17,6 +17,10 @@ public EditGroup(string groupId) } protected override RequestMethod Method => RequestMethod.PUT; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public EditGroup SetDescription(string description) { diff --git a/smsapi/Api/Action/Contacts/EditGroupPermission.cs b/smsapi/Api/Action/Contacts/EditGroupPermission.cs index 9288f93..c85f74e 100644 --- a/smsapi/Api/Action/Contacts/EditGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/EditGroupPermission.cs @@ -19,6 +19,10 @@ public EditGroupPermission(string groupId, string username) } protected override RequestMethod Method => RequestMethod.PUT; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.FormWww; public EditGroupPermission SetRead(bool read) { diff --git a/smsapi/Api/Action/Contacts/GetContact.cs b/smsapi/Api/Action/Contacts/GetContact.cs index 581688e..1437a4d 100644 --- a/smsapi/Api/Action/Contacts/GetContact.cs +++ b/smsapi/Api/Action/Contacts/GetContact.cs @@ -13,6 +13,8 @@ public GetContact(string contactId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/GetContactGroup.cs b/smsapi/Api/Action/Contacts/GetContactGroup.cs index e6161e2..fad5194 100644 --- a/smsapi/Api/Action/Contacts/GetContactGroup.cs +++ b/smsapi/Api/Action/Contacts/GetContactGroup.cs @@ -15,6 +15,8 @@ public GetContactGroup(string contactId, string groupId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/GetGroup.cs b/smsapi/Api/Action/Contacts/GetGroup.cs index cf3d3d3..8c51da9 100644 --- a/smsapi/Api/Action/Contacts/GetGroup.cs +++ b/smsapi/Api/Action/Contacts/GetGroup.cs @@ -13,6 +13,8 @@ public GetGroup(string groupId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/GetGroupPermission.cs b/smsapi/Api/Action/Contacts/GetGroupPermission.cs index 5d24349..62a5f2e 100644 --- a/smsapi/Api/Action/Contacts/GetGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/GetGroupPermission.cs @@ -15,6 +15,8 @@ public GetGroupPermission(string groupId, string username) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/ListContactGroups.cs b/smsapi/Api/Action/Contacts/ListContactGroups.cs index 1232906..6a8b1a0 100644 --- a/smsapi/Api/Action/Contacts/ListContactGroups.cs +++ b/smsapi/Api/Action/Contacts/ListContactGroups.cs @@ -12,6 +12,8 @@ public ListContactGroups(string contactId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/ListContacts.cs b/smsapi/Api/Action/Contacts/ListContacts.cs index 64e1fa8..00918b1 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -19,6 +19,8 @@ public class ListContacts : Action protected override RequestMethod Method => RequestMethod.GET; + protected override ApiType ApiType() => Action.ApiType.Rest; + public ListContacts SetBirthdayDate(DateTime? birthdayDate) { this.birthdayDate = birthdayDate; diff --git a/smsapi/Api/Action/Contacts/ListFieldOptions.cs b/smsapi/Api/Action/Contacts/ListFieldOptions.cs index 282fd46..76909f6 100644 --- a/smsapi/Api/Action/Contacts/ListFieldOptions.cs +++ b/smsapi/Api/Action/Contacts/ListFieldOptions.cs @@ -12,6 +12,8 @@ public ListFieldOptions(string fieldId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/ListFields.cs b/smsapi/Api/Action/Contacts/ListFields.cs index 6e27dd0..fea36c3 100644 --- a/smsapi/Api/Action/Contacts/ListFields.cs +++ b/smsapi/Api/Action/Contacts/ListFields.cs @@ -5,6 +5,8 @@ namespace SMSApi.Api.Action public class ListFields : Action { protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/ListGroupPermissions.cs b/smsapi/Api/Action/Contacts/ListGroupPermissions.cs index de5e109..e5d61c6 100644 --- a/smsapi/Api/Action/Contacts/ListGroupPermissions.cs +++ b/smsapi/Api/Action/Contacts/ListGroupPermissions.cs @@ -12,6 +12,8 @@ public ListGroupPermissions(string groupId) } protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Action/Contacts/ListGroups.cs b/smsapi/Api/Action/Contacts/ListGroups.cs index 6c36a87..8dcf0b0 100644 --- a/smsapi/Api/Action/Contacts/ListGroups.cs +++ b/smsapi/Api/Action/Contacts/ListGroups.cs @@ -9,6 +9,8 @@ public class ListGroups : Action private string name; protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; public ListGroups SetId(string id) { diff --git a/smsapi/Api/Action/Contacts/UnbindContactFromGroup.cs b/smsapi/Api/Action/Contacts/UnbindContactFromGroup.cs index 4385041..7261b13 100644 --- a/smsapi/Api/Action/Contacts/UnbindContactFromGroup.cs +++ b/smsapi/Api/Action/Contacts/UnbindContactFromGroup.cs @@ -1,5 +1,4 @@ using System; -using SMSApi.Api.Response; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Action @@ -16,6 +15,8 @@ public UnbindContactFromGroup(string contactId, string groupId) } protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; protected override string Uri() { diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index 4c36644..f1aa51f 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -5,7 +5,7 @@ namespace SMSApi.Api.Response { [DataContract] - public class Contact : ErrorAwareResponse + public class Contact : IResponseCodeAwareResolver { public const string FemaleGender = "female"; public const string MaleGender = "male"; diff --git a/smsapi/Api/Response/Deserialization/DeserializationResult.cs b/smsapi/Api/Response/Deserialization/DeserializationResult.cs index 1d6f555..86bf942 100644 --- a/smsapi/Api/Response/Deserialization/DeserializationResult.cs +++ b/smsapi/Api/Response/Deserialization/DeserializationResult.cs @@ -14,10 +14,10 @@ public readonly struct ResponseError public readonly string Message; public readonly int Code; - public ResponseError(string message, int code) + public ResponseError(string message, dynamic code) { Message = message; - Code = code; + Code = code is int i ? i : 0; } } } diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index ea08844..f76d3fc 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -46,7 +46,7 @@ private void HandleError(HttpResponseEntity responseEntity, DeserializationRe { var error = _baseJsonDeserializer.Deserialize(responseEntity).Result; - if (!error.IsError()) return; + if (!error!.IsError()) return; if (IsHostError(error.ErrorCode)) { @@ -79,7 +79,7 @@ private void HandleError(HttpResponseEntity responseEntity, DeserializationRe * 1000 Akcja dostępna tylko dla użytkownika głównego * 1001 Nieprawidłowa akcja */ - private static bool IsClientError(int code) + private static bool IsClientError(dynamic code) { switch (code) { @@ -103,7 +103,7 @@ private static bool IsClientError(int code) * 999 Wewnętrzny błąd systemu * 201 Wewnętrzny błąd systemu */ - private static bool IsHostError(int code) + private static bool IsHostError(dynamic code) { switch (code) { diff --git a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs index e007bc7..0752581 100644 --- a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs +++ b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs @@ -6,12 +6,23 @@ namespace SMSApi.Api.Response.ResponseResolver public class ErrorAwareResponse: IErrorResponse { [DataMember(Name = "error", IsRequired = false)] - public readonly int ErrorCode; + public readonly dynamic? ErrorCode; [DataMember(Name = "message", IsRequired = false)] public readonly string ErrorMessage; + + public bool IsError() + { + if (ErrorCode == null) return false; + + if (ErrorCode is string) + { + return ErrorCode != ""; + } + + return ErrorCode != 0; + } - public bool IsError() => ErrorCode != 0; public string GetErrorMessage() => ErrorMessage; } } diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 3ba0350..5f432cc 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using SMSApi.Api.Action; namespace SMSApi.Api { @@ -59,7 +60,7 @@ private static HttpContent ConvertNameValueCollectionToHttpContent( .ToList(); var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); - if (files == null) return formUrlEncodedContent; + if (files == null || files.Count == 0) return formUrlEncodedContent; var multipartContent = new MultipartFormDataContent(); @@ -72,5 +73,17 @@ private static HttpContent ConvertNameValueCollectionToHttpContent( return multipartContent; } + + public static void AddContentTypeHeader(this HttpClient httpClient, ActionContentType actionContentType) + { + var contentType = actionContentType switch + { + ActionContentType.Json => "application/json", + ActionContentType.FormWww => "application/x-www-form-urlencoded", + _ => throw new ArgumentOutOfRangeException(nameof(actionContentType), actionContentType, @"Not supported content type") + }; + + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", contentType); + } } } diff --git a/smsapi/Proxy.cs b/smsapi/Proxy.cs index 693593b..02f4573 100644 --- a/smsapi/Proxy.cs +++ b/smsapi/Proxy.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using SMSApi.Api.Action; namespace SMSApi.Api { @@ -11,23 +12,27 @@ public interface Proxy void Authentication(IClient client); HttpResponseEntity Execute( + ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method); HttpResponseEntity Execute( + ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method); HttpResponseEntity Execute( + ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method); Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, @@ -35,6 +40,7 @@ Task ExecuteAsync( ); Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, Stream file, @@ -43,6 +49,7 @@ Task ExecuteAsync( ); Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index b09decf..d888c60 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using SMSApi.Api.Action; namespace SMSApi.Api { @@ -24,21 +25,23 @@ public void Authentication(IClient client) authentication = client; } - public HttpResponseEntity Execute(string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) { - return Execute(uri, data, new Dictionary(), method); + return Execute(contentType, uri, data, new Dictionary(), method); } public HttpResponseEntity Execute( + ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method) { - return Execute(uri, data, new Dictionary { { "file", file } }, method); + return Execute(contentType, uri, data, new Dictionary { { "file", file } }, method); } public HttpResponseEntity Execute( + ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, @@ -50,6 +53,7 @@ public HttpResponseEntity Execute( try { + client.AddContentTypeHeader(contentType); return client.SendRequest(method, uri, data, files).Result; } catch (System.Exception e) @@ -59,16 +63,18 @@ public HttpResponseEntity Execute( } public async Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default ) { - return await ExecuteAsync(uri, data, new Dictionary(), method); + return await ExecuteAsync(contentType, uri, data, new Dictionary(), method); } public async Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, Stream file, @@ -76,10 +82,11 @@ public async Task ExecuteAsync( CancellationToken cancellationToken = default ) { - return await ExecuteAsync(uri, data, new Dictionary { { "file", file } }, method); + return await ExecuteAsync(contentType, uri, data, new Dictionary { { "file", file } }, method); } public async Task ExecuteAsync( + ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, @@ -93,6 +100,7 @@ public async Task ExecuteAsync( try { + client.AddContentTypeHeader(contentType); return await client.SendRequest(method, uri, data, files, cancellationToken); } catch (System.Exception e) diff --git a/smsapiTests/Unit/Fixture/ProxyStub.cs b/smsapiTests/Unit/Fixture/ProxyStub.cs index ef9264a..537ee4e 100644 --- a/smsapiTests/Unit/Fixture/ProxyStub.cs +++ b/smsapiTests/Unit/Fixture/ProxyStub.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using SMSApi.Api; +using SMSApi.Api.Action; namespace smsapiTests.Unit.Fixture; @@ -16,32 +17,32 @@ public void Authentication(IClient client) throw new System.NotImplementedException(); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) { throw new System.NotImplementedException(); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, Stream file, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method) { throw new System.NotImplementedException(); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, Dictionary files, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method) { return SyncExecutionResponse; } - public Task ExecuteAsync(string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } - public Task ExecuteAsync(string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } - public Task ExecuteAsync(string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index f93e406..664f194 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using SMSApi.Api; +using SMSApi.Api.Action; namespace smsapiTests.Unit; @@ -18,10 +19,10 @@ public class SpyProxy : Proxy public void Authentication(IClient client) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -29,7 +30,7 @@ public HttpResponseEntity Execute(string uri, NameValueCollection data, RequestM return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, Stream file, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -37,7 +38,7 @@ public HttpResponseEntity Execute(string uri, NameValueCollection data, Stream f return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } - public HttpResponseEntity Execute(string uri, NameValueCollection data, Dictionary files, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -45,7 +46,7 @@ public HttpResponseEntity Execute(string uri, NameValueCollection data, Dictiona return new HttpResponseEntity(Task.FromResult(Stream.Null), HttpStatusCode.OK); } - public Task ExecuteAsync(string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); @@ -53,7 +54,7 @@ public Task ExecuteAsync(string uri, NameValueCollection dat return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } - public Task ExecuteAsync(string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); @@ -61,7 +62,7 @@ public Task ExecuteAsync(string uri, NameValueCollection dat return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } - public Task ExecuteAsync(string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); From 05548137134d653ffdb4580d9968a46b81a3e8e5 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 11 Dec 2023 16:37:22 +0100 Subject: [PATCH 027/142] Blacklist feature --- examples/blacklist/Remove.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/blacklist/Remove.cs b/examples/blacklist/Remove.cs index b91e287..d059325 100644 --- a/examples/blacklist/Remove.cs +++ b/examples/blacklist/Remove.cs @@ -14,7 +14,7 @@ //record is deleted at this point } -catch (BlacklistRecordDoesNotExistException exception) +catch (BlacklistRecordDoesNotExistException ex) { - System.Console.WriteLine(e.Message); + System.Console.WriteLine(ex.Message); } From f7ed6f3bffff135e6041fc1e6af69f3f7313a319 Mon Sep 17 00:00:00 2001 From: jlabno Date: Wed, 13 Dec 2023 09:25:00 +0100 Subject: [PATCH 028/142] MFA examples --- examples/mfa/VerifyMFACode.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/mfa/VerifyMFACode.cs b/examples/mfa/VerifyMFACode.cs index fbab675..d6d355f 100644 --- a/examples/mfa/VerifyMFACode.cs +++ b/examples/mfa/VerifyMFACode.cs @@ -32,3 +32,7 @@ { Console.WriteLine("Error: " + ex.Message); } +catch (ClientException ex) +{ + Console.WriteLine("Message: " + ex.Message); +} From 7c87997a558347cfa2a62f8e94aa97145755d41f Mon Sep 17 00:00:00 2001 From: jlabno Date: Wed, 13 Dec 2023 17:24:11 +0100 Subject: [PATCH 029/142] MFA examples --- examples/mfa/VerifyMFACode.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/mfa/VerifyMFACode.cs b/examples/mfa/VerifyMFACode.cs index d6d355f..eb2000f 100644 --- a/examples/mfa/VerifyMFACode.cs +++ b/examples/mfa/VerifyMFACode.cs @@ -32,6 +32,10 @@ { Console.WriteLine("Error: " + ex.Message); } +catch (TooManyRequestsException ex) +{ + Console.WriteLine("Error: " + ex.Message); +} catch (ClientException ex) { Console.WriteLine("Message: " + ex.Message); From 43793273f206f07b616a83cad2a23a8b8aec56e8 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Thu, 14 Dec 2023 11:09:56 +0100 Subject: [PATCH 030/142] FIX template message in SMS --- smsapi/Api/Action/SMS/Send.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapi/Api/Action/SMS/Send.cs b/smsapi/Api/Action/SMS/Send.cs index 84814c8..06f2f98 100644 --- a/smsapi/Api/Action/SMS/Send.cs +++ b/smsapi/Api/Action/SMS/Send.cs @@ -186,7 +186,7 @@ protected override string Uri() protected override void Validate() { - if (text == null) + if (text == null && template == null) { throw new ArgumentException("Cannot send message without text!"); } From 14de7b15a88a93aec94ce53f32c30cf9f985a64d Mon Sep 17 00:00:00 2001 From: jlabno Date: Wed, 20 Dec 2023 12:58:19 +0100 Subject: [PATCH 031/142] Fix contacts responses --- smsapi/Api/Action/Contacts/CreateGroup.cs | 2 +- smsapi/Api/Action/Contacts/ListContacts.cs | 5 ++++- smsapi/Api/Response/Contact.cs | 11 +++++++++++ .../Exception/ContactAlreadyExistsException.cs | 10 ++++++++++ smsapi/Api/Response/Field.cs | 3 ++- smsapi/Api/Response/Group.cs | 2 +- smsapi/Api/Response/GroupPermission.cs | 2 +- .../Response/ResponseResolver/ErrorAwareResponse.cs | 2 +- 8 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 smsapi/Api/Response/Contacts/Exception/ContactAlreadyExistsException.cs diff --git a/smsapi/Api/Action/Contacts/CreateGroup.cs b/smsapi/Api/Action/Contacts/CreateGroup.cs index 2a43229..4b1ba00 100644 --- a/smsapi/Api/Action/Contacts/CreateGroup.cs +++ b/smsapi/Api/Action/Contacts/CreateGroup.cs @@ -48,7 +48,7 @@ protected override NameValueCollection Values() if (description != null) { - values.Add("desciption", description); + values.Add("description", description); } if (idx != null) diff --git a/smsapi/Api/Action/Contacts/ListContacts.cs b/smsapi/Api/Action/Contacts/ListContacts.cs index 00918b1..bf6a3fb 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -4,7 +4,7 @@ namespace SMSApi.Api.Action { - public class ListContacts : Action + public class ListContacts : Action, IPaginable { private DateTime? birthdayDate; private string email; @@ -141,5 +141,8 @@ protected override NameValueCollection Values() return parameters; } + + public uint? Limit { get; set; } + public uint? Offset { get; set; } } } diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index f1aa51f..9189571 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Runtime.Serialization; +using smsapi.Api.Response.Contacts.Exception; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response @@ -59,6 +62,14 @@ public class Contact : IResponseCodeAwareResolver public DateTime? BirthdayDate { get; private set; } + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 409, _ => throw new ContactAlreadyExistsException() } + }; + } + [Obsolete("use DateCreated instead")] public uint DateAdd { diff --git a/smsapi/Api/Response/Contacts/Exception/ContactAlreadyExistsException.cs b/smsapi/Api/Response/Contacts/Exception/ContactAlreadyExistsException.cs new file mode 100644 index 0000000..90403fa --- /dev/null +++ b/smsapi/Api/Response/Contacts/Exception/ContactAlreadyExistsException.cs @@ -0,0 +1,10 @@ +using SMSApi.Api; + +namespace smsapi.Api.Response.Contacts.Exception; + +public class ContactAlreadyExistsException : ClientException +{ + public ContactAlreadyExistsException() : base("Contact already exists", 409) + { + } +} diff --git a/smsapi/Api/Response/Field.cs b/smsapi/Api/Response/Field.cs index bfa9824..ba71ed4 100644 --- a/smsapi/Api/Response/Field.cs +++ b/smsapi/Api/Response/Field.cs @@ -1,9 +1,10 @@ using System.Runtime.Serialization; +using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response { [DataContract] - public class Field + public class Field : IResponseCodeAwareResolver { public const string DateType = "DATE"; public const string EmailType = "EMAIL"; diff --git a/smsapi/Api/Response/Group.cs b/smsapi/Api/Response/Group.cs index ec6a3a2..ecbb0d6 100644 --- a/smsapi/Api/Response/Group.cs +++ b/smsapi/Api/Response/Group.cs @@ -6,7 +6,7 @@ namespace SMSApi.Api.Response { [DataContract] - public class Group : ErrorAwareResponse + public class Group : ErrorAwareResponse, IResponseCodeAwareResolver { [DataMember(Name = "created_by", IsRequired = false)] public readonly string CreatedBy; diff --git a/smsapi/Api/Response/GroupPermission.cs b/smsapi/Api/Response/GroupPermission.cs index dd333cb..71817b9 100644 --- a/smsapi/Api/Response/GroupPermission.cs +++ b/smsapi/Api/Response/GroupPermission.cs @@ -4,7 +4,7 @@ namespace SMSApi.Api.Response { [DataContract] - public class GroupPermission : ErrorAwareResponse + public class GroupPermission : ErrorAwareResponse, IResponseCodeAwareResolver { [DataMember(Name = "group_id", IsRequired = false)] public readonly string GroupId; diff --git a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs index 0752581..501f4b4 100644 --- a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs +++ b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs @@ -3,7 +3,7 @@ namespace SMSApi.Api.Response.ResponseResolver { [DataContract] - public class ErrorAwareResponse: IErrorResponse + public class ErrorAwareResponse: IResponseCodeAwareResolver { [DataMember(Name = "error", IsRequired = false)] public readonly dynamic? ErrorCode; From 422eecd58663a342d886daade290ecf6a9734391 Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 23 Jan 2024 22:40:31 +0100 Subject: [PATCH 032/142] Fix contacts list limit/offset query --- smsapi/Api/Action/Contacts/ListContacts.cs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/smsapi/Api/Action/Contacts/ListContacts.cs b/smsapi/Api/Action/Contacts/ListContacts.cs index bf6a3fb..057de75 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -12,8 +12,6 @@ public class ListContacts : Action, IPaginable private string gender; private int? groupId; private string lastName; - private int? limit; - private int? offset; private string phoneNumber; private string search; @@ -57,15 +55,21 @@ public ListContacts SetLastName(string lastName) return this; } + [Obsolete($"Use {nameof(Limit)} instead", false)] public ListContacts SetLimit(int? limit) { - this.limit = limit; + if (limit != null) + Limit = (uint?)limit; + return this; } + [Obsolete($"Use {nameof(Offset)} instead", false)] public ListContacts SetOffset(int? offset) { - this.offset = offset; + if (offset != null) + Offset = (uint?)offset; + return this; } @@ -94,16 +98,6 @@ protected override NameValueCollection Values() parameters.Add("q", search); } - if (offset != null) - { - parameters.Add("offset", offset.Value.ToString()); - } - - if (limit != null) - { - parameters.Add("limit", limit.Value.ToString()); - } - if (phoneNumber != null) { parameters.Add("phone_number", phoneNumber); From a3c650d417a7bdc7a5026774215bc1b99a441440 Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 23 Jan 2024 22:43:28 +0100 Subject: [PATCH 033/142] Contacts list - group id by string --- smsapi/Api/Action/Contacts/ListContacts.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Action/Contacts/ListContacts.cs b/smsapi/Api/Action/Contacts/ListContacts.cs index 057de75..e0f2e02 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -10,7 +10,7 @@ public class ListContacts : Action, IPaginable private string email; private string firstName; private string gender; - private int? groupId; + private string? groupId; private string lastName; private string phoneNumber; private string search; @@ -44,6 +44,12 @@ public ListContacts SetGender(string gender) } public ListContacts SetGroupId(int? groupId) + { + this.groupId = groupId.ToString(); + return this; + } + + public ListContacts SetGroupId(string groupId) { this.groupId = groupId; return this; @@ -120,7 +126,7 @@ protected override NameValueCollection Values() if (groupId != null) { - parameters.Add("group_id", groupId.Value.ToString()); + parameters.Add("group_id", groupId); } if (gender != null) From 1e452756c4ba6825179ceb4c01cb630c44aaea0a Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 23 Jan 2024 22:48:37 +0100 Subject: [PATCH 034/142] Fix field options listing --- smsapi/Api/Response/FieldOptions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/smsapi/Api/Response/FieldOptions.cs b/smsapi/Api/Response/FieldOptions.cs index f59f20e..13457b6 100644 --- a/smsapi/Api/Response/FieldOptions.cs +++ b/smsapi/Api/Response/FieldOptions.cs @@ -5,7 +5,5 @@ namespace SMSApi.Api.Response [DataContract] public class FieldOptions : BasicCollection { - private FieldOptions() - { } } } From 225f8f50b4acc508750560eb09a8ca2168860e82 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 29 Jan 2024 19:49:45 +0100 Subject: [PATCH 035/142] Fix GET query, additional parameters --- smsapi/Api/Action/Action.cs | 33 +++++++++++++++------ smsapi/Api/Action/ActionPaginationHelper.cs | 2 +- smsapi/Api/Action/UriHelper.cs | 11 +++++++ 3 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 smsapi/Api/Action/UriHelper.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 74bc0e8..cde16c0 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -33,8 +33,9 @@ public T Execute() public async Task ExecuteAsync(CancellationToken cancellationToken = default) { Validate(); - return ProcessResponse(await _proxy.ExecuteAsync(ContentType, UriWithPagination(), GetValues(), Files(), Method, cancellationToken)); - } + return ProcessResponse(await _proxy.ExecuteAsync(ContentType, UriWithPagination(), GetValues(), Files(), Method, + cancellationToken)); + } public void Proxy(Proxy proxy) { @@ -67,7 +68,7 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of return deserializationResult.Result; } - protected abstract string Uri(); + protected abstract string Uri(); protected virtual void Validate() { @@ -80,16 +81,30 @@ protected virtual NameValueCollection Values() private string UriWithPagination() { - if (!typeof(IPaginable).IsAssignableFrom(GetType())) return Uri(); - - var uri = new UriBuilder + var uriBuilder = new UriBuilder { Path = Uri() }; - - var action = (IPaginable) this; - return uri.ToUriWithPagination(action.Limit, action.Offset); + assignValuesToQuery(uriBuilder); + + if (!typeof(IPaginable).IsAssignableFrom(GetType())) + return uriBuilder.ToPathWithQuery(); + + var action = (IPaginable)this; + + return uriBuilder.ToUriWithPagination(action.Limit, action.Offset); + } + + private void assignValuesToQuery(UriBuilder uriBuilder) + { + if (!Method.Equals(RequestMethod.GET)) return; + + var query = HttpUtility.ParseQueryString(uriBuilder.Query); + + query.Add(Values()); + + uriBuilder.Query = query.ToString(); } private T ProcessResponse(HttpResponseEntity responseEntity) diff --git a/smsapi/Api/Action/ActionPaginationHelper.cs b/smsapi/Api/Action/ActionPaginationHelper.cs index 368fda5..8d5bd27 100644 --- a/smsapi/Api/Action/ActionPaginationHelper.cs +++ b/smsapi/Api/Action/ActionPaginationHelper.cs @@ -17,6 +17,6 @@ public static string ToUriWithPagination(this UriBuilder uriBuilder, uint? limit uriBuilder.Query = query.ToString(); - return uriBuilder.Path + uriBuilder.Query; + return uriBuilder.ToPathWithQuery(); } } diff --git a/smsapi/Api/Action/UriHelper.cs b/smsapi/Api/Action/UriHelper.cs new file mode 100644 index 0000000..fe8a90b --- /dev/null +++ b/smsapi/Api/Action/UriHelper.cs @@ -0,0 +1,11 @@ +using System; + +namespace SMSApi.Api.Action; + +public static class UriHelper +{ + public static string ToPathWithQuery(this UriBuilder uriBuilder) + { + return uriBuilder.Path + uriBuilder.Query; + } +} From 7ec29d48a8d498f401958e649bd1abf1d7dec025 Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 5 Feb 2024 23:04:56 +0100 Subject: [PATCH 036/142] Add 404 default handler --- smsapi/Api/Action/Action.cs | 3 ++- .../Deserialization/NotFoundErrorResolver.cs | 18 ++++++++++++++++++ .../REST/Exception/NotFoundException.cs | 10 ++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 smsapi/Api/Response/Deserialization/NotFoundErrorResolver.cs create mode 100644 smsapi/Api/Response/REST/Exception/NotFoundException.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index cde16c0..5af53d1 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -55,7 +55,8 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of new LegacyJsonResponseDeserializer(), new ValidationErrorsResolver(new BaseJsonDeserializer()), new TooManyRequestsErrorResolver(), - new AccessErrorResolver() + new AccessErrorResolver(), + new NotFoundErrorResolver() ), Action.ApiType.Legacy => new LegacyJsonResponseDeserializer(), _ => throw new Exception("Unknown api type") diff --git a/smsapi/Api/Response/Deserialization/NotFoundErrorResolver.cs b/smsapi/Api/Response/Deserialization/NotFoundErrorResolver.cs new file mode 100644 index 0000000..efa24e9 --- /dev/null +++ b/smsapi/Api/Response/Deserialization/NotFoundErrorResolver.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SMSApi.Api.Response.ResponseResolver; +using smsapi.Api.Response.REST.Exception; + +namespace SMSApi.Api.Response.Deserialization; + +public class NotFoundErrorResolver : IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 404, _ => throw new NotFoundException() }, + }; + } +} diff --git a/smsapi/Api/Response/REST/Exception/NotFoundException.cs b/smsapi/Api/Response/REST/Exception/NotFoundException.cs new file mode 100644 index 0000000..3c78fc2 --- /dev/null +++ b/smsapi/Api/Response/REST/Exception/NotFoundException.cs @@ -0,0 +1,10 @@ +using SMSApi.Api; + +namespace smsapi.Api.Response.REST.Exception; + +public class NotFoundException : ClientException +{ + public NotFoundException() : base("Not found", 404) + { + } +} From 969254ec11ee7cf1bac64f0a60dbaf669c914f3f Mon Sep 17 00:00:00 2001 From: jlabno Date: Mon, 5 Feb 2024 23:05:14 +0100 Subject: [PATCH 037/142] Fix group contacts listing, count can be null --- smsapi/Api/Response/Group.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/smsapi/Api/Response/Group.cs b/smsapi/Api/Response/Group.cs index ecbb0d6..c5dfd3f 100644 --- a/smsapi/Api/Response/Group.cs +++ b/smsapi/Api/Response/Group.cs @@ -6,7 +6,7 @@ namespace SMSApi.Api.Response { [DataContract] - public class Group : ErrorAwareResponse, IResponseCodeAwareResolver + public class Group : ErrorAwareResponse { [DataMember(Name = "created_by", IsRequired = false)] public readonly string CreatedBy; @@ -23,11 +23,8 @@ public class Group : ErrorAwareResponse, IResponseCodeAwareResolver [DataMember(Name = "permissions", IsRequired = false)] private List permissions; - private Group() - { } - [DataMember(Name = "contacts_count", IsRequired = false)] - public int ContactsCount { get; private set; } + public int? ContactsCount { get; private set; } public DateTime? DateCreated { get; private set; } From eda66cbaed0b3f2c8c734cd00afc994d3967098d Mon Sep 17 00:00:00 2001 From: jlabno Date: Tue, 21 May 2024 12:49:21 +0200 Subject: [PATCH 038/142] Allow using own HttpClient in default proxy --- smsapi/Api/Features.cs | 8 ++++++++ smsapi/ProxyHTTP.cs | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Features.cs b/smsapi/Api/Features.cs index 502093d..f4e6252 100644 --- a/smsapi/Api/Features.cs +++ b/smsapi/Api/Features.cs @@ -1,3 +1,5 @@ +using System.Net.Http; + namespace SMSApi.Api; public class Features @@ -11,6 +13,12 @@ public Features(IClient client, ProxyAddress proxy = ProxyAddress.SmsApiIo) Client = client; } + public Features(IClient client, HttpClient httpClient) + { + Proxy = new ProxyHTTP(ProxyAddress.SmsApiIo.GetUrl(), httpClient); + Client = client; + } + public Features(IClient client, Proxy proxy) { Proxy = proxy; diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index d888c60..5278b6d 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -13,11 +13,13 @@ namespace SMSApi.Api public class ProxyHTTP : Proxy { private readonly string baseUrl; + private readonly HttpClient? httpClient; private IClient authentication; - public ProxyHTTP(string baseUrl) + public ProxyHTTP(string baseUrl, HttpClient? httpClient = null) { this.baseUrl = baseUrl; + this.httpClient = httpClient; } public void Authentication(IClient client) @@ -111,7 +113,8 @@ public async Task ExecuteAsync( private HttpClient CreateClient() { - var client = new HttpClient(); + var client = httpClient ?? new HttpClient(); + client.BaseAddress = new Uri(baseUrl); client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", authentication.GetClientAgent()); From d126e22e3929cf52a166213874f1e7a28af009f3 Mon Sep 17 00:00:00 2001 From: jlabno Date: Wed, 22 May 2024 16:04:41 +0200 Subject: [PATCH 039/142] Remove useless base exception --- .../LegacyJsonResponseDeserializer.cs | 1 + smsapi/Exception.cs | 13 ------------- smsapi/ProxyException.cs | 2 +- smsapi/ProxyHTTP.cs | 4 ++-- smsapi/SmsapiException.cs | 4 +++- 5 files changed, 7 insertions(+), 17 deletions(-) delete mode 100644 smsapi/Exception.cs diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index f76d3fc..ad173ea 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.IO; using System.Runtime.Serialization; using smsapi.Api.Response.Deserialization.Exception; diff --git a/smsapi/Exception.cs b/smsapi/Exception.cs deleted file mode 100644 index 5078194..0000000 --- a/smsapi/Exception.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace SMSApi.Api -{ - public class Exception : System.Exception - { - public Exception(string message) - : base(message) - { } - - public Exception(string message, System.Exception inner) - : base(message, inner) - { } - } -} diff --git a/smsapi/ProxyException.cs b/smsapi/ProxyException.cs index 144705e..be404d2 100644 --- a/smsapi/ProxyException.cs +++ b/smsapi/ProxyException.cs @@ -1,6 +1,6 @@ namespace SMSApi.Api { - public class ProxyException : Exception + public class ProxyException : System.Exception { public ProxyException(string message) : base(message) diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index 5278b6d..bc29ee5 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -58,7 +58,7 @@ public HttpResponseEntity Execute( client.AddContentTypeHeader(contentType); return client.SendRequest(method, uri, data, files).Result; } - catch (System.Exception e) + catch (Exception e) { throw new ProxyException("Failed to get response from " + uri, e); } @@ -105,7 +105,7 @@ public async Task ExecuteAsync( client.AddContentTypeHeader(contentType); return await client.SendRequest(method, uri, data, files, cancellationToken); } - catch (System.Exception e) + catch (Exception e) { throw new ProxyException("Failed to get response from " + uri, e); } diff --git a/smsapi/SmsapiException.cs b/smsapi/SmsapiException.cs index 544d3fc..6115ced 100644 --- a/smsapi/SmsapiException.cs +++ b/smsapi/SmsapiException.cs @@ -1,4 +1,6 @@ -namespace SMSApi.Api +using System; + +namespace SMSApi.Api { public class SmsapiException : Exception { From d9b22835600113d5106549621c657a47c813b0c7 Mon Sep 17 00:00:00 2001 From: jlabno Date: Fri, 24 May 2024 09:43:41 +0200 Subject: [PATCH 040/142] Allow using own HttpClient in default proxy --- smsapi/Api/Features.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Features.cs b/smsapi/Api/Features.cs index f4e6252..a298b15 100644 --- a/smsapi/Api/Features.cs +++ b/smsapi/Api/Features.cs @@ -12,13 +12,19 @@ public Features(IClient client, ProxyAddress proxy = ProxyAddress.SmsApiIo) Proxy = new ProxyHTTP(proxy.GetUrl()); Client = client; } - + public Features(IClient client, HttpClient httpClient) { Proxy = new ProxyHTTP(ProxyAddress.SmsApiIo.GetUrl(), httpClient); Client = client; } - + + public Features(IClient client, HttpClient httpClient, ProxyAddress proxy = ProxyAddress.SmsApiIo) + { + Proxy = new ProxyHTTP(proxy.GetUrl(), httpClient); + Client = client; + } + public Features(IClient client, Proxy proxy) { Proxy = proxy; From d87aa6d6a709b4981c1d1743f5bc8a7c7e890397 Mon Sep 17 00:00:00 2001 From: jlabno Date: Fri, 24 May 2024 09:44:30 +0200 Subject: [PATCH 041/142] Allow using own HttpClient in default proxy --- smsapi/Api/Features.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/smsapi/Api/Features.cs b/smsapi/Api/Features.cs index a298b15..c3d5a97 100644 --- a/smsapi/Api/Features.cs +++ b/smsapi/Api/Features.cs @@ -13,12 +13,6 @@ public Features(IClient client, ProxyAddress proxy = ProxyAddress.SmsApiIo) Client = client; } - public Features(IClient client, HttpClient httpClient) - { - Proxy = new ProxyHTTP(ProxyAddress.SmsApiIo.GetUrl(), httpClient); - Client = client; - } - public Features(IClient client, HttpClient httpClient, ProxyAddress proxy = ProxyAddress.SmsApiIo) { Proxy = new ProxyHTTP(proxy.GetUrl(), httpClient); From a1481b0ab59885bc8730ccbd9564940ed00d5c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 26 Nov 2024 12:18:50 +0000 Subject: [PATCH 042/142] Add Opt-out feature #deletion --- smsapi/Api/Action/OptOut/DeleteOptOut.cs | 28 ++++++++++ smsapi/Api/OptOutFactory.cs | 46 ++++++++++++++++ .../Exception/OptOutNotFoundException.cs | 8 +++ .../Response/OptOut/OptOutDeletionResponse.cs | 18 +++++++ .../Action/OptOut/DeleteOptOutResponseTest.cs | 52 +++++++++++++++++++ .../Unit/Action/OptOut/DeleteOptOutTest.cs | 44 ++++++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 6 +++ smsapiTests/Unit/SpyProxy.cs | 12 ++++- 8 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 smsapi/Api/Action/OptOut/DeleteOptOut.cs create mode 100644 smsapi/Api/OptOutFactory.cs create mode 100644 smsapi/Api/Response/OptOut/Exception/OptOutNotFoundException.cs create mode 100644 smsapi/Api/Response/OptOut/OptOutDeletionResponse.cs create mode 100644 smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs create mode 100644 smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs diff --git a/smsapi/Api/Action/OptOut/DeleteOptOut.cs b/smsapi/Api/Action/OptOut/DeleteOptOut.cs new file mode 100644 index 0000000..575a4af --- /dev/null +++ b/smsapi/Api/Action/OptOut/DeleteOptOut.cs @@ -0,0 +1,28 @@ +using System; +using SMSApi.Api.Response.OptOut; + +namespace SMSApi.Api.Action.OptOut; + +public class DeleteOptOut : Action +{ + private readonly string _optOutId; + + public DeleteOptOut(string optOutId) + { + _optOutId = optOutId; + } + + public DeleteOptOut(Guid optOutId) + { + _optOutId = optOutId.ToString(); + } + + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return $"opt_outs/{_optOutId}"; + } +} diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs new file mode 100644 index 0000000..4327669 --- /dev/null +++ b/smsapi/Api/OptOutFactory.cs @@ -0,0 +1,46 @@ +using System; +using SMSApi.Api.Action.OptOut; + +namespace SMSApi.Api; + +public class OptOutFactory : Factory +{ + public OptOutFactory(ProxyAddress address = ProxyAddress.SmsApiIo) + : base(address) + { + } + + public OptOutFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) + : base(client, address) + { + } + + public OptOutFactory(IClient client, Proxy proxy) + : base(client, proxy) + { + } + + public DeleteOptOut DeleteOptOut(string optOutId) + { + var action = new DeleteOptOut(optOutId); + action.Proxy(proxy); + + return action; + } + + public DeleteOptOut DeleteOptOut(Guid optOutId) + { + var action = new DeleteOptOut(optOutId); + action.Proxy(proxy); + + return action; + } +} + +public static class OptOutFeatureRegister +{ + public static OptOutFactory OptOut(this Features features) + { + return new OptOutFactory(features.Client, features.Proxy); + } +} diff --git a/smsapi/Api/Response/OptOut/Exception/OptOutNotFoundException.cs b/smsapi/Api/Response/OptOut/Exception/OptOutNotFoundException.cs new file mode 100644 index 0000000..19c6004 --- /dev/null +++ b/smsapi/Api/Response/OptOut/Exception/OptOutNotFoundException.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Response.OptOut.Exception; + +public class OptOutNotFoundException : ClientException +{ + public OptOutNotFoundException() : base("Opt-out not found", 404) + { + } +} diff --git a/smsapi/Api/Response/OptOut/OptOutDeletionResponse.cs b/smsapi/Api/Response/OptOut/OptOutDeletionResponse.cs new file mode 100644 index 0000000..4f35f18 --- /dev/null +++ b/smsapi/Api/Response/OptOut/OptOutDeletionResponse.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SMSApi.Api.Response.OptOut.Exception; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.OptOut; + +public class OptOutDeletionResponse : IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 404, _ => throw new OptOutNotFoundException() }, + }; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs b/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs new file mode 100644 index 0000000..2557083 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; +using SMSApi.Api.Response.OptOut; +using SMSApi.Api.Response.OptOut.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class DeleteOptOutResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void pass_when_opt_out_exists() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + new Dictionary().ToHttpEntityStreamTask(), + HttpStatusCode.NoContent + ); + + DeleteOptOut().Execute(); + + Assert.IsTrue(true); + } + + [TestMethod] + public void throw_when_opt_out_does_not_exist() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + new Dictionary().ToHttpEntityStreamTask(), + HttpStatusCode.NotFound + ); + + OptOutDeletionResponse Delete() => DeleteOptOut().Execute(); + + Assert.ThrowsException((Func)Delete); + } + + private DeleteOptOut DeleteOptOut() + { + var action = new DeleteOptOut(Guid.NewGuid()); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs b/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs new file mode 100644 index 0000000..2d75f21 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class DeleteOptOutTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public DeleteOptOutTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + var optOutId = Guid.NewGuid(); + + CreateOptOutDelete(optOutId).Execute(); + + _proxyAssert.AssertUriEquals($"opt_outs/{optOutId}"); + } + + [TestMethod] + public void valid_method() + { + CreateOptOutDelete(Guid.NewGuid()).Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); + } + + private DeleteOptOut CreateOptOutDelete(Guid optOutId) + { + var action = new DeleteOptOut(optOutId); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 6ecd9d1..1690f40 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -1,11 +1,17 @@ using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; namespace smsapiTests.Unit; public class ProxyAssert(SpyProxy proxy) { + public void AssertRequestMethod(RequestMethod requestMethod) + { + Assert.AreEqual(requestMethod, proxy.RequestMethod); + } + public void AssertUriEquals(string uri) { Assert.IsTrue(proxy.RequestedUri.Equals(uri)); diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index 664f194..c71d9a0 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -14,7 +14,9 @@ namespace smsapiTests.Unit; public class SpyProxy : Proxy { public string RequestedUri { get; private set; } - + + public RequestMethod RequestMethod { get; private set; } + public Dictionary Parameters { get; } = new(); public void Authentication(IClient client) @@ -26,6 +28,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam { RequestedUri = uri; SetParameters(data); + RequestMethod = method; return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } @@ -34,6 +37,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam { RequestedUri = uri; SetParameters(data); + RequestMethod = method; return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } @@ -42,7 +46,8 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam { RequestedUri = uri; SetParameters(data); - + RequestMethod = method; + return new HttpResponseEntity(Task.FromResult(Stream.Null), HttpStatusCode.OK); } @@ -50,6 +55,7 @@ public Task ExecuteAsync(ActionContentType contentType, stri { RequestedUri = uri; SetParameters(data); + RequestMethod = method; return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } @@ -58,6 +64,7 @@ public Task ExecuteAsync(ActionContentType contentType, stri { RequestedUri = uri; SetParameters(data); + RequestMethod = method; return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } @@ -66,6 +73,7 @@ public Task ExecuteAsync(ActionContentType contentType, stri { RequestedUri = uri; SetParameters(data); + RequestMethod = method; return new Task(null); } From 6b8ef482e36faa683141f66d32efa664e5ad7208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 26 Nov 2024 13:23:00 +0000 Subject: [PATCH 043/142] Add Opt-out feature #list --- smsapi/Api/Action/OptOut/OptOutList.cs | 41 ++++++++++++ smsapi/Api/OptOutFactory.cs | 8 +++ smsapi/Api/Response/BasicCollection.cs | 3 - smsapi/Api/Response/OptOut/OptOut.cs | 21 ++++++ smsapi/OperationsHelper.cs | 11 ++++ .../Action/OptOut/OptOutListResponseTest.cs | 66 +++++++++++++++++++ .../Unit/Action/OptOut/OptOutListTest.cs | 53 +++++++++++++++ 7 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 smsapi/Api/Action/OptOut/OptOutList.cs create mode 100644 smsapi/Api/Response/OptOut/OptOut.cs create mode 100644 smsapi/OperationsHelper.cs create mode 100644 smsapiTests/Unit/Action/OptOut/OptOutListResponseTest.cs create mode 100644 smsapiTests/Unit/Action/OptOut/OptOutListTest.cs diff --git a/smsapi/Api/Action/OptOut/OptOutList.cs b/smsapi/Api/Action/OptOut/OptOutList.cs new file mode 100644 index 0000000..b366acc --- /dev/null +++ b/smsapi/Api/Action/OptOut/OptOutList.cs @@ -0,0 +1,41 @@ +using System.Collections.Specialized; +using SMSApi.Api.Response; +using OptOutModel = SMSApi.Api.Response.OptOut.OptOut; + +namespace SMSApi.Api.Action.OptOut; + +public sealed class OptOutList : Action>, IPaginable +{ + private string? _phoneNumber; + + protected override RequestMethod Method => RequestMethod.GET; + + public uint? Limit { get; set; } + public uint? Offset { get; set; } + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return "opt_outs"; + } + + public OptOutList FilterByPhoneNumber(string phoneNumber) + { + _phoneNumber = phoneNumber; + + return this; + } + + protected override NameValueCollection Values() + { + var values = new NameValueCollection(); + + _phoneNumber?.Let(number => values.Add("phone_number", number)); + + return values; + } +} diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs index 4327669..17a5925 100644 --- a/smsapi/Api/OptOutFactory.cs +++ b/smsapi/Api/OptOutFactory.cs @@ -35,6 +35,14 @@ public DeleteOptOut DeleteOptOut(Guid optOutId) return action; } + + public OptOutList List() + { + var action = new OptOutList(); + action.Proxy(proxy); + + return action; + } } public static class OptOutFeatureRegister diff --git a/smsapi/Api/Response/BasicCollection.cs b/smsapi/Api/Response/BasicCollection.cs index b4da875..be17713 100644 --- a/smsapi/Api/Response/BasicCollection.cs +++ b/smsapi/Api/Response/BasicCollection.cs @@ -14,9 +14,6 @@ public class BasicCollection : Countable, IResponseCodeAwareResolver [DataMember(Name = "size", IsRequired = false)] protected int size; - protected BasicCollection() - { } - public List Collection { get diff --git a/smsapi/Api/Response/OptOut/OptOut.cs b/smsapi/Api/Response/OptOut/OptOut.cs new file mode 100644 index 0000000..8c4d0d7 --- /dev/null +++ b/smsapi/Api/Response/OptOut/OptOut.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.Serialization; + +namespace SMSApi.Api.Response.OptOut; + +[DataContract] +public class OptOut +{ + [DataMember(Name = "id")] public readonly string Id; + + [DataMember(Name = "phone_number")] public readonly string PhoneNumber; + + public DateTime CreationTime { get; private set; } + + [DataMember(Name = "creation_time")] + private string CreationTimeSerializer + { + set => CreationTime = DateTime.Parse(value); + get => default!; + } +} diff --git a/smsapi/OperationsHelper.cs b/smsapi/OperationsHelper.cs new file mode 100644 index 0000000..a55f25c --- /dev/null +++ b/smsapi/OperationsHelper.cs @@ -0,0 +1,11 @@ +using System; + +namespace SMSApi.Api; + +public static class OperationsHelper +{ + public static void Let(this T value, Action action) where T : class + { + action(value); + } +} diff --git a/smsapiTests/Unit/Action/OptOut/OptOutListResponseTest.cs b/smsapiTests/Unit/Action/OptOut/OptOutListResponseTest.cs new file mode 100644 index 0000000..1e371b0 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/OptOutListResponseTest.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class OptOutListResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = CollectionMother.Empty(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_opt_outs() + { + var id = "655B26893332330011B0B297"; + var phoneNumber = "48500100100"; + var creationTime = "2024-11-26T14:20:53+01:00"; + var response = CollectionMother.WithItems( + new Dictionary + { + { "id", id }, + { "phone_number", phoneNumber }, + { "creation_time", creationTime } + }); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(phoneNumber, firstElement.PhoneNumber); + Assert.AreEqual(DateTime.Parse(creationTime), firstElement.CreationTime); + } + + private OptOutList GetList() + { + var action = new OptOutList(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/OptOutListTest.cs b/smsapiTests/Unit/Action/OptOut/OptOutListTest.cs new file mode 100644 index 0000000..31d0049 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/OptOutListTest.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class OptOutListTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public OptOutListTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateOptOutList().Execute(); + + _proxyAssert.AssertUriEquals("opt_outs"); + } + + [TestMethod] + public void valid_uri_with_phone_number_filtering() + { + var phoneNumberToFilterBy = "48500100100"; + + CreateOptOutList() + .FilterByPhoneNumber(phoneNumberToFilterBy) + .Execute(); + + _proxyAssert.AssertUriEquals($"opt_outs?phone_number={phoneNumberToFilterBy}"); + } + + [TestMethod] + public void valid_method() + { + CreateOptOutList().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + private OptOutList CreateOptOutList() + { + var action = new OptOutList(); + action.Proxy(_spyProxy); + + return action; + } +} From 4ac2847c24ebe1bb8862107bf55f70a32f5af594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 26 Nov 2024 16:17:19 +0000 Subject: [PATCH 044/142] Add Opt-out feature #settings read --- smsapi/Api/Action/OptOut/GetOptOutSettings.cs | 14 +++++++ smsapi/Api/OptOutFactory.cs | 8 ++++ smsapi/Api/Response/OptOut/OptOutSettings.cs | 10 +++++ .../OptOut/OptOutSettingsResponseTest.cs | 42 +++++++++++++++++++ .../Unit/Action/OptOut/OptOutSettingsTest.cs | 41 ++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 smsapi/Api/Action/OptOut/GetOptOutSettings.cs create mode 100644 smsapi/Api/Response/OptOut/OptOutSettings.cs create mode 100644 smsapiTests/Unit/Action/OptOut/OptOutSettingsResponseTest.cs create mode 100644 smsapiTests/Unit/Action/OptOut/OptOutSettingsTest.cs diff --git a/smsapi/Api/Action/OptOut/GetOptOutSettings.cs b/smsapi/Api/Action/OptOut/GetOptOutSettings.cs new file mode 100644 index 0000000..935a2ab --- /dev/null +++ b/smsapi/Api/Action/OptOut/GetOptOutSettings.cs @@ -0,0 +1,14 @@ +using SMSApi.Api.Response.OptOut; + +namespace SMSApi.Api.Action.OptOut; + +public sealed class GetOptOutSettings : Action +{ + protected override RequestMethod Method => RequestMethod.GET; + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "opt_outs/settings"; + } +} diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs index 17a5925..7fc9e0d 100644 --- a/smsapi/Api/OptOutFactory.cs +++ b/smsapi/Api/OptOutFactory.cs @@ -43,6 +43,14 @@ public OptOutList List() return action; } + + public GetOptOutSettings Settings() + { + var action = new GetOptOutSettings(); + action.Proxy(proxy); + + return action; + } } public static class OptOutFeatureRegister diff --git a/smsapi/Api/Response/OptOut/OptOutSettings.cs b/smsapi/Api/Response/OptOut/OptOutSettings.cs new file mode 100644 index 0000000..7e8fe2c --- /dev/null +++ b/smsapi/Api/Response/OptOut/OptOutSettings.cs @@ -0,0 +1,10 @@ +using System.Runtime.Serialization; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.OptOut; + +[DataContract] +public record struct OptOutSettings : IResponseCodeAwareResolver +{ + [DataMember(Name = "brand")] public readonly string Brand; +} diff --git a/smsapiTests/Unit/Action/OptOut/OptOutSettingsResponseTest.cs b/smsapiTests/Unit/Action/OptOut/OptOutSettingsResponseTest.cs new file mode 100644 index 0000000..54c7f95 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/OptOutSettingsResponseTest.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class OptOutSettingsResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void see_brand() + { + var brand = "any brand"; + + var response = new Dictionary + { + { "brand", brand } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetSettings().Execute(); + + Assert.AreEqual(brand, result.Brand); + } + + private GetOptOutSettings GetSettings() + { + var action = new GetOptOutSettings(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/OptOutSettingsTest.cs b/smsapiTests/Unit/Action/OptOut/OptOutSettingsTest.cs new file mode 100644 index 0000000..79ed355 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/OptOutSettingsTest.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class OptOutSettingsTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public OptOutSettingsTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateOptOutSettings().Execute(); + + _proxyAssert.AssertUriEquals("opt_outs/settings"); + } + + [TestMethod] + public void valid_method() + { + CreateOptOutSettings().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + private GetOptOutSettings CreateOptOutSettings() + { + var action = new GetOptOutSettings(); + action.Proxy(_spyProxy); + + return action; + } +} From 61c79270d2ea06e4d68d309d60b8b65bb4422104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 26 Nov 2024 16:33:19 +0000 Subject: [PATCH 045/142] Add Opt-out feature #settings update --- .../Api/Action/OptOut/ChangeOptOutSettings.cs | 33 ++++++++++ .../ChangeOptOutSettingsResponseTest.cs | 44 +++++++++++++ .../Action/OptOut/ChangeOptOutSettingsTest.cs | 61 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs create mode 100644 smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsResponseTest.cs create mode 100644 smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsTest.cs diff --git a/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs b/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs new file mode 100644 index 0000000..7388e67 --- /dev/null +++ b/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs @@ -0,0 +1,33 @@ +using System.Collections.Specialized; +using SMSApi.Api.Response.OptOut; + +namespace SMSApi.Api.Action.OptOut; + +public sealed class ChangeOptOutSettings : Action +{ + private string? _brandName; + + protected override RequestMethod Method => RequestMethod.PUT; + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "opt_outs/settings"; + } + + public ChangeOptOutSettings ChangeBrandName(string brandName) + { + _brandName = brandName; + + return this; + } + + protected override NameValueCollection Values() + { + var values = new NameValueCollection(); + + _brandName?.Let(newName => values.Add("brand", newName)); + + return values; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsResponseTest.cs b/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsResponseTest.cs new file mode 100644 index 0000000..197ba0f --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsResponseTest.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class ChangeOptOutSettingsResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void see_brand_after_update() + { + var brand = "any brand"; + + var response = new Dictionary + { + { "brand", brand } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = CreateChangeOptOutSettings() + .ChangeBrandName(brand) + .Execute(); + + Assert.AreEqual(brand, result.Brand); + } + + private ChangeOptOutSettings CreateChangeOptOutSettings() + { + var action = new ChangeOptOutSettings(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsTest.cs b/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsTest.cs new file mode 100644 index 0000000..032e98e --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/ChangeOptOutSettingsTest.cs @@ -0,0 +1,61 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.OptOut; + +namespace smsapiTests.Unit.Action.OptOut; + +[TestClass] +public class ChangeOptOutSettingsTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public ChangeOptOutSettingsTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateChangeOptOutSettings().Execute(); + + _proxyAssert.AssertUriEquals("opt_outs/settings"); + } + + [TestMethod] + public void request_is_empty_when_no_changes() + { + CreateChangeOptOutSettings().Execute(); + + _proxyAssert.AssertNoParameters(); + } + + [TestMethod] + public void request_contains_brand_name() + { + var brandName = "any brand name"; + + CreateChangeOptOutSettings() + .ChangeBrandName(brandName) + .Execute(); + + _proxyAssert.AssertParametersContain("brand", brandName); + } + + [TestMethod] + public void valid_method() + { + CreateChangeOptOutSettings().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.PUT); + } + + private ChangeOptOutSettings CreateChangeOptOutSettings() + { + var action = new ChangeOptOutSettings(); + action.Proxy(_spyProxy); + + return action; + } +} From f74fc199e58cb9f3d0193bea8683876c84c0be98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 27 Nov 2024 08:56:12 +0000 Subject: [PATCH 046/142] Add Opt-out feature #deletion --- smsapi/Api/Action/OptOut/DeleteOptOut.cs | 5 ----- smsapi/Api/OptOutFactory.cs | 11 +---------- .../Unit/Action/OptOut/DeleteOptOutResponseTest.cs | 2 +- smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs | 9 +++++---- 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/smsapi/Api/Action/OptOut/DeleteOptOut.cs b/smsapi/Api/Action/OptOut/DeleteOptOut.cs index 575a4af..511d973 100644 --- a/smsapi/Api/Action/OptOut/DeleteOptOut.cs +++ b/smsapi/Api/Action/OptOut/DeleteOptOut.cs @@ -11,11 +11,6 @@ public DeleteOptOut(string optOutId) { _optOutId = optOutId; } - - public DeleteOptOut(Guid optOutId) - { - _optOutId = optOutId.ToString(); - } protected override RequestMethod Method => RequestMethod.DELETE; diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs index 7fc9e0d..4240a2b 100644 --- a/smsapi/Api/OptOutFactory.cs +++ b/smsapi/Api/OptOutFactory.cs @@ -1,5 +1,4 @@ -using System; -using SMSApi.Api.Action.OptOut; +using SMSApi.Api.Action.OptOut; namespace SMSApi.Api; @@ -28,14 +27,6 @@ public DeleteOptOut DeleteOptOut(string optOutId) return action; } - public DeleteOptOut DeleteOptOut(Guid optOutId) - { - var action = new DeleteOptOut(optOutId); - action.Proxy(proxy); - - return action; - } - public OptOutList List() { var action = new OptOutList(); diff --git a/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs b/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs index 2557083..288535a 100644 --- a/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs +++ b/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs @@ -44,7 +44,7 @@ public void throw_when_opt_out_does_not_exist() private DeleteOptOut DeleteOptOut() { - var action = new DeleteOptOut(Guid.NewGuid()); + var action = new DeleteOptOut("any"); action.Proxy(_proxyStub); return action; diff --git a/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs b/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs index 2d75f21..8b9f713 100644 --- a/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs +++ b/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api; using SMSApi.Api.Action.OptOut; @@ -19,7 +18,7 @@ public DeleteOptOutTest() [TestMethod] public void valid_uri() { - var optOutId = Guid.NewGuid(); + var optOutId = AnyId(); CreateOptOutDelete(optOutId).Execute(); @@ -29,16 +28,18 @@ public void valid_uri() [TestMethod] public void valid_method() { - CreateOptOutDelete(Guid.NewGuid()).Execute(); + CreateOptOutDelete(AnyId()).Execute(); _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); } - private DeleteOptOut CreateOptOutDelete(Guid optOutId) + private DeleteOptOut CreateOptOutDelete(string optOutId) { var action = new DeleteOptOut(optOutId); action.Proxy(_spyProxy); return action; } + + private static string AnyId() => "5A5359173738303F2F95B7E2"; } From a384a83cf2272e61bc279b9b4474cc0b05baaa1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 27 Nov 2024 08:59:06 +0000 Subject: [PATCH 047/142] Add Opt-out feature #deletion --- smsapi/Api/Action/OptOut/DeleteOptOut.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/smsapi/Api/Action/OptOut/DeleteOptOut.cs b/smsapi/Api/Action/OptOut/DeleteOptOut.cs index 511d973..37f7184 100644 --- a/smsapi/Api/Action/OptOut/DeleteOptOut.cs +++ b/smsapi/Api/Action/OptOut/DeleteOptOut.cs @@ -1,9 +1,8 @@ -using System; using SMSApi.Api.Response.OptOut; namespace SMSApi.Api.Action.OptOut; -public class DeleteOptOut : Action +public sealed class DeleteOptOut : Action { private readonly string _optOutId; From 82192add80cda4ca640a2bd129e7066df3cbc1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 27 Nov 2024 13:17:18 +0000 Subject: [PATCH 048/142] Add Profile read feature --- smsapi/Api/Action/Profile/GetProfile.cs | 12 ++++ smsapi/Api/ProfileFactory.cs | 37 +++++++++++ smsapi/Api/Response/Profile/Profile.cs | 32 ++++++++++ .../Action/Profile/GetProfileResponseTest.cs | 63 +++++++++++++++++++ .../Unit/Action/Profile/GetProfileTest.cs | 41 ++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 smsapi/Api/Action/Profile/GetProfile.cs create mode 100644 smsapi/Api/ProfileFactory.cs create mode 100644 smsapi/Api/Response/Profile/Profile.cs create mode 100644 smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Profile/GetProfileTest.cs diff --git a/smsapi/Api/Action/Profile/GetProfile.cs b/smsapi/Api/Action/Profile/GetProfile.cs new file mode 100644 index 0000000..a3058f9 --- /dev/null +++ b/smsapi/Api/Action/Profile/GetProfile.cs @@ -0,0 +1,12 @@ +namespace SMSApi.Api.Action.Profile; + +public sealed class GetProfile : Action +{ + protected override RequestMethod Method => RequestMethod.GET; + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "profile"; + } +} diff --git a/smsapi/Api/ProfileFactory.cs b/smsapi/Api/ProfileFactory.cs new file mode 100644 index 0000000..3999f9b --- /dev/null +++ b/smsapi/Api/ProfileFactory.cs @@ -0,0 +1,37 @@ +using SMSApi.Api.Action.Profile; + +namespace SMSApi.Api; + +public class ProfileFactory : Factory +{ + public ProfileFactory(ProxyAddress address = ProxyAddress.SmsApiIo) + : base(address) + { + } + + public ProfileFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) + : base(client, address) + { + } + + public ProfileFactory(IClient client, Proxy proxy) + : base(client, proxy) + { + } + + public GetProfile GetProfile() + { + var action = new GetProfile(); + action.Proxy(proxy); + + return action; + } +} + +public static class ProfileFeatureRegister +{ + public static ProfileFactory Profile(this Features features) + { + return new ProfileFactory(features.Client, features.Proxy); + } +} diff --git a/smsapi/Api/Response/Profile/Profile.cs b/smsapi/Api/Response/Profile/Profile.cs new file mode 100644 index 0000000..44d94cb --- /dev/null +++ b/smsapi/Api/Response/Profile/Profile.cs @@ -0,0 +1,32 @@ +using System.Runtime.Serialization; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Profile; + +[DataContract] +public record struct Profile : IResponseCodeAwareResolver +{ + [DataMember(Name = "id")] + public readonly string Id; + + [DataMember(Name = "name")] + public readonly string Name; + + [DataMember(Name = "username")] + public readonly string Username; + + [DataMember(Name = "email")] + public readonly string Email; + + [DataMember(Name = "phone_number")] + public readonly string PhoneNumber; + + [DataMember(Name = "user_type")] + public readonly string UserType; + + [DataMember(Name = "points")] + public readonly decimal Points; + + [DataMember(Name = "payment_type")] + public readonly string PaymentType; +} diff --git a/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs new file mode 100644 index 0000000..08bd640 --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Profile; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Profile; + +[TestClass] +public class ProfileTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void see_profile_data() + { + var id = "5A5359173738303F2F95B7E2"; + var name = "fancy name"; + var username = "fancy_username"; + var email = "any@any.pl"; + var phoneNumber = "48500100100"; + var userType = "native"; + var points = 500.25m; + var paymentType = "prepaid"; + + var response = new Dictionary + { + { "id", id }, + { "name", name }, + { "username", username }, + { "email", email }, + { "phone_number", phoneNumber }, + { "user_type", userType }, + { "points", points }, + { "payment_type", paymentType } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = CreateGetProfile().Execute(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(name, result.Name); + Assert.AreEqual(username, result.Username); + Assert.AreEqual(email, result.Email); + Assert.AreEqual(phoneNumber, result.PhoneNumber); + Assert.AreEqual(userType, result.UserType); + Assert.AreEqual(points, result.Points); + Assert.AreEqual(paymentType, result.PaymentType); + } + + private GetProfile CreateGetProfile() + { + var action = new GetProfile(); + action.Proxy(_proxyStub); + + return action; + } +} \ No newline at end of file diff --git a/smsapiTests/Unit/Action/Profile/GetProfileTest.cs b/smsapiTests/Unit/Action/Profile/GetProfileTest.cs new file mode 100644 index 0000000..494a15e --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/GetProfileTest.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Profile; + +namespace smsapiTests.Unit.Action.Profile; + +[TestClass] +public class GetProfileResponseTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public GetProfileResponseTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateGetProfile().Execute(); + + _proxyAssert.AssertUriEquals("profile"); + } + + [TestMethod] + public void valid_method() + { + CreateGetProfile().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + private GetProfile CreateGetProfile() + { + var action = new GetProfile(); + action.Proxy(_spyProxy); + + return action; + } +} From ba5d127d57a4f52afb7eb75c4ff437ddaca802a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 27 Nov 2024 20:36:22 +0000 Subject: [PATCH 049/142] Add Profile read feature --- smsapi/Api/Response/Profile/Profile.cs | 5 +---- .../Unit/Action/Profile/GetProfileResponseTest.cs | 9 +++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/smsapi/Api/Response/Profile/Profile.cs b/smsapi/Api/Response/Profile/Profile.cs index 44d94cb..7d5be14 100644 --- a/smsapi/Api/Response/Profile/Profile.cs +++ b/smsapi/Api/Response/Profile/Profile.cs @@ -6,9 +6,6 @@ namespace SMSApi.Api.Response.Profile; [DataContract] public record struct Profile : IResponseCodeAwareResolver { - [DataMember(Name = "id")] - public readonly string Id; - [DataMember(Name = "name")] public readonly string Name; @@ -25,7 +22,7 @@ public record struct Profile : IResponseCodeAwareResolver public readonly string UserType; [DataMember(Name = "points")] - public readonly decimal Points; + public readonly double Points; [DataMember(Name = "payment_type")] public readonly string PaymentType; diff --git a/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs index 08bd640..ef39955 100644 --- a/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs +++ b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs @@ -16,18 +16,16 @@ public class ProfileTest [TestMethod] public void see_profile_data() { - var id = "5A5359173738303F2F95B7E2"; var name = "fancy name"; var username = "fancy_username"; var email = "any@any.pl"; var phoneNumber = "48500100100"; var userType = "native"; - var points = 500.25m; + var points = 500.25d; var paymentType = "prepaid"; var response = new Dictionary { - { "id", id }, { "name", name }, { "username", username }, { "email", email }, @@ -42,8 +40,7 @@ public void see_profile_data() ); var result = CreateGetProfile().Execute(); - - Assert.AreEqual(id, result.Id); + Assert.AreEqual(name, result.Name); Assert.AreEqual(username, result.Username); Assert.AreEqual(email, result.Email); @@ -60,4 +57,4 @@ private GetProfile CreateGetProfile() return action; } -} \ No newline at end of file +} From e764046f4d069a034961d8ced4f7ed959518f035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 29 Nov 2024 09:53:34 +0000 Subject: [PATCH 050/142] Add Opt-out feature #examples --- examples/optOut/ChangeSettings.cs | 11 +++++++++++ examples/optOut/Delete.cs | 30 ++++++++++++++++++++++++++++++ examples/optOut/GetSettings.cs | 10 ++++++++++ examples/optOut/List.cs | 15 +++++++++++++++ smsapi/Api/OptOutFactory.cs | 8 ++++++++ 5 files changed, 74 insertions(+) create mode 100644 examples/optOut/ChangeSettings.cs create mode 100644 examples/optOut/Delete.cs create mode 100644 examples/optOut/GetSettings.cs create mode 100644 examples/optOut/List.cs diff --git a/examples/optOut/ChangeSettings.cs b/examples/optOut/ChangeSettings.cs new file mode 100644 index 0000000..c215896 --- /dev/null +++ b/examples/optOut/ChangeSettings.cs @@ -0,0 +1,11 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var optOutSettingsUpdateResult = features.OptOut() + .ChangeSettings() + .ChangeBrandName("new brand name") + .Execute(); + +Console.WriteLine($"Brand: {optOutSettingsUpdateResult.Brand}"); diff --git a/examples/optOut/Delete.cs b/examples/optOut/Delete.cs new file mode 100644 index 0000000..ceb231a --- /dev/null +++ b/examples/optOut/Delete.cs @@ -0,0 +1,30 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var optOutList = features.OptOut() + .List() + .Execute(); + +OptOutDeletionResponse DeleteOptOut(string optOutId) +{ + return features.OptOut() + .DeleteOptOut(optOutId) + .Execute(); +} + +optOutList.Collection.ForEach(opt => +{ + try + { + DeleteOptOut(opt.Id); + + //optOut is deleted at this point + Console.WriteLine($"Deleted opt out {opt.Id}"); + } + catch (OptOutNotFoundException ex) + { + Console.WriteLine(ex.Message); + } +}); diff --git a/examples/optOut/GetSettings.cs b/examples/optOut/GetSettings.cs new file mode 100644 index 0000000..0520337 --- /dev/null +++ b/examples/optOut/GetSettings.cs @@ -0,0 +1,10 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var optOutSettings = features.OptOut() + .Settings() + .Execute(); + +Console.WriteLine($"Brand: {optOutSettings.Brand}"); diff --git a/examples/optOut/List.cs b/examples/optOut/List.cs new file mode 100644 index 0000000..634b7db --- /dev/null +++ b/examples/optOut/List.cs @@ -0,0 +1,15 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var optOutList = features.OptOut() + .List() + .Execute(); + +optOutList.Collection.ForEach(opt => +{ + Console.WriteLine(opt.Id); + Console.WriteLine(opt.PhoneNumber); + Console.WriteLine(opt.CreationTime); +}); diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs index 4240a2b..44e1b0c 100644 --- a/smsapi/Api/OptOutFactory.cs +++ b/smsapi/Api/OptOutFactory.cs @@ -42,6 +42,14 @@ public GetOptOutSettings Settings() return action; } + + public ChangeOptOutSettings ChangeSettings() + { + var action = new ChangeOptOutSettings(); + action.Proxy(proxy); + + return action; + } } public static class OptOutFeatureRegister From 6922bc21e3df627c77e7fabaceaa14eea5174d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 29 Nov 2024 10:12:02 +0000 Subject: [PATCH 051/142] OperationsHelper visibility --- smsapi/OperationsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapi/OperationsHelper.cs b/smsapi/OperationsHelper.cs index a55f25c..a4d78e5 100644 --- a/smsapi/OperationsHelper.cs +++ b/smsapi/OperationsHelper.cs @@ -2,7 +2,7 @@ namespace SMSApi.Api; -public static class OperationsHelper +internal static class OperationsHelper { public static void Let(this T value, Action action) where T : class { From 79f43ac0815e3ed550d036838995b44688acc5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 29 Nov 2024 10:43:13 +0000 Subject: [PATCH 052/142] Add Profile read feature example --- examples/profile/GetProfile.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 examples/profile/GetProfile.cs diff --git a/examples/profile/GetProfile.cs b/examples/profile/GetProfile.cs new file mode 100644 index 0000000..7f8696d --- /dev/null +++ b/examples/profile/GetProfile.cs @@ -0,0 +1,16 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var profile = features.Profile() + .GetProfile() + .Execute(); + +Console.WriteLine($"Name: {profile.Name}"); +Console.WriteLine($"Username: {profile.Username}"); +Console.WriteLine($"Email: {profile.Email}"); +Console.WriteLine($"Phone number: {profile.PhoneNumber}"); +Console.WriteLine($"User Type: {profile.UserType}"); +Console.WriteLine($"Points: {profile.Points}"); +Console.WriteLine($"Payment type: {profile.PaymentType}"); From 7bfdab0af299b1ea8696bf786c48eae94ed9c15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 2 Dec 2024 13:38:09 +0000 Subject: [PATCH 053/142] Allow requests to be processed as json --- smsapi/Api/Action/Action.cs | 25 +++-- smsapi/NativeHttpClientHelper.cs | 128 +++++++++++++------------- smsapi/Proxy.cs | 13 ++- smsapi/ProxyHTTP.cs | 19 ++-- smsapiTests/Unit/Fixture/ProxyStub.cs | 26 +++--- smsapiTests/Unit/SpyProxy.cs | 26 +++--- 6 files changed, 121 insertions(+), 116 deletions(-) diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 5af53d1..aa79cc8 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -80,6 +81,8 @@ protected virtual NameValueCollection Values() return new NameValueCollection(); } + protected virtual ISet>? Request() => default; + private string UriWithPagination() { var uriBuilder = new UriBuilder @@ -87,7 +90,7 @@ private string UriWithPagination() Path = Uri() }; - assignValuesToQuery(uriBuilder); + AssignValuesToQuery(uriBuilder); if (!typeof(IPaginable).IsAssignableFrom(GetType())) return uriBuilder.ToPathWithQuery(); @@ -97,7 +100,7 @@ private string UriWithPagination() return uriBuilder.ToUriWithPagination(action.Limit, action.Offset); } - private void assignValuesToQuery(UriBuilder uriBuilder) + private void AssignValuesToQuery(UriBuilder uriBuilder) { if (!Method.Equals(RequestMethod.GET)) return; @@ -113,12 +116,20 @@ private T ProcessResponse(HttpResponseEntity responseEntity) return ResponseToObject(responseEntity); } - private NameValueCollection GetValues() + private ISet> GetValues() { - var values = Values(); + var values = new HashSet> + { + KeyValuePair.Create("format", "json") , + }; + + Request()?.Let(requestData => requestData.ToList().ForEach(data => values.Add(data))); + + foreach (string key in Values().AllKeys) + { + values.Add(KeyValuePair.Create(key, Values().Get(key))); + } - return values.Count > 0 - ? new NameValueCollection { { "format", "json" }, values } - : HttpUtility.ParseQueryString(string.Empty); + return values; } } diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 5f432cc..e42ffeb 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -1,89 +1,89 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using SMSApi.Api.Action; -namespace SMSApi.Api +namespace SMSApi.Api; + +public static class NativeHttpClientHelper { - public static class NativeHttpClientHelper + public static async Task SendRequest( + this HttpClient httpClient, + ActionContentType actionContentType, + RequestMethod method, + string uri, + ISet> body = null, + Dictionary files = null, + CancellationToken cancellationToken = default + ) { - public static async Task SendRequest( - this HttpClient httpClient, - RequestMethod method, - string uri, - NameValueCollection body = null, - Dictionary files = null, - CancellationToken cancellationToken = default - ) - { - HttpContent httpContent; + HttpContent httpContent; - switch (method) - { - case RequestMethod.GET: - var getResponse = await httpClient.GetAsync(uri, cancellationToken); + switch (method) + { + case RequestMethod.GET: + var getResponse = await httpClient.GetAsync(uri, cancellationToken); - return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(), getResponse.StatusCode); - case RequestMethod.POST: - httpContent = ConvertNameValueCollectionToHttpContent(body, files); - var postResponse = await httpClient.PostAsync(uri, httpContent, cancellationToken); + return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(), getResponse.StatusCode); + case RequestMethod.POST: + httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); + var postResponse = await httpClient.PostAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(), postResponse.StatusCode); - case RequestMethod.PUT: - httpContent = ConvertNameValueCollectionToHttpContent(body, files); - var putResponse = await httpClient.PutAsync(uri, httpContent, cancellationToken); + return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(), postResponse.StatusCode); + case RequestMethod.PUT: + httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); + var putResponse = await httpClient.PutAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(), putResponse.StatusCode); - case RequestMethod.DELETE: - var deleteResult = await httpClient.DeleteAsync(uri, cancellationToken); + return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(), putResponse.StatusCode); + case RequestMethod.DELETE: + var deleteResult = await httpClient.DeleteAsync(uri, cancellationToken); - return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); - default: - throw new ArgumentOutOfRangeException(nameof(method), method, null); - } + return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); + default: + throw new ArgumentOutOfRangeException(nameof(method), method, null); } + } - private static HttpContent ConvertNameValueCollectionToHttpContent( - NameValueCollection collection, - Dictionary files = null - ) + private static HttpContent ConvertRequestDataToHttpContent( + ActionContentType contentType, + ISet> collection, + Dictionary files = null + ) + { + var collectionDictionary = collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + if (contentType == ActionContentType.Json) { - var contentCollectionKeys = collection.AllKeys; - - var contentCollection = contentCollectionKeys - .Select(key => new KeyValuePair(key, collection[key])) - .ToList(); - var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); - - if (files == null || files.Count == 0) return formUrlEncodedContent; - - var multipartContent = new MultipartFormDataContent(); - - foreach (var keyValuePair in contentCollection) - multipartContent.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); - - files - .ToList() - .ForEach(pair => multipartContent.Add(new StreamContent(pair.Value), "file", pair.Key)); - - return multipartContent; + return new StringContent(JsonSerializer.Serialize(collectionDictionary), Encoding.UTF8, "application/json"); } + + var contentCollection = collectionDictionary.Keys + .Select(key => new KeyValuePair(key, collectionDictionary[key])) + .ToList(); - public static void AddContentTypeHeader(this HttpClient httpClient, ActionContentType actionContentType) + var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); + + if (files == null || files.Count == 0) return formUrlEncodedContent; + + var multipartContent = new MultipartFormDataContent(); + multipartContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); + + foreach (var keyValuePair in collection) { - var contentType = actionContentType switch - { - ActionContentType.Json => "application/json", - ActionContentType.FormWww => "application/x-www-form-urlencoded", - _ => throw new ArgumentOutOfRangeException(nameof(actionContentType), actionContentType, @"Not supported content type") - }; - - httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", contentType); + multipartContent.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); } + + files + .ToList() + .ForEach(pair => multipartContent.Add(new StreamContent(pair.Value), "file", pair.Key)); + + return multipartContent; } } diff --git a/smsapi/Proxy.cs b/smsapi/Proxy.cs index 02f4573..284d159 100644 --- a/smsapi/Proxy.cs +++ b/smsapi/Proxy.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -14,27 +13,27 @@ public interface Proxy HttpResponseEntity Execute( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, RequestMethod method); HttpResponseEntity Execute( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Stream file, RequestMethod method); HttpResponseEntity Execute( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Dictionary files, RequestMethod method); Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, RequestMethod method, CancellationToken cancellationToken = default ); @@ -42,7 +41,7 @@ Task ExecuteAsync( Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default @@ -51,7 +50,7 @@ Task ExecuteAsync( Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index bc29ee5..7396704 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Http; @@ -27,7 +26,7 @@ public void Authentication(IClient client) authentication = client; } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, RequestMethod method) { return Execute(contentType, uri, data, new Dictionary(), method); } @@ -35,7 +34,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam public HttpResponseEntity Execute( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Stream file, RequestMethod method) { @@ -45,7 +44,7 @@ public HttpResponseEntity Execute( public HttpResponseEntity Execute( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Dictionary files, RequestMethod method) { @@ -55,8 +54,7 @@ public HttpResponseEntity Execute( try { - client.AddContentTypeHeader(contentType); - return client.SendRequest(method, uri, data, files).Result; + return client.SendRequest(contentType, method, uri, data, files).Result; } catch (Exception e) { @@ -67,7 +65,7 @@ public HttpResponseEntity Execute( public async Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, RequestMethod method, CancellationToken cancellationToken = default ) @@ -78,7 +76,7 @@ public async Task ExecuteAsync( public async Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default @@ -90,7 +88,7 @@ public async Task ExecuteAsync( public async Task ExecuteAsync( ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default @@ -102,8 +100,7 @@ public async Task ExecuteAsync( try { - client.AddContentTypeHeader(contentType); - return await client.SendRequest(method, uri, data, files, cancellationToken); + return await client.SendRequest(contentType, method, uri, data, files, cancellationToken); } catch (Exception e) { diff --git a/smsapiTests/Unit/Fixture/ProxyStub.cs b/smsapiTests/Unit/Fixture/ProxyStub.cs index 537ee4e..84ea947 100644 --- a/smsapiTests/Unit/Fixture/ProxyStub.cs +++ b/smsapiTests/Unit/Fixture/ProxyStub.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -14,36 +14,36 @@ public class ProxyStub : Proxy public void Authentication(IClient client) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, RequestMethod method) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, Stream file, RequestMethod method) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, Dictionary files, RequestMethod method) { return SyncExecutionResponse; } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, RequestMethod method, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index c71d9a0..7c99f80 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net; @@ -24,7 +23,7 @@ public void Authentication(IClient client) throw new NotImplementedException(); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -33,7 +32,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, Stream file, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -42,7 +41,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } - public HttpResponseEntity Execute(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> data, Dictionary files, RequestMethod method) { RequestedUri = uri; SetParameters(data); @@ -51,7 +50,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, Nam return new HttpResponseEntity(Task.FromResult(Stream.Null), HttpStatusCode.OK); } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); @@ -60,7 +59,7 @@ public Task ExecuteAsync(ActionContentType contentType, stri return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); @@ -69,7 +68,7 @@ public Task ExecuteAsync(ActionContentType contentType, stri return new Task(() => new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK)); } - public Task ExecuteAsync(ActionContentType contentType, string uri, NameValueCollection data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) + public Task ExecuteAsync(ActionContentType contentType, string uri, ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); @@ -78,18 +77,17 @@ public Task ExecuteAsync(ActionContentType contentType, stri return new Task(null); } - private void SetParameters(NameValueCollection collection) + private void SetParameters(ISet> collection) { Parameters.Clear(); - var map = collection.AllKeys.SelectMany( - collection.GetValues, - (k, v) => new KeyValuePair(k ,v) - ); - - foreach (var entry in map) + var dictionary = collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + foreach (var entry in dictionary) { Parameters.Add(entry.Key, entry.Value); } + + Parameters.Remove("format");//for easier, more concise testing } } From d8717ad7cdac25414b7416c1c4ab8b8e85475446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 2 Dec 2024 13:42:52 +0000 Subject: [PATCH 054/142] Allow requests to be processed as json --- smsapi/Api/Action/Action.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index aa79cc8..d93d98a 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -76,6 +76,7 @@ protected virtual void Validate() { } + [Obsolete($"Use {nameof(Request)}, that supports json types")] protected virtual NameValueCollection Values() { return new NameValueCollection(); From 35b398194446741642769ee4472ff395dc7add92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 2 Dec 2024 13:56:10 +0000 Subject: [PATCH 055/142] Allow requests to be processed as json --- smsapi/Api/Action/Action.cs | 17 +++++++---------- smsapi/Api/Action/Blacklist/Add.cs | 6 +++--- smsapi/Api/Action/Contacts/CreateContact.cs | 5 +++-- smsapi/Api/Action/Contacts/CreateField.cs | 5 +++-- smsapi/Api/Action/Contacts/CreateGroup.cs | 5 +++-- .../Action/Contacts/CreateGroupPermission.cs | 5 +++-- smsapi/Api/Action/Contacts/EditContact.cs | 5 +++-- smsapi/Api/Action/Contacts/EditField.cs | 5 +++-- smsapi/Api/Action/Contacts/EditGroup.cs | 5 +++-- .../Api/Action/Contacts/EditGroupPermission.cs | 7 ++++--- smsapi/Api/Action/Contacts/ListContacts.cs | 5 +++-- smsapi/Api/Action/Contacts/ListGroups.cs | 5 +++-- smsapi/Api/Action/HLR/CheckNumber.cs | 7 ++++--- smsapi/Api/Action/HLR/Lookup.cs | 5 +++-- smsapi/Api/Action/MFA/CreateMFACode.cs | 5 +++-- smsapi/Api/Action/MFA/VerifyMFACode.cs | 5 +++-- smsapi/Api/Action/MMS/Delete.cs | 9 +++++---- smsapi/Api/Action/MMS/Get.cs | 9 +++++---- smsapi/Api/Action/MMS/Send.cs | 5 +++-- .../Api/Action/OptOut/ChangeOptOutSettings.cs | 5 +++-- smsapi/Api/Action/OptOut/OptOutList.cs | 5 +++-- smsapi/Api/Action/SMS/Delete.cs | 9 +++++---- smsapi/Api/Action/SMS/Get.cs | 9 +++++---- smsapi/Api/Action/Sender/Add.cs | 9 +++++---- smsapi/Api/Action/Sender/Delete.cs | 9 +++++---- smsapi/Api/Action/Sender/List.cs | 6 +++--- smsapi/Api/Action/Sender/SetDefault.cs | 9 +++++---- smsapi/Api/Action/User/Add.cs | 7 ++++--- smsapi/Api/Action/User/Edit.cs | 7 ++++--- smsapi/Api/Action/User/Get.cs | 9 +++++---- smsapi/Api/Action/User/GetPoints.cs | 9 +++++---- smsapi/Api/Action/User/List.cs | 6 +++--- smsapi/Api/Action/VMS/Delete.cs | 9 +++++---- smsapi/Api/Action/VMS/Get.cs | 9 +++++---- smsapi/Api/Action/VMS/Send.cs | 4 ++-- 35 files changed, 134 insertions(+), 107 deletions(-) diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index d93d98a..37cfb0d 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -75,15 +75,12 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of protected virtual void Validate() { } - - [Obsolete($"Use {nameof(Request)}, that supports json types")] - protected virtual NameValueCollection Values() + + protected virtual (NameValueCollection, ISet>?) Values() { - return new NameValueCollection(); + return (new NameValueCollection(), default); } - protected virtual ISet>? Request() => default; - private string UriWithPagination() { var uriBuilder = new UriBuilder @@ -107,7 +104,7 @@ private void AssignValuesToQuery(UriBuilder uriBuilder) var query = HttpUtility.ParseQueryString(uriBuilder.Query); - query.Add(Values()); + query.Add(Values().Item1); uriBuilder.Query = query.ToString(); } @@ -124,11 +121,11 @@ private T ProcessResponse(HttpResponseEntity responseEntity) KeyValuePair.Create("format", "json") , }; - Request()?.Let(requestData => requestData.ToList().ForEach(data => values.Add(data))); + Values().Item2?.Let(requestData => requestData.ToList().ForEach(data => values.Add(data))); - foreach (string key in Values().AllKeys) + foreach (string key in Values().Item1.AllKeys) { - values.Add(KeyValuePair.Create(key, Values().Get(key))); + values.Add(KeyValuePair.Create(key, Values().Item1.Get(key))); } return values; diff --git a/smsapi/Api/Action/Blacklist/Add.cs b/smsapi/Api/Action/Blacklist/Add.cs index d81f522..0d85693 100644 --- a/smsapi/Api/Action/Blacklist/Add.cs +++ b/smsapi/Api/Action/Blacklist/Add.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; -using SMSApi.Api.Response; using smsapi.Api.Response.Blacklist; namespace SMSApi.Api.Action.Blacklist; @@ -44,13 +44,13 @@ protected override ApiType ApiType() return Action.ApiType.Rest; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection { { "phone_number", phoneNumber } }; if (withExpireAt != null) values.Add("expire_at", withExpireAt.Value.ToString("O")); - return values; + return (values, default); } } diff --git a/smsapi/Api/Action/Contacts/CreateContact.cs b/smsapi/Api/Action/Contacts/CreateContact.cs index ed8f641..e68567c 100644 --- a/smsapi/Api/Action/Contacts/CreateContact.cs +++ b/smsapi/Api/Action/Contacts/CreateContact.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -81,7 +82,7 @@ protected override string Uri() return "contacts"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); @@ -130,7 +131,7 @@ protected override NameValueCollection Values() values.Add("source", source); } - return values; + return (values, default); } } } diff --git a/smsapi/Api/Action/Contacts/CreateField.cs b/smsapi/Api/Action/Contacts/CreateField.cs index fb6b489..7740cf1 100644 --- a/smsapi/Api/Action/Contacts/CreateField.cs +++ b/smsapi/Api/Action/Contacts/CreateField.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -31,7 +32,7 @@ protected override string Uri() return "contacts/fields"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); if (name != null) @@ -44,7 +45,7 @@ protected override NameValueCollection Values() values.Add("type", type); } - return values; + return (values, default); } } } diff --git a/smsapi/Api/Action/Contacts/CreateGroup.cs b/smsapi/Api/Action/Contacts/CreateGroup.cs index 4b1ba00..1edfacd 100644 --- a/smsapi/Api/Action/Contacts/CreateGroup.cs +++ b/smsapi/Api/Action/Contacts/CreateGroup.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -38,7 +39,7 @@ protected override string Uri() return "contacts/groups"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); if (name != null) @@ -56,7 +57,7 @@ protected override NameValueCollection Values() values.Add("idx", idx); } - return values; + return (values, default); } } } diff --git a/smsapi/Api/Action/Contacts/CreateGroupPermission.cs b/smsapi/Api/Action/Contacts/CreateGroupPermission.cs index 65009a4..c118e7c 100644 --- a/smsapi/Api/Action/Contacts/CreateGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/CreateGroupPermission.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -52,7 +53,7 @@ protected override string Uri() return "contacts/groups/" + groupId + "/permissions"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection { @@ -66,7 +67,7 @@ protected override NameValueCollection Values() values.Add("username", username); } - return values; + return (values, default); } } } diff --git a/smsapi/Api/Action/Contacts/EditContact.cs b/smsapi/Api/Action/Contacts/EditContact.cs index 5adb177..e3a90f8 100644 --- a/smsapi/Api/Action/Contacts/EditContact.cs +++ b/smsapi/Api/Action/Contacts/EditContact.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -88,7 +89,7 @@ protected override string Uri() return "contacts/" + ContactId; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); @@ -137,7 +138,7 @@ protected override NameValueCollection Values() values.Add("source", source); } - return values; + return (values, default); } protected override void Validate() diff --git a/smsapi/Api/Action/Contacts/EditField.cs b/smsapi/Api/Action/Contacts/EditField.cs index 7ee553a..1f45353 100644 --- a/smsapi/Api/Action/Contacts/EditField.cs +++ b/smsapi/Api/Action/Contacts/EditField.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Text.RegularExpressions; using SMSApi.Api.Response; @@ -32,7 +33,7 @@ protected override string Uri() return "contacts/fields/" + fieldId; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection(); if (name != null) @@ -40,7 +41,7 @@ protected override NameValueCollection Values() parameters.Add("name", name); } - return parameters; + return (parameters, default); } protected override void Validate() diff --git a/smsapi/Api/Action/Contacts/EditGroup.cs b/smsapi/Api/Action/Contacts/EditGroup.cs index ee468e6..9bce042 100644 --- a/smsapi/Api/Action/Contacts/EditGroup.cs +++ b/smsapi/Api/Action/Contacts/EditGroup.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -45,7 +46,7 @@ protected override string Uri() return "contacts/groups/" + groupId; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection(); if (name != null) @@ -63,7 +64,7 @@ protected override NameValueCollection Values() parameters.Add("idx", idx); } - return parameters; + return (parameters, default); } protected override void Validate() diff --git a/smsapi/Api/Action/Contacts/EditGroupPermission.cs b/smsapi/Api/Action/Contacts/EditGroupPermission.cs index c85f74e..8d0031b 100644 --- a/smsapi/Api/Action/Contacts/EditGroupPermission.cs +++ b/smsapi/Api/Action/Contacts/EditGroupPermission.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -47,14 +48,14 @@ protected override string Uri() return "contacts/groups/" + groupId + "/permissions/" + username; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "read", Convert.ToInt32(read).ToString() }, { "write", Convert.ToInt32(write).ToString() }, { "send", Convert.ToInt32(send).ToString() } - }; + }, default); } protected override void Validate() diff --git a/smsapi/Api/Action/Contacts/ListContacts.cs b/smsapi/Api/Action/Contacts/ListContacts.cs index e0f2e02..75d173c 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -96,7 +97,7 @@ protected override string Uri() return "contacts"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection(); if (search != null) @@ -139,7 +140,7 @@ protected override NameValueCollection Values() parameters.Add("birthday_date", birthdayDate.Value.ToString("yyyy-MM-dd")); } - return parameters; + return (parameters, default); } public uint? Limit { get; set; } diff --git a/smsapi/Api/Action/Contacts/ListGroups.cs b/smsapi/Api/Action/Contacts/ListGroups.cs index 8dcf0b0..fca4108 100644 --- a/smsapi/Api/Action/Contacts/ListGroups.cs +++ b/smsapi/Api/Action/Contacts/ListGroups.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -29,7 +30,7 @@ protected override string Uri() return "contacts/groups"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection { @@ -46,7 +47,7 @@ protected override NameValueCollection Values() parameters.Add("name", name); } - return parameters; + return (parameters, default); } } } diff --git a/smsapi/Api/Action/HLR/CheckNumber.cs b/smsapi/Api/Action/HLR/CheckNumber.cs index 1f8f56b..bb8d52d 100644 --- a/smsapi/Api/Action/HLR/CheckNumber.cs +++ b/smsapi/Api/Action/HLR/CheckNumber.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; @@ -22,12 +23,12 @@ protected override string Uri() return "hlrsync.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "number", this.number } - }; + }, default); } } } diff --git a/smsapi/Api/Action/HLR/Lookup.cs b/smsapi/Api/Action/HLR/Lookup.cs index f321e34..f3f01c2 100644 --- a/smsapi/Api/Action/HLR/Lookup.cs +++ b/smsapi/Api/Action/HLR/Lookup.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response.HLR; @@ -18,8 +19,8 @@ public Lookup(string numberToCheck) protected override ApiType ApiType() => Action.ApiType.Rest; - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection { { "phone_number", _numberToCheck } }; + return (new NameValueCollection { { "phone_number", _numberToCheck } }, default); } } diff --git a/smsapi/Api/Action/MFA/CreateMFACode.cs b/smsapi/Api/Action/MFA/CreateMFACode.cs index 21fba6e..4315020 100644 --- a/smsapi/Api/Action/MFA/CreateMFACode.cs +++ b/smsapi/Api/Action/MFA/CreateMFACode.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response.MFA; @@ -48,7 +49,7 @@ protected override string Uri() return "mfa/codes"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection { { "phone_number", _phoneNumber } }; @@ -61,6 +62,6 @@ protected override NameValueCollection Values() if (_from != null) parameters.Add("from", _from); - return parameters; + return (parameters, default); } } diff --git a/smsapi/Api/Action/MFA/VerifyMFACode.cs b/smsapi/Api/Action/MFA/VerifyMFACode.cs index 30ec8c8..e0840e7 100644 --- a/smsapi/Api/Action/MFA/VerifyMFACode.cs +++ b/smsapi/Api/Action/MFA/VerifyMFACode.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response.MFA; @@ -26,8 +27,8 @@ protected override string Uri() return "mfa/codes/verifications"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection { { "phone_number", phoneNumber }, { "code", code } }; + return (new NameValueCollection { { "phone_number", phoneNumber }, { "code", code } }, default); } } diff --git a/smsapi/Api/Action/MMS/Delete.cs b/smsapi/Api/Action/MMS/Delete.cs index 94e23d7..4728430 100644 --- a/smsapi/Api/Action/MMS/Delete.cs +++ b/smsapi/Api/Action/MMS/Delete.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -26,12 +27,12 @@ protected override string Uri() return "mms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "sch_del", string.Join("|", ids) } - }; + }, default); } } } diff --git a/smsapi/Api/Action/MMS/Get.cs b/smsapi/Api/Action/MMS/Get.cs index 85fced2..c16f408 100644 --- a/smsapi/Api/Action/MMS/Get.cs +++ b/smsapi/Api/Action/MMS/Get.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -26,12 +27,12 @@ protected override string Uri() return "mms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "status", string.Join("|", ids) } - }; + }, default); } } } diff --git a/smsapi/Api/Action/MMS/Send.cs b/smsapi/Api/Action/MMS/Send.cs index 6793145..a3d12a7 100644 --- a/smsapi/Api/Action/MMS/Send.cs +++ b/smsapi/Api/Action/MMS/Send.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; namespace SMSApi.Api.Action @@ -101,7 +102,7 @@ protected override void Validate() } } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var collection = new NameValueCollection(); @@ -143,7 +144,7 @@ protected override NameValueCollection Values() collection.Add("idx", string.Join("|", Idx)); } - return collection; + return (collection, default); } } } diff --git a/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs b/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs index 7388e67..c34244a 100644 --- a/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs +++ b/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response.OptOut; @@ -22,12 +23,12 @@ public ChangeOptOutSettings ChangeBrandName(string brandName) return this; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); _brandName?.Let(newName => values.Add("brand", newName)); - return values; + return (values, default); } } diff --git a/smsapi/Api/Action/OptOut/OptOutList.cs b/smsapi/Api/Action/OptOut/OptOutList.cs index b366acc..2bc40fc 100644 --- a/smsapi/Api/Action/OptOut/OptOutList.cs +++ b/smsapi/Api/Action/OptOut/OptOutList.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; using OptOutModel = SMSApi.Api.Response.OptOut.OptOut; @@ -30,12 +31,12 @@ public OptOutList FilterByPhoneNumber(string phoneNumber) return this; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); _phoneNumber?.Let(number => values.Add("phone_number", number)); - return values; + return (values, default); } } diff --git a/smsapi/Api/Action/SMS/Delete.cs b/smsapi/Api/Action/SMS/Delete.cs index 32c97df..a2ac764 100644 --- a/smsapi/Api/Action/SMS/Delete.cs +++ b/smsapi/Api/Action/SMS/Delete.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -20,12 +21,12 @@ protected override string Uri() return "sms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "sch_del", id } - }; + }, default); } } } diff --git a/smsapi/Api/Action/SMS/Get.cs b/smsapi/Api/Action/SMS/Get.cs index 1f44bce..e0a7b9b 100644 --- a/smsapi/Api/Action/SMS/Get.cs +++ b/smsapi/Api/Action/SMS/Get.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -26,12 +27,12 @@ protected override string Uri() return "sms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "status", string.Join("|", id) } - }; + }, default); } } } diff --git a/smsapi/Api/Action/Sender/Add.cs b/smsapi/Api/Action/Sender/Add.cs index 09c1b53..6c1752f 100644 --- a/smsapi/Api/Action/Sender/Add.cs +++ b/smsapi/Api/Action/Sender/Add.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; using SMSApi.Api.Response.ResponseResolver; @@ -21,12 +22,12 @@ protected override string Uri() return "sender.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "add", name } - }; + }, default); } } } diff --git a/smsapi/Api/Action/Sender/Delete.cs b/smsapi/Api/Action/Sender/Delete.cs index eb22e4c..a09af14 100644 --- a/smsapi/Api/Action/Sender/Delete.cs +++ b/smsapi/Api/Action/Sender/Delete.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; using SMSApi.Api.Response.ResponseResolver; @@ -21,12 +22,12 @@ protected override string Uri() return "sender.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "delete", name } - }; + }, default); } } } diff --git a/smsapi/Api/Action/Sender/List.cs b/smsapi/Api/Action/Sender/List.cs index d3bdf15..14db8a7 100644 --- a/smsapi/Api/Action/Sender/List.cs +++ b/smsapi/Api/Action/Sender/List.cs @@ -23,12 +23,12 @@ protected override string Uri() return "sender.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "list", "1" } - }; + }, default); } } } diff --git a/smsapi/Api/Action/Sender/SetDefault.cs b/smsapi/Api/Action/Sender/SetDefault.cs index 7fb9db5..6957138 100644 --- a/smsapi/Api/Action/Sender/SetDefault.cs +++ b/smsapi/Api/Action/Sender/SetDefault.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; using SMSApi.Api.Response.ResponseResolver; @@ -21,12 +22,12 @@ protected override string Uri() return "sender.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "default", name } - }; + }, default); } } } diff --git a/smsapi/Api/Action/User/Add.cs b/smsapi/Api/Action/User/Add.cs index 13d22cd..2a76f45 100644 --- a/smsapi/Api/Action/User/Add.cs +++ b/smsapi/Api/Action/User/Add.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using SMSApi.Api.Response; @@ -93,7 +94,7 @@ protected override string Uri() return "user.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var collection = new NameValueCollection { @@ -141,7 +142,7 @@ protected override NameValueCollection Values() collection.Add("without_prefix", "1"); } - return collection; + return (collection, default); } } } diff --git a/smsapi/Api/Action/User/Edit.cs b/smsapi/Api/Action/User/Edit.cs index 90cb2c5..b318437 100644 --- a/smsapi/Api/Action/User/Edit.cs +++ b/smsapi/Api/Action/User/Edit.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using SMSApi.Api.Response; @@ -93,7 +94,7 @@ protected override string Uri() return "user.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var collection = new NameValueCollection { @@ -145,7 +146,7 @@ protected override NameValueCollection Values() collection.Add("without_prefix", "1"); } - return collection; + return (collection, default); } } } diff --git a/smsapi/Api/Action/User/Get.cs b/smsapi/Api/Action/User/Get.cs index 8e9c747..dd4c4f1 100644 --- a/smsapi/Api/Action/User/Get.cs +++ b/smsapi/Api/Action/User/Get.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -20,12 +21,12 @@ protected override string Uri() return "user.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "get_user", username } - }; + }, default); } } } diff --git a/smsapi/Api/Action/User/GetPoints.cs b/smsapi/Api/Action/User/GetPoints.cs index 7d652e6..3ea7fa4 100644 --- a/smsapi/Api/Action/User/GetPoints.cs +++ b/smsapi/Api/Action/User/GetPoints.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -12,13 +13,13 @@ protected override string Uri() return "user.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "credits", "1" }, { "details", "1" } - }; + }, default); } } } diff --git a/smsapi/Api/Action/User/List.cs b/smsapi/Api/Action/User/List.cs index 7344b91..7bba928 100644 --- a/smsapi/Api/Action/User/List.cs +++ b/smsapi/Api/Action/User/List.cs @@ -23,12 +23,12 @@ protected override string Uri() return "user.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "list", "1" } - }; + }, default); } } } diff --git a/smsapi/Api/Action/VMS/Delete.cs b/smsapi/Api/Action/VMS/Delete.cs index d29b2d0..d1ae42c 100644 --- a/smsapi/Api/Action/VMS/Delete.cs +++ b/smsapi/Api/Action/VMS/Delete.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -26,12 +27,12 @@ protected override string Uri() return "vms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "sch_del", string.Join("|", ids) } - }; + }, default); } } } diff --git a/smsapi/Api/Action/VMS/Get.cs b/smsapi/Api/Action/VMS/Get.cs index 3f12d77..6457172 100644 --- a/smsapi/Api/Action/VMS/Get.cs +++ b/smsapi/Api/Action/VMS/Get.cs @@ -1,4 +1,5 @@ -using System.Collections.Specialized; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action @@ -26,12 +27,12 @@ protected override string Uri() return "vms.do"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { - return new NameValueCollection + return (new NameValueCollection { { "status", string.Join("|", ids) } - }; + }, default); } } } diff --git a/smsapi/Api/Action/VMS/Send.cs b/smsapi/Api/Action/VMS/Send.cs index befbe58..365dc93 100644 --- a/smsapi/Api/Action/VMS/Send.cs +++ b/smsapi/Api/Action/VMS/Send.cs @@ -154,7 +154,7 @@ protected override void Validate() } } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var collection = new NameValueCollection(); @@ -214,7 +214,7 @@ protected override NameValueCollection Values() collection.Add("idx", string.Join("|", Idx)); } - return collection; + return (collection, default); } } } From 261107ce2e627268bd87999ab10be248c97a21da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 3 Dec 2024 08:51:39 +0000 Subject: [PATCH 056/142] Add Opt-out feature examples --- examples/optOut/Delete.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/optOut/Delete.cs b/examples/optOut/Delete.cs index ceb231a..9904955 100644 --- a/examples/optOut/Delete.cs +++ b/examples/optOut/Delete.cs @@ -1,4 +1,5 @@ using SMSApi.Api; +using SMSApi.Api.Response.OptOut.Exception; var client = new ClientOAuth("token"); var features = new Features(client); @@ -7,9 +8,9 @@ .List() .Execute(); -OptOutDeletionResponse DeleteOptOut(string optOutId) +void DeleteOptOut(string optOutId) { - return features.OptOut() + features.OptOut() .DeleteOptOut(optOutId) .Execute(); } From 09a45e3fd761ee793d859897b5ab871797e3ab87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 6 Dec 2024 09:33:30 +0000 Subject: [PATCH 057/142] Change deserialization process --- .../Deserialization/BaseJsonDeserializer.cs | 48 ++++++++-------- .../Response/Deserialization/IDeserializer.cs | 2 +- .../PrivateFieldsContractResolver.cs | 55 +++++++++++++++++++ smsapi/smsapi.csproj | 6 +- 4 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs diff --git a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs index aa75717..65173d1 100644 --- a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs @@ -1,31 +1,35 @@ using System; -using System.Runtime.Serialization.Json; +using System.IO; +using Newtonsoft.Json; -namespace SMSApi.Api.Response.Deserialization +namespace SMSApi.Api.Response.Deserialization; + +public class BaseJsonDeserializer : IDeserializer { - public class BaseJsonDeserializer : IDeserializer + public DeserializationResult Deserialize(HttpResponseEntity responseEntity) { - public DeserializationResult Deserialize(HttpResponseEntity responseEntity) + T result; + var data = responseEntity.Content.Result; + + if (data.Length > 0) { - T result; - var data = responseEntity.Content.Result; - - if (data.Length > 0) - { - data.Position = 0; - var serializer = new DataContractJsonSerializer(typeof(T)); - result = (T)serializer.ReadObject(data); - data.Position = 0; - } - else - { - result = Activator.CreateInstance(); - } + var stringData = new StreamReader(data).ReadToEnd(); - return new DeserializationResult - { - Result = result - }; + result = JsonConvert.DeserializeObject( + stringData, + new JsonSerializerSettings + { + ContractResolver = new PrivateFieldsContractResolver(), + }); + } + else + { + result = Activator.CreateInstance(); } + + return new DeserializationResult + { + Result = result + }; } } diff --git a/smsapi/Api/Response/Deserialization/IDeserializer.cs b/smsapi/Api/Response/Deserialization/IDeserializer.cs index ba3416f..906b616 100644 --- a/smsapi/Api/Response/Deserialization/IDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/IDeserializer.cs @@ -1,6 +1,6 @@ namespace SMSApi.Api.Response.Deserialization { - public interface IDeserializer + internal interface IDeserializer { public DeserializationResult Deserialize(HttpResponseEntity responseEntity); } diff --git a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs new file mode 100644 index 0000000..59043be --- /dev/null +++ b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace SMSApi.Api.Response.Deserialization; + +internal class PrivateFieldsContractResolver : DefaultContractResolver +{ + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) + { + var jsonProperties = base.CreateProperties(type, memberSerialization); + + var readonlyFields = GetPublicReadonlyFields(type); + foreach (var field in readonlyFields) + if (jsonProperties.All(p => p.PropertyName != field.Name)) + jsonProperties.Add(CreateProperty(field, memberSerialization)); + + return jsonProperties; + } + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var jsonProperty = base.CreateProperty(member, memberSerialization); + + switch (member) + { + case PropertyInfo propertyInfo: + { + if (HasPrivateSetter(propertyInfo)) jsonProperty.Writable = true; + break; + } + case FieldInfo { IsInitOnly: true }: + jsonProperty.Writable = true; + break; + } + + return jsonProperty; + } + + private static IEnumerable GetPublicReadonlyFields(Type type) + { + return type.GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(field => field.IsInitOnly); + } + + private static bool HasPrivateSetter(PropertyInfo propertyInfo) + { + var setMethod = propertyInfo.GetSetMethod(true); + + return setMethod != null && !setMethod.IsPublic; + } +} diff --git a/smsapi/smsapi.csproj b/smsapi/smsapi.csproj index 23a1d86..c1161e9 100644 --- a/smsapi/smsapi.csproj +++ b/smsapi/smsapi.csproj @@ -3,7 +3,6 @@ 8.0.30703 2.0 - netcoreapp3.1;net5.0;net6.0;net7.0 false false 10 @@ -17,6 +16,7 @@ README.md logo.jpg enable + net6.0;net7.0;net8.0;netcoreapp3.1 SMSAPI.pl @@ -27,7 +27,6 @@ SMSAPI Client that allows to send SMS, MMS, VMS and manage your SMSAPI account. SMSAPI Client that allows to send SMS, MMS, VMS and manage your SMSAPI account. smsapi;sms;marketing;shipment;mms;vms;message - net6.0;net7.0;net8.0;netcoreapp3.1 True @@ -61,4 +60,7 @@ ..\..\..\..\.nuget\packages\newtonsoft.json\10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll + + + From ac23cd6a6736e7425cc4feab4c88630e224325e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 6 Dec 2024 10:00:15 +0000 Subject: [PATCH 058/142] Change deserialization process --- .../BaseJsonDeserializerTest.cs | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs diff --git a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs new file mode 100644 index 0000000..654c6e5 --- /dev/null +++ b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs @@ -0,0 +1,187 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using SMSApi.Api; +using SMSApi.Api.Response.Deserialization; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace smsapiTests.Unit.Response.Deserialization; + +[TestClass] +public class BaseJsonDeserializerTest +{ + private readonly BaseJsonDeserializer _baseJsonDeserializer = new(); + + [TestMethod] + public void deserialize_public_field() + { + var json = new Dictionary + { + { "Field", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + [TestMethod] + public void deserialize_public_readonly_field() + { + var json = new Dictionary + { + { "Field", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + [TestMethod] + public void deserialize_public_field_with_public_setter() + { + var json = new Dictionary + { + { "Field", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + [TestMethod] + public void deserialize_public_field_with_public_private_setter() + { + var json = new Dictionary + { + { "Field", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + [TestMethod] + public void deserialize_public_field_with_type_reference() + { + var json = new Dictionary>> + { + { "Collection", new List> { new() { { "Field", "nested" } } } } + }; + + var result = Deserialize(json); + Assert.AreEqual(1, result.Collection.Count); + Assert.AreEqual("nested", result.Collection.First().Field); + } + + [TestMethod] + public void deserialize_public_field_with_readonly_type_reference() + { + var json = new Dictionary>> + { + { "Collection", new List> { new() { { "Field", "nested" } } } } + }; + + var result = Deserialize(json); + Assert.AreEqual(1, result.Collection.Count); + Assert.AreEqual("nested", result.Collection.First().Field); + } + + [TestMethod] + public void deserialize_public_field_with_private_set_type_reference() + { + var json = new Dictionary>> + { + { "Collection", new List> { new() { { "Field", "nested" } } } } + }; + + var result = Deserialize(json); + Assert.AreEqual(1, result.Collection.Count); + Assert.AreEqual("nested", result.Collection.First().Field); + } + + [TestMethod] + public void deserialize_with_custom_name() + { + var json = new Dictionary + { + { "another_name", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + [TestMethod] + public void deserialize_readonly_record_struct() + { + var json = new Dictionary + { + { "Field", "abc" } + }; + + var result = Deserialize(json); + Assert.AreEqual("abc", result.Field); + } + + private T Deserialize(dynamic content) + { + var stream = new MemoryStream(); + JsonSerializer.Serialize(stream, content); + stream.Seek(0, SeekOrigin.Begin); + + var streamTask = Task.FromResult(stream); + var responseEntity = new HttpResponseEntity(streamTask, HttpStatusCode.OK); + + return _baseJsonDeserializer.Deserialize(responseEntity).Result; + } + + private class PublicFields + { + public string Field; + } + + private class FieldWithAnotherName + { + [JsonProperty("another_name")] + public string Field; + } + + private class PublicReadonlyFields + { + public readonly string Field; + } + + private class PublicFieldsWithPublicSet + { + public string Field { get; set; } + } + + private class PublicFieldsWithPrivateSet + { + public string Field { get; private set; } + } + + private class PublicNestedFields + { + public List Collection; + } + + private class PublicNestedFieldsWithReadonlyField + { + public readonly ICollection Collection = new List(); + } + + private class PublicNestedFieldsWithPrivateSet + { + public readonly ICollection Collection = new List(); + } + + private readonly record struct ReadonlyRecordStruct + { + public readonly string Field; + } +} From 244d33ef69d21510ecfc336f7bcd74cbbced5361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 9 Dec 2024 19:55:58 +0000 Subject: [PATCH 059/142] Add Fallback support in SMS --- smsapi/Api/Action/SMS/Send.cs | 447 +++++++++--------- smsapi/Api/Response/Status.cs | 39 +- .../SMS/Fixture/SmsSendResponseMother.cs | 43 ++ .../Action/SMS/SMSFallbackResponseTest.cs | 73 +++ .../Unit/Action/SMS/SMSFallbackTest.cs | 44 ++ .../Unit/{ => Action}/SMS/SMSSendTest.cs | 0 smsapiTests/Unit/ProxyAssert.cs | 15 +- smsapiTests/Unit/SpyProxy.cs | 2 +- 8 files changed, 423 insertions(+), 240 deletions(-) create mode 100644 smsapiTests/Unit/Action/SMS/Fixture/SmsSendResponseMother.cs create mode 100644 smsapiTests/Unit/Action/SMS/SMSFallbackResponseTest.cs create mode 100644 smsapiTests/Unit/Action/SMS/SMSFallbackTest.cs rename smsapiTests/Unit/{ => Action}/SMS/SMSSendTest.cs (100%) diff --git a/smsapi/Api/Action/SMS/Send.cs b/smsapi/Api/Action/SMS/Send.cs index 06f2f98..370138f 100644 --- a/smsapi/Api/Action/SMS/Send.cs +++ b/smsapi/Api/Action/SMS/Send.cs @@ -1,284 +1,269 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Runtime.Serialization; -namespace SMSApi.Api.Action +namespace SMSApi.Api.Action; + +public class SMSSend : Send { - public class SMSSend : Send + public enum SmsFallbacks { - private const string Encoding = "UTF-8"; - - private string dataCoding; - private string dateExpire; - private bool fast; - private bool flash; - private int maxParts; - private bool normalize; - private bool noUnicode; - private string[] @params; - private string sender; - private bool single; - private string text; - private string? template; - - protected override RequestMethod Method => RequestMethod.POST; - - public SMSSend SetCheckIDx(bool check = true) - { - IdxCheck = check; - return this; - } + [EnumMember(Value = "vms")] Vms + } - public SMSSend SetDataCoding(string dataCoding) - { - this.dataCoding = dataCoding; - return this; - } + private const string Encoding = "UTF-8"; + private SmsFallbacks? _fallback; + + private string dataCoding; + private string dateExpire; + private bool fast; + private bool flash; + private int maxParts; + private bool normalize; + private bool noUnicode; + private string[] @params; + private string sender; + private bool single; + private string? template; + private string text; + + protected override RequestMethod Method => RequestMethod.POST; + protected override ActionContentType ContentType => ActionContentType.Json; + + public SMSSend SetCheckIDx(bool check = true) + { + IdxCheck = check; + return this; + } - public SMSSend SetDateExpire(string data) - { - dateExpire = data; - return this; - } + public SMSSend SetDataCoding(string dataCoding) + { + this.dataCoding = dataCoding; + return this; + } - public SMSSend SetDateExpire(DateTime data) - { - dateExpire = data.ToString("yyyy-MM-ddTHH:mm:ssK"); - return this; - } + public SMSSend SetDateExpire(string data) + { + dateExpire = data; + return this; + } - public SMSSend SetDateSent(string data) - { - DateSent = data; - return this; - } + public SMSSend SetDateExpire(DateTime data) + { + dateExpire = data.ToString("yyyy-MM-ddTHH:mm:ssK"); + return this; + } - public SMSSend SetDateSent(DateTime data) - { - DateSent = data.ToString("yyyy-MM-ddTHH:mm:ssK"); - return this; - } + public SMSSend SetDateSent(string data) + { + DateSent = data; + return this; + } - /* - public SMSSend SetEncoding(string encoding) - { - this.encoding = encoding; - return this; - } - */ + public SMSSend SetDateSent(DateTime data) + { + DateSent = data.ToString("yyyy-MM-ddTHH:mm:ssK"); + return this; + } - public SMSSend SetFast(bool fast = true) - { - this.fast = fast; - return this; - } + /* + public SMSSend SetEncoding(string encoding) + { + this.encoding = encoding; + return this; + } + */ - public SMSSend SetFlash(bool flash = true) - { - this.flash = flash; - return this; - } + public SMSSend SetFast(bool fast = true) + { + this.fast = fast; + return this; + } - public SMSSend SetGroup(string group) - { - Group = group; - return this; - } + public SMSSend SetFlash(bool flash = true) + { + this.flash = flash; + return this; + } - public SMSSend SetIDx(string idx) - { - Idx = new[] { idx }; - return this; - } + public SMSSend SetGroup(string group) + { + Group = group; + return this; + } - public SMSSend SetIDx(string[] idx) - { - Idx = idx; - return this; - } + public SMSSend SetIDx(string idx) + { + Idx = new[] { idx }; + return this; + } - public SMSSend SetNormalize(bool flag = true) - { - normalize = flag; - return this; - } + public SMSSend SetIDx(string[] idx) + { + Idx = idx; + return this; + } - public SMSSend SetNoUnicode(bool noUnicode = true) - { - this.noUnicode = noUnicode; - return this; - } + public SMSSend SetNormalize(bool flag = true) + { + normalize = flag; + return this; + } - public SMSSend SetParam(int i, string[] text) - { - return SetParam(i, string.Join("|", text)); - } + public SMSSend SetNoUnicode(bool noUnicode = true) + { + this.noUnicode = noUnicode; + return this; + } - public SMSSend SetParam(int i, string text) - { - if (i > 3 || i < 0) - { - throw new IndexOutOfRangeException(); - } + public SMSSend SetParam(int i, string[] text) + { + return SetParam(i, string.Join("|", text)); + } - if (@params == null) - { - @params = new string[4]; - } + public SMSSend SetParam(int i, string text) + { + if (i > 3 || i < 0) throw new IndexOutOfRangeException(); - @params[i] = text; + if (@params == null) @params = new string[4]; - return this; - } + @params[i] = text; - public SMSSend SetPartner(string partner) - { - Partner = partner; - return this; - } + return this; + } - public SMSSend SetSender(string sender) - { - this.sender = sender; - return this; - } + public SMSSend SetPartner(string partner) + { + Partner = partner; + return this; + } - public SMSSend SetSingle(bool single = true) - { - this.single = single; - return this; - } + public SMSSend SetSender(string sender) + { + this.sender = sender; + return this; + } - public SMSSend SetTest(bool test = true) - { - Test = test; - return this; - } + public SMSSend SetSingle(bool single = true) + { + this.single = single; + return this; + } - public SMSSend SetText(string text) - { - this.text = text; - return this; - } + public SMSSend SetTest(bool test = true) + { + Test = test; + return this; + } - public SMSSend SetTo(string to) - { - To = new[] { to }; - return this; - } + public SMSSend SetText(string text) + { + this.text = text; + return this; + } - public SMSSend SetTo(string[] to) - { - To = to; - return this; - } - - public SMSSend SetTemplate(string templateName) - { - template = templateName; - - return this; - } + public SMSSend SetTo(string to) + { + To = new[] { to }; + return this; + } - protected override string Uri() - { - return "sms.do"; - } + public SMSSend SetTo(string[] to) + { + To = to; + return this; + } - protected override void Validate() - { - if (text == null && template == null) - { - throw new ArgumentException("Cannot send message without text!"); - } - } + public SMSSend SetTemplate(string templateName) + { + template = templateName; - protected override NameValueCollection Values() - { - var collection = new NameValueCollection(); + return this; + } - if (sender != null) - { - collection.Add("from", sender); - } + public SMSSend WithFallback(SmsFallbacks fallback) + { + _fallback = fallback; - if (To != null) - { - collection.Add("to", string.Join(",", To)); - } + return this; + } - if (Group != null) - { - collection.Add("group", Group); - } + protected override string Uri() + { + return "sms.do"; + } - collection.Add("message", text); + protected override void Validate() + { + if (text == null && template == null) throw new ArgumentException("Cannot send message without text!"); + } - collection.Add("single", single ? "1" : "0"); - collection.Add("nounicode", noUnicode ? "1" : "0"); - collection.Add("flash", flash ? "1" : "0"); - collection.Add("fast", fast ? "1" : "0"); - collection.Add("details", "1"); + protected override (NameValueCollection, ISet>?) Values() + { + var collection = new NameValueCollection(); - if (dataCoding != null) - { - collection.Add("datacoding", dataCoding); - } + if (sender != null) collection.Add("from", sender); - if (maxParts > 0) - { - collection.Add("max_parts", maxParts.ToString()); - } + if (To != null) collection.Add("to", string.Join(",", To)); - if (DateSent != null) - { - collection.Add("date", DateSent); - } + if (Group != null) collection.Add("group", Group); - if (dateExpire != null) - { - collection.Add("expiration_date", dateExpire); - } + collection.Add("message", text); - if (Partner != null) - { - collection.Add("partner_id", Partner); - } + collection.Add("single", single ? "1" : "0"); + collection.Add("nounicode", noUnicode ? "1" : "0"); + collection.Add("flash", flash ? "1" : "0"); + collection.Add("fast", fast ? "1" : "0"); + collection.Add("details", "1"); - collection.Add("encoding", Encoding); + if (dataCoding != null) collection.Add("datacoding", dataCoding); - if (normalize) - { - collection.Add("normalize", "1"); - } + if (maxParts > 0) collection.Add("max_parts", maxParts.ToString()); - if (Test) - { - collection.Add("test", "1"); - } + if (DateSent != null) collection.Add("date", DateSent); - if (Idx != null && Idx.Length > 0) - { - collection.Add("check_idx", IdxCheck ? "1" : "0"); - collection.Add("idx", string.Join("|", Idx)); - } + if (dateExpire != null) collection.Add("expiration_date", dateExpire); - if (@params != null) - { - for (int i = 0; i < @params.Length; i++) - { - if (@params[i] != null) - { - collection.Add("param" + (i + 1), @params[i]); - } - } - } + if (Partner != null) collection.Add("partner_id", Partner); - if (template != null) - { - collection.Add("template", template); - } + collection.Add("encoding", Encoding); - return collection; + if (normalize) collection.Add("normalize", "1"); + + if (Test) collection.Add("test", "1"); + + if (Idx != null && Idx.Length > 0) + { + collection.Add("check_idx", IdxCheck ? "1" : "0"); + collection.Add("idx", string.Join("|", Idx)); } + + if (@params != null) + for (var i = 0; i < @params.Length; i++) + if (@params[i] != null) + collection.Add("param" + (i + 1), @params[i]); + + if (template != null) collection.Add("template", template); + + return (collection, PrepareRequestBody()); + } + + private ISet>? PrepareRequestBody() + { + ISet>? values = null; + + _fallback?.Let(fallback => + { + var fallbacks = new HashSet> + { new() { { "type", fallback.GetEnumValue() } } }; + + values = new HashSet> + { + new("fallback", fallbacks) + }; + }); + + return values; } } diff --git a/smsapi/Api/Response/Status.cs b/smsapi/Api/Response/Status.cs index 0f3e74a..99a9d4e 100644 --- a/smsapi/Api/Response/Status.cs +++ b/smsapi/Api/Response/Status.cs @@ -1,26 +1,51 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { [DataContract] public class Status : Countable { - [DataMember(Name = "length", IsRequired = false)] + [JsonProperty("length")] public readonly int? Length; - [DataMember(Name = "message", IsRequired = false)] + [JsonProperty("message")] public readonly string Message; - [DataMember(Name = "parts", IsRequired = false)] + [JsonProperty("parts")] public readonly int? Parts; - [DataMember(Name = "list", IsRequired = false)] + [JsonProperty("fallbacks")] public Dictionary? Fallbacks = default; + + [JsonProperty("list")] private List list; + + [DataContract] + public class Fallback : Countable + { + [JsonProperty("list")] + public List List { get; set; } = new List(); + } - private Status() - { } + [DataContract] + public class FallbackItem + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("idx")] + public string Idx { get; set; } // Note: Adjust type if 'idx' can be numeric + + [JsonProperty("date_sent")] + public long DateSent { get; set; } // Ensure the timestamp format is handled correctly + + [JsonProperty("points")] + public double Points { get; set; } + } + public List List { get diff --git a/smsapiTests/Unit/Action/SMS/Fixture/SmsSendResponseMother.cs b/smsapiTests/Unit/Action/SMS/Fixture/SmsSendResponseMother.cs new file mode 100644 index 0000000..2f37f12 --- /dev/null +++ b/smsapiTests/Unit/Action/SMS/Fixture/SmsSendResponseMother.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace smsapiTests.Unit.SMS.Fixture; + +public static class SmsSendResponseMother +{ + public static Dictionary VmsFallback( + string id, + string idx, + long dateSent, + double points + ) + { + return new Dictionary + { + { "count", 0 }, + { "list", new List() }, + { + "fallbacks", new Dictionary + { + { + "vms", new Dictionary + { + { "count", 1 }, + { + "list", new List + { + new Dictionary + { + { "id", id }, + { "idx", idx }, + { "date_sent", dateSent }, + { "points", points } + } + } + } + } + } + } + } + }; + } +} diff --git a/smsapiTests/Unit/Action/SMS/SMSFallbackResponseTest.cs b/smsapiTests/Unit/Action/SMS/SMSFallbackResponseTest.cs new file mode 100644 index 0000000..d94e945 --- /dev/null +++ b/smsapiTests/Unit/Action/SMS/SMSFallbackResponseTest.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; +using smsapiTests.Unit.SMS.Fixture; + +namespace smsapiTests.Unit.SMS; + +[TestClass] +public class SMSFallbackResponseTest : UnitTestBase +{ + private readonly ProxyStub _proxyStub; + + public SMSFallbackResponseTest() + { + _proxyStub = new ProxyStub(); + } + + [TestMethod] + public void see_vms_fallback_requested() + { + var id = "1238f47da26ee45dc41fb987"; + var idx = "any idx"; + var dateSent = DateTime.Now.Ticks; + var points = 2.01d; + var response = SmsSendResponseMother.VmsFallback( + id, + idx, + dateSent, + points + ); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = CreateAction() + .WithFallback(SMSSend.SmsFallbacks.Vms) + .Execute(); + + Assert.AreEqual(0, result.Count); + Assert.AreEqual(0, result.List.Capacity); + Assert.AreEqual(1, result.Fallbacks?.Count); + Assert.AreEqual("vms", result.Fallbacks!.First().Key); + + var vmsFallbacks = result.Fallbacks!.First().Value; + Assert.AreEqual(1, vmsFallbacks.Count); + Assert.AreEqual(id, vmsFallbacks.List.First().Id); + Assert.AreEqual(idx, vmsFallbacks.List.First().Idx); + Assert.AreEqual(points, vmsFallbacks.List.First().Points); + Assert.AreEqual(dateSent, vmsFallbacks.List.First().DateSent); + } + + protected override SMSSend CreateAction() + { + var action = base.CreateAction(); + action.Proxy(_proxyStub); + AddNecessaryParameters(action); + + return action; + } + + private static void AddNecessaryParameters(SMSSend action) + { + action.SetText("any"); + action.SetTo("any"); + } +} diff --git a/smsapiTests/Unit/Action/SMS/SMSFallbackTest.cs b/smsapiTests/Unit/Action/SMS/SMSFallbackTest.cs new file mode 100644 index 0000000..1276395 --- /dev/null +++ b/smsapiTests/Unit/Action/SMS/SMSFallbackTest.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action; + +namespace smsapiTests.Unit.SMS; + +[TestClass] +public class SMSFallbackTest : UnitTestBase +{ + private readonly ProxyAssert _proxyAssert; + + public SMSFallbackTest() + { + _proxyAssert = new ProxyAssert(SpyProxy); + } + + [TestMethod] + public void see_vms_fallback_requested() + { + CreateAction() + .WithFallback(SMSSend.SmsFallbacks.Vms) + .Execute(); + + var expectedFallback = new HashSet> + { + new() { { "type", "vms" } } + }; + _proxyAssert.AssertParametersContain("fallback", expectedFallback); + } + + protected override SMSSend CreateAction() + { + var action = base.CreateAction(); + AddNecessaryParameters(action); + + return action; + } + + private static void AddNecessaryParameters(SMSSend action) + { + action.SetText("any"); + action.SetTo("any"); + } +} diff --git a/smsapiTests/Unit/SMS/SMSSendTest.cs b/smsapiTests/Unit/Action/SMS/SMSSendTest.cs similarity index 100% rename from smsapiTests/Unit/SMS/SMSSendTest.cs rename to smsapiTests/Unit/Action/SMS/SMSSendTest.cs diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 1690f40..57920ca 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api; @@ -26,7 +27,7 @@ public void AssertNoParameters() public void AssertParametersContain(string name, string value) { - var expectedParameter = new KeyValuePair(name, value); + var expectedParameter = new KeyValuePair(name, value); Assert.IsTrue( proxy.Parameters.Contains(value: expectedParameter), @@ -34,6 +35,18 @@ public void AssertParametersContain(string name, string value) ); } + public void AssertParametersContain(string name, dynamic value) + { + Assert.IsTrue( + proxy.Parameters.ContainsKey(name), + $"Key not found in sent parameters: {name}" + ); + Assert.AreEqual( + JsonSerializer.Serialize(value), + JsonSerializer.Serialize(proxy.Parameters[name]) + ); + } + public void AssertParametersDoesNotContain(string name) { Assert.IsFalse( diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index 7c99f80..0f1fa30 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -16,7 +16,7 @@ public class SpyProxy : Proxy public RequestMethod RequestMethod { get; private set; } - public Dictionary Parameters { get; } = new(); + public Dictionary Parameters { get; } = new(); public void Authentication(IClient client) { From ab54d541e0df5a8ae8a881b030e6919c3c9b94c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 9 Dec 2024 19:59:17 +0000 Subject: [PATCH 060/142] Change deserialization process --- .../Deserialization/BaseJsonDeserializer.cs | 3 +- .../LegacyJsonResponseDeserializer.cs | 7 +-- .../PrivateFieldsContractResolver.cs | 52 +++++++++++++------ .../ValidationErrorsResolver.cs | 29 ++++++----- .../LegacyResponseDeserializationTest.cs | 7 ++- .../RestJsonResponseDeserializerTest.cs | 5 +- 6 files changed, 60 insertions(+), 43 deletions(-) diff --git a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs index 65173d1..58e452b 100644 --- a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs @@ -13,13 +13,14 @@ public DeserializationResult Deserialize(HttpResponseEntity responseEntity if (data.Length > 0) { + data.Position = 0; var stringData = new StreamReader(data).ReadToEnd(); result = JsonConvert.DeserializeObject( stringData, new JsonSerializerSettings { - ContractResolver = new PrivateFieldsContractResolver(), + ContractResolver = new PrivateFieldsContractResolver() }); } else diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index ad173ea..ee99e33 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -23,16 +23,13 @@ public DeserializationResult Deserialize(HttpResponseEntity responseEntity errorDeserializationResult.ThrowErrors(); data = responseEntity.Content.Result; + response = _baseJsonDeserializer.Deserialize(responseEntity); } catch (SerializationException e) { throw new HostException(e.Message, HostException.E_JSON_DECODE); } - catch (Exception e) - { - throw e; - } finally { data?.Close(); @@ -46,7 +43,7 @@ private void HandleError(HttpResponseEntity responseEntity, DeserializationRe try { var error = _baseJsonDeserializer.Deserialize(responseEntity).Result; - + if (!error!.IsError()) return; if (IsHostError(error.ErrorCode)) diff --git a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs index 59043be..b713bd3 100644 --- a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs +++ b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs @@ -11,31 +11,40 @@ internal class PrivateFieldsContractResolver : DefaultContractResolver { protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { - var jsonProperties = base.CreateProperties(type, memberSerialization); + var jsonProperties = base.CreateProperties(type, memberSerialization) + .GroupBy(property => property.UnderlyingName, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .ToHashSet(); - var readonlyFields = GetPublicReadonlyFields(type); - foreach (var field in readonlyFields) - if (jsonProperties.All(p => p.PropertyName != field.Name)) - jsonProperties.Add(CreateProperty(field, memberSerialization)); + AddReadonlyMembers(type, memberSerialization, jsonProperties); - return jsonProperties; + return jsonProperties.ToList(); + } + + private void AddReadonlyMembers(Type type, MemberSerialization memberSerialization, + HashSet jsonProperties) + { + IList readonlyProperties = new List(); + + foreach (var field in GetPublicReadonlyFields(type)) + readonlyProperties.Add(CreateProperty(field, memberSerialization)); + + foreach (var property in GetReadonlyProperties(type)) + readonlyProperties.Add(CreateProperty(property, memberSerialization)); + + jsonProperties.RemoveWhere(property => readonlyProperties.Contains(property)); } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var jsonProperty = base.CreateProperty(member, memberSerialization); - switch (member) + jsonProperty.Writable = member switch { - case PropertyInfo propertyInfo: - { - if (HasPrivateSetter(propertyInfo)) jsonProperty.Writable = true; - break; - } - case FieldInfo { IsInitOnly: true }: - jsonProperty.Writable = true; - break; - } + PropertyInfo propertyInfo when HasPrivateSetter(propertyInfo) => true, + FieldInfo { IsInitOnly: true } => true, + _ => jsonProperty.Writable + }; return jsonProperty; } @@ -46,10 +55,21 @@ private static IEnumerable GetPublicReadonlyFields(Type type) .Where(field => field.IsInitOnly); } + private static IEnumerable GetReadonlyProperties(Type type) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(IsInitOnly); + } + private static bool HasPrivateSetter(PropertyInfo propertyInfo) { var setMethod = propertyInfo.GetSetMethod(true); + return setMethod != null && !setMethod.IsPublic; + } + private static bool IsInitOnly(PropertyInfo propertyInfo) + { + var setMethod = propertyInfo.GetSetMethod(true); return setMethod != null && !setMethod.IsPublic; } } diff --git a/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs b/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs index 46fce90..43ab939 100644 --- a/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs +++ b/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Net; -using System.Runtime.Serialization; +using Newtonsoft.Json; using System.Threading.Tasks; using SMSApi.Api.Response.ResponseResolver; using smsapi.Api.Response.REST.Exception; @@ -11,11 +11,11 @@ namespace SMSApi.Api.Response.Deserialization; public class ValidationErrorsResolver : IResponseCodeAwareResolver { - private readonly BaseJsonDeserializer baseJsonDeserializer; + private readonly BaseJsonDeserializer _baseJsonDeserializer; public ValidationErrorsResolver(BaseJsonDeserializer baseJsonDeserializer) { - this.baseJsonDeserializer = baseJsonDeserializer; + _baseJsonDeserializer = baseJsonDeserializer; } public Dictionary> HandleExceptionActions() @@ -28,24 +28,25 @@ public Dictionary> HandleExceptionActions() private void ResolveErrors(Stream stream) { - var validationErrors = baseJsonDeserializer.Deserialize( + var validationErrors = _baseJsonDeserializer.Deserialize( new HttpResponseEntity(Task.FromResult(stream), HttpStatusCode.BadRequest) ).Result; throw ValidationException.Create(validationErrors); } - - [DataContract] - public readonly struct ValidationErrors + + public sealed class ValidationErrors { - [DataMember(Name = "errors")] public readonly IEnumerable Errors; + [JsonProperty("errors")] + public readonly IEnumerable Errors; } - - [DataContract] - public readonly struct ValidationError + + public sealed class ValidationError { - [DataMember(Name = "message")] public readonly string Message; - - [DataMember(Name = "error")] public readonly string Error; + [JsonProperty("message")] + public readonly string Message; + + [JsonProperty("error")] + public readonly string Error; } } diff --git a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs index e844bd1..3bdc338 100644 --- a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs @@ -41,10 +41,9 @@ protected override string Uri() return ""; } } - - [DataContract] + private class BaseResponse : ErrorAwareResponse { - [DataMember] public string TestProperty; + public string TestProperty { get; set; } } -} \ No newline at end of file +} diff --git a/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs index a6a7964..f617b18 100644 --- a/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs @@ -64,11 +64,10 @@ protected override string Uri() return ""; } } - - [DataContract] + private class ResponseWithExceptionMapper : IResponseCodeAwareResolver { - [DataMember] public string TestProperty; + public string TestProperty { get; private set; } public Dictionary> HandleExceptionActions() { From 14e895c48d9eb4731ea6e2653be7711a462f5b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 9 Dec 2024 20:00:30 +0000 Subject: [PATCH 061/142] Change deserialization process --- .../Api/Response/Deserialization/ValidationErrorsResolver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs b/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs index 43ab939..febc2fc 100644 --- a/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs +++ b/smsapi/Api/Response/Deserialization/ValidationErrorsResolver.cs @@ -35,13 +35,13 @@ private void ResolveErrors(Stream stream) throw ValidationException.Create(validationErrors); } - public sealed class ValidationErrors + public readonly record struct ValidationErrors { [JsonProperty("errors")] public readonly IEnumerable Errors; } - public sealed class ValidationError + public readonly record struct ValidationError { [JsonProperty("message")] public readonly string Message; From 1e9655c10ea1ae9d0dd2fadce7d60933d5f2ce06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 10 Dec 2024 10:52:54 +0000 Subject: [PATCH 062/142] Allow deleting many scheduled SMSes at once --- smsapi/Api/Action/SMS/Delete.cs | 56 ++++++++++------ smsapi/Api/Response/Countable.cs | 17 +++-- smsapi/Api/SMSFactory.cs | 8 +-- .../Unit/Action/SMS/DeleteScheduledSmsTest.cs | 64 +++++++++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 5 +- 5 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 smsapiTests/Unit/Action/SMS/DeleteScheduledSmsTest.cs diff --git a/smsapi/Api/Action/SMS/Delete.cs b/smsapi/Api/Action/SMS/Delete.cs index a2ac764..9dbd30a 100644 --- a/smsapi/Api/Action/SMS/Delete.cs +++ b/smsapi/Api/Action/SMS/Delete.cs @@ -1,32 +1,48 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using SMSApi.Api.Response; -namespace SMSApi.Api.Action +namespace SMSApi.Api.Action; + +public sealed class SMSDelete : Action { - public class SMSDelete : Action + private string[] _ids; + + public SMSDelete(params string[] id) { - private string id; + _ids = id; + } - protected override RequestMethod Method => RequestMethod.POST; + protected override RequestMethod Method => RequestMethod.POST; - public SMSDelete Id(string id) - { - this.id = id; - return this; - } + [Obsolete($"Use {nameof(SMSDelete)} instead")] + public SMSDelete Id(string id) + { + _ids = new[] { id }; - protected override string Uri() - { - return "sms.do"; - } + return this; + } - protected override (NameValueCollection, ISet>?) Values() + [Obsolete($"Use {nameof(SMSDelete)} instead")] + public SMSDelete Id(string[] ids) + { + _ids = ids; + + return this; + } + + protected override string Uri() + { + return "sms.do"; + } + + protected override (NameValueCollection, ISet>?) Values() + { + return (new NameValueCollection { - return (new NameValueCollection - { - { "sch_del", id } - }, default); - } + { "sch_del", string.Join(",", _ids.ToHashSet()) } + }, default); } } diff --git a/smsapi/Api/Response/Countable.cs b/smsapi/Api/Response/Countable.cs index d352b29..004a13b 100644 --- a/smsapi/Api/Response/Countable.cs +++ b/smsapi/Api/Response/Countable.cs @@ -1,22 +1,25 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class Countable { - private int count; + private int _count; + + public Countable() + { + } protected Countable(int count = 0) { - this.count = count; + this._count = count; } - [DataMember(Name = "count", IsRequired = false)] + [JsonProperty("count")] public virtual int Count { - get => count; - private set => count = value; + get => _count; + set => _count = value; } } } diff --git a/smsapi/Api/SMSFactory.cs b/smsapi/Api/SMSFactory.cs index 0dfbba3..ab62a8a 100644 --- a/smsapi/Api/SMSFactory.cs +++ b/smsapi/Api/SMSFactory.cs @@ -17,11 +17,11 @@ public SMSFactory(IClient client, Proxy proxy) : base(client, proxy) { } - public SMSDelete ActionDelete(string id = null) + public SMSDelete ActionDelete(params string[] id) { - var action = new SMSDelete(); + var action = new SMSDelete(id); action.Proxy(proxy); - action.Id(id); + return action; } @@ -64,4 +64,4 @@ public static SMSFactory SMS(this Features features) { return new SMSFactory(features.Client, features.Proxy); } -} \ No newline at end of file +} diff --git a/smsapiTests/Unit/Action/SMS/DeleteScheduledSmsTest.cs b/smsapiTests/Unit/Action/SMS/DeleteScheduledSmsTest.cs new file mode 100644 index 0000000..0e5098d --- /dev/null +++ b/smsapiTests/Unit/Action/SMS/DeleteScheduledSmsTest.cs @@ -0,0 +1,64 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api.Action; + +namespace smsapiTests.Unit.SMS; + +[TestClass] +public class DeleteScheduledSmsTest +{ + private readonly ProxyAssert _proxyAssert; + private readonly SpyProxy _spyProxy = new(); + + public DeleteScheduledSmsTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void see_correct_uri() + { + CreateAction("any").Execute(); + + _proxyAssert.AssertUriEquals("sms.do"); + } + + [TestMethod] + public void delete_one_message() + { + var id = "anyId"; + + CreateAction(id).Execute(); + + _proxyAssert.AssertParametersContain("sch_del", id); + } + + [TestMethod] + public void delete_few_message() + { + string[] ids = { "first", "second" }; + + CreateAction(ids).Execute(); + + var expectedParams = $"{ids[0]},{ids[1]}"; + _proxyAssert.AssertParametersContain("sch_del", expectedParams); + } + + [TestMethod] + public void skip_duplicates() + { + string[] ids = { "duplicate", "duplicate" }; + + CreateAction(ids).Execute(); + + var expectedParams = "duplicate"; + _proxyAssert.AssertParametersContain("sch_del", expectedParams); + } + + private SMSDelete CreateAction(params string[] id) + { + var action = new SMSDelete(id); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 57920ca..f0e58b0 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -15,7 +15,10 @@ public void AssertRequestMethod(RequestMethod requestMethod) public void AssertUriEquals(string uri) { - Assert.IsTrue(proxy.RequestedUri.Equals(uri)); + Assert.IsTrue( + proxy.RequestedUri.Equals(uri), + $"expected: {uri}, got: {proxy.RequestedUri}" + ); } public void AssertNoParameters() From b1fe9d90f3fae41a8bd97fd5d555e2acd79a413c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 10 Dec 2024 13:28:55 +0000 Subject: [PATCH 063/142] Change default naming strategy to snake case --- smsapi/Api/Response/BasicCollection.cs | 18 +++++++++--------- smsapi/Api/Response/Countable.cs | 3 ++- .../PrivateFieldsContractResolver.cs | 5 +++++ .../LegacyResponseDeserializationTest.cs | 2 +- .../RestJsonResponseDeserializerTest.cs | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/smsapi/Api/Response/BasicCollection.cs b/smsapi/Api/Response/BasicCollection.cs index be17713..c90a3ea 100644 --- a/smsapi/Api/Response/BasicCollection.cs +++ b/smsapi/Api/Response/BasicCollection.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using Newtonsoft.Json; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response { - [DataContract] public class BasicCollection : Countable, IResponseCodeAwareResolver { - [DataMember(Name = "collection", IsRequired = false)] protected List collection; - - [DataMember(Name = "size", IsRequired = false)] - protected int size; + + [JsonProperty("size")] + private int _size; public List Collection { @@ -31,26 +29,28 @@ public List Collection } [Obsolete("use Size instead")] + [JsonIgnore] public override int Count => Size; [Obsolete("use Collection instead")] - [DataMember(Name = "list", IsRequired = false)] + [JsonProperty("list")] public List List { get => Collection; protected set => collection = value; } + [JsonIgnore] public int Size { get { - if (size == 0) + if (_size == 0) { return base.Count; } - return size; + return _size; } } } diff --git a/smsapi/Api/Response/Countable.cs b/smsapi/Api/Response/Countable.cs index 004a13b..45ad967 100644 --- a/smsapi/Api/Response/Countable.cs +++ b/smsapi/Api/Response/Countable.cs @@ -4,6 +4,7 @@ namespace SMSApi.Api.Response { public class Countable { + [JsonIgnore] private int _count; public Countable() @@ -12,7 +13,7 @@ public Countable() protected Countable(int count = 0) { - this._count = count; + _count = count; } [JsonProperty("count")] diff --git a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs index b713bd3..c482e4c 100644 --- a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs +++ b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs @@ -9,6 +9,11 @@ namespace SMSApi.Api.Response.Deserialization; internal class PrivateFieldsContractResolver : DefaultContractResolver { + public PrivateFieldsContractResolver() + { + NamingStrategy = new SnakeCaseNamingStrategy(); + } + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { var jsonProperties = base.CreateProperties(type, memberSerialization) diff --git a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs index 3bdc338..e5d26c6 100644 --- a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs @@ -21,7 +21,7 @@ public void map_response_to_object() var action = new TestAction(); action.Proxy(_proxyStub); var testValue = "test value"; - Dictionary errorResponse = new() { { "TestProperty", testValue } }; + Dictionary errorResponse = new() { { "test_property", testValue } }; _proxyStub.SyncExecutionResponse = new HttpResponseEntity( errorResponse.ToHttpEntityStreamTask(), HttpStatusCode.OK diff --git a/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs index f617b18..f7b842d 100644 --- a/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/RestJsonResponseDeserializerTest.cs @@ -39,7 +39,7 @@ public void deserialize_to_object_when_no_exception_mapper_found() { var action = new TestAction(); action.Proxy(_proxyStub); - Dictionary response = new() { { "TestProperty", "abc" } }; + Dictionary response = new() { { "test_property", "abc" } }; _proxyStub.SyncExecutionResponse = new HttpResponseEntity( response.ToHttpEntityStreamTask(), HttpStatusCode.OK From 4968fa16d4b093adcb613d603b6cf3096e26c298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 10 Dec 2024 16:31:26 +0000 Subject: [PATCH 064/142] Add ShortUrl feature #list --- smsapi/Api/Action/ShortUrl/ShortUrlList.cs | 22 +++++ smsapi/Api/Response/ShortUrl/ShortLink.cs | 31 +++++++ smsapi/Api/ShortUrlFactory.cs | 37 ++++++++ .../ShortUrl/ShortUrlListResponseTest.cs | 88 +++++++++++++++++++ .../Unit/Action/ShortUrl/ShortUrlListTest.cs | 41 +++++++++ 5 files changed, 219 insertions(+) create mode 100644 smsapi/Api/Action/ShortUrl/ShortUrlList.cs create mode 100644 smsapi/Api/Response/ShortUrl/ShortLink.cs create mode 100644 smsapi/Api/ShortUrlFactory.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ShortUrlListTest.cs diff --git a/smsapi/Api/Action/ShortUrl/ShortUrlList.cs b/smsapi/Api/Action/ShortUrl/ShortUrlList.cs new file mode 100644 index 0000000..bcc7037 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/ShortUrlList.cs @@ -0,0 +1,22 @@ +using SMSApi.Api.Response; +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class ShortUrlList : Action>, IPaginable +{ + protected override RequestMethod Method => RequestMethod.GET; + + public uint? Limit { get; set; } + public uint? Offset { get; set; } + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return "short_url/links"; + } +} diff --git a/smsapi/Api/Response/ShortUrl/ShortLink.cs b/smsapi/Api/Response/ShortUrl/ShortLink.cs new file mode 100644 index 0000000..b82776e --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/ShortLink.cs @@ -0,0 +1,31 @@ +using System; +using Newtonsoft.Json; + +namespace SMSApi.Api.Response.ShortUrl; + +public readonly record struct ShortLink +{ + public readonly string Id; + + public readonly string Name; + + public readonly string Url; + + public readonly string ShortUrl; + + + [JsonProperty("filename")] + public readonly string? FileName; + + public readonly string Type; + + [JsonProperty("expire")] + public readonly DateTime ExpireAt; + + public readonly int Hits; + + [JsonProperty("hits_unique")] + public readonly int UniqueHits; + + public readonly string Description; +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs new file mode 100644 index 0000000..c6a28f3 --- /dev/null +++ b/smsapi/Api/ShortUrlFactory.cs @@ -0,0 +1,37 @@ +using SMSApi.Api.Action.ShortUrl; + +namespace SMSApi.Api; + +public class ShortUrlFactory : Factory +{ + public ShortUrlFactory(ProxyAddress address = ProxyAddress.SmsApiIo) + : base(address) + { + } + + public ShortUrlFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) + : base(client, address) + { + } + + public ShortUrlFactory(IClient client, Proxy proxy) + : base(client, proxy) + { + } + + public ShortUrlList List() + { + var action = new ShortUrlList(); + action.Proxy(proxy); + + return action; + } +} + +public static class ShortUrlFeatureRegister +{ + public static ShortUrlFactory ShortUrl(this Features features) + { + return new ShortUrlFactory(features.Client, features.Proxy); + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs new file mode 100644 index 0000000..e91f492 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ShortUrlListResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = CollectionMother.Empty(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_opt_outs() + { + var id = "655B26893332330011B0B297"; + var name = "short link"; + var url = "https://example.com"; + var shortUrl = "https://example.com"; + object? filename; + filename = null; + var type = "link"; + var expirationDate = "2024-11-26T14:20:53+01:00"; + var hits = 2; + var uniqueHits = 1; + var description = "fancy link"; + var response = CollectionMother.WithItems( + new Dictionary + { + { "id", id }, + { "name", name }, + { "url", url }, + { "short_url", shortUrl }, + { "filename", filename }, + { "type", type }, + { "expire", expirationDate }, + { "hits", hits }, + { "hits_unique", uniqueHits }, + { "description", description } + }); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(1, result.Size); + var firstElement = result.Collection.First(); + Assert.AreEqual(id, firstElement.Id); + Assert.AreEqual(name, firstElement.Name); + Assert.AreEqual(url, firstElement.Url); + Assert.AreEqual(shortUrl, firstElement.ShortUrl); + Assert.AreEqual(filename, firstElement.FileName); + Assert.AreEqual(type, firstElement.Type); + Assert.AreEqual(DateTime.Parse(expirationDate), firstElement.ExpireAt); + Assert.AreEqual(hits, firstElement.Hits); + Assert.AreEqual(uniqueHits, firstElement.UniqueHits); + Assert.AreEqual(description, firstElement.Description); + } + + private ShortUrlList GetList() + { + var action = new ShortUrlList(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/ShortUrlListTest.cs b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListTest.cs new file mode 100644 index 0000000..19667f1 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListTest.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ShortUrlListTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public ShortUrlListTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateShortUrlList().Execute(); + + _proxyAssert.AssertUriEquals("short_url/links"); + } + + [TestMethod] + public void valid_method() + { + CreateShortUrlList().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + private ShortUrlList CreateShortUrlList() + { + var action = new ShortUrlList(); + action.Proxy(_spyProxy); + + return action; + } +} From b79f705ccc5005cbb88ccdf65f11c01183503f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 09:59:20 +0000 Subject: [PATCH 065/142] Add ShortUrl feature #create --- smsapi/Api/Action/ShortUrl/CreateShortUrl.cs | 97 +++++++++++++++ .../ShortUrlWithNameAlreadyExistsException.cs | 8 ++ smsapi/Api/Response/ShortUrl/ShortLink.cs | 14 ++- smsapi/Api/ShortUrlFactory.cs | 19 ++- .../Action/ShortUrl/CreateShortUrlTest.cs | 113 ++++++++++++++++++ .../CreateShortUrlWithUrlResponseTest.cs | 85 +++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 17 +++ smsapiTests/Unit/SpyProxy.cs | 7 ++ 8 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 smsapi/Api/Action/ShortUrl/CreateShortUrl.cs create mode 100644 smsapi/Api/Response/ShortUrl/Exception/ShortUrlWithNameAlreadyExistsException.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs diff --git a/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs new file mode 100644 index 0000000..edbec42 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Runtime.Serialization; +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class CreateShortUrl : Action +{ + public enum ShortUrlExpirationUnit + { + [EnumMember(Value = "seconds")] Seconds, + + [EnumMember(Value = "minutes")] Minutes, + + [EnumMember(Value = "hours")] Hours, + + [EnumMember(Value = "days")] Days + } + + private string? _description; + private (uint, string)? _expireAt; + private readonly Stream? _file; + + private readonly string _name; + private readonly string? _url; + + public CreateShortUrl(string name, string url) + { + _name = name; + _url = url; + } + + public CreateShortUrl(string name, Stream file) + { + _name = name; + _file = file; + } + + protected override RequestMethod Method => RequestMethod.POST; + + public CreateShortUrl WithExpiration(uint expireIn, ShortUrlExpirationUnit expirationUnit) + { + _expireAt = (expireIn, expirationUnit.GetEnumValue()); + + return this; + } + + public CreateShortUrl WithDescription(string description) + { + _description = description; + + return this; + } + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return "short_url/links"; + } + + protected override (NameValueCollection, ISet>?) Values() + { + var body = new HashSet> + { + KeyValuePair.Create("name", _name) + }; + + _url?.Let(url => body.Add(("url", url))); + + _description?.Let(description => body.Add(("description", description))); + + _expireAt?.Let(expiration => + { + body.Add( + ("expire_time", expiration.Item1), + ("expire_unit", expiration.Item2) + ); + }); + + return (new NameValueCollection(), body); + } + + protected override Dictionary Files() + { + var files = new Dictionary(); + + _file?.Let(file => files.Add("file", file)); + + return files; + } +} diff --git a/smsapi/Api/Response/ShortUrl/Exception/ShortUrlWithNameAlreadyExistsException.cs b/smsapi/Api/Response/ShortUrl/Exception/ShortUrlWithNameAlreadyExistsException.cs new file mode 100644 index 0000000..69b7279 --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/Exception/ShortUrlWithNameAlreadyExistsException.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Response.ShortUrl.Exception; + +public class ShortUrlWithNameAlreadyExistsException : ClientException +{ + public ShortUrlWithNameAlreadyExistsException() : base("Short url with name already exists", 409) + { + } +} diff --git a/smsapi/Api/Response/ShortUrl/ShortLink.cs b/smsapi/Api/Response/ShortUrl/ShortLink.cs index b82776e..d75ef0b 100644 --- a/smsapi/Api/Response/ShortUrl/ShortLink.cs +++ b/smsapi/Api/Response/ShortUrl/ShortLink.cs @@ -1,10 +1,22 @@ using System; +using System.Collections.Generic; +using System.IO; using Newtonsoft.Json; +using SMSApi.Api.Response.ResponseResolver; +using SMSApi.Api.Response.ShortUrl.Exception; namespace SMSApi.Api.Response.ShortUrl; -public readonly record struct ShortLink +public readonly record struct ShortLink: IResponseCodeAwareResolver { + public Dictionary> HandleExceptionActions() + { + return new() + { + { 409, _ => throw new ShortUrlWithNameAlreadyExistsException() }, + }; + } + public readonly string Id; public readonly string Name; diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index c6a28f3..e68f59a 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -1,4 +1,5 @@ -using SMSApi.Api.Action.ShortUrl; +using System.IO; +using SMSApi.Api.Action.ShortUrl; namespace SMSApi.Api; @@ -26,6 +27,22 @@ public ShortUrlList List() return action; } + + public CreateShortUrl Create(string name, string uri) + { + var action = new CreateShortUrl(name, uri); + action.Proxy(proxy); + + return action; + } + + public CreateShortUrl Create(string name, Stream file) + { + var action = new CreateShortUrl(name, file); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs new file mode 100644 index 0000000..19a23be --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs @@ -0,0 +1,113 @@ +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class CreateShortUrlTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public CreateShortUrlTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateShortUrl().Execute(); + + _proxyAssert.AssertUriEquals("short_url/links"); + } + + [TestMethod] + public void send_post_request() + { + CreateShortUrl().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.POST); + } + + [TestMethod] + public void send_name_and_url() + { + var name = "any name"; + var url = "http://example.com"; + + CreateShortUrl(name, url).Execute(); + + _proxyAssert.AssertParametersCount(2); + _proxyAssert.AssertParametersContain("name", name); + _proxyAssert.AssertParametersContain("url", url); + } + + [TestMethod] + public void send_description() + { + var description = "any description"; + + CreateShortUrl() + .WithDescription(description) + .Execute(); + + _proxyAssert.AssertParametersCount(3);//obligatory name and url + _proxyAssert.AssertParametersContain("description", description); + } + + [TestMethod] + public void send_name_and_file() + { + var name = "fancy name"; + var file = new MemoryStream(); + + CreateShortUrl(name, file).Execute(); + + _proxyAssert.AssertParametersCount(1); + _proxyAssert.AssertParametersContain("name", name); + _proxyAssert.AssertFileAttached(file); + } + + [TestMethod] + [DataRow(1, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Days, "days")] + [DataRow(2, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Hours, "hours")] + [DataRow(300, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Minutes, "minutes")] + [DataRow(60000, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Seconds, "seconds")] + public void send_expiration(int expirationTime, CreateShortUrl.ShortUrlExpirationUnit expirationUnit, string expectedExpirationUnit) + { + CreateShortUrl("any name", "http://example.com") + .WithExpiration((uint)expirationTime, expirationUnit) + .Execute(); + + _proxyAssert.AssertParametersCount(4);//obligatory name and url + _proxyAssert.AssertParametersContain("expire_time", expirationTime); + _proxyAssert.AssertParametersContain("expire_unit", expectedExpirationUnit); + } + + private CreateShortUrl CreateShortUrl(string name, string uri) + { + var action = new CreateShortUrl(name, uri); + action.Proxy(_spyProxy); + + return action; + } + + private CreateShortUrl CreateShortUrl(string name, Stream file) + { + var action = new CreateShortUrl(name, file); + action.Proxy(_spyProxy); + + return action; + } + + private CreateShortUrl CreateShortUrl() + { + var action = new CreateShortUrl("any", "any"); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs new file mode 100644 index 0000000..0948b61 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using SMSApi.Api.Response.ShortUrl.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class CreateShortUrlWithUrlResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void create_opt_out() + { + var id = "655B26893332330011B0B297"; + var name = "short link"; + var url = "https://example.com"; + var shortUrl = "https://example.com"; + object? filename; + filename = null; + var type = "link"; + var expirationDate = "2024-11-26T14:20:53+01:00"; + var hits = 0; + var uniqueHits = 0; + var description = "fancy link"; + var response = + new Dictionary + { + { "id", id }, + { "name", name }, + { "url", url }, + { "short_url", shortUrl }, + { "filename", filename }, + { "type", type }, + { "expire", expirationDate }, + { "hits", hits }, + { "hits_unique", uniqueHits }, + { "description", description } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = CreateShortUrl(name, url).Execute(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(name, result.Name); + Assert.AreEqual(url, result.Url); + Assert.AreEqual(shortUrl, result.ShortUrl); + Assert.AreEqual(filename, result.FileName); + Assert.AreEqual(type, result.Type); + Assert.AreEqual(DateTime.Parse(expirationDate), result.ExpireAt); + Assert.AreEqual(hits, result.Hits); + Assert.AreEqual(uniqueHits, result.UniqueHits); + Assert.AreEqual(description, result.Description); + } + + [TestMethod] + public void see_conflict_response() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + null, + HttpStatusCode.Conflict + ); + + var action = () => CreateShortUrl("any", "http://any.com").ExecuteAsync(); + + Assert.ThrowsExceptionAsync(action); + } + + private CreateShortUrl CreateShortUrl(string name, string url) + { + var action = new CreateShortUrl(name, url); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index f0e58b0..b6eddfa 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.Json; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -27,6 +28,14 @@ public void AssertNoParameters() Assert.IsTrue(parametersCount == 0, $"Parameters expected to be empty, {parametersCount} found"); } + + public void AssertParametersCount(int expectedCount) + { + Assert.AreEqual( + expectedCount, + proxy.Parameters.Count + ); + } public void AssertParametersContain(string name, string value) { @@ -37,6 +46,14 @@ public void AssertParametersContain(string name, string value) $"Expected {value}, actual value: {proxy.Parameters[name]}" ); } + + public void AssertFileAttached(Stream file) + { + Assert.IsTrue( + proxy.Files.Contains(value: file), + "Not attached file found" + ); + } public void AssertParametersContain(string name, dynamic value) { diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index 0f1fa30..dd02796 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -17,6 +17,7 @@ public class SpyProxy : Proxy public RequestMethod RequestMethod { get; private set; } public Dictionary Parameters { get; } = new(); + public ICollection Files { get; } = new List(); public void Authentication(IClient client) { @@ -28,6 +29,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISe RequestedUri = uri; SetParameters(data); RequestMethod = method; + return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } @@ -37,6 +39,7 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISe RequestedUri = uri; SetParameters(data); RequestMethod = method; + Files.Add(file); return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } @@ -46,6 +49,10 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISe RequestedUri = uri; SetParameters(data); RequestMethod = method; + foreach (var file in files.Values) + { + Files.Add(file); + } return new HttpResponseEntity(Task.FromResult(Stream.Null), HttpStatusCode.OK); } From c75bd348b5684842cb972ca3db8a4fd39983a658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 11:56:32 +0000 Subject: [PATCH 066/142] Add ShortUrl feature #get single --- smsapi/Api/Action/ShortUrl/GetShortUrl.cs | 25 ++++++ smsapi/Api/ShortUrlFactory.cs | 8 ++ .../ShortUrl/GetShortUrlResponseTest.cs | 85 +++++++++++++++++++ .../Unit/Action/ShortUrl/GetShortUrlTest.cs | 43 ++++++++++ .../Unit/Helper/DictionaryToStreamHelper.cs | 2 + 5 files changed, 163 insertions(+) create mode 100644 smsapi/Api/Action/ShortUrl/GetShortUrl.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/GetShortUrlResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/GetShortUrlTest.cs diff --git a/smsapi/Api/Action/ShortUrl/GetShortUrl.cs b/smsapi/Api/Action/ShortUrl/GetShortUrl.cs new file mode 100644 index 0000000..e320591 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/GetShortUrl.cs @@ -0,0 +1,25 @@ +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class GetShortUrl : Action +{ + private readonly string _id; + + public GetShortUrl(string id) + { + _id = id; + } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return $"short_url/links/{_id}"; + } +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index e68f59a..c7023a2 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -43,6 +43,14 @@ public CreateShortUrl Create(string name, Stream file) return action; } + + public GetShortUrl GetShortUrl(string id) + { + var action = new GetShortUrl(id); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/GetShortUrlResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/GetShortUrlResponseTest.cs new file mode 100644 index 0000000..06ca8f8 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/GetShortUrlResponseTest.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapi.Api.Response.REST.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class GetShortUrlResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void get_short_url() + { + var id = "655B26893332330011B0B297"; + var name = "short link"; + var url = "https://example.com"; + var shortUrl = "https://example.com"; + object? filename; + filename = null; + var type = "link"; + var expirationDate = "2024-11-26T14:20:53+01:00"; + var hits = 0; + var uniqueHits = 0; + var description = "fancy link"; + var response = + new Dictionary + { + { "id", id }, + { "name", name }, + { "url", url }, + { "short_url", shortUrl }, + { "filename", filename }, + { "type", type }, + { "expire", expirationDate }, + { "hits", hits }, + { "hits_unique", uniqueHits }, + { "description", description } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetShortUrl().Execute(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(name, result.Name); + Assert.AreEqual(url, result.Url); + Assert.AreEqual(shortUrl, result.ShortUrl); + Assert.AreEqual(filename, result.FileName); + Assert.AreEqual(type, result.Type); + Assert.AreEqual(DateTime.Parse(expirationDate), result.ExpireAt); + Assert.AreEqual(hits, result.Hits); + Assert.AreEqual(uniqueHits, result.UniqueHits); + Assert.AreEqual(description, result.Description); + } + + [TestMethod] + public void map_not_found_status() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NotFound + ); + + var action = () => { _ = GetShortUrl().Execute(); }; + + Assert.ThrowsException(action); + } + + private GetShortUrl GetShortUrl() + { + var action = new GetShortUrl("any id"); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/GetShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/GetShortUrlTest.cs new file mode 100644 index 0000000..b403c1c --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/GetShortUrlTest.cs @@ -0,0 +1,43 @@ +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class GetShortUrlTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public GetShortUrlTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + var id = "1"; + + GetShortUrl(id).Execute(); + + _proxyAssert.AssertUriEquals($"short_url/links/{id}"); + } + + [TestMethod] + public void send_get_request() + { + GetShortUrl("any id").Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + private GetShortUrl GetShortUrl(string id) + { + var action = new GetShortUrl(id); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/Helper/DictionaryToStreamHelper.cs b/smsapiTests/Unit/Helper/DictionaryToStreamHelper.cs index e90d323..2a49e78 100644 --- a/smsapiTests/Unit/Helper/DictionaryToStreamHelper.cs +++ b/smsapiTests/Unit/Helper/DictionaryToStreamHelper.cs @@ -15,4 +15,6 @@ public static Task ToHttpEntityStreamTask(this Dictionary EmptyStream => Task.FromResult(new MemoryStream()); } From 177c3a2633bd5cae8292bfcb4bd01b22fa53c172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 11:59:47 +0000 Subject: [PATCH 067/142] Shorturl #fix tests names --- .../Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs | 4 ++-- smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs index 0948b61..8f64a69 100644 --- a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlWithUrlResponseTest.cs @@ -16,7 +16,7 @@ public class CreateShortUrlWithUrlResponseTest private readonly ProxyStub _proxyStub = new(); [TestMethod] - public void create_opt_out() + public void create_short_url() { var id = "655B26893332330011B0B297"; var name = "short link"; @@ -70,7 +70,7 @@ public void see_conflict_response() HttpStatusCode.Conflict ); - var action = () => CreateShortUrl("any", "http://any.com").ExecuteAsync(); + var action = () => CreateShortUrl("any", "http://example.com").ExecuteAsync(); Assert.ThrowsExceptionAsync(action); } diff --git a/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs index e91f492..b0d68c9 100644 --- a/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs @@ -30,7 +30,7 @@ public void empty_list() } [TestMethod] - public void list_opt_outs() + public void list_short_urls() { var id = "655B26893332330011B0B297"; var name = "short link"; From c923f2a618e123ad049297d7536e4b62b51fa848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 11:59:47 +0000 Subject: [PATCH 068/142] Add ShortUrl feature #update link --- smsapi/Api/Action/ShortUrl/UpdateShortUrl.cs | 66 ++++++++++++++ smsapi/Api/ShortUrlFactory.cs | 8 ++ .../ShortUrl/UpdateShortUrlResponseTest.cs | 85 ++++++++++++++++++ .../Action/ShortUrl/UpdateShortUrlTest.cs | 90 +++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 smsapi/Api/Action/ShortUrl/UpdateShortUrl.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlTest.cs diff --git a/smsapi/Api/Action/ShortUrl/UpdateShortUrl.cs b/smsapi/Api/Action/ShortUrl/UpdateShortUrl.cs new file mode 100644 index 0000000..384b3aa --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/UpdateShortUrl.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class UpdateShortUrl : Action +{ + private readonly string _id; + private string? _url; + private string? _name; + private string? _description; + + public UpdateShortUrl(string id) + { + _id = id; + } + + public UpdateShortUrl ChangeUrl(string url) + { + _url = url; + + return this; + } + + public UpdateShortUrl ChangeName(string name) + { + _name = name; + + return this; + } + + public UpdateShortUrl ChangeDescription(string description) + { + _description = description; + + return this; + } + + protected override RequestMethod Method => RequestMethod.PUT; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return $"short_url/links/{_id}"; + } + + protected override (NameValueCollection, ISet>?) Values() + { + var values = new HashSet>(); + + _url?.Let(url => values.Add(("url", url))); + + _name?.Let(name => values.Add(("name", name))); + + _description?.Let(description => values.Add(("description", description))); + + return (new NameValueCollection(), values); + } +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index c7023a2..68ca843 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -51,6 +51,14 @@ public GetShortUrl GetShortUrl(string id) return action; } + + public UpdateShortUrl UpdateShortUrl(string id) + { + var action = new UpdateShortUrl(id); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlResponseTest.cs new file mode 100644 index 0000000..ebac431 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlResponseTest.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapi.Api.Response.REST.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class UpdateShortUrlResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void update_short_url() + { + var id = "655B26893332330011B0B297"; + var name = "short link"; + var url = "https://example.com"; + var shortUrl = "https://example.com"; + object? filename; + filename = null; + var type = "link"; + var expirationDate = "2024-11-26T14:20:53+01:00"; + var hits = 0; + var uniqueHits = 0; + var description = "fancy link"; + var response = + new Dictionary + { + { "id", id }, + { "name", name }, + { "url", url }, + { "short_url", shortUrl }, + { "filename", filename }, + { "type", type }, + { "expire", expirationDate }, + { "hits", hits }, + { "hits_unique", uniqueHits }, + { "description", description } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = UpdateShortUrl().Execute(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(name, result.Name); + Assert.AreEqual(url, result.Url); + Assert.AreEqual(shortUrl, result.ShortUrl); + Assert.AreEqual(filename, result.FileName); + Assert.AreEqual(type, result.Type); + Assert.AreEqual(DateTime.Parse(expirationDate), result.ExpireAt); + Assert.AreEqual(hits, result.Hits); + Assert.AreEqual(uniqueHits, result.UniqueHits); + Assert.AreEqual(description, result.Description); + } + + [TestMethod] + public void map_not_found_when_updating() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NotFound + ); + + var action = () => { _ = UpdateShortUrl().Execute(); }; + + Assert.ThrowsException(action); + } + + private UpdateShortUrl UpdateShortUrl() + { + var action = new UpdateShortUrl("any id"); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlTest.cs new file mode 100644 index 0000000..bddb9a8 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/UpdateShortUrlTest.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class UpdateShortUrlTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public UpdateShortUrlTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + var id = "1"; + + UpdateShortUrl(id).Execute(); + + _proxyAssert.AssertUriEquals($"short_url/links/{id}"); + } + + [TestMethod] + public void send_put_request() + { + UpdateShortUrl("any id").Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.PUT); + } + + [TestMethod] + public void not_parameters_when_no_changes() + { + UpdateShortUrl("any id").Execute(); + + _proxyAssert.AssertParametersCount(0); + } + + [TestMethod] + public void change_url() + { + var newUrl = "http://example.com"; + + UpdateShortUrl("any id") + .ChangeUrl(newUrl) + .Execute(); + + _proxyAssert.AssertParametersCount(1); + _proxyAssert.AssertParametersContain("url", newUrl); + } + + [TestMethod] + public void change_name() + { + var newName = "newLinkName"; + + UpdateShortUrl("any id") + .ChangeName(newName) + .Execute(); + + _proxyAssert.AssertParametersCount(1); + _proxyAssert.AssertParametersContain("name", newName); + } + + [TestMethod] + public void change_description() + { + var newDescription = "new description"; + + UpdateShortUrl("any id") + .ChangeDescription(newDescription) + .Execute(); + + _proxyAssert.AssertParametersCount(1); + _proxyAssert.AssertParametersContain("description", newDescription); + } + + private UpdateShortUrl UpdateShortUrl(string id) + { + var action = new UpdateShortUrl(id); + action.Proxy(_spyProxy); + + return action; + } +} From da5e4253f09e68f55c883d274237a1216527e5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 12:55:22 +0000 Subject: [PATCH 069/142] Add ShortUrl feature #remove link --- smsapi/Api/Action/ShortUrl/DeleteShortUrl.cs | 25 ++++++++++ .../ShortUrl/ShortLinkRemovalResult.cs | 7 +++ smsapi/Api/ShortUrlFactory.cs | 8 +++ .../ShortUrl/DeleteShortUrlResponseTest.cs | 49 +++++++++++++++++++ .../Action/ShortUrl/DeleteShortUrlTest.cs | 43 ++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 smsapi/Api/Action/ShortUrl/DeleteShortUrl.cs create mode 100644 smsapi/Api/Response/ShortUrl/ShortLinkRemovalResult.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlTest.cs diff --git a/smsapi/Api/Action/ShortUrl/DeleteShortUrl.cs b/smsapi/Api/Action/ShortUrl/DeleteShortUrl.cs new file mode 100644 index 0000000..1e13286 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/DeleteShortUrl.cs @@ -0,0 +1,25 @@ +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class DeleteShortUrl : Action +{ + private readonly string _id; + + public DeleteShortUrl(string id) + { + _id = id; + } + + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return $"short_url/links/{_id}"; + } +} diff --git a/smsapi/Api/Response/ShortUrl/ShortLinkRemovalResult.cs b/smsapi/Api/Response/ShortUrl/ShortLinkRemovalResult.cs new file mode 100644 index 0000000..4e93738 --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/ShortLinkRemovalResult.cs @@ -0,0 +1,7 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.ShortUrl; + +public sealed class ShortLinkRemovalResult : IResponseCodeAwareResolver +{ +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index 68ca843..694cfe8 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -59,6 +59,14 @@ public UpdateShortUrl UpdateShortUrl(string id) return action; } + + public DeleteShortUrl DeleteShortUrl(string id) + { + var action = new DeleteShortUrl(id); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlResponseTest.cs new file mode 100644 index 0000000..cd357cd --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlResponseTest.cs @@ -0,0 +1,49 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapi.Api.Response.REST.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class DeleteShortUrlResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void delete_short_url() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NoContent + ); + + DeleteShortUrl("any id").Execute(); + + Assert.IsTrue(true); + } + + [TestMethod] + public void map_not_found_error_when_delete() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NotFound + ); + + var action = () => { _ = DeleteShortUrl("any id").Execute(); }; + + Assert.ThrowsException(action); + } + + private DeleteShortUrl DeleteShortUrl(string id) + { + var action = new DeleteShortUrl(id); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlTest.cs new file mode 100644 index 0000000..e7f1013 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/DeleteShortUrlTest.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class DeleteShortUrlTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public DeleteShortUrlTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + var id = "1"; + + DeleteShortUrl(id).Execute(); + + _proxyAssert.AssertUriEquals($"short_url/links/{id}"); + } + + [TestMethod] + public void send_delete_request() + { + DeleteShortUrl("any id").Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); + } + + private DeleteShortUrl DeleteShortUrl(string id) + { + var action = new DeleteShortUrl(id); + action.Proxy(_spyProxy); + + return action; + } +} From 12426653d2ba3353769f2e9bca04eaef9c9fd324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 11 Dec 2024 13:03:50 +0000 Subject: [PATCH 070/142] Add http 503 response to known statuses --- smsapi/Api/Action/Action.cs | 3 +- .../Deserialization/HostErrorsResolver.cs | 18 +++++++ .../Exception/ServiceUnavailableException.cs | 10 ++++ .../ServiceUnavailableResponseTest.cs | 50 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 smsapi/Api/Response/Deserialization/HostErrorsResolver.cs create mode 100644 smsapi/Api/Response/REST/Exception/ServiceUnavailableException.cs create mode 100644 smsapiTests/Unit/Response/Deserialization/ServiceUnavailableResponseTest.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 37cfb0d..b06f401 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -57,7 +57,8 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of new ValidationErrorsResolver(new BaseJsonDeserializer()), new TooManyRequestsErrorResolver(), new AccessErrorResolver(), - new NotFoundErrorResolver() + new NotFoundErrorResolver(), + new HostErrorsResolver() ), Action.ApiType.Legacy => new LegacyJsonResponseDeserializer(), _ => throw new Exception("Unknown api type") diff --git a/smsapi/Api/Response/Deserialization/HostErrorsResolver.cs b/smsapi/Api/Response/Deserialization/HostErrorsResolver.cs new file mode 100644 index 0000000..af1206d --- /dev/null +++ b/smsapi/Api/Response/Deserialization/HostErrorsResolver.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SMSApi.Api.Response.ResponseResolver; +using smsapi.Api.Response.REST.Exception; + +namespace SMSApi.Api.Response.Deserialization; + +public class HostErrorsResolver : IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 503, _ => throw new ServiceUnavailableException() }, + }; + } +} diff --git a/smsapi/Api/Response/REST/Exception/ServiceUnavailableException.cs b/smsapi/Api/Response/REST/Exception/ServiceUnavailableException.cs new file mode 100644 index 0000000..9b04acd --- /dev/null +++ b/smsapi/Api/Response/REST/Exception/ServiceUnavailableException.cs @@ -0,0 +1,10 @@ +using SMSApi.Api; + +namespace smsapi.Api.Response.REST.Exception; + +public class ServiceUnavailableException : HostException +{ + public ServiceUnavailableException() : base("Service is temporary unavailable", "503") + { + } +} diff --git a/smsapiTests/Unit/Response/Deserialization/ServiceUnavailableResponseTest.cs b/smsapiTests/Unit/Response/Deserialization/ServiceUnavailableResponseTest.cs new file mode 100644 index 0000000..cb12a5f --- /dev/null +++ b/smsapiTests/Unit/Response/Deserialization/ServiceUnavailableResponseTest.cs @@ -0,0 +1,50 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using SMSApi.Api.Response.ResponseResolver; +using smsapi.Api.Response.REST.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Response.Deserialization; + +[TestClass] +public class ServiceUnavailableResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void map_http_503_to_exception() + { + var action = new TestAction(); + action.Proxy(_proxyStub); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.ServiceUnavailable + ); + + var execution = () => action.Execute(); + + Assert.ThrowsException(execution); + } + + private class TestAction : Action + { + protected override RequestMethod Method { get; } + + protected override ApiType ApiType() + { + return SMSApi.Api.Action.ApiType.Rest; + } + + protected override string Uri() + { + return ""; + } + } + + private class ResponseWithExceptionMapper : IResponseCodeAwareResolver + { + } +} \ No newline at end of file From d00c7c25ffa8bb3a4d7fd361a7bbd5bdf746388a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 13 Dec 2024 10:09:29 +0000 Subject: [PATCH 071/142] Add ShortUrl feature #clicks listing --- .../Api/Action/ShortUrl/ListShortUrlClicks.cs | 47 ++++++++++ .../Api/Response/ShortUrl/ShortLinkClick.cs | 21 +++++ smsapi/Api/ShortUrlFactory.cs | 8 ++ .../ListShortUrlClicksResponseTest.cs | 66 ++++++++++++++ .../Action/ShortUrl/ListShortUrlClicksTest.cs | 89 +++++++++++++++++++ smsapiTests/Unit/ProxyAssert.cs | 8 +- 6 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 smsapi/Api/Action/ShortUrl/ListShortUrlClicks.cs create mode 100644 smsapi/Api/Response/ShortUrl/ShortLinkClick.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksTest.cs diff --git a/smsapi/Api/Action/ShortUrl/ListShortUrlClicks.cs b/smsapi/Api/Action/ShortUrl/ListShortUrlClicks.cs new file mode 100644 index 0000000..d377eaf --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/ListShortUrlClicks.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response; +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class ListShortUrlClicks : Action> +{ + private DateTime? _listFrom; + private DateTime? _listTo; + + protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "short_url/clicks"; + } + + public ListShortUrlClicks ListFrom(DateTime date) + { + _listFrom = date; + + return this; + } + + public ListShortUrlClicks ListTo(DateTime date) + { + _listTo = date; + + return this; + } + + protected override (NameValueCollection, ISet>?) Values() + { + var values = new HashSet>(); + + _listFrom?.Let(from => values.Add(("date_from", from.ToString("yyyy-MM-dd")))); + + _listTo?.Let(to => values.Add(("date_to", to.ToString("yyyy-MM-dd")))); + + return (new NameValueCollection(), values!); + } +} diff --git a/smsapi/Api/Response/ShortUrl/ShortLinkClick.cs b/smsapi/Api/Response/ShortUrl/ShortLinkClick.cs new file mode 100644 index 0000000..9fdd855 --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/ShortLinkClick.cs @@ -0,0 +1,21 @@ +using System; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.ShortUrl; + +public readonly record struct ShortLinkClick : IResponseCodeAwareResolver +{ + public readonly string Browser; + + public readonly DateTime DateHit; + + public readonly string Device; + + public readonly string Name; + + public readonly string Os; + + public readonly string PhoneNumber; + + public readonly string ShortUrl; +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index 694cfe8..cd849c8 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -67,6 +67,14 @@ public DeleteShortUrl DeleteShortUrl(string id) return action; } + + public ListShortUrlClicks ListClicks() + { + var action = new ListShortUrlClicks(); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksResponseTest.cs new file mode 100644 index 0000000..2b1a7fa --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksResponseTest.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ListShortUrlClicksResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void list_clicks() + { + var phoneNumber = "48500100100"; + var hitDate = "2024-11-26T14:20:53+01:00"; + var name = "short link"; + var shortUrl = "https://example.com"; + var os = "Linux"; + var browser = "Firefox 16.1"; + var device = "Mobile device"; + + var response = + new Dictionary + { + { "phone_number", phoneNumber }, + { "date_hit", hitDate }, + { "name", name }, + { "short_url", shortUrl }, + { "os", os }, + { "browser", browser }, + { "device", device } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + CollectionMother.WithItems(response).ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = ListShortUrlClicks().Execute(); + + Assert.AreEqual(1, result.Size); + Assert.AreEqual(1, result.Collection.Count); + var firstClick = result.Collection.First(); + Assert.AreEqual(phoneNumber, firstClick.PhoneNumber); + Assert.AreEqual(DateTime.Parse(hitDate), firstClick.DateHit); + Assert.AreEqual(name, firstClick.Name); + Assert.AreEqual(shortUrl, firstClick.ShortUrl); + Assert.AreEqual(os, firstClick.Os); + Assert.AreEqual(browser, firstClick.Browser); + Assert.AreEqual(device, firstClick.Device); + } + + private ListShortUrlClicks ListShortUrlClicks() + { + var action = new ListShortUrlClicks(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksTest.cs b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksTest.cs new file mode 100644 index 0000000..96746ad --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksTest.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ListShortUrlClicksTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public ListShortUrlClicksTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void valid_uri() + { + CreateListShortUrlClicks().Execute(); + + _proxyAssert.AssertUriEquals("short_url/clicks"); + } + + [TestMethod] + public void get_for_list() + { + CreateListShortUrlClicks().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + [TestMethod] + public void empty_parameters_when_no_date_filtering() + { + CreateListShortUrlClicks().Execute(); + + _proxyAssert.AssertNoParameters(); + } + + [TestMethod] + public void filter_by_from_date() + { + var fromLiteral = "2024-12-13"; + var from = DateTime.Parse(fromLiteral); + CreateListShortUrlClicks() + .ListFrom(from) + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("date_from", fromLiteral); + } + + [TestMethod] + public void filter_by_to_date() + { + var toLiteral = "2024-12-13"; + var to = DateTime.Parse(toLiteral); + CreateListShortUrlClicks() + .ListTo(to) + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("date_to", toLiteral); + } + + [TestMethod] + public void filter_by_from_and_to_date() + { + CreateListShortUrlClicks() + .ListFrom(DateTime.MinValue) + .ListTo(DateTime.MaxValue) + .Execute(); + + _proxyAssert.AssertParametersCount(2); + } + + private ListShortUrlClicks CreateListShortUrlClicks() + { + var action = new ListShortUrlClicks(); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index b6eddfa..2488a40 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -29,15 +29,17 @@ public void AssertNoParameters() Assert.IsTrue(parametersCount == 0, $"Parameters expected to be empty, {parametersCount} found"); } - public void AssertParametersCount(int expectedCount) + public ProxyAssert AssertParametersCount(int expectedCount) { Assert.AreEqual( expectedCount, proxy.Parameters.Count ); + + return this; } - public void AssertParametersContain(string name, string value) + public ProxyAssert AssertParametersContain(string name, string value) { var expectedParameter = new KeyValuePair(name, value); @@ -45,6 +47,8 @@ public void AssertParametersContain(string name, string value) proxy.Parameters.Contains(value: expectedParameter), $"Expected {value}, actual value: {proxy.Parameters[name]}" ); + + return this; } public void AssertFileAttached(Stream file) From f3802cae164eeb548a7071585859ac9429601c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 13 Dec 2024 10:54:29 +0000 Subject: [PATCH 072/142] Serialization changes --- smsapi/Api/Response/Array.cs | 1 - .../Api/Response/Blacklist/BlacklistRecord.cs | 29 +++-------- smsapi/Api/Response/CheckNumber.cs | 3 -- .../Api/Response/Common/Telephony/Country.cs | 7 +-- .../Api/Response/Common/Telephony/Network.cs | 7 +-- smsapi/Api/Response/Contact.cs | 39 +++----------- smsapi/Api/Response/Contacts.cs | 1 - smsapi/Api/Response/Field.cs | 11 ++-- smsapi/Api/Response/FieldOption.cs | 5 -- smsapi/Api/Response/Group.cs | 20 +++---- smsapi/Api/Response/GroupPermission.cs | 5 -- smsapi/Api/Response/HLR/LookupResult.cs | 52 +++++++------------ .../Api/Response/MFA/MFACreationResponse.cs | 10 ++-- smsapi/Api/Response/MessageStatus.cs | 14 +++-- smsapi/Api/Response/NumberStatus.cs | 16 ++---- smsapi/Api/Response/OptOut/OptOut.cs | 19 ++----- smsapi/Api/Response/OptOut/OptOutSettings.cs | 6 +-- .../Api/Response/Ping/PingServiceResponse.cs | 9 ++-- smsapi/Api/Response/Points.cs | 14 ++--- .../Response/Profile/Prices/PriceResponse.cs | 15 +++--- smsapi/Api/Response/Profile/Profile.cs | 23 +++----- .../ResponseResolver/ErrorAwareResponse.cs | 51 ++++++++++-------- smsapi/Api/Response/Sender.cs | 10 ++-- smsapi/Api/Response/Senders.cs | 3 +- .../Api/Response/Subusers/SubuserDetails.cs | 26 +++++----- smsapi/Api/Response/User.cs | 15 +++--- smsapi/OperationsHelper.cs | 25 ++++++++- 27 files changed, 175 insertions(+), 261 deletions(-) diff --git a/smsapi/Api/Response/Array.cs b/smsapi/Api/Response/Array.cs index 4d3385f..c49a574 100644 --- a/smsapi/Api/Response/Array.cs +++ b/smsapi/Api/Response/Array.cs @@ -6,7 +6,6 @@ namespace SMSApi.Api.Response [DataContract] public class Array : Countable { - [DataMember(Name = "list", IsRequired = true)] public readonly List List; public Array(List list) diff --git a/smsapi/Api/Response/Blacklist/BlacklistRecord.cs b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs index 9c06330..a878a7e 100644 --- a/smsapi/Api/Response/Blacklist/BlacklistRecord.cs +++ b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs @@ -1,31 +1,16 @@ using System; -using System.Runtime.Serialization; +using Newtonsoft.Json; using SMSApi.Api.Response.ResponseResolver; namespace smsapi.Api.Response.Blacklist; -[DataContract] public record struct BlacklistRecord : IResponseCodeAwareResolver { - [DataMember(Name = "id")] public readonly string Id; - - [DataMember(Name = "phone_number")] public readonly string PhoneNumber; + public readonly string Id; - public DateTime DateCreated; - - public DateTime? DateExpired; - - [DataMember(Name = "created_at")] - private string DateCreatedDeserializer - { - set => DateCreated = DateTime.Parse(value); - get => default; - } - - [DataMember(Name = "expire_at")] - private string? DateExpiredDeserializer - { - set => DateExpired = value != null ? DateTime.Parse(value) : null; - get => default; - } + public readonly string PhoneNumber; + + [JsonProperty("created_at")] public readonly DateTime DateCreated; + + [JsonProperty("expire_at")] public readonly DateTime? DateExpired; } diff --git a/smsapi/Api/Response/CheckNumber.cs b/smsapi/Api/Response/CheckNumber.cs index c9765cd..0258142 100644 --- a/smsapi/Api/Response/CheckNumber.cs +++ b/smsapi/Api/Response/CheckNumber.cs @@ -1,12 +1,9 @@ using System.Collections.Generic; -using System.Runtime.Serialization; namespace SMSApi.Api.Response { - [DataContract] public class CheckNumber : Countable { - [DataMember(Name = "list", IsRequired = true)] private List list; protected CheckNumber() diff --git a/smsapi/Api/Response/Common/Telephony/Country.cs b/smsapi/Api/Response/Common/Telephony/Country.cs index 1d44bde..f8df4ae 100644 --- a/smsapi/Api/Response/Common/Telephony/Country.cs +++ b/smsapi/Api/Response/Common/Telephony/Country.cs @@ -1,12 +1,9 @@ -using System.Runtime.Serialization; - namespace SMSApi.Api.Response.Common.Telephony; -[DataContract] public readonly record struct Country { - [DataMember(Name = "name")] public readonly string Name; - [DataMember(Name = "mcc")] public readonly int MCC; + public readonly string Name; + public readonly int MCC; public Country(string name, int mcc) { diff --git a/smsapi/Api/Response/Common/Telephony/Network.cs b/smsapi/Api/Response/Common/Telephony/Network.cs index c6bc6d6..a9b7f72 100644 --- a/smsapi/Api/Response/Common/Telephony/Network.cs +++ b/smsapi/Api/Response/Common/Telephony/Network.cs @@ -1,12 +1,9 @@ -using System.Runtime.Serialization; - namespace SMSApi.Api.Response.Common.Telephony; -[DataContract] public readonly record struct Network { - [DataMember(Name = "name")] public readonly string Name; - [DataMember(Name = "mnc")] public readonly int MNC; + public readonly string Name; + public readonly int MNC; public Network(string name, int mnc) { diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index 9189571..f4fa1a4 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.Serialization; +using Newtonsoft.Json; using smsapi.Api.Response.Contacts.Exception; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response { - [DataContract] public class Contact : IResponseCodeAwareResolver { public const string FemaleGender = "female"; @@ -15,52 +14,39 @@ public class Contact : IResponseCodeAwareResolver public const string UndefinedGender = "undefined"; [Obsolete("use BirthdayDate instead")] - [DataMember(Name = "birthday", IsRequired = false)] public readonly string Birthday; - [DataMember(Name = "city", IsRequired = false)] public readonly string City; - [DataMember(Name = "description", IsRequired = false)] public readonly string Description; - [DataMember(Name = "email", IsRequired = false)] public readonly string Email; - [DataMember(Name = "first_name", IsRequired = false)] public readonly string FirstName; - [DataMember(Name = "gender", IsRequired = false)] public readonly string Gender; - [DataMember(Name = "id", IsRequired = false)] public readonly string Id; - [DataMember(Name = "idx", IsRequired = false)] public readonly string Idx; [Obsolete("use Description instead")] - [DataMember(Name = "info", IsRequired = false)] public readonly string info; - [DataMember(Name = "last_name", IsRequired = false)] public readonly string LastName; [Obsolete("use Id instead")] - [DataMember(Name = "number", IsRequired = false)] public readonly string Number; - [DataMember(Name = "phone_number", IsRequired = false)] public readonly string PhoneNumber; - [DataMember(Name = "source", IsRequired = false)] public readonly string Source; private DateTime? dateCreated; private DateTime? dateUpdated; - public DateTime? BirthdayDate { get; private set; } + public readonly DateTime BirthdayDate; public Dictionary> HandleExceptionActions() { @@ -94,20 +80,7 @@ public uint DateMod public DateTime? DateUpdated => dateUpdated; - [DataMember(Name = "birthday_date", IsRequired = false)] - private string BirthdayDateSerializationHelper - { - set - { - if (value != null) - { - BirthdayDate = DateTime.Parse(value); - } - } - get => ""; - } - - [DataMember(Name = "date_add", IsRequired = false)] + [JsonProperty("date_add")] private uint DateAddSerializationHelper { set @@ -118,14 +91,14 @@ private uint DateAddSerializationHelper get => 0; } - [DataMember(Name = "date_created", IsRequired = false)] + [JsonProperty("date_created")] private string DateCreatedSerializationHelper { set => dateCreated = DateTime.Parse(value); get => ""; } - [DataMember(Name = "date_mod", IsRequired = false)] + [JsonProperty("date_mod")] private uint DateModSerializationHelper { set @@ -136,7 +109,7 @@ private uint DateModSerializationHelper get => 0; } - [DataMember(Name = "date_updated", IsRequired = false)] + [JsonProperty("date_updated")] private string DateUpdatedSerializationHelper { set => dateUpdated = DateTime.Parse(value); diff --git a/smsapi/Api/Response/Contacts.cs b/smsapi/Api/Response/Contacts.cs index 9816c3a..6d88c6d 100644 --- a/smsapi/Api/Response/Contacts.cs +++ b/smsapi/Api/Response/Contacts.cs @@ -7,7 +7,6 @@ namespace SMSApi.Api.Response public class Contacts : BasicCollection { [Obsolete("")] - [DataMember(Name = "total", IsRequired = false)] public readonly int Total; } } diff --git a/smsapi/Api/Response/Field.cs b/smsapi/Api/Response/Field.cs index ba71ed4..ac5813c 100644 --- a/smsapi/Api/Response/Field.cs +++ b/smsapi/Api/Response/Field.cs @@ -1,9 +1,7 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response { - [DataContract] public class Field : IResponseCodeAwareResolver { public const string DateType = "DATE"; @@ -11,14 +9,11 @@ public class Field : IResponseCodeAwareResolver public const string NumberType = "NUMBER"; public const string PhoneNumberType = "PHONE_NUMBER"; public const string TextType = "TEXT"; - - [DataMember(Name = "id", IsRequired = false)] + public readonly string Id; - - [DataMember(Name = "name", IsRequired = false)] + public readonly string Name; - - [DataMember(Name = "type", IsRequired = false)] + public readonly string Type; } } diff --git a/smsapi/Api/Response/FieldOption.cs b/smsapi/Api/Response/FieldOption.cs index 0c26c13..cb3a272 100644 --- a/smsapi/Api/Response/FieldOption.cs +++ b/smsapi/Api/Response/FieldOption.cs @@ -1,14 +1,9 @@ -using System.Runtime.Serialization; - namespace SMSApi.Api.Response { - [DataContract] public class FieldOption { - [DataMember(Name = "name", IsRequired = false)] public readonly string Name; - [DataMember(Name = "value", IsRequired = false)] public readonly string Value; } } diff --git a/smsapi/Api/Response/Group.cs b/smsapi/Api/Response/Group.cs index c5dfd3f..d2091fb 100644 --- a/smsapi/Api/Response/Group.cs +++ b/smsapi/Api/Response/Group.cs @@ -2,39 +2,35 @@ using System.Collections.Generic; using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class Group : ErrorAwareResponse { - [DataMember(Name = "created_by", IsRequired = false)] public readonly string CreatedBy; - [DataMember(Name = "id", IsRequired = false)] public readonly string Id; - [DataMember(Name = "idx", IsRequired = false)] public readonly string Idx; - [DataMember(Name = "name", IsRequired = true)] + [JsonRequired] public readonly string Name; - [DataMember(Name = "permissions", IsRequired = false)] private List permissions; - [DataMember(Name = "contacts_count", IsRequired = false)] + [JsonProperty("contacts_count")] public int? ContactsCount { get; private set; } public DateTime? DateCreated { get; private set; } public DateTime? DateUpdated { get; private set; } - [DataMember(Name = "description", IsRequired = false)] + [JsonProperty("description")] public string Description { get; private set; } [Obsolete("use Description instead")] - [DataMember(Name = "info", IsRequired = false)] + [JsonProperty("info")] public string Info { get => Description; @@ -42,7 +38,7 @@ public string Info } [Obsolete("use ContactsCount instead")] - [DataMember(Name = "numbers_count", IsRequired = false)] + [JsonProperty("numbers_count")] public uint NumbersCount { get => (uint)ContactsCount; @@ -62,14 +58,14 @@ public List Permissions } } - [DataMember(Name = "date_created", IsRequired = false)] + [JsonProperty("date_created")] private string DateCreatedSerializationHelper { set => DateCreated = DateTime.Parse(value); get => ""; } - [DataMember(Name = "date_updated", IsRequired = false)] + [JsonProperty("date_updated")] private string DateUpdatedSerializationHelper { set => DateUpdated = DateTime.Parse(value); diff --git a/smsapi/Api/Response/GroupPermission.cs b/smsapi/Api/Response/GroupPermission.cs index 71817b9..da384bf 100644 --- a/smsapi/Api/Response/GroupPermission.cs +++ b/smsapi/Api/Response/GroupPermission.cs @@ -6,19 +6,14 @@ namespace SMSApi.Api.Response [DataContract] public class GroupPermission : ErrorAwareResponse, IResponseCodeAwareResolver { - [DataMember(Name = "group_id", IsRequired = false)] public readonly string GroupId; - [DataMember(Name = "read", IsRequired = false)] public readonly bool Read; - [DataMember(Name = "send", IsRequired = false)] public readonly bool Send; - [DataMember(Name = "username", IsRequired = false)] public readonly string Username; - [DataMember(Name = "write", IsRequired = false)] public readonly bool Write; } } diff --git a/smsapi/Api/Response/HLR/LookupResult.cs b/smsapi/Api/Response/HLR/LookupResult.cs index 668c33e..34443e8 100644 --- a/smsapi/Api/Response/HLR/LookupResult.cs +++ b/smsapi/Api/Response/HLR/LookupResult.cs @@ -1,43 +1,33 @@ using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using Newtonsoft.Json; using SMSApi.Api.Response.Common.Telephony; namespace SMSApi.Api.Response.HLR; -[DataContract] -public class LookupResult +public record struct LookupResult { - [DataMember(Name = "id")] public readonly string Id; + public readonly LookupCost Cost; - [DataMember(Name = "phone_number")] public readonly string PhoneNumber; - - [DataMember(Name = "interface")] public readonly string Interface; - - [DataMember(Name = "country")] public readonly Country? Country; - - [DataMember(Name = "network")] public readonly Network? Network; - - [DataMember(Name = "cost")] public readonly LookupCost Cost; - - [DataMember(Name = "ported")] public readonly Ported? Ported; - - [DataMember(Name = "error_code")] public readonly uint? ErrorCode; + public readonly Country? Country; - public DateTime SentAt; - - [DataMember(Name = "sent_at")] - private string? SentAtDeserializer - { - set => SentAt = DateTime.Parse(value); - get => default; - } + public readonly uint? ErrorCode; + public readonly string Id; + + public readonly string Interface; + + public readonly Network? Network; + + public readonly string PhoneNumber; + + public readonly Ported? Ported; + + public readonly DateTime SentAt; } -[DataContract] public readonly record struct LookupCost { - [DataMember(Name = "points")] public readonly double Points; + public readonly double Points; public LookupCost(double points) { @@ -45,10 +35,9 @@ public LookupCost(double points) } } -[DataContract] public readonly record struct Ported { - [DataMember(Name = "ported")] public readonly IEnumerable PortedFrom; + [JsonProperty("ported")] public readonly IEnumerable PortedFrom; public Ported(IEnumerable portedFrom) { @@ -56,13 +45,12 @@ public Ported(IEnumerable portedFrom) } } -[DataContract] public readonly record struct MCC { - [DataMember(Name = "mcc")] public readonly int Mcc; + public readonly int Mcc; public MCC(int mcc) { Mcc = mcc; } -} +} \ No newline at end of file diff --git a/smsapi/Api/Response/MFA/MFACreationResponse.cs b/smsapi/Api/Response/MFA/MFACreationResponse.cs index efdd356..ddd0535 100644 --- a/smsapi/Api/Response/MFA/MFACreationResponse.cs +++ b/smsapi/Api/Response/MFA/MFACreationResponse.cs @@ -1,16 +1,14 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.MFA; -[DataContract] public class MFACreationResponse : IResponseCodeAwareResolver { - [DataMember(Name = "code")] public readonly string Code; + public readonly string Code; - [DataMember(Name = "from")] public readonly string From; + public readonly string From; - [DataMember(Name = "id")] public readonly string Id; + public readonly string Id; - [DataMember(Name = "phone_number")] public readonly string PhoneNumber; + public readonly string PhoneNumber; } diff --git a/smsapi/Api/Response/MessageStatus.cs b/smsapi/Api/Response/MessageStatus.cs index 7c64400..7ee338c 100644 --- a/smsapi/Api/Response/MessageStatus.cs +++ b/smsapi/Api/Response/MessageStatus.cs @@ -1,26 +1,24 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class MessageStatus { - [DataMember(Name = "error", IsRequired = false)] public readonly string Error; - [DataMember(Name = "id", IsRequired = true)] + [JsonRequired] public readonly string ID; - [DataMember(Name = "idx", IsRequired = false)] + [JsonProperty("idx")] public readonly string IDx; - [DataMember(Name = "number", IsRequired = true)] + [JsonRequired] public readonly string Number; - [DataMember(Name = "points", IsRequired = true)] + [JsonRequired] public readonly double Points; - [DataMember(Name = "status", IsRequired = true)] + [JsonRequired] public readonly string Status; private MessageStatus() diff --git a/smsapi/Api/Response/NumberStatus.cs b/smsapi/Api/Response/NumberStatus.cs index 8a32df8..8ac0ab8 100644 --- a/smsapi/Api/Response/NumberStatus.cs +++ b/smsapi/Api/Response/NumberStatus.cs @@ -1,38 +1,30 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class NumberStatus { - [DataMember(Name = "date", IsRequired = false)] public readonly int Date; - [DataMember(Name = "id", IsRequired = false)] + [JsonProperty("id")] public readonly string ID; - [DataMember(Name = "info", IsRequired = false)] public readonly string Info; - [DataMember(Name = "mcc", IsRequired = false)] public readonly int MCC; - [DataMember(Name = "mnc", IsRequired = false)] public readonly int MNC; - [DataMember(Name = "number", IsRequired = true)] + [JsonRequired] public readonly string Number; - [DataMember(Name = "price", IsRequired = false)] + [JsonProperty("price")] public readonly double Points; - [DataMember(Name = "ported", IsRequired = false)] public readonly int Ported; - [DataMember(Name = "ported_from", IsRequired = false)] public readonly int PortedFrom; - [DataMember(Name = "status", IsRequired = false)] public readonly string Status; private NumberStatus() diff --git a/smsapi/Api/Response/OptOut/OptOut.cs b/smsapi/Api/Response/OptOut/OptOut.cs index 8c4d0d7..4a86e75 100644 --- a/smsapi/Api/Response/OptOut/OptOut.cs +++ b/smsapi/Api/Response/OptOut/OptOut.cs @@ -1,21 +1,12 @@ using System; -using System.Runtime.Serialization; namespace SMSApi.Api.Response.OptOut; -[DataContract] -public class OptOut +public sealed class OptOut { - [DataMember(Name = "id")] public readonly string Id; + public readonly string Id; - [DataMember(Name = "phone_number")] public readonly string PhoneNumber; - - public DateTime CreationTime { get; private set; } - - [DataMember(Name = "creation_time")] - private string CreationTimeSerializer - { - set => CreationTime = DateTime.Parse(value); - get => default!; - } + public readonly string PhoneNumber; + + public readonly DateTime CreationTime; } diff --git a/smsapi/Api/Response/OptOut/OptOutSettings.cs b/smsapi/Api/Response/OptOut/OptOutSettings.cs index 7e8fe2c..62ef6c2 100644 --- a/smsapi/Api/Response/OptOut/OptOutSettings.cs +++ b/smsapi/Api/Response/OptOut/OptOutSettings.cs @@ -1,10 +1,8 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.OptOut; -[DataContract] -public record struct OptOutSettings : IResponseCodeAwareResolver +public sealed class OptOutSettings : IResponseCodeAwareResolver { - [DataMember(Name = "brand")] public readonly string Brand; + public readonly string Brand; } diff --git a/smsapi/Api/Response/Ping/PingServiceResponse.cs b/smsapi/Api/Response/Ping/PingServiceResponse.cs index 038951a..67f8c2f 100644 --- a/smsapi/Api/Response/Ping/PingServiceResponse.cs +++ b/smsapi/Api/Response/Ping/PingServiceResponse.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; -using System.Runtime.Serialization; +using Newtonsoft.Json; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.Ping; -[DataContract] -public readonly record struct PingServiceResponse : IResponseCodeAwareResolver +public sealed class PingServiceResponse : IResponseCodeAwareResolver { - [DataMember(Name = "authorized")] public readonly bool Authorized; + public readonly bool Authorized; - [DataMember(Name = "unavailable")] public readonly IEnumerable UnavailableServices; + [JsonProperty("unavailable")] public readonly IEnumerable UnavailableServices; } diff --git a/smsapi/Api/Response/Points.cs b/smsapi/Api/Response/Points.cs index 3cfb807..23f603f 100644 --- a/smsapi/Api/Response/Points.cs +++ b/smsapi/Api/Response/Points.cs @@ -1,27 +1,29 @@ using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; +using Newtonsoft.Json; namespace SMSApi.Api.Response { [DataContract] public class Credits : ErrorAwareResponse { - [DataMember(Name = "ecoCount", IsRequired = false)] + [JsonProperty("ecoCount")] public readonly int EcoCount; - [DataMember(Name = "mmsCount", IsRequired = false)] + [JsonProperty("mmsCount")] public readonly int MmsCount; - [DataMember(Name = "points", IsRequired = true)] + [JsonRequired] + [JsonProperty("points")] public readonly double Points; - [DataMember(Name = "proCount", IsRequired = false)] + [JsonProperty("proCount")] public readonly int ProCount; - [DataMember(Name = "vmsGsmCount", IsRequired = false)] + [JsonProperty("vmsGsmCount")] public readonly int VmsGsmCount; - [DataMember(Name = "vmsLandCount", IsRequired = false)] + [JsonProperty("vmsLandCount")] public readonly int VmsLandCount; private Credits() diff --git a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs index acf6c90..34029d9 100644 --- a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs +++ b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs @@ -1,23 +1,20 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.Common.Telephony; namespace SMSApi.Api.Response.Profile.Prices; -[DataContract] public readonly struct PriceResponse { - [DataMember(Name = "price")] public readonly Price Price; + public readonly Price Price; - [DataMember(Name = "country")] public readonly Country Country; + public readonly Country Country; - [DataMember(Name = "network")] public readonly Network Network; + public readonly Network Network; - [DataMember(Name = "type")] public readonly string Type; + public readonly string Type; } -[DataContract] public readonly struct Price { - [DataMember(Name = "amount")] public readonly float Amount; - [DataMember(Name = "currency")] public readonly string Currency; + public readonly float Amount; + public readonly string Currency; } diff --git a/smsapi/Api/Response/Profile/Profile.cs b/smsapi/Api/Response/Profile/Profile.cs index 7d5be14..3ad32aa 100644 --- a/smsapi/Api/Response/Profile/Profile.cs +++ b/smsapi/Api/Response/Profile/Profile.cs @@ -1,29 +1,20 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.Profile; -[DataContract] -public record struct Profile : IResponseCodeAwareResolver +public sealed class Profile : IResponseCodeAwareResolver { - [DataMember(Name = "name")] public readonly string Name; - - [DataMember(Name = "username")] + public readonly string Username; - - [DataMember(Name = "email")] + public readonly string Email; - - [DataMember(Name = "phone_number")] + public readonly string PhoneNumber; - - [DataMember(Name = "user_type")] + public readonly string UserType; - - [DataMember(Name = "points")] + public readonly double Points; - - [DataMember(Name = "payment_type")] + public readonly string PaymentType; } diff --git a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs index 501f4b4..70eb032 100644 --- a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs +++ b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs @@ -1,28 +1,33 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; -namespace SMSApi.Api.Response.ResponseResolver +namespace SMSApi.Api.Response.ResponseResolver; + +public class ErrorAwareResponse : IResponseCodeAwareResolver { - [DataContract] - public class ErrorAwareResponse: IResponseCodeAwareResolver + [JsonProperty("message")] public readonly string ErrorMessage; + + [JsonProperty("error")] public readonly int? ErrorCode; + + // [JsonProperty("error")] + // private JsonElement? _errorCode + // { + // set => value?.Let(val => + // { + // ErrorCode = val.ValueKind == JsonValueKind.Number ? val.GetInt32() : val.GetString(); + // }); + // } + + public bool IsError() + { + if (ErrorCode == null) return false; + + // if (ErrorCode is string) return ErrorCode != ""; + + return (ErrorCode as int? ?? 0) != 0; + } + + public string GetErrorMessage() { - [DataMember(Name = "error", IsRequired = false)] - public readonly dynamic? ErrorCode; - - [DataMember(Name = "message", IsRequired = false)] - public readonly string ErrorMessage; - - public bool IsError() - { - if (ErrorCode == null) return false; - - if (ErrorCode is string) - { - return ErrorCode != ""; - } - - return ErrorCode != 0; - } - - public string GetErrorMessage() => ErrorMessage; + return ErrorMessage; } } diff --git a/smsapi/Api/Response/Sender.cs b/smsapi/Api/Response/Sender.cs index 39e5a91..a842d71 100644 --- a/smsapi/Api/Response/Sender.cs +++ b/smsapi/Api/Response/Sender.cs @@ -1,17 +1,17 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class Sender { - [DataMember(Name = "default", IsRequired = true)] + [JsonRequired] public readonly bool Default; - [DataMember(Name = "sender", IsRequired = true)] + [JsonRequired] + [JsonProperty("sender")] public readonly string Name; - [DataMember(Name = "status", IsRequired = true)] + [JsonRequired] public readonly string Status; } } diff --git a/smsapi/Api/Response/Senders.cs b/smsapi/Api/Response/Senders.cs index dc74d4b..a4b87d2 100644 --- a/smsapi/Api/Response/Senders.cs +++ b/smsapi/Api/Response/Senders.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { [DataContract] public class Senders : Countable { - [DataMember(Name = "list", IsRequired = false)] + [JsonProperty("list")] private List list; private Senders() diff --git a/smsapi/Api/Response/Subusers/SubuserDetails.cs b/smsapi/Api/Response/Subusers/SubuserDetails.cs index cb2738a..fd1a9cc 100644 --- a/smsapi/Api/Response/Subusers/SubuserDetails.cs +++ b/smsapi/Api/Response/Subusers/SubuserDetails.cs @@ -1,25 +1,27 @@ -using System.Runtime.Serialization; - namespace SMSApi.Api.Response.Subusers; -[DataContract] public readonly record struct SubuserDetails { - [DataMember(Name = "active")] public readonly bool Active; + public readonly bool Active; - [DataMember(Name = "description")] public readonly string Description; + public readonly string Description; - [DataMember(Name = "id")] public readonly string Id; + public readonly string Id; - [DataMember(Name = "points")] public readonly UserPoints Points; + public readonly UserPoints Points; - [DataMember(Name = "username")] public readonly string Username; + public readonly string Username; } -[DataContract] -public readonly record struct UserPoints(double FromAccount, double PerMonth) +public readonly record struct UserPoints { - [DataMember(Name = "from_account")] public readonly double FromAccount = FromAccount; + public readonly double FromAccount; + + public readonly double PerMonth; - [DataMember(Name = "per_month")] public readonly double PerMonth = PerMonth; + public UserPoints(double fromAccount, double perMonth) + { + FromAccount = fromAccount; + PerMonth = perMonth; + } } diff --git a/smsapi/Api/Response/User.cs b/smsapi/Api/Response/User.cs index 8919437..a7206b5 100644 --- a/smsapi/Api/Response/User.cs +++ b/smsapi/Api/Response/User.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using Newtonsoft.Json; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response @@ -6,25 +7,25 @@ namespace SMSApi.Api.Response [DataContract] public class User : ErrorAwareResponse { - [DataMember(Name = "active", IsRequired = true)] + [JsonRequired] public readonly bool Active; - [DataMember(Name = "info", IsRequired = true)] + [JsonRequired] public readonly string Info; - [DataMember(Name = "limit", IsRequired = true)] + [JsonRequired] public readonly double Limit; - [DataMember(Name = "month_limit", IsRequired = true)] + [JsonRequired] public readonly double MonthLimit; - [DataMember(Name = "phonebook", IsRequired = true)] + [JsonRequired] public readonly uint Phonebook; - [DataMember(Name = "senders", IsRequired = true)] + [JsonRequired] public readonly uint Senders; - [DataMember(Name = "username", IsRequired = true)] + [JsonRequired] public readonly string Username; private User() diff --git a/smsapi/OperationsHelper.cs b/smsapi/OperationsHelper.cs index a4d78e5..f37d013 100644 --- a/smsapi/OperationsHelper.cs +++ b/smsapi/OperationsHelper.cs @@ -1,11 +1,34 @@ using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; namespace SMSApi.Api; internal static class OperationsHelper { - public static void Let(this T value, Action action) where T : class + public static void Let(this T value, Action action) { action(value); } + + public static string GetEnumValue(this T enumValue) where T : Enum + { + var type = enumValue.GetType(); + MemberInfo[] memInfo = type.GetMember(enumValue.ToString()); + + if (memInfo.Length <= 0) return enumValue.ToString(); + var attributes = memInfo[0].GetCustomAttributes(typeof(EnumMemberAttribute), false); + if (attributes.Length > 0) return ((EnumMemberAttribute)attributes[0]).Value; + + return enumValue.ToString(); + } + + public static void Add(this ISet> set, params (string key, dynamic? value)[] values) + { + foreach (var valueTuple in values) + { + set.Add(KeyValuePair.Create(valueTuple.key, valueTuple.value)); + } + } } From ee7563e63a5a2199696566332b81eb9d835d0dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 13 Dec 2024 14:22:38 +0000 Subject: [PATCH 073/142] Add ShortUrl feature #clicks grouped by device --- smsapi/Api/Action/Action.cs | 20 +++++++ .../ListShortUrlClicksGroupedByDevice.cs | 40 +++++++++++++ .../ShortUrl/ShortLinkClickByDevices.cs | 18 ++++++ smsapi/Api/ShortUrlFactory.cs | 8 +++ ...ortUrlClicksGroupedByDeviceResponseTest.cs | 60 +++++++++++++++++++ .../ListShortUrlClicksGroupedByDeviceTest.cs | 53 ++++++++++++++++ 6 files changed, 199 insertions(+) create mode 100644 smsapi/Api/Action/ShortUrl/ListShortUrlClicksGroupedByDevice.cs create mode 100644 smsapi/Api/Response/ShortUrl/ShortLinkClickByDevices.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceResponseTest.cs create mode 100644 smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index b06f401..11c9e89 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -107,6 +107,26 @@ private void AssignValuesToQuery(UriBuilder uriBuilder) query.Add(Values().Item1); + Values().Item2?.ToList().ForEach(pair => + { + switch (pair.Value) + { + case string[] list: + { + foreach (var item in list) + { + query.Add($"{pair.Key}[]", item); + } + + break; + } + case string singleValue: + query.Add(pair.Key, singleValue); + break; + default: throw new Exception($"Unsupported query parameter type for parameter {pair.Key}"); + } + }); + uriBuilder.Query = query.ToString(); } diff --git a/smsapi/Api/Action/ShortUrl/ListShortUrlClicksGroupedByDevice.cs b/smsapi/Api/Action/ShortUrl/ListShortUrlClicksGroupedByDevice.cs new file mode 100644 index 0000000..5afba14 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/ListShortUrlClicksGroupedByDevice.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response; +using SMSApi.Api.Response.ShortUrl; + +namespace SMSApi.Api.Action.ShortUrl; + +public sealed class ListShortUrlClicksGroupedByDevice : Action> +{ + private readonly string[] _ids; + + public ListShortUrlClicksGroupedByDevice(params string[] ids) + { + if (ids.Length == 0) + throw new ArgumentException("Invalid ids count, at least one is required."); + + _ids = ids; + } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "short_url/clicks_by_mobile_device"; + } + + protected override (NameValueCollection, ISet>?) Values() + { + return ( + new NameValueCollection(), + new HashSet> + { + KeyValuePair.Create("links", _ids), + } + ); + } +} diff --git a/smsapi/Api/Response/ShortUrl/ShortLinkClickByDevices.cs b/smsapi/Api/Response/ShortUrl/ShortLinkClickByDevices.cs new file mode 100644 index 0000000..6d082c4 --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/ShortLinkClickByDevices.cs @@ -0,0 +1,18 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.ShortUrl; + +public readonly record struct ShortLinkClickByDevices : IResponseCodeAwareResolver +{ + public readonly ShortLinkDevicesClickCount Clicks; + public readonly string LinkId; +} + +public readonly record struct ShortLinkDevicesClickCount +{ + public readonly int Android; + public readonly int Ios; + public readonly int Other; + public readonly int Sum; + public readonly int Wp; +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index cd849c8..441d339 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -75,6 +75,14 @@ public ListShortUrlClicks ListClicks() return action; } + + public ListShortUrlClicksGroupedByDevice ListClicksGroupedByDeviceType(params string[] linkId) + { + var action = new ListShortUrlClicksGroupedByDevice(linkId); + action.Proxy(proxy); + + return action; + } } public static class ShortUrlFeatureRegister diff --git a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceResponseTest.cs new file mode 100644 index 0000000..9c8f01d --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceResponseTest.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ListShortUrlClicksGroupedByDeviceResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void list_grouped_clicks() + { + var linkId = "any"; + var clicks = new Dictionary + { + { "android", 1 }, + { "ios", 2 }, + { "wp", 3 }, + { "other", 4 }, + { "sum", 10 } + }; + + var response = + new Dictionary + { + { "link_id", linkId }, + { "clicks", clicks } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + CollectionMother.WithItems(response).ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = CreateShortUrClicksGroupedByDevice().Execute(); + + Assert.AreEqual(1, result.Size); + Assert.AreEqual(1, result.Collection.Count); + var firstLink = result.Collection[0]; + Assert.AreEqual(linkId, firstLink.LinkId); + Assert.AreEqual(1, firstLink.Clicks.Android); + Assert.AreEqual(2, firstLink.Clicks.Ios); + Assert.AreEqual(3, firstLink.Clicks.Wp); + Assert.AreEqual(4, firstLink.Clicks.Other); + Assert.AreEqual(10, firstLink.Clicks.Sum); + } + + private ListShortUrlClicksGroupedByDevice CreateShortUrClicksGroupedByDevice() + { + var action = new ListShortUrlClicksGroupedByDevice("anyId"); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs new file mode 100644 index 0000000..cf53466 --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; + +namespace smsapiTests.Unit.Action.ShortUrl; + +[TestClass] +public class ListShortUrlClicksGroupedByDeviceTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public ListShortUrlClicksGroupedByDeviceTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void add_ids_to_query() + { + var ids = new[] {"1", "2"}; + + CreateShortUrGroupedByDevice(ids).Execute(); + + _proxyAssert.AssertUriEquals("short_url/clicks_by_mobile_device?links[]=1&links[]=2"); + } + + [TestMethod] + public void valid_method() + { + CreateShortUrGroupedByDevice("any").Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + [TestMethod] + public void require_at_least_1_id() + { + var action = () => CreateShortUrGroupedByDevice().Execute(); + + var exception = Assert.ThrowsException(action); + Assert.AreEqual("Invalid ids count, at least one is required.", exception.Message); + } + + private ListShortUrlClicksGroupedByDevice CreateShortUrGroupedByDevice(params string[] ids) + { + var action = new ListShortUrlClicksGroupedByDevice(ids); + action.Proxy(_spyProxy); + + return action; + } +} From b34dc53dde556d23c949d59947886f2e463f8d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 13 Dec 2024 14:29:04 +0000 Subject: [PATCH 074/142] Serialization changes --- .../Action/Subusers/Fixture/SubuersCollectionMother.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs index 9ddd543..cce5619 100644 --- a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs @@ -21,8 +21,12 @@ UserPoints userPoints { "active", active }, { "description", description }, { - "points", userPoints + "points", new Dictionary + { + { "from_account", userPoints.FromAccount }, + { "per_month", userPoints.PerMonth }, + } } }); } -} +} \ No newline at end of file From 6070955b9915f5de29542e28e37e2ef1689987b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 16 Dec 2024 22:29:54 +0000 Subject: [PATCH 075/142] Add ShortUrl feature examples --- examples/shortUrl/Create.cs | 29 +++++++++++++++ examples/shortUrl/DeleteLink.cs | 19 ++++++++++ examples/shortUrl/GetLink.cs | 28 +++++++++++++++ examples/shortUrl/GetLinksClicks.cs | 23 ++++++++++++ .../shortUrl/GetLinksClicksGroupedByDevice.cs | 21 +++++++++++ examples/shortUrl/List.cs | 22 ++++++++++++ examples/shortUrl/UpdateLink.cs | 36 +++++++++++++++++++ 7 files changed, 178 insertions(+) create mode 100644 examples/shortUrl/Create.cs create mode 100644 examples/shortUrl/DeleteLink.cs create mode 100644 examples/shortUrl/GetLink.cs create mode 100644 examples/shortUrl/GetLinksClicks.cs create mode 100644 examples/shortUrl/GetLinksClicksGroupedByDevice.cs create mode 100644 examples/shortUrl/List.cs create mode 100644 examples/shortUrl/UpdateLink.cs diff --git a/examples/shortUrl/Create.cs b/examples/shortUrl/Create.cs new file mode 100644 index 0000000..424001b --- /dev/null +++ b/examples/shortUrl/Create.cs @@ -0,0 +1,29 @@ +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapi.Api.Response.REST.Exception; +using SMSApi.Api.Response.ShortUrl.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string name = "abc"; +const string url = "http://example.com"; + +try +{ + var link = features.ShortUrl() + .Create(name, url) + .WithDescription("my fancy link") //Set description (optional) + .WithExpiration(1, CreateShortUrl.ShortUrlExpirationUnit.Hours) //Set expiration period (optional) + .Execute(); + + Console.WriteLine(link.Id); +} +catch (ShortUrlWithNameAlreadyExistsException) +{ +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/examples/shortUrl/DeleteLink.cs b/examples/shortUrl/DeleteLink.cs new file mode 100644 index 0000000..3e4dca3 --- /dev/null +++ b/examples/shortUrl/DeleteLink.cs @@ -0,0 +1,19 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string linkId = "5"; + +try +{ + features.ShortUrl() + .DeleteShortUrl(linkId) + .Execute(); + + //link is deleted at this point +} +catch (NotFoundException) +{ +} diff --git a/examples/shortUrl/GetLink.cs b/examples/shortUrl/GetLink.cs new file mode 100644 index 0000000..e4f3000 --- /dev/null +++ b/examples/shortUrl/GetLink.cs @@ -0,0 +1,28 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string linkId = "5"; + +try +{ + var link = features.ShortUrl() + .GetShortUrl(linkId) + .Execute(); + + Console.WriteLine(link.Id); + Console.WriteLine(link.Description); + Console.WriteLine(link.ExpireAt); + Console.WriteLine(link.FileName); + Console.WriteLine(link.Hits); + Console.WriteLine(link.UniqueHits); + Console.WriteLine(link.Name); + Console.WriteLine(link.Url); + Console.WriteLine(link.Type); + Console.WriteLine(link.ShortUrl); +} +catch (NotFoundException) +{ +} diff --git a/examples/shortUrl/GetLinksClicks.cs b/examples/shortUrl/GetLinksClicks.cs new file mode 100644 index 0000000..f1425fd --- /dev/null +++ b/examples/shortUrl/GetLinksClicks.cs @@ -0,0 +1,23 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string linkId = "5"; + +var linkClicks = features.ShortUrl() + .ListClicks() + .ListFrom(DateTime.MinValue) //optional + .ListTo(DateTime.MaxValue) //optional + .Execute(); + +linkClicks.Collection.ForEach(click => +{ + Console.WriteLine($"Short link: {click.ShortUrl}"); + Console.WriteLine($"Short link name: {click.Name}"); + Console.WriteLine($"Browser: {click.Browser}"); + Console.WriteLine($"Device: {click.Device}"); + Console.WriteLine($"Operating system: {click.Os}"); + Console.WriteLine($"Phone number: {click.PhoneNumber}"); + Console.WriteLine($"Hit date: {click.DateHit}"); +}); diff --git a/examples/shortUrl/GetLinksClicksGroupedByDevice.cs b/examples/shortUrl/GetLinksClicksGroupedByDevice.cs new file mode 100644 index 0000000..1b73b1b --- /dev/null +++ b/examples/shortUrl/GetLinksClicksGroupedByDevice.cs @@ -0,0 +1,21 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string[] linksIds = ["1", "5"]; + +var linkClicks = features.ShortUrl() + .ListClicksGroupedByDeviceType(linksIds) + .Execute(); + +linkClicks.Collection.ForEach(link => +{ + Console.WriteLine($"Link id: {link.LinkId}"); + + Console.WriteLine($"Clicks from Android: {link.Clicks.Android}"); + Console.WriteLine($"Clicks from Ios: {link.Clicks.Ios}"); + Console.WriteLine($"Clicks from Windows Phone: {link.Clicks.Wp}"); + Console.WriteLine($"Clicks from Unknown os: {link.Clicks.Other}"); + Console.WriteLine($"All clicks: {link.Clicks.Sum}"); +}); diff --git a/examples/shortUrl/List.cs b/examples/shortUrl/List.cs new file mode 100644 index 0000000..4a63b47 --- /dev/null +++ b/examples/shortUrl/List.cs @@ -0,0 +1,22 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var links = features.ShortUrl() + .List() + .Execute(); + +links.Collection.ForEach(link => +{ + Console.WriteLine(link.Id); + Console.WriteLine(link.Description); + Console.WriteLine(link.ExpireAt); + Console.WriteLine(link.FileName); + Console.WriteLine(link.Hits); + Console.WriteLine(link.UniqueHits); + Console.WriteLine(link.Name); + Console.WriteLine(link.Url); + Console.WriteLine(link.Type); + Console.WriteLine(link.ShortUrl); +}); diff --git a/examples/shortUrl/UpdateLink.cs b/examples/shortUrl/UpdateLink.cs new file mode 100644 index 0000000..8682da9 --- /dev/null +++ b/examples/shortUrl/UpdateLink.cs @@ -0,0 +1,36 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string linkId = "5"; + +try +{ + var updatedLink = features.ShortUrl() + .UpdateShortUrl(linkId) + .ChangeDescription("new description") //optional + .ChangeName("new name") //optional + .ChangeUrl("htp://example.com") //optional + .Execute(); + + Console.WriteLine(updatedLink.Id); + Console.WriteLine(updatedLink.Description); + Console.WriteLine(updatedLink.ExpireAt); + Console.WriteLine(updatedLink.FileName); + Console.WriteLine(updatedLink.Hits); + Console.WriteLine(updatedLink.UniqueHits); + Console.WriteLine(updatedLink.Name); + Console.WriteLine(updatedLink.Url); + Console.WriteLine(updatedLink.Type); + Console.WriteLine(updatedLink.ShortUrl); +} +catch (NotFoundException) +{ +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} From 36c56c60a33bf9e5d6766ac60ee711eef8d677ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 10:19:54 +0000 Subject: [PATCH 076/142] Add ShortUrl feature #rich media --- smsapi/Api/Action/ShortUrl/CreateShortUrl.cs | 3 ++ smsapi/NativeHttpClientHelper.cs | 37 ++++++++++--------- .../Action/ShortUrl/CreateShortUrlTest.cs | 3 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs index edbec42..d274b8d 100644 --- a/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs +++ b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs @@ -39,6 +39,7 @@ public CreateShortUrl(string name, Stream file) } protected override RequestMethod Method => RequestMethod.POST; + protected override ActionContentType ContentType => ActionContentType.FormWww; public CreateShortUrl WithExpiration(uint expireIn, ShortUrlExpirationUnit expirationUnit) { @@ -83,6 +84,8 @@ protected override (NameValueCollection, ISet>?) ); }); + _file?.Let(_ => body.Add(("type", "FILE"))); + return (new NameValueCollection(), body); } diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index e42ffeb..891e4f2 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -58,32 +58,33 @@ private static HttpContent ConvertRequestDataToHttpContent( ) { var collectionDictionary = collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - + if (contentType == ActionContentType.Json) - { return new StringContent(JsonSerializer.Serialize(collectionDictionary), Encoding.UTF8, "application/json"); - } - + var contentCollection = collectionDictionary.Keys .Select(key => new KeyValuePair(key, collectionDictionary[key])) .ToList(); var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); - + if (files == null || files.Count == 0) return formUrlEncodedContent; - - var multipartContent = new MultipartFormDataContent(); - multipartContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); - - foreach (var keyValuePair in collection) + + var streamContent = new StreamContent(files.Values.First()); + + streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { - multipartContent.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); - } - - files - .ToList() - .ForEach(pair => multipartContent.Add(new StreamContent(pair.Value), "file", pair.Key)); - - return multipartContent; + Name = "\"file\"", + FileName = "\"abc\"" + }; + + var content = new MultipartFormDataContent + { + streamContent + }; + + foreach (var keyValuePair in collection) content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); + + return content; } } diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs index 19a23be..0b63d0c 100644 --- a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs @@ -66,8 +66,9 @@ public void send_name_and_file() CreateShortUrl(name, file).Execute(); - _proxyAssert.AssertParametersCount(1); + _proxyAssert.AssertParametersCount(2); _proxyAssert.AssertParametersContain("name", name); + _proxyAssert.AssertParametersContain("type", "FILE"); _proxyAssert.AssertFileAttached(file); } From 293d3f32bed53523f54d98a048e6281fbdfeab6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 10:21:39 +0000 Subject: [PATCH 077/142] Add ShortUrl feature examples --- examples/shortUrl/CreateFileLink.cs | 29 +++++++++++++++++++ .../shortUrl/{Create.cs => CreateUrlLink.cs} | 0 2 files changed, 29 insertions(+) create mode 100644 examples/shortUrl/CreateFileLink.cs rename examples/shortUrl/{Create.cs => CreateUrlLink.cs} (100%) diff --git a/examples/shortUrl/CreateFileLink.cs b/examples/shortUrl/CreateFileLink.cs new file mode 100644 index 0000000..745f671 --- /dev/null +++ b/examples/shortUrl/CreateFileLink.cs @@ -0,0 +1,29 @@ +using SMSApi.Api; +using SMSApi.Api.Action.ShortUrl; +using smsapi.Api.Response.REST.Exception; +using SMSApi.Api.Response.ShortUrl.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string name = "abc"; +var file = new FileStream("", FileMode.Open); + +try +{ + var link = features.ShortUrl() + .Create(name, file) + .WithDescription("my fancy link") //Set description (optional) + .WithExpiration(1, CreateShortUrl.ShortUrlExpirationUnit.Hours) //Set expiration period (optional) + .Execute(); + + Console.WriteLine(link.Id); +} +catch (ShortUrlWithNameAlreadyExistsException) +{ +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/examples/shortUrl/Create.cs b/examples/shortUrl/CreateUrlLink.cs similarity index 100% rename from examples/shortUrl/Create.cs rename to examples/shortUrl/CreateUrlLink.cs From 0e1b06ba27f8f464fc34770ea84f6f485dcd158f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 10:55:22 +0000 Subject: [PATCH 078/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/run-unit-tests.yml diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml new file mode 100644 index 0000000..8ace0f7 --- /dev/null +++ b/.github/workflows/run-unit-tests.yml @@ -0,0 +1,34 @@ +name: Unit tests + +on: + push: + branches: + - 3.x.x-dev + pull_request: + branches: + - 3.x.x-dev + +jobs: + build-and-test: + runs-on: debian-latest + + strategy: + matrix: + dotnet-version: ['6.0', '7.0', '8.0'] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ matrix.dotnet-version }} + + - name: Install dependencies + run: dotnet restore + + - name: Build the solution + run: dotnet build --configuration Release --no-restore + + - name: Run unit tests + run: dotnet test smsapiTests/Unit --configuration Release --no-build --verbosity normal From 366c53ee0538991f111b0d1008ce98c07e509beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= <64330419+jakublabno@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:21:22 +0100 Subject: [PATCH 079/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 8ace0f7..ac9e765 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -10,7 +10,7 @@ on: jobs: build-and-test: - runs-on: debian-latest + runs-on: ubuntu-latest strategy: matrix: From be00d2b3fd0d2105aa0fae18c6eef6c46120392c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 13:05:26 +0000 Subject: [PATCH 080/142] Tests lang version --- smsapiTests/smsapiTests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index b984ac6..fd3fc9c 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -4,6 +4,8 @@ false net7.0 3.0.0 + 12.0 + enable From a879c5399dd1cc68ce78fad35f77fcbf83add02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 13:09:19 +0000 Subject: [PATCH 081/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index ac9e765..09eafc5 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -28,7 +28,7 @@ jobs: run: dotnet restore - name: Build the solution - run: dotnet build --configuration Release --no-restore + run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore - name: Run unit tests - run: dotnet test smsapiTests/Unit --configuration Release --no-build --verbosity normal + run: dotnet test smsapiTests/smsapiTests.csproj --filter TestCategory=Unit --configuration Release --no-build --verbosity normal From edc73ee4b9457c93d967bd6f846044f88450c225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 14:04:08 +0000 Subject: [PATCH 082/142] Allow running tests locally --- Dockerfile | 18 ++++++++++++++++++ Makefile | 15 +++++++++++++++ smsapiTests/smsapiTests.csproj | 13 +++---------- 3 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 Dockerfile create mode 100644 Makefile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b81ffd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build + +RUN apt-get update \ + && apt-get install -y wget \ + && wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ + && chmod +x dotnet-install.sh \ + && ./dotnet-install.sh --runtime aspnetcore --version 6.0.0 --install-dir /usr/share/dotnet \ + && ./dotnet-install.sh --runtime aspnetcore --version 7.0.0 --install-dir /usr/share/dotnet \ + && ./dotnet-install.sh --runtime aspnetcore --version 8.0.0 --install-dir /usr/share/dotnet \ + && rm dotnet-install.sh + +WORKDIR /app + +COPY . . + +RUN dotnet restore + +RUN dotnet build --configuration Release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6477b5c --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +DOCKER_IMAGE = smsapi-tests +PROJECT_PATH = smsapiTests/smsapiTests.csproj + +.PHONY: build +build: + docker build -t $(DOCKER_IMAGE) . + +.PHONY: test +test: + docker run --rm $(DOCKER_IMAGE) \ + dotnet test $(PROJECT_PATH) --configuration Release --no-build --verbosity normal + +.PHONY: clean +clean: + docker rmi -f $(DOCKER_IMAGE) diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index fd3fc9c..37f2eea 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,17 +2,11 @@ false - net7.0 + net6.0;net7.0;net8.0;net9.0 3.0.0 12.0 enable - - - - - - @@ -20,8 +14,7 @@ + + - - - From 72cf16717f918646165e542609103e55c0e9c777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 14:06:39 +0000 Subject: [PATCH 083/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 09eafc5..f3ef142 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - dotnet-version: ['6.0', '7.0', '8.0'] + dotnet-version: ['6.0', '7.0', '8.0', '9.0'] steps: - name: Checkout code @@ -31,4 +31,4 @@ jobs: run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --filter TestCategory=Unit --configuration Release --no-build --verbosity normal + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal From ff066963a46bc004158126c5fdfcf5d5d9ca12ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 14:08:39 +0000 Subject: [PATCH 084/142] Add github workflow --- smsapiTests/smsapiTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index 37f2eea..b9f65ad 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,7 +2,7 @@ false - net6.0;net7.0;net8.0;net9.0 + net9.0 3.0.0 12.0 enable From 68e84daa1049d5dabf8d0f0ee56e9639000e1738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 14:09:24 +0000 Subject: [PATCH 085/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index f3ef142..01eb6c1 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - dotnet-version: ['6.0', '7.0', '8.0', '9.0'] + dotnet-version: ['9.0'] steps: - name: Checkout code From e2c47c93a476f3305fec162132cb33bb641bf8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 14:10:43 +0000 Subject: [PATCH 086/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- smsapiTests/smsapiTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 01eb6c1..a3160ab 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - dotnet-version: ['9.0'] + dotnet-version: ['8.0'] steps: - name: Checkout code diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index b9f65ad..ba13d1b 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,7 +2,7 @@ false - net9.0 + net8.0 3.0.0 12.0 enable From bef247e6d7228ba7984b78d52f9e289f2e426cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 16:09:12 +0000 Subject: [PATCH 087/142] Sms fallback -> vms example --- examples/sms/SmsWithVmsFallback.cs | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/sms/SmsWithVmsFallback.cs diff --git a/examples/sms/SmsWithVmsFallback.cs b/examples/sms/SmsWithVmsFallback.cs new file mode 100644 index 0000000..75793c7 --- /dev/null +++ b/examples/sms/SmsWithVmsFallback.cs @@ -0,0 +1,31 @@ +using SMSApi.Api; +using SMSApi.Api.Action; +using SMSApi.Api.Response; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string recipient = "48322320667"; +const string message = "message"; + +var sendResult = features.SMS() + .ActionSend(recipient, message) + .WithFallback(SMSSend.SmsFallbacks.Vms) + .Execute(); + +Console.WriteLine($"SMS sent count: {sendResult.Count}"); //no sms sent +Console.WriteLine($"Fallback sent count: {sendResult.Fallbacks?.Count ?? 0}"); //fallbacks count + +foreach (var sendResultFallback in sendResult.Fallbacks ?? new()) +{ + Console.WriteLine($"Fallback type: {sendResultFallback.Key}"); //fallback type + Console.WriteLine($"Fallbacks of type sent: {sendResultFallback.Value.Count}"); //fallbacks count + + sendResultFallback.Value.List.ForEach(fallback => + { + Console.WriteLine($"Fallback id: {fallback.Id}"); + Console.WriteLine(fallback.Idx); + Console.WriteLine(fallback.Points); + Console.WriteLine(fallback.DateSent); + }); +} From b5179f3a5ba7bfc5f9d08235e868ec57dd9ab7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 17 Dec 2024 16:10:53 +0000 Subject: [PATCH 088/142] Sms fallback -> vms example --- examples/sms/SmsWithVmsFallback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sms/SmsWithVmsFallback.cs b/examples/sms/SmsWithVmsFallback.cs index 75793c7..2ffccb6 100644 --- a/examples/sms/SmsWithVmsFallback.cs +++ b/examples/sms/SmsWithVmsFallback.cs @@ -5,7 +5,7 @@ var client = new ClientOAuth("token"); var features = new Features(client); -const string recipient = "48322320667"; +const string recipient = "4850010010"; const string message = "message"; var sendResult = features.SMS() From 963297d1b6e85f6bcd0dae1bd05faae4ca7ca8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 18 Dec 2024 11:35:17 +0000 Subject: [PATCH 089/142] Add Subusers feature #creation --- .../Action/Subusers/Creation/CreateSubuser.cs | 86 ++++++++++ .../Subusers/Creation/SubuserCredentials.cs | 8 + .../Action/Subusers/Creation/SubuserPoints.cs | 7 + .../Api/Response/Subusers/SubuserDetails.cs | 4 +- smsapi/Api/SubUsersFactory.cs | 10 ++ .../Subusers/CreateSubuserResponseTest.cs | 65 +++++++ .../Unit/Action/Subusers/CreateSubuserTest.cs | 162 ++++++++++++++++++ .../Fixture/SubuserCredentialsMother.cs | 11 ++ 8 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs create mode 100644 smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs create mode 100644 smsapi/Api/Action/Subusers/Creation/SubuserPoints.cs create mode 100644 smsapiTests/Unit/Action/Subusers/CreateSubuserResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs create mode 100644 smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs diff --git a/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs new file mode 100644 index 0000000..5782e51 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response.Subusers; + +namespace SMSApi.Api.Action.Subusers.Creation; + +public sealed class CreateSubuser : Action +{ + private readonly SubuserCredentials _credentials; + + private bool _active; + private string? _desription; + + private SubuserPoints? _points; + + public CreateSubuser(SubuserCredentials credentials) + { + _credentials = credentials; + } + + protected override RequestMethod Method => RequestMethod.POST; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return "subusers"; + } + + public CreateSubuser AsActive() + { + _active = true; + + return this; + } + + public CreateSubuser WithDescription(string description) + { + _desription = description; + + return this; + } + + public CreateSubuser WithPoints(SubuserPoints points) + { + _points = points; + + return this; + } + + protected override (NameValueCollection, ISet>?) Values() + { + var values = new HashSet> + { + { + ("credentials", new Dictionary + { + { "username", _credentials.Username }, + { "password", _credentials.Password }, + { "api_password", _credentials.ApiPassword } + }), + ("active", _active) + } + }; + + _desription?.Let(description => values.Add(("description", description))); + + _points?.Let(points => + { + var pointsStructure = new Dictionary(); + + points.FromAccount?.Let(fromAccount => pointsStructure.Add("from_account", fromAccount)); + points.PerMonth?.Let(perMonth => pointsStructure.Add("per_month", perMonth)); + + if (pointsStructure.Count > 0) + values.Add(("points", pointsStructure)); + }); + + return (new NameValueCollection(), values); + } +} diff --git a/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs new file mode 100644 index 0000000..a984f61 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Action.Subusers.Creation; + +public readonly record struct SubuserCredentials(string Username, string Password, string ApiPassword) +{ + public readonly string Username = Username; + public readonly string Password = Password; + public readonly string ApiPassword = ApiPassword; +} diff --git a/smsapi/Api/Action/Subusers/Creation/SubuserPoints.cs b/smsapi/Api/Action/Subusers/Creation/SubuserPoints.cs new file mode 100644 index 0000000..5277801 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/SubuserPoints.cs @@ -0,0 +1,7 @@ +namespace SMSApi.Api.Action.Subusers.Creation; + +public readonly record struct SubuserPoints(double? FromAccount = null, double? PerMonth = null) +{ + public readonly double? FromAccount = FromAccount; + public readonly double? PerMonth = PerMonth; +} diff --git a/smsapi/Api/Response/Subusers/SubuserDetails.cs b/smsapi/Api/Response/Subusers/SubuserDetails.cs index fd1a9cc..d7fec8a 100644 --- a/smsapi/Api/Response/Subusers/SubuserDetails.cs +++ b/smsapi/Api/Response/Subusers/SubuserDetails.cs @@ -1,6 +1,8 @@ +using SMSApi.Api.Response.ResponseResolver; + namespace SMSApi.Api.Response.Subusers; -public readonly record struct SubuserDetails +public readonly record struct SubuserDetails : IResponseCodeAwareResolver { public readonly bool Active; diff --git a/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs index f66bd37..cfdec60 100644 --- a/smsapi/Api/SubUsersFactory.cs +++ b/smsapi/Api/SubUsersFactory.cs @@ -1,4 +1,5 @@ using SMSApi.Api.Action.Subusers; +using SMSApi.Api.Action.Subusers.Creation; namespace SMSApi.Api; @@ -27,6 +28,15 @@ public List List() return action; } + + public CreateSubuser Create(SubuserCredentials credentials) + { + var action = new CreateSubuser(credentials); + + action.Proxy(proxy); + + return action; + } } public static class SubusersFeatureRegister diff --git a/smsapiTests/Unit/Action/Subusers/CreateSubuserResponseTest.cs b/smsapiTests/Unit/Action/Subusers/CreateSubuserResponseTest.cs new file mode 100644 index 0000000..d7da7b2 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/CreateSubuserResponseTest.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Action.Subusers.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class CreateSubuserResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void create_subuser(bool active) + { + var id = "655B26893332330011B0B297"; + var username = "subuser_name"; + var description = "any description"; + var fromAccountPoints = Random.Shared.NextDouble(); + var perMonthPoints = Random.Shared.NextDouble(); + var response = + new Dictionary + { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, + { + "points", new Dictionary + { + { "from_account", fromAccountPoints }, + { "per_month", perMonthPoints } + } + } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + var result = CreateSubuser(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(username, result.Username); + Assert.AreEqual(active, result.Active); + Assert.AreEqual(description, result.Description); + Assert.AreEqual(new UserPoints(fromAccountPoints, perMonthPoints), result.Points); + } + + private SubuserDetails CreateSubuser() + { + var action = new CreateSubuser(SubuserCredentialsMother.Any()); + action.Proxy(_proxyStub); + + return action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs b/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs new file mode 100644 index 0000000..f0ee6c9 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs @@ -0,0 +1,162 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using smsapiTests.Unit.Action.Subusers.Fixture; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class CreateSubuserTest +{ + private readonly ProxyAssert _proxyAssert; + private readonly SpyProxy _spyProxy = new(); + + public CreateSubuserTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void use_post_request_method() + { + CreateSubuser(SubuserCredentialsMother.Any()).Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.POST); + } + + [TestMethod] + public void request_proper_uri() + { + CreateSubuser(SubuserCredentialsMother.Any()).Execute(); + + _proxyAssert.AssertUriEquals("subusers"); + } + + [TestMethod] + public void default_activity_is_false() + { + CreateSubuser(SubuserCredentialsMother.Any()).Execute(); + + _proxyAssert + .AssertParametersCount(2) //credentials + active + .AssertParametersContain("active", false); + } + + [TestMethod] + public void make_user_active() + { + CreateSubuser(SubuserCredentialsMother.Any()) + .AsActive() + .Execute(); + + _proxyAssert + .AssertParametersCount(2) //credentials + active + .AssertParametersContain("active", true); + } + + [TestMethod] + public void request_contains_credentials() + { + var username = "new_username"; + var password = "password"; + var apiPassword = "api_password"; + var credentials = new SubuserCredentials(username, password, apiPassword); + + CreateSubuser(credentials).Execute(); + + var expectedCredentials = new Dictionary + { + { "username", username }, + { "password", password }, + { "api_password", apiPassword } + }; + _proxyAssert + .AssertParametersCount(2) //credentials + active + .AssertParametersContain("credentials", expectedCredentials); + } + + [TestMethod] + public void set_description() + { + var description = "any description"; + CreateSubuser(SubuserCredentialsMother.Any()) + .WithDescription(description) + .Execute(); + + _proxyAssert + .AssertParametersCount(3) //credentials + active + .AssertParametersContain("description", description); + } + + [TestMethod] + public void do_not_send_points_when_empty() + { + var points = new SubuserPoints(); + CreateSubuser(SubuserCredentialsMother.Any()) + .WithPoints(points) + .Execute(); + + _proxyAssert + .AssertParametersCount(2) //credentials + active + .AssertParametersDoesNotContain("points"); + } + + [TestMethod] + public void send_only_from_account_points_value() + { + var fromAccount = 10; + var points = new SubuserPoints(fromAccount); + CreateSubuser(SubuserCredentialsMother.Any()) + .WithPoints(points) + .Execute(); + + var expectedPoints = new Dictionary { { "from_account", fromAccount } }; + _proxyAssert + .AssertParametersCount(3) //credentials + active + .AssertParametersContain("points", expectedPoints); + } + + [TestMethod] + public void send_only_per_month_points_value() + { + var perMonth = 10; + var points = new SubuserPoints(PerMonth: perMonth); + CreateSubuser(SubuserCredentialsMother.Any()) + .WithPoints(points) + .Execute(); + + var expectedPoints = new Dictionary { { "per_month", perMonth } }; + _proxyAssert + .AssertParametersCount(3) //credentials + active + .AssertParametersContain("points", expectedPoints); + } + + [TestMethod] + public void send_from_account_and_per_month_points_value() + { + var fromAccount = 15; + var perMonth = 10; + var points = new SubuserPoints(fromAccount, perMonth); + CreateSubuser(SubuserCredentialsMother.Any()) + .WithPoints(points) + .Execute(); + + var expectedPoints = new Dictionary + { + { "from_account", fromAccount }, + { "per_month", perMonth } + }; + _proxyAssert + .AssertParametersCount(3) //credentials + active + .AssertParametersContain("points", expectedPoints); + } + + private CreateSubuser CreateSubuser(SubuserCredentials credentials) + { + var action = new CreateSubuser(credentials); + action.Proxy(_spyProxy); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs new file mode 100644 index 0000000..b3dbaba --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs @@ -0,0 +1,11 @@ +using SMSApi.Api.Action.Subusers.Creation; + +namespace smsapiTests.Unit.Action.Subusers.Fixture; + +public static class SubuserCredentialsMother +{ + public static SubuserCredentials Any() + { + return new SubuserCredentials("any", "any", "any"); + } +} From 4c0c1a2f4e7738b4337c6db3d816d9691434d1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 18 Dec 2024 12:34:37 +0000 Subject: [PATCH 090/142] Add Subusers feature #get --- smsapi/Api/Action/Subusers/GetSubuser.cs | 19 +++++ smsapi/Api/SubUsersFactory.cs | 9 +++ .../Action/Subusers/GetSubuserResponseTest.cs | 81 +++++++++++++++++++ .../Unit/Action/Subusers/GetSubuserTest.cs | 43 ++++++++++ 4 files changed, 152 insertions(+) create mode 100644 smsapi/Api/Action/Subusers/GetSubuser.cs create mode 100644 smsapiTests/Unit/Action/Subusers/GetSubuserResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Subusers/GetSubuserTest.cs diff --git a/smsapi/Api/Action/Subusers/GetSubuser.cs b/smsapi/Api/Action/Subusers/GetSubuser.cs new file mode 100644 index 0000000..b195979 --- /dev/null +++ b/smsapi/Api/Action/Subusers/GetSubuser.cs @@ -0,0 +1,19 @@ +using SMSApi.Api.Response.Subusers; + +namespace SMSApi.Api.Action.Subusers; + +public class GetSubuser : Action +{ + private readonly string _userId; + + public GetSubuser(string userId) + { + _userId = userId; + } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override string Uri() => $"subusers/{_userId}"; + + protected override ApiType ApiType() => Action.ApiType.Rest; +} diff --git a/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs index cfdec60..1bd4c27 100644 --- a/smsapi/Api/SubUsersFactory.cs +++ b/smsapi/Api/SubUsersFactory.cs @@ -37,6 +37,15 @@ public CreateSubuser Create(SubuserCredentials credentials) return action; } + + public GetSubuser Get(string userId) + { + var action = new GetSubuser(userId); + + action.Proxy(proxy); + + return action; + } } public static class SubusersFeatureRegister diff --git a/smsapiTests/Unit/Action/Subusers/GetSubuserResponseTest.cs b/smsapiTests/Unit/Action/Subusers/GetSubuserResponseTest.cs new file mode 100644 index 0000000..defb121 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/GetSubuserResponseTest.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers; +using smsapi.Api.Response.REST.Exception; +using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class GetSubuserResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void get_subuser(bool active) + { + var id = "655B26893332330011B0B297"; + var username = "subuser_name"; + var description = "any description"; + var fromAccountPoints = Random.Shared.NextDouble(); + var perMonthPoints = Random.Shared.NextDouble(); + var response = + new Dictionary + { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, + { + "points", new Dictionary + { + { "from_account", fromAccountPoints }, + { "per_month", perMonthPoints } + } + } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetSubuser(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(username, result.Username); + Assert.AreEqual(active, result.Active); + Assert.AreEqual(description, result.Description); + Assert.AreEqual(new UserPoints(fromAccountPoints, perMonthPoints), result.Points); + } + + [TestMethod] + public void map_http_404_to_not_found_exception() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NotFound + ); + + var result = () => + { + GetSubuser(); + }; + + Assert.ThrowsException(result); + } + + private SubuserDetails GetSubuser() + { + var action = new GetSubuser("any"); + action.Proxy(_proxyStub); + + return action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Subusers/GetSubuserTest.cs b/smsapiTests/Unit/Action/Subusers/GetSubuserTest.cs new file mode 100644 index 0000000..31313f0 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/GetSubuserTest.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class GetSubuserTest +{ + private readonly ProxyAssert _proxyAssert; + private readonly SpyProxy _spyProxy = new(); + + public GetSubuserTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void use_get_request_method() + { + GetSubuser().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + [TestMethod] + public void request_proper_uri() + { + var userId = "1238f47da26ee45dc41fb987"; + + GetSubuser(userId).Execute(); + + _proxyAssert.AssertUriEquals($"subusers/{userId}"); + } + + private GetSubuser GetSubuser(string id = "any") + { + var action = new GetSubuser(id); + action.Proxy(_spyProxy); + + return action; + } +} From 16410aaca7df802d019a9b5af477a16d344d4c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 18 Dec 2024 13:12:12 +0000 Subject: [PATCH 091/142] Add Subusers feature #creation --- smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs | 1 - smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs | 3 +-- smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs | 4 +--- .../Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs index 5782e51..354eb44 100644 --- a/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs +++ b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs @@ -62,7 +62,6 @@ protected override (NameValueCollection, ISet>?) { { "username", _credentials.Username }, { "password", _credentials.Password }, - { "api_password", _credentials.ApiPassword } }), ("active", _active) } diff --git a/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs index a984f61..7beb0f0 100644 --- a/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs +++ b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs @@ -1,8 +1,7 @@ namespace SMSApi.Api.Action.Subusers.Creation; -public readonly record struct SubuserCredentials(string Username, string Password, string ApiPassword) +public readonly record struct SubuserCredentials(string Username, string Password) { public readonly string Username = Username; public readonly string Password = Password; - public readonly string ApiPassword = ApiPassword; } diff --git a/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs b/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs index f0ee6c9..3852589 100644 --- a/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs +++ b/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs @@ -60,8 +60,7 @@ public void request_contains_credentials() { var username = "new_username"; var password = "password"; - var apiPassword = "api_password"; - var credentials = new SubuserCredentials(username, password, apiPassword); + var credentials = new SubuserCredentials(username, password); CreateSubuser(credentials).Execute(); @@ -69,7 +68,6 @@ public void request_contains_credentials() { { "username", username }, { "password", password }, - { "api_password", apiPassword } }; _proxyAssert .AssertParametersCount(2) //credentials + active diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs index b3dbaba..cdcec31 100644 --- a/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs @@ -6,6 +6,6 @@ public static class SubuserCredentialsMother { public static SubuserCredentials Any() { - return new SubuserCredentials("any", "any", "any"); + return new SubuserCredentials("any", "any"); } } From 388052d796fdcbe831dd21837891a885ac2d947d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 18 Dec 2024 15:24:16 +0000 Subject: [PATCH 092/142] Add Subusers feature #deletion --- .../Action/Subusers/Creation/DeleteSubuser.cs | 19 +++++++ .../Subusers/SubuserDeletionResult.cs | 7 +++ smsapi/Api/SubUsersFactory.cs | 9 ++++ .../Subusers/DeleteSubuserResponseTest.cs | 50 +++++++++++++++++++ .../Unit/Action/Subusers/DeleteSubuserTest.cs | 43 ++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 smsapi/Api/Action/Subusers/Creation/DeleteSubuser.cs create mode 100644 smsapi/Api/Response/Subusers/SubuserDeletionResult.cs create mode 100644 smsapiTests/Unit/Action/Subusers/DeleteSubuserResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Subusers/DeleteSubuserTest.cs diff --git a/smsapi/Api/Action/Subusers/Creation/DeleteSubuser.cs b/smsapi/Api/Action/Subusers/Creation/DeleteSubuser.cs new file mode 100644 index 0000000..59435c2 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/DeleteSubuser.cs @@ -0,0 +1,19 @@ +using SMSApi.Api.Response.Subusers; + +namespace SMSApi.Api.Action.Subusers.Creation; + +public sealed class DeleteSubuser : Action +{ + private readonly string _userId; + + public DeleteSubuser(string userId) + { + _userId = userId; + } + + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override string Uri() => $"subusers/{_userId}"; + + protected override ApiType ApiType() => Action.ApiType.Rest; +} diff --git a/smsapi/Api/Response/Subusers/SubuserDeletionResult.cs b/smsapi/Api/Response/Subusers/SubuserDeletionResult.cs new file mode 100644 index 0000000..b52449f --- /dev/null +++ b/smsapi/Api/Response/Subusers/SubuserDeletionResult.cs @@ -0,0 +1,7 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Subusers; + +public sealed class SubuserDeletionResult : IResponseCodeAwareResolver +{ +} diff --git a/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs index 1bd4c27..2a70798 100644 --- a/smsapi/Api/SubUsersFactory.cs +++ b/smsapi/Api/SubUsersFactory.cs @@ -46,6 +46,15 @@ public GetSubuser Get(string userId) return action; } + + public DeleteSubuser Delete(string userId) + { + var action = new DeleteSubuser(userId); + + action.Proxy(proxy); + + return action; + } } public static class SubusersFeatureRegister diff --git a/smsapiTests/Unit/Action/Subusers/DeleteSubuserResponseTest.cs b/smsapiTests/Unit/Action/Subusers/DeleteSubuserResponseTest.cs new file mode 100644 index 0000000..3161dbe --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/DeleteSubuserResponseTest.cs @@ -0,0 +1,50 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using smsapi.Api.Response.REST.Exception; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class DeleteSubuserResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void delete_subuser() + { + var subuserId = "1238f47da26ee45dc41fb987"; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NoContent + ); + + DeleteSubuser(subuserId); + + Assert.IsTrue(true); + } + + [TestMethod] + public void map_http_404_to_not_found_exception() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + DictionaryToStreamHelper.EmptyStream, + HttpStatusCode.NotFound + ); + + var result = () => { DeleteSubuser(); }; + + Assert.ThrowsException(result); + } + + private void DeleteSubuser(string userId = "any") + { + var action = new DeleteSubuser(userId); + action.Proxy(_proxyStub); + + action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Subusers/DeleteSubuserTest.cs b/smsapiTests/Unit/Action/Subusers/DeleteSubuserTest.cs new file mode 100644 index 0000000..2b7d65d --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/DeleteSubuserTest.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class DeleteSubuserTest +{ + private readonly ProxyAssert _proxyAssert; + private readonly SpyProxy _spyProxy = new(); + + public DeleteSubuserTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void use_delete_request_method() + { + DeleteSubuser(); + + _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); + } + + [TestMethod] + public void request_proper_uri() + { + var userId = "1238f47da26ee45dc41fb987"; + + DeleteSubuser(userId); + + _proxyAssert.AssertUriEquals($"subusers/{userId}"); + } + + private void DeleteSubuser(string userId = "any") + { + var action = new DeleteSubuser(userId); + action.Proxy(_spyProxy); + + action.Execute(); + } +} \ No newline at end of file From fcebc5221c862db49d4707404e55ad465778fdff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 09:22:40 +0000 Subject: [PATCH 093/142] Add Subusers feature #edit --- .../Action/Subusers/Creation/EditSubuser.cs | 98 ++++++++++ .../Subusers/EditSubuserResponseTest.cs | 64 +++++++ .../Unit/Action/Subusers/EditSubuserTest.cs | 169 ++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 smsapi/Api/Action/Subusers/Creation/EditSubuser.cs create mode 100644 smsapiTests/Unit/Action/Subusers/EditSubuserResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Subusers/EditSubuserTest.cs diff --git a/smsapi/Api/Action/Subusers/Creation/EditSubuser.cs b/smsapi/Api/Action/Subusers/Creation/EditSubuser.cs new file mode 100644 index 0000000..7e8cbd3 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/EditSubuser.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response.Subusers; + +namespace SMSApi.Api.Action.Subusers.Creation; + +public sealed class EditSubuser : Action +{ + private readonly string _userId; + + private bool? _active; + private string? _desription; + private string? _password; + private SubuserPoints? _points; + + public EditSubuser(string userId) + { + _userId = userId; + } + + protected override RequestMethod Method => RequestMethod.PUT; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override ApiType ApiType() + { + return Action.ApiType.Rest; + } + + protected override string Uri() + { + return $"subusers/{_userId}"; + } + + public EditSubuser Activate() + { + _active = true; + + return this; + } + + public EditSubuser Deactivate() + { + _active = false; + + return this; + } + + public EditSubuser ChangeDescription(string description) + { + _desription = description; + + return this; + } + + public EditSubuser ChangePoints(SubuserPoints points) + { + _points = points; + + return this; + } + + public EditSubuser ChangePassword(string newPassword) + { + _password = newPassword; + + return this; + } + + protected override (NameValueCollection, ISet>?) Values() + { + var values = new HashSet>(); + + _active?.Let(newStatus => values.Add(("active", newStatus))); + + _password?.Let(newPassword => + { + values.Add( + ("credentials", new Dictionary { { "password", newPassword } }) + ); + }); + + _desription?.Let(newDescription => values.Add(("description", newDescription))); + + _points?.Let(points => + { + var pointsStructure = new Dictionary(); + + points.FromAccount?.Let(fromAccount => pointsStructure.Add("from_account", fromAccount)); + points.PerMonth?.Let(perMonth => pointsStructure.Add("per_month", perMonth)); + + if (pointsStructure.Count > 0) + values.Add(("points", pointsStructure)); + }); + + return (new NameValueCollection(), values); + } +} diff --git a/smsapiTests/Unit/Action/Subusers/EditSubuserResponseTest.cs b/smsapiTests/Unit/Action/Subusers/EditSubuserResponseTest.cs new file mode 100644 index 0000000..2a24e41 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/EditSubuserResponseTest.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class EditSubuserResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void edit_subuser(bool active) + { + var id = "655B26893332330011B0B297"; + var username = "subuser_name"; + var description = "any description"; + var fromAccountPoints = Random.Shared.NextDouble(); + var perMonthPoints = Random.Shared.NextDouble(); + var response = + new Dictionary + { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, + { + "points", new Dictionary + { + { "from_account", fromAccountPoints }, + { "per_month", perMonthPoints } + } + } + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + var result = EditSubuser(); + + Assert.AreEqual(id, result.Id); + Assert.AreEqual(username, result.Username); + Assert.AreEqual(active, result.Active); + Assert.AreEqual(description, result.Description); + Assert.AreEqual(new UserPoints(fromAccountPoints, perMonthPoints), result.Points); + } + + private SubuserDetails EditSubuser() + { + var action = new EditSubuser("any"); + action.Proxy(_proxyStub); + + return action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Subusers/EditSubuserTest.cs b/smsapiTests/Unit/Action/Subusers/EditSubuserTest.cs new file mode 100644 index 0000000..8888196 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/EditSubuserTest.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; + +namespace smsapiTests.Unit.Action.Subusers; + +[TestClass] +public class EditSubuserTest +{ + private readonly ProxyAssert _proxyAssert; + private readonly SpyProxy _spyProxy = new(); + + public EditSubuserTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void use_put_request_method() + { + EditSubuser().Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.PUT); + } + + [TestMethod] + public void request_proper_uri() + { + var userId = "1238f47da26ee45dc41fb987"; + + EditSubuser(userId).Execute(); + + _proxyAssert.AssertUriEquals($"subusers/{userId}"); + } + + [TestMethod] + public void do_not_change_anything_when_not_requested() + { + EditSubuser().Execute(); + + _proxyAssert.AssertNoParameters(); + } + + [TestMethod] + public void activate_user() + { + EditSubuser() + .Activate() + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("active", true); + } + + [TestMethod] + public void deactivate_user() + { + EditSubuser() + .Deactivate() + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("active", false); + } + + [TestMethod] + public void change_password() + { + var newPassword = "newPassword"; + + EditSubuser() + .ChangePassword(newPassword) + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("credentials", new Dictionary { { "password", newPassword } }); + } + + [TestMethod] + public void change_description() + { + var newDescription = "any description"; + + EditSubuser() + .ChangeDescription(newDescription) + .Execute(); + + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("description", newDescription); + } + + [TestMethod] + public void do_not_change_points_when_empty() + { + var emptyPoints = new SubuserPoints(); + + EditSubuser() + .ChangePoints(emptyPoints) + .Execute(); + + _proxyAssert.AssertNoParameters(); + } + + [TestMethod] + public void send_only_from_account_points_value() + { + var fromAccount = 10; + var points = new SubuserPoints(fromAccount); + + EditSubuser() + .ChangePoints(points) + .Execute(); + + var expectedPoints = new Dictionary { { "from_account", fromAccount } }; + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("points", expectedPoints); + } + + [TestMethod] + public void send_only_per_month_points_value() + { + var perMonth = 10; + var points = new SubuserPoints(PerMonth: perMonth); + + EditSubuser() + .ChangePoints(points) + .Execute(); + + var expectedPoints = new Dictionary { { "per_month", perMonth } }; + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("points", expectedPoints); + } + + [TestMethod] + public void send_from_account_and_per_month_points_value() + { + var fromAccount = 15; + var perMonth = 10; + var points = new SubuserPoints(fromAccount, perMonth); + + EditSubuser() + .ChangePoints(points) + .Execute(); + + var expectedPoints = new Dictionary + { + { "from_account", fromAccount }, + { "per_month", perMonth } + }; + _proxyAssert + .AssertParametersCount(1) + .AssertParametersContain("points", expectedPoints); + } + + private EditSubuser EditSubuser(string userId = "any") + { + var action = new EditSubuser(userId); + action.Proxy(_spyProxy); + + return action; + } +} From 670b815111b4910920cde5bf122caf0da106c275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 09:26:53 +0000 Subject: [PATCH 094/142] Add Subusers feature #edit --- smsapi/Api/SubUsersFactory.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs index 2a70798..c6755a5 100644 --- a/smsapi/Api/SubUsersFactory.cs +++ b/smsapi/Api/SubUsersFactory.cs @@ -55,6 +55,15 @@ public DeleteSubuser Delete(string userId) return action; } + + public EditSubuser Edit(string userId) + { + var action = new EditSubuser(userId); + + action.Proxy(proxy); + + return action; + } } public static class SubusersFeatureRegister From 4a398cfeaa1c87a6ff65e42e95f540ebe8ed4d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 09:31:44 +0000 Subject: [PATCH 095/142] Add Subusers feature #examples --- examples/subusers/Activate.cs | 20 ++++++++++++++++++++ examples/subusers/Create.cs | 30 ++++++++++++++++++++++++++++++ examples/subusers/Deactivate.cs | 20 ++++++++++++++++++++ examples/subusers/Delete.cs | 19 +++++++++++++++++++ examples/subusers/Edit.cs | 29 +++++++++++++++++++++++++++++ examples/subusers/Get.cs | 22 ++++++++++++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 examples/subusers/Activate.cs create mode 100644 examples/subusers/Create.cs create mode 100644 examples/subusers/Deactivate.cs create mode 100644 examples/subusers/Delete.cs create mode 100644 examples/subusers/Edit.cs create mode 100644 examples/subusers/Get.cs diff --git a/examples/subusers/Activate.cs b/examples/subusers/Activate.cs new file mode 100644 index 0000000..735e939 --- /dev/null +++ b/examples/subusers/Activate.cs @@ -0,0 +1,20 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string subuserId = "593FAFB33361354EAF84E7A2"; + +try +{ + features.Subusers() + .Edit(subuserId) + .Activate() + .Execute(); + + //subuser is activated at this point +} +catch (NotFoundException) +{ +} diff --git a/examples/subusers/Create.cs b/examples/subusers/Create.cs new file mode 100644 index 0000000..858b5df --- /dev/null +++ b/examples/subusers/Create.cs @@ -0,0 +1,30 @@ +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string username = $"new_subuser_{Random.Shared.Next()}"; +string password = ""; + +try +{ + var createdSubuser = features.Subusers() + .Create(new SubuserCredentials(username, password)) + .AsActive() //optional + .WithDescription("subuser description") //optional + .WithPoints(new SubuserPoints(FromAccount: 10, PerMonth: 5)) //optional + .Execute(); + + Console.WriteLine($"Created subuser id: {createdSubuser.Id}"); + Console.WriteLine($"Created subuser username: {createdSubuser.Username}"); + Console.WriteLine($"Created subuser status: {createdSubuser.Active}"); + Console.WriteLine($"Created subuser description: {createdSubuser.Description}"); + Console.WriteLine($"Created subuser points: {createdSubuser.Points}"); +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/examples/subusers/Deactivate.cs b/examples/subusers/Deactivate.cs new file mode 100644 index 0000000..03a8417 --- /dev/null +++ b/examples/subusers/Deactivate.cs @@ -0,0 +1,20 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string subuserId = "593FAFB33361354EAF84E7A2"; + +try +{ + features.Subusers() + .Edit(subuserId) + .Deactivate() + .Execute(); + + //subuser is deactivated at this point +} +catch (NotFoundException) +{ +} diff --git a/examples/subusers/Delete.cs b/examples/subusers/Delete.cs new file mode 100644 index 0000000..f472e64 --- /dev/null +++ b/examples/subusers/Delete.cs @@ -0,0 +1,19 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string subuserId = "593FAFB33361354EAF84E7A2"; + +try +{ + features.Subusers() + .Delete(subuserId) + .Execute(); + + //subuser is deleted at this point +} +catch (NotFoundException) +{ +} diff --git a/examples/subusers/Edit.cs b/examples/subusers/Edit.cs new file mode 100644 index 0000000..7d55818 --- /dev/null +++ b/examples/subusers/Edit.cs @@ -0,0 +1,29 @@ +using SMSApi.Api; +using SMSApi.Api.Action.Subusers.Creation; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var subuserId = "593FAFB33361354EAF84E7A2"; + +try +{ + features.Subusers() + .Edit(subuserId) + .ChangeDescription("new description") //optional + .ChangePassword("new password") //optional + .ChangePoints(new SubuserPoints( //optional + 10, // optional + 10 //optional + )) + .Execute(); +} +catch (NotFoundException) +{ +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/examples/subusers/Get.cs b/examples/subusers/Get.cs new file mode 100644 index 0000000..092a4ba --- /dev/null +++ b/examples/subusers/Get.cs @@ -0,0 +1,22 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +string subuserId = "593FAFB33361354EAF84E7A2"; + +try +{ + var createdSubuser = features.Subusers() + .Get(subuserId) + .Execute(); + + Console.WriteLine($"SubuserId username: {createdSubuser.Username}"); + Console.WriteLine($"SubuserId status: {createdSubuser.Active}"); + Console.WriteLine($"SubuserId description: {createdSubuser.Description}"); + Console.WriteLine($"SubuserId points: {createdSubuser.Points}"); +} +catch (NotFoundException) +{ +} From fc721dcd1f8b693eca9d4590d70e6e7622443a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 13:52:07 +0000 Subject: [PATCH 096/142] Allow running tests locally --- Dockerfile | 18 ------------------ Makefile | 15 --------------- smsapi/smsapi.csproj | 2 +- smsapiTests/Dockerfile | 24 ++++++++++++++++++++++++ smsapiTests/Makefile | 21 +++++++++++++++++++++ smsapiTests/README.md | 32 ++++++++++++++++++++++++++++++++ smsapiTests/smsapiTests.csproj | 3 ++- 7 files changed, 80 insertions(+), 35 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Makefile create mode 100644 smsapiTests/Dockerfile create mode 100644 smsapiTests/Makefile create mode 100644 smsapiTests/README.md diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1b81ffd..0000000 --- a/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build - -RUN apt-get update \ - && apt-get install -y wget \ - && wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \ - && chmod +x dotnet-install.sh \ - && ./dotnet-install.sh --runtime aspnetcore --version 6.0.0 --install-dir /usr/share/dotnet \ - && ./dotnet-install.sh --runtime aspnetcore --version 7.0.0 --install-dir /usr/share/dotnet \ - && ./dotnet-install.sh --runtime aspnetcore --version 8.0.0 --install-dir /usr/share/dotnet \ - && rm dotnet-install.sh - -WORKDIR /app - -COPY . . - -RUN dotnet restore - -RUN dotnet build --configuration Release diff --git a/Makefile b/Makefile deleted file mode 100644 index 6477b5c..0000000 --- a/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -DOCKER_IMAGE = smsapi-tests -PROJECT_PATH = smsapiTests/smsapiTests.csproj - -.PHONY: build -build: - docker build -t $(DOCKER_IMAGE) . - -.PHONY: test -test: - docker run --rm $(DOCKER_IMAGE) \ - dotnet test $(PROJECT_PATH) --configuration Release --no-build --verbosity normal - -.PHONY: clean -clean: - docker rmi -f $(DOCKER_IMAGE) diff --git a/smsapi/smsapi.csproj b/smsapi/smsapi.csproj index c1161e9..77f44a0 100644 --- a/smsapi/smsapi.csproj +++ b/smsapi/smsapi.csproj @@ -16,7 +16,7 @@ README.md logo.jpg enable - net6.0;net7.0;net8.0;netcoreapp3.1 + net6.0;net7.0;net8.0;net9.0;netcoreapp3.1 SMSAPI.pl diff --git a/smsapiTests/Dockerfile b/smsapiTests/Dockerfile new file mode 100644 index 0000000..be01927 --- /dev/null +++ b/smsapiTests/Dockerfile @@ -0,0 +1,24 @@ +FROM debian:12-slim + +RUN apt-get update && \ + apt-get install -y wget apt-transport-https software-properties-common curl + +RUN curl https://packages.microsoft.com/keys/microsoft.asc | tee /etc/apt/trusted.gpg.d/microsoft.asc + +RUN wget https://packages.microsoft.com/config/debian/12/prod.list -O /etc/apt/sources.list.d/microsoft-prod.list + +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg + +RUN apt-get update && apt-get install -y \ + dotnet-sdk-6.0 \ + dotnet-sdk-7.0 \ + dotnet-sdk-8.0 \ + dotnet-sdk-9.0 + +WORKDIR /app + +COPY . . + +RUN dotnet restore + +RUN dotnet build --configuration Release diff --git a/smsapiTests/Makefile b/smsapiTests/Makefile new file mode 100644 index 0000000..af70ca2 --- /dev/null +++ b/smsapiTests/Makefile @@ -0,0 +1,21 @@ +DOCKER_IMAGE = smsapi-csharp-client-tests +PROJECT_PATH = smsapiTests/smsapiTests.csproj +DOTNET_VERSIONS = 6.0 7.0 8.0 9.0 + +.PHONY: build +build: + docker build -t $(DOCKER_IMAGE) -f Dockerfile ../ + +.PHONY: test +test: + @for version in $(DOTNET_VERSIONS); do \ + echo "Running tests on .NET $$version"; \ + docker run --rm \ + -w /app \ + $(DOCKER_IMAGE) \ + dotnet test $(PROJECT_PATH) --configuration Release --no-build --framework net$$version; \ + done + +.PHONY: clean +clean: + docker rmi -f $(DOCKER_IMAGE) diff --git a/smsapiTests/README.md b/smsapiTests/README.md new file mode 100644 index 0000000..313b1d1 --- /dev/null +++ b/smsapiTests/README.md @@ -0,0 +1,32 @@ +# Running Tests Locally with Makefile + +This project uses a `Makefile` to simplify the process of running tests locally. The `Makefile` defines the necessary commands to build the Docker image and run tests with multiple .NET versions. + +## Prerequisites + +Before running tests locally, ensure you have the following installed: + +- [Docker](https://www.docker.com/get-started): Docker is used to run the tests inside a container. +- [Make](https://www.gnu.org/software/make/): Make is used to invoke commands defined in the `Makefile`. + +## Project Structure + +This project includes the following key files and directories: + +- `Makefile`: The file that defines the commands for building and testing the project. +- `smsapiTests/`: The directory containing the test project (`smsapiTests.csproj`). +- `Dockerfile`: The file that defines the Docker container used to run the tests. + +## Running Tests + +The tests are executed inside a Docker container, which ensures that the correct environment is used for all .NET versions specified. + +### 1. Build project +```bash +make build +``` + +### 2. Run tests +```bash +make test +``` diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index ba13d1b..dc47028 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,7 +2,7 @@ false - net8.0 + net6.0;net7.0;net8.0;net9.0 3.0.0 12.0 enable @@ -12,6 +12,7 @@ + From 01e4861f65cf349e7bd3ceb839f0c66867cd6414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 13:56:34 +0000 Subject: [PATCH 097/142] Allow running tests locally --- smsapiTests/.dockerignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 smsapiTests/.dockerignore diff --git a/smsapiTests/.dockerignore b/smsapiTests/.dockerignore new file mode 100644 index 0000000..bfae5c0 --- /dev/null +++ b/smsapiTests/.dockerignore @@ -0,0 +1,3 @@ +**/.git +**/.gitignore +**/bin From c811c976c3a8fbf52782ac409cef8b3fbbc72227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:08:27 +0000 Subject: [PATCH 098/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index a3160ab..1bba8eb 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -28,7 +28,7 @@ jobs: run: dotnet restore - name: Build the solution - run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore + run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} From 7267a323fb195be5dc33cc2d070ac23d239408dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:10:44 +0000 Subject: [PATCH 099/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 1bba8eb..e70a02e 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -14,11 +14,12 @@ jobs: strategy: matrix: - dotnet-version: ['8.0'] + dotnet-version: ['8.0', '9.0'] steps: - name: Checkout code uses: actions/checkout@v3 + - name: Setup .NET uses: actions/setup-dotnet@v3 with: @@ -27,8 +28,8 @@ jobs: - name: Install dependencies run: dotnet restore - - name: Build the solution + - name: Build the solution with current .NET version run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal From 71c5025560abed3a25a76e681db2c23498ab1e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:11:59 +0000 Subject: [PATCH 100/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index e70a02e..1973744 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -23,7 +23,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: ${{ matrix.dotnet-version }} + dotnet-version: 9.0 - name: Install dependencies run: dotnet restore @@ -32,4 +32,4 @@ jobs: run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} From 9460bdf89500a567f29957b1b92ed1e99a69672c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:13:53 +0000 Subject: [PATCH 101/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 1973744..bf0a16b 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -32,4 +32,4 @@ jobs: run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --framework net${{ matrix.dotnet-version }} From 99b6df1a31984194f2952db93ca03dbadbf8558a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:16:20 +0000 Subject: [PATCH 102/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index bf0a16b..22200b1 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -20,16 +20,24 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup .NET + - name: Setup .NET SDK 8.0 uses: actions/setup-dotnet@v3 with: - dotnet-version: 9.0 + dotnet-version: '8.0' + + - name: Setup .NET SDK 9.0 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0' + + - name: Check .NET SDKs Installed + run: dotnet --list-sdks - name: Install dependencies run: dotnet restore - - name: Build the solution with current .NET version + - name: Build the solution run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} - name: Run unit tests - run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --framework net${{ matrix.dotnet-version }} + run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} From c022b53b3fd2c73010cb66f40192fba18b126377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:18:13 +0000 Subject: [PATCH 103/142] Allow running tests locally --- .github/workflows/run-unit-tests.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 22200b1..6f25408 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -20,15 +20,8 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup .NET SDK 8.0 + - name: Setup .NET SDKs uses: actions/setup-dotnet@v3 - with: - dotnet-version: '8.0' - - - name: Setup .NET SDK 9.0 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: '9.0' - name: Check .NET SDKs Installed run: dotnet --list-sdks @@ -37,7 +30,7 @@ jobs: run: dotnet restore - name: Build the solution - run: dotnet build smsapi/smsapi.csproj --configuration Release --no-restore --framework net${{ matrix.dotnet-version }} + run: dotnet build --configuration Release - name: Run unit tests run: dotnet test smsapiTests/smsapiTests.csproj --configuration Release --no-build --verbosity normal --framework net${{ matrix.dotnet-version }} From 570620239a89e6d674ad2cba1d32c36ea87fa9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:20:21 +0000 Subject: [PATCH 104/142] Allow running tests locally --- .github/workflows/run-unit-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 6f25408..b7af67a 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -22,6 +22,8 @@ jobs: - name: Setup .NET SDKs uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0' - name: Check .NET SDKs Installed run: dotnet --list-sdks From 5b842af14369f32f8389883cd14733bfdad79fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:22:18 +0000 Subject: [PATCH 105/142] Allow running tests locally --- .github/workflows/run-unit-tests.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index b7af67a..4b0f094 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -20,13 +20,25 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup .NET SDKs + - name: Setup .NET 6 SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: '9.0' + dotnet-version: '6.0' + + - name: Setup .NET 7 SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0' + + - name: Setup .NET 8 SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '8.0' - - name: Check .NET SDKs Installed - run: dotnet --list-sdks + - name: Setup .NET 9 SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0' - name: Install dependencies run: dotnet restore From 6b6bc714ef511b9e8b6642e6fd74dab62dfd4a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:23:08 +0000 Subject: [PATCH 106/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 4b0f094..d8a3689 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - dotnet-version: ['8.0', '9.0'] + dotnet-version: ['6.0', '7.0', '8.0', '9.0'] steps: - name: Checkout code From c578f1dbdc939d5398071f7820e742450bc9b3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:25:44 +0000 Subject: [PATCH 107/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index d8a3689..eedcc26 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -15,6 +15,7 @@ jobs: strategy: matrix: dotnet-version: ['6.0', '7.0', '8.0', '9.0'] + fail-fast: false steps: - name: Checkout code From 734724c49d2a98ca54641fc4c17cd6247f376375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 14:26:55 +0000 Subject: [PATCH 108/142] Add github workflow --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index eedcc26..4e39f5b 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: dotnet-version: ['6.0', '7.0', '8.0', '9.0'] - fail-fast: false steps: - name: Checkout code From 7f909fd50c4459c39e0d8980dcf85f0e29915049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 15:00:13 +0000 Subject: [PATCH 109/142] Adjust query composing to .net9 --- smsapi/Api/Action/Action.cs | 15 ++++++++++----- .../ListShortUrlClicksGroupedByDeviceTest.cs | 5 +++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 11c9e89..0a7e400 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -76,7 +76,7 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of protected virtual void Validate() { } - + protected virtual (NameValueCollection, ISet>?) Values() { return (new NameValueCollection(), default); @@ -115,7 +115,12 @@ private void AssignValuesToQuery(UriBuilder uriBuilder) { foreach (var item in list) { - query.Add($"{pair.Key}[]", item); + var key = $"{pair.Key}[]"; + + if (Environment.Version.Major < 9) + key = HttpUtility.UrlEncode(key); + + query.Add(key, item); } break; @@ -126,7 +131,7 @@ private void AssignValuesToQuery(UriBuilder uriBuilder) default: throw new Exception($"Unsupported query parameter type for parameter {pair.Key}"); } }); - + Console.WriteLine(query.ToString()); uriBuilder.Query = query.ToString(); } @@ -141,9 +146,9 @@ private T ProcessResponse(HttpResponseEntity responseEntity) { KeyValuePair.Create("format", "json") , }; - + Values().Item2?.Let(requestData => requestData.ToList().ForEach(data => values.Add(data))); - + foreach (string key in Values().Item1.AllKeys) { values.Add(KeyValuePair.Create(key, Values().Item1.Get(key))); diff --git a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs index cf53466..e1accaa 100644 --- a/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Web; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api; using SMSApi.Api.Action.ShortUrl; @@ -22,8 +23,8 @@ public void add_ids_to_query() var ids = new[] {"1", "2"}; CreateShortUrGroupedByDevice(ids).Execute(); - - _proxyAssert.AssertUriEquals("short_url/clicks_by_mobile_device?links[]=1&links[]=2"); + + _proxyAssert.AssertUriEquals("short_url/clicks_by_mobile_device?links%5b%5d=1&links%5b%5d=2"); } [TestMethod] From ee9622ebeb6360aba365758c2687ccb876ced0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 19 Dec 2024 15:00:48 +0000 Subject: [PATCH 110/142] Adjust query composing to .net9 --- smsapi/Api/Action/Action.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapi/Api/Action/Action.cs b/smsapi/Api/Action/Action.cs index 0a7e400..7ce51df 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -131,7 +131,7 @@ private void AssignValuesToQuery(UriBuilder uriBuilder) default: throw new Exception($"Unsupported query parameter type for parameter {pair.Key}"); } }); - Console.WriteLine(query.ToString()); + uriBuilder.Query = query.ToString(); } From 8e728a249bd34db9336236a1bc5969b0e5554bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 20 Dec 2024 09:53:57 +0000 Subject: [PATCH 111/142] Add Pagination example --- examples/Pagination.cs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/Pagination.cs diff --git a/examples/Pagination.cs b/examples/Pagination.cs new file mode 100644 index 0000000..1f2ddb5 --- /dev/null +++ b/examples/Pagination.cs @@ -0,0 +1,31 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var listSendernames = (uint collectionLimit, uint collectionOffset) => +{ + var list = features.Sendernames().List(); + + list.Limit = collectionLimit; + list.Offset = collectionOffset; + + return list.Execute(); +}; + +const uint limit = 25; +uint offset = 0; +bool hasMoreItems; + +do +{ + var sendernames = listSendernames(limit, offset); + + sendernames.Collection.ForEach(sendername => + { + Console.WriteLine($"Sender: {sendername.Sender}"); + }); + + hasMoreItems = sendernames.Size > limit + offset; + offset += limit; +} while (hasMoreItems); From 24ceeeac5144369090811630322451036287506b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 20 Dec 2024 13:18:17 +0000 Subject: [PATCH 112/142] Ease pagination usage --- examples/Pagination.cs | 30 ++---- smsapi/Api/Action/CollectionIterator.cs | 96 +++++++++++++++++++ .../LegacyJsonResponseDeserializer.cs | 4 - .../Unit/Action/CollectionIteratorTest.cs | 84 ++++++++++++++++ 4 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 smsapi/Api/Action/CollectionIterator.cs create mode 100644 smsapiTests/Unit/Action/CollectionIteratorTest.cs diff --git a/examples/Pagination.cs b/examples/Pagination.cs index 1f2ddb5..1d4383f 100644 --- a/examples/Pagination.cs +++ b/examples/Pagination.cs @@ -3,29 +3,11 @@ var client = new ClientOAuth("token"); var features = new Features(client); -var listSendernames = (uint collectionLimit, uint collectionOffset) => -{ - var list = features.Sendernames().List(); - - list.Limit = collectionLimit; - list.Offset = collectionOffset; - - return list.Execute(); -}; +var list = features.Sendernames() + .List() + .ToIterator(); -const uint limit = 25; -uint offset = 0; -bool hasMoreItems; - -do +foreach (var sendername in list) { - var sendernames = listSendernames(limit, offset); - - sendernames.Collection.ForEach(sendername => - { - Console.WriteLine($"Sender: {sendername.Sender}"); - }); - - hasMoreItems = sendernames.Size > limit + offset; - offset += limit; -} while (hasMoreItems); + Console.WriteLine(sendername.Sender); +} diff --git a/smsapi/Api/Action/CollectionIterator.cs b/smsapi/Api/Action/CollectionIterator.cs new file mode 100644 index 0000000..11cdfeb --- /dev/null +++ b/smsapi/Api/Action/CollectionIterator.cs @@ -0,0 +1,96 @@ +using System.Collections; +using System.Collections.Generic; +using SMSApi.Api.Response; + +namespace SMSApi.Api.Action; + +public sealed class CollectionIterator : IEnumerable +{ + private readonly Action> _action; + private readonly uint _limit; + + public CollectionIterator(Action> action, uint limit) + { + _action = action; + _limit = limit; + } + + public IEnumerator GetEnumerator() + { + return new ActionCollectionEnumerator(_action, _limit); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private class ActionCollectionEnumerator : IEnumerator + { + private readonly uint _limit; + private readonly Action> _action; + private List _currentBatch = new(); + private int _internalCollectionOffset; + private int _apiCollectionSize; + private uint _offset; + private uint _overallOffset; + + public ActionCollectionEnumerator(Action> action, uint limit) + { + _action = action; + _limit = limit; + } + + public bool MoveNext() + { + if (_internalCollectionOffset >= _currentBatch.Count) + { + FetchNextFromApi(); + _internalCollectionOffset = 0; + + if (_currentBatch.Count == 0 || _overallOffset >= _apiCollectionSize) return false; + } + + Current = _currentBatch[_internalCollectionOffset++]; + _overallOffset++; + + return true; + } + + public void Reset() + { + _internalCollectionOffset = 0; + _offset = 0; + _currentBatch = new(); + } + + public T Current { get; private set; } + + object IEnumerator.Current => Current; + + public void Dispose() + { + _currentBatch = new(); + } + + private void FetchNextFromApi() + { + (_action as IPaginable)!.Limit = _limit; + (_action as IPaginable)!.Offset = _offset; + + var apiResult = _action.Execute(); + _apiCollectionSize = apiResult.Size; + _currentBatch = apiResult.Collection; + + _offset += _limit; + } + } +} + +public static class ActionIteratorExtensions +{ + public static CollectionIterator ToIterator(this Action> action, uint limit = 25) + { + return new CollectionIterator(action, limit); + } +} diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index ee99e33..dd2fa60 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -30,10 +30,6 @@ public DeserializationResult Deserialize(HttpResponseEntity responseEntity { throw new HostException(e.Message, HostException.E_JSON_DECODE); } - finally - { - data?.Close(); - } return response; } diff --git a/smsapiTests/Unit/Action/CollectionIteratorTest.cs b/smsapiTests/Unit/Action/CollectionIteratorTest.cs new file mode 100644 index 0000000..3dd9e8e --- /dev/null +++ b/smsapiTests/Unit/Action/CollectionIteratorTest.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using SMSApi.Api.Response; +using SMSApi.Api.Response.ResponseResolver; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action; + +[TestClass] +public class CollectionIteratorTest +{ + private readonly ProxyStub _proxy = new(); + + [TestMethod] + public void iterate_through_single_result() + { + var mockedResult = CollectionMother.WithItems( + new Dictionary { { "i", 0 } }, + new Dictionary { { "i", 1 } } + ); + _proxy.SyncExecutionResponse = new HttpResponseEntity( + mockedResult.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetAction().ToIterator(10); + + var i = 0; + foreach (var iterableResult in result) + Assert.AreEqual(i++, iterableResult.I); + + Assert.AreEqual(2, i); + } + + [TestMethod] + public void iterate_through_pages() + { + var mockedResult = CollectionMother.WithItems( + new Dictionary { { "i", 0 } }, + new Dictionary { { "i", 1 } }, + new Dictionary { { "i", 2 } }, + new Dictionary { { "i", 3 } } + ); + _proxy.SyncExecutionResponse = new HttpResponseEntity( + mockedResult.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetAction().ToIterator(1); + + var i = 0; + foreach (var iterableResult in result) + Assert.AreEqual(i++, iterableResult.I); + + Assert.AreEqual(4, i); + } + + private IterableAction GetAction() + { + var action = new IterableAction(); + action.Proxy(_proxy); + + return action; + } + + private class IterableAction : Action>, IPaginable + { + protected override RequestMethod Method => RequestMethod.GET; + + public uint? Limit { get; set; } + public uint? Offset { get; set; } + + protected override string Uri() => ""; + } + + private class IterableResult : IResponseCodeAwareResolver + { + public int I; + } +} From 3deb4dafa4facf97ac60f90f07b73ab6487912d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 20 Dec 2024 13:32:02 +0000 Subject: [PATCH 113/142] Ease pagination usage --- .../Unit/Action/CollectionIteratorTest.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/smsapiTests/Unit/Action/CollectionIteratorTest.cs b/smsapiTests/Unit/Action/CollectionIteratorTest.cs index 3dd9e8e..ff9ac1c 100644 --- a/smsapiTests/Unit/Action/CollectionIteratorTest.cs +++ b/smsapiTests/Unit/Action/CollectionIteratorTest.cs @@ -15,6 +15,22 @@ public class CollectionIteratorTest { private readonly ProxyStub _proxy = new(); + [TestMethod] + public void empty_result() + { + var mockedResult = CollectionMother.WithItems(); + _proxy.SyncExecutionResponse = new HttpResponseEntity( + mockedResult.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetAction().ToIterator(); + + var i = 0; + foreach (var iterableResult in result) + Assert.AreEqual(i++, iterableResult.I); + } + [TestMethod] public void iterate_through_single_result() { From 207853e7d99d7cd6b54cf1122eb45dfa272e41c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 20 Dec 2024 13:35:28 +0000 Subject: [PATCH 114/142] Ease pagination usage --- smsapi/Api/Action/CollectionIterator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smsapi/Api/Action/CollectionIterator.cs b/smsapi/Api/Action/CollectionIterator.cs index 11cdfeb..80906d3 100644 --- a/smsapi/Api/Action/CollectionIterator.cs +++ b/smsapi/Api/Action/CollectionIterator.cs @@ -61,6 +61,8 @@ public void Reset() { _internalCollectionOffset = 0; _offset = 0; + _overallOffset = 0; + _apiCollectionSize = 0; _currentBatch = new(); } From 6e27aac0f791881717eaaff06f9da67e5cd753df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 30 Dec 2024 21:30:28 +0000 Subject: [PATCH 115/142] Add Sendername feature #list --- examples/sendernames/List.cs | 16 ++++++++ .../Api/Action/Sendernames/ListSendernames.cs | 19 ++++++++++ smsapi/Api/Response/Sendernames/Sendername.cs | 14 +++++++ smsapi/Api/SenderFactory.cs | 5 ++- smsapi/Api/SendernamesFactory.cs | 37 +++++++++++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 examples/sendernames/List.cs create mode 100644 smsapi/Api/Action/Sendernames/ListSendernames.cs create mode 100644 smsapi/Api/Response/Sendernames/Sendername.cs create mode 100644 smsapi/Api/SendernamesFactory.cs diff --git a/examples/sendernames/List.cs b/examples/sendernames/List.cs new file mode 100644 index 0000000..1b62c47 --- /dev/null +++ b/examples/sendernames/List.cs @@ -0,0 +1,16 @@ +using SMSApi.Api; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var sendernames = features.Sendernames() + .List() + .Execute(); + +sendernames.Collection.ForEach(sendername => +{ + Console.WriteLine($"Sender: {sendername.Sender}"); + Console.WriteLine($"Is default: {sendername.IsDefault}"); + Console.WriteLine($"Status: {sendername.Status}"); + Console.WriteLine($"Created at: {sendername.CreatedAt}"); +}); diff --git a/smsapi/Api/Action/Sendernames/ListSendernames.cs b/smsapi/Api/Action/Sendernames/ListSendernames.cs new file mode 100644 index 0000000..9d70b94 --- /dev/null +++ b/smsapi/Api/Action/Sendernames/ListSendernames.cs @@ -0,0 +1,19 @@ +using SMSApi.Api.Response; +using SMSApi.Api.Response.Sendernames; + +namespace SMSApi.Api.Action.Sendernames; + +public sealed class ListSendernames : Action>, IPaginable +{ + public uint? Limit { get; set; } + public uint? Offset { get; set; } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return "sms/sendernames"; + } +} diff --git a/smsapi/Api/Response/Sendernames/Sendername.cs b/smsapi/Api/Response/Sendernames/Sendername.cs new file mode 100644 index 0000000..9e318a1 --- /dev/null +++ b/smsapi/Api/Response/Sendernames/Sendername.cs @@ -0,0 +1,14 @@ +using System; + +namespace SMSApi.Api.Response.Sendernames; + +public readonly record struct Sendername +{ + public readonly DateTime CreatedAt; + + public readonly bool IsDefault; + + public readonly string Sender; + + public readonly string Status; +} diff --git a/smsapi/Api/SenderFactory.cs b/smsapi/Api/SenderFactory.cs index f649135..46a3c96 100644 --- a/smsapi/Api/SenderFactory.cs +++ b/smsapi/Api/SenderFactory.cs @@ -1,8 +1,10 @@ -using SMSApi.Api; +using System; +using SMSApi.Api; using SMSApi.Api.Action; namespace SMSApi.Api { + [Obsolete($"use {nameof(SendernamesFactory)} instead")] public class SenderFactory : Factory { public SenderFactory(ProxyAddress address = ProxyAddress.SmsApiIo) @@ -52,6 +54,7 @@ public SenderSetDefault ActionSetDefault(string name = null) public static class SenderFeatureRegister { + [Obsolete($"use {nameof(SendernamesFeatureRegister.Sendernames)} instead")] public static SenderFactory Sender(this Features features) { return new SenderFactory(features.Client, features.Proxy); diff --git a/smsapi/Api/SendernamesFactory.cs b/smsapi/Api/SendernamesFactory.cs new file mode 100644 index 0000000..23a9e3b --- /dev/null +++ b/smsapi/Api/SendernamesFactory.cs @@ -0,0 +1,37 @@ +using SMSApi.Api.Action.Sendernames; + +namespace SMSApi.Api; + +public class SendernamesFactory : Factory +{ + public SendernamesFactory(ProxyAddress address = ProxyAddress.SmsApiIo) + : base(address) + { + } + + public SendernamesFactory(IClient client, ProxyAddress address = ProxyAddress.SmsApiIo) + : base(client, address) + { + } + + public SendernamesFactory(IClient client, Proxy proxy) + : base(client, proxy) + { + } + + public ListSendernames List() + { + var action = new ListSendernames(); + action.Proxy(proxy); + + return action; + } +} + +public static class SendernamesFeatureRegister +{ + public static SendernamesFactory Sendernames(this Features features) + { + return new SendernamesFactory(features.Client, features.Proxy); + } +} From caae873749f84da52eb8649b0e1ba90d1c06e39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 2 Jan 2025 09:06:57 +0000 Subject: [PATCH 116/142] Revert "Ease pagination usage" This reverts commit 207853e7d99d7cd6b54cf1122eb45dfa272e41c4. --- smsapi/Api/Action/CollectionIterator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/smsapi/Api/Action/CollectionIterator.cs b/smsapi/Api/Action/CollectionIterator.cs index 80906d3..11cdfeb 100644 --- a/smsapi/Api/Action/CollectionIterator.cs +++ b/smsapi/Api/Action/CollectionIterator.cs @@ -61,8 +61,6 @@ public void Reset() { _internalCollectionOffset = 0; _offset = 0; - _overallOffset = 0; - _apiCollectionSize = 0; _currentBatch = new(); } From 2e74ac4eb76bee7f7d59170fdcdaf689c0ceb960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 2 Jan 2025 09:06:57 +0000 Subject: [PATCH 117/142] Revert "Ease pagination usage" This reverts commit 3deb4dafa4facf97ac60f90f07b73ab6487912d2. --- .../Unit/Action/CollectionIteratorTest.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/smsapiTests/Unit/Action/CollectionIteratorTest.cs b/smsapiTests/Unit/Action/CollectionIteratorTest.cs index ff9ac1c..3dd9e8e 100644 --- a/smsapiTests/Unit/Action/CollectionIteratorTest.cs +++ b/smsapiTests/Unit/Action/CollectionIteratorTest.cs @@ -15,22 +15,6 @@ public class CollectionIteratorTest { private readonly ProxyStub _proxy = new(); - [TestMethod] - public void empty_result() - { - var mockedResult = CollectionMother.WithItems(); - _proxy.SyncExecutionResponse = new HttpResponseEntity( - mockedResult.ToHttpEntityStreamTask(), - HttpStatusCode.OK - ); - - var result = GetAction().ToIterator(); - - var i = 0; - foreach (var iterableResult in result) - Assert.AreEqual(i++, iterableResult.I); - } - [TestMethod] public void iterate_through_single_result() { From 4da21444b151ae1ea7b24d4597b2826a54d9283f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 2 Jan 2025 09:06:57 +0000 Subject: [PATCH 118/142] Revert "Ease pagination usage" This reverts commit 24ceeeac5144369090811630322451036287506b. --- examples/Pagination.cs | 30 ++++-- smsapi/Api/Action/CollectionIterator.cs | 96 ------------------- .../LegacyJsonResponseDeserializer.cs | 4 + .../Unit/Action/CollectionIteratorTest.cs | 84 ---------------- 4 files changed, 28 insertions(+), 186 deletions(-) delete mode 100644 smsapi/Api/Action/CollectionIterator.cs delete mode 100644 smsapiTests/Unit/Action/CollectionIteratorTest.cs diff --git a/examples/Pagination.cs b/examples/Pagination.cs index 1d4383f..1f2ddb5 100644 --- a/examples/Pagination.cs +++ b/examples/Pagination.cs @@ -3,11 +3,29 @@ var client = new ClientOAuth("token"); var features = new Features(client); -var list = features.Sendernames() - .List() - .ToIterator(); +var listSendernames = (uint collectionLimit, uint collectionOffset) => +{ + var list = features.Sendernames().List(); + + list.Limit = collectionLimit; + list.Offset = collectionOffset; + + return list.Execute(); +}; -foreach (var sendername in list) +const uint limit = 25; +uint offset = 0; +bool hasMoreItems; + +do { - Console.WriteLine(sendername.Sender); -} + var sendernames = listSendernames(limit, offset); + + sendernames.Collection.ForEach(sendername => + { + Console.WriteLine($"Sender: {sendername.Sender}"); + }); + + hasMoreItems = sendernames.Size > limit + offset; + offset += limit; +} while (hasMoreItems); diff --git a/smsapi/Api/Action/CollectionIterator.cs b/smsapi/Api/Action/CollectionIterator.cs deleted file mode 100644 index 11cdfeb..0000000 --- a/smsapi/Api/Action/CollectionIterator.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using SMSApi.Api.Response; - -namespace SMSApi.Api.Action; - -public sealed class CollectionIterator : IEnumerable -{ - private readonly Action> _action; - private readonly uint _limit; - - public CollectionIterator(Action> action, uint limit) - { - _action = action; - _limit = limit; - } - - public IEnumerator GetEnumerator() - { - return new ActionCollectionEnumerator(_action, _limit); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private class ActionCollectionEnumerator : IEnumerator - { - private readonly uint _limit; - private readonly Action> _action; - private List _currentBatch = new(); - private int _internalCollectionOffset; - private int _apiCollectionSize; - private uint _offset; - private uint _overallOffset; - - public ActionCollectionEnumerator(Action> action, uint limit) - { - _action = action; - _limit = limit; - } - - public bool MoveNext() - { - if (_internalCollectionOffset >= _currentBatch.Count) - { - FetchNextFromApi(); - _internalCollectionOffset = 0; - - if (_currentBatch.Count == 0 || _overallOffset >= _apiCollectionSize) return false; - } - - Current = _currentBatch[_internalCollectionOffset++]; - _overallOffset++; - - return true; - } - - public void Reset() - { - _internalCollectionOffset = 0; - _offset = 0; - _currentBatch = new(); - } - - public T Current { get; private set; } - - object IEnumerator.Current => Current; - - public void Dispose() - { - _currentBatch = new(); - } - - private void FetchNextFromApi() - { - (_action as IPaginable)!.Limit = _limit; - (_action as IPaginable)!.Offset = _offset; - - var apiResult = _action.Execute(); - _apiCollectionSize = apiResult.Size; - _currentBatch = apiResult.Collection; - - _offset += _limit; - } - } -} - -public static class ActionIteratorExtensions -{ - public static CollectionIterator ToIterator(this Action> action, uint limit = 25) - { - return new CollectionIterator(action, limit); - } -} diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index dd2fa60..ee99e33 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -30,6 +30,10 @@ public DeserializationResult Deserialize(HttpResponseEntity responseEntity { throw new HostException(e.Message, HostException.E_JSON_DECODE); } + finally + { + data?.Close(); + } return response; } diff --git a/smsapiTests/Unit/Action/CollectionIteratorTest.cs b/smsapiTests/Unit/Action/CollectionIteratorTest.cs deleted file mode 100644 index 3dd9e8e..0000000 --- a/smsapiTests/Unit/Action/CollectionIteratorTest.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SMSApi.Api; -using SMSApi.Api.Action; -using SMSApi.Api.Response; -using SMSApi.Api.Response.ResponseResolver; -using smsapiTests.Unit.Fixture; -using smsapiTests.Unit.Helper; - -namespace smsapiTests.Unit.Action; - -[TestClass] -public class CollectionIteratorTest -{ - private readonly ProxyStub _proxy = new(); - - [TestMethod] - public void iterate_through_single_result() - { - var mockedResult = CollectionMother.WithItems( - new Dictionary { { "i", 0 } }, - new Dictionary { { "i", 1 } } - ); - _proxy.SyncExecutionResponse = new HttpResponseEntity( - mockedResult.ToHttpEntityStreamTask(), - HttpStatusCode.OK - ); - - var result = GetAction().ToIterator(10); - - var i = 0; - foreach (var iterableResult in result) - Assert.AreEqual(i++, iterableResult.I); - - Assert.AreEqual(2, i); - } - - [TestMethod] - public void iterate_through_pages() - { - var mockedResult = CollectionMother.WithItems( - new Dictionary { { "i", 0 } }, - new Dictionary { { "i", 1 } }, - new Dictionary { { "i", 2 } }, - new Dictionary { { "i", 3 } } - ); - _proxy.SyncExecutionResponse = new HttpResponseEntity( - mockedResult.ToHttpEntityStreamTask(), - HttpStatusCode.OK - ); - - var result = GetAction().ToIterator(1); - - var i = 0; - foreach (var iterableResult in result) - Assert.AreEqual(i++, iterableResult.I); - - Assert.AreEqual(4, i); - } - - private IterableAction GetAction() - { - var action = new IterableAction(); - action.Proxy(_proxy); - - return action; - } - - private class IterableAction : Action>, IPaginable - { - protected override RequestMethod Method => RequestMethod.GET; - - public uint? Limit { get; set; } - public uint? Offset { get; set; } - - protected override string Uri() => ""; - } - - private class IterableResult : IResponseCodeAwareResolver - { - public int I; - } -} From f4c93b38e43ceca38703ae65ec0750d170ed69f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Tue, 7 Jan 2025 08:02:25 +0000 Subject: [PATCH 119/142] Fix form content payload --- smsapi/NativeHttpClientHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 891e4f2..d2a9aa4 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -63,7 +63,7 @@ private static HttpContent ConvertRequestDataToHttpContent( return new StringContent(JsonSerializer.Serialize(collectionDictionary), Encoding.UTF8, "application/json"); var contentCollection = collectionDictionary.Keys - .Select(key => new KeyValuePair(key, collectionDictionary[key])) + .Select(key => new KeyValuePair(key, collectionDictionary[key]?.ToString())) .ToList(); var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); From d14c64eb7999f603b14e603eda18ad77334671ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 9 Jan 2025 12:12:54 +0000 Subject: [PATCH 120/142] Fix form content payload --- smsapi/NativeHttpClientHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index d2a9aa4..970cdeb 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -75,7 +75,7 @@ private static HttpContent ConvertRequestDataToHttpContent( streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"file\"", - FileName = "\"abc\"" + FileName = "\"file\"" }; var content = new MultipartFormDataContent @@ -83,7 +83,7 @@ private static HttpContent ConvertRequestDataToHttpContent( streamContent }; - foreach (var keyValuePair in collection) content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); + foreach (var keyValuePair in collection) content.Add(new StringContent(keyValuePair.Value?.ToString()), keyValuePair.Key); return content; } From 39a260a94ccc5df49165e97ea1dc67698e98ebfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 9 Jan 2025 12:56:23 +0000 Subject: [PATCH 121/142] Propagate shorturl filename --- examples/shortUrl/CreateFileLink.cs | 2 +- smsapi/Api/Action/ShortUrl/CreateShortUrl.cs | 6 +++--- smsapi/Api/ShortUrlFactory.cs | 2 +- smsapi/NativeHttpClientHelper.cs | 2 +- .../Unit/Action/ShortUrl/CreateShortUrlTest.cs | 10 +++++++--- smsapiTests/Unit/ProxyAssert.cs | 4 ++-- smsapiTests/Unit/SpyProxy.cs | 12 ++++++++---- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/examples/shortUrl/CreateFileLink.cs b/examples/shortUrl/CreateFileLink.cs index 745f671..0a287fc 100644 --- a/examples/shortUrl/CreateFileLink.cs +++ b/examples/shortUrl/CreateFileLink.cs @@ -7,7 +7,7 @@ var features = new Features(client); const string name = "abc"; -var file = new FileStream("", FileMode.Open); +var file = new FileInfo(""); try { diff --git a/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs index d274b8d..269b518 100644 --- a/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs +++ b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs @@ -21,7 +21,7 @@ public enum ShortUrlExpirationUnit private string? _description; private (uint, string)? _expireAt; - private readonly Stream? _file; + private readonly FileInfo? _file; private readonly string _name; private readonly string? _url; @@ -32,7 +32,7 @@ public CreateShortUrl(string name, string url) _url = url; } - public CreateShortUrl(string name, Stream file) + public CreateShortUrl(string name, FileInfo file) { _name = name; _file = file; @@ -93,7 +93,7 @@ protected override Dictionary Files() { var files = new Dictionary(); - _file?.Let(file => files.Add("file", file)); + _file?.Let(file => files.Add(file.Name, file.OpenRead())); return files; } diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs index 441d339..857da47 100644 --- a/smsapi/Api/ShortUrlFactory.cs +++ b/smsapi/Api/ShortUrlFactory.cs @@ -36,7 +36,7 @@ public CreateShortUrl Create(string name, string uri) return action; } - public CreateShortUrl Create(string name, Stream file) + public CreateShortUrl Create(string name, FileInfo file) { var action = new CreateShortUrl(name, file); action.Proxy(proxy); diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 970cdeb..361d4ef 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -75,7 +75,7 @@ private static HttpContent ConvertRequestDataToHttpContent( streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"file\"", - FileName = "\"file\"" + FileName = $"\"{files.Keys.First()}\"" }; var content = new MultipartFormDataContent diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs index 0b63d0c..ad2d805 100644 --- a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs @@ -62,14 +62,18 @@ public void send_description() public void send_name_and_file() { var name = "fancy name"; - var file = new MemoryStream(); + var fileName = "richMedia.txt"; + var fileContent = "file content"; + var filePath = Path.Combine(Path.GetTempPath(), fileName); + File.WriteAllText(filePath, fileContent); + var file = new FileInfo(filePath); CreateShortUrl(name, file).Execute(); _proxyAssert.AssertParametersCount(2); _proxyAssert.AssertParametersContain("name", name); _proxyAssert.AssertParametersContain("type", "FILE"); - _proxyAssert.AssertFileAttached(file); + _proxyAssert.AssertFileAttached(fileName, file.OpenRead()); } [TestMethod] @@ -96,7 +100,7 @@ private CreateShortUrl CreateShortUrl(string name, string uri) return action; } - private CreateShortUrl CreateShortUrl(string name, Stream file) + private CreateShortUrl CreateShortUrl(string name, FileInfo file) { var action = new CreateShortUrl(name, file); action.Proxy(_spyProxy); diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 2488a40..4d01049 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -51,10 +51,10 @@ public ProxyAssert AssertParametersContain(string name, string value) return this; } - public void AssertFileAttached(Stream file) + public void AssertFileAttached(string name, Stream file) { Assert.IsTrue( - proxy.Files.Contains(value: file), + proxy.Files.Contains(value: KeyValuePair.Create(name, new StreamReader(file).ReadToEnd())), "Not attached file found" ); } diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index dd02796..bcab892 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -17,7 +17,7 @@ public class SpyProxy : Proxy public RequestMethod RequestMethod { get; private set; } public Dictionary Parameters { get; } = new(); - public ICollection Files { get; } = new List(); + public ICollection> Files { get; } = new List>(); public void Authentication(IClient client) { @@ -39,7 +39,8 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISe RequestedUri = uri; SetParameters(data); RequestMethod = method; - Files.Add(file); + Files.Add(KeyValuePair.Create("", new StreamReader(file).ReadToEnd())); + file.Position = 0; return new HttpResponseEntity(new Task(() => new MemoryStream()), HttpStatusCode.OK); } @@ -49,9 +50,12 @@ public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISe RequestedUri = uri; SetParameters(data); RequestMethod = method; - foreach (var file in files.Values) + foreach (var file in files) { - Files.Add(file); + var content = new StreamReader(file.Value).ReadToEnd(); + file.Value.Position = 0; + + Files.Add(KeyValuePair.Create(file.Key, content)); } return new HttpResponseEntity(Task.FromResult(Stream.Null), HttpStatusCode.OK); From 4a8cb9045ca6d4ad6ff0177a75afa9a921a5cf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 10 Jan 2025 11:15:41 +0000 Subject: [PATCH 122/142] Support cancellation token in streams --- smsapi/NativeHttpClientHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 361d4ef..51776a0 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -31,21 +31,21 @@ public static async Task SendRequest( case RequestMethod.GET: var getResponse = await httpClient.GetAsync(uri, cancellationToken); - return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(), getResponse.StatusCode); + return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(cancellationToken), getResponse.StatusCode); case RequestMethod.POST: httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); var postResponse = await httpClient.PostAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(), postResponse.StatusCode); + return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(cancellationToken), postResponse.StatusCode); case RequestMethod.PUT: httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); var putResponse = await httpClient.PutAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(), putResponse.StatusCode); + return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(cancellationToken), putResponse.StatusCode); case RequestMethod.DELETE: var deleteResult = await httpClient.DeleteAsync(uri, cancellationToken); - return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); + return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(cancellationToken), deleteResult.StatusCode); default: throw new ArgumentOutOfRangeException(nameof(method), method, null); } From 2b16000e588cd59304cdf7d20e84996fe9577c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 10 Jan 2025 11:18:49 +0000 Subject: [PATCH 123/142] Support cancellation token in streams --- smsapi/NativeHttpClientHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 51776a0..361d4ef 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -31,21 +31,21 @@ public static async Task SendRequest( case RequestMethod.GET: var getResponse = await httpClient.GetAsync(uri, cancellationToken); - return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(cancellationToken), getResponse.StatusCode); + return new HttpResponseEntity(getResponse.Content.ReadAsStreamAsync(), getResponse.StatusCode); case RequestMethod.POST: httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); var postResponse = await httpClient.PostAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(cancellationToken), postResponse.StatusCode); + return new HttpResponseEntity(postResponse.Content.ReadAsStreamAsync(), postResponse.StatusCode); case RequestMethod.PUT: httpContent = ConvertRequestDataToHttpContent(actionContentType, body, files); var putResponse = await httpClient.PutAsync(uri, httpContent, cancellationToken); - return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(cancellationToken), putResponse.StatusCode); + return new HttpResponseEntity(putResponse.Content.ReadAsStreamAsync(), putResponse.StatusCode); case RequestMethod.DELETE: var deleteResult = await httpClient.DeleteAsync(uri, cancellationToken); - return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(cancellationToken), deleteResult.StatusCode); + return new HttpResponseEntity(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); default: throw new ArgumentOutOfRangeException(nameof(method), method, null); } From dc18a12d427e6677954c64cdc30ddce9bf53f8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 15 Jan 2025 11:22:42 +0000 Subject: [PATCH 124/142] Support utf8 in file names --- smsapi/NativeHttpClientHelper.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 361d4ef..44c1401 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading; @@ -71,12 +70,11 @@ private static HttpContent ConvertRequestDataToHttpContent( if (files == null || files.Count == 0) return formUrlEncodedContent; var streamContent = new StreamContent(files.Values.First()); + var filename = files.Keys.First(); + var encodedFilename = Uri.EscapeDataString(filename); - streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") - { - Name = "\"file\"", - FileName = $"\"{files.Keys.First()}\"" - }; + streamContent.Headers.TryAddWithoutValidation("Content-Disposition", + $"form-data; name=\"file\"; filename=\"{filename}\"; filename*=utf-8''{encodedFilename}"); var content = new MultipartFormDataContent { From 9fed2053f62d09058ccea765de18180b93c24fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 17 Jan 2025 08:42:51 +0000 Subject: [PATCH 125/142] Propagate shorturl filename --- .../Unit/Action/ShortUrl/CreateShortUrlTest.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs index ad2d805..81fa7dd 100644 --- a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs @@ -76,6 +76,24 @@ public void send_name_and_file() _proxyAssert.AssertFileAttached(fileName, file.OpenRead()); } + [TestMethod] + public void send_file_with_unicode_chars() + { + var name = "fancy name"; + var fileName = "Gżegżółka.txt"; + var fileContent = "file content"; + var filePath = Path.Combine(Path.GetTempPath(), fileName); + File.WriteAllText(filePath, fileContent); + var file = new FileInfo(filePath); + + CreateShortUrl(name, file).Execute(); + + _proxyAssert.AssertParametersCount(2); + _proxyAssert.AssertParametersContain("name", name); + _proxyAssert.AssertParametersContain("type", "FILE"); + _proxyAssert.AssertFileAttached(fileName, file.OpenRead()); + } + [TestMethod] [DataRow(1, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Days, "days")] [DataRow(2, SMSApi.Api.Action.ShortUrl.CreateShortUrl.ShortUrlExpirationUnit.Hours, "hours")] From d981470a8c7d40dd5cd03e7df42cbeb5144eca51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 2 Oct 2025 20:15:57 +0000 Subject: [PATCH 126/142] HLR Feature --- smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs | 2 +- smsapi/Api/Response/HLR/SingleCheckResult.cs | 2 -- smsapi/HttpResponseEntity.cs | 5 +++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs index 58e452b..13d3d5d 100644 --- a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs @@ -11,7 +11,7 @@ public DeserializationResult Deserialize(HttpResponseEntity responseEntity T result; var data = responseEntity.Content.Result; - if (data.Length > 0) + if (data.Length > 0 && !responseEntity.IsEmptyContentCode) { data.Position = 0; var stringData = new StreamReader(data).ReadToEnd(); diff --git a/smsapi/Api/Response/HLR/SingleCheckResult.cs b/smsapi/Api/Response/HLR/SingleCheckResult.cs index 6bc2c7b..7947b52 100644 --- a/smsapi/Api/Response/HLR/SingleCheckResult.cs +++ b/smsapi/Api/Response/HLR/SingleCheckResult.cs @@ -1,7 +1,5 @@ -using System.Runtime.Serialization; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.HLR; -[DataContract] public readonly record struct SingleCheckResult : IResponseCodeAwareResolver; diff --git a/smsapi/HttpResponseEntity.cs b/smsapi/HttpResponseEntity.cs index 1b1d020..6ccb9aa 100644 --- a/smsapi/HttpResponseEntity.cs +++ b/smsapi/HttpResponseEntity.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Net; using System.Threading.Tasks; @@ -6,9 +7,13 @@ namespace SMSApi.Api { public readonly struct HttpResponseEntity { + private static readonly HttpStatusCode[] EmptyResponseCodes = { HttpStatusCode.Accepted, HttpStatusCode.NoContent }; + public readonly Task Content; public readonly HttpStatusCode StatusCode; + public bool IsEmptyContentCode => EmptyResponseCodes.Contains(StatusCode); + public HttpResponseEntity(Task content, HttpStatusCode statusCode) { Content = content; From e68894cc9b8da71a4249258cd1fbd0bb47e27e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 3 Oct 2025 10:03:26 +0000 Subject: [PATCH 127/142] HLR Feature --- examples/hlr/ListLookups.cs | 2 +- smsapi/Api/Response/HLR/LookupResult.cs | 12 +------ .../HLR/Fixture/LookupsCollectionMother.cs | 2 +- .../Action/HLR/ListLookupsResponseTest.cs | 7 ++-- .../Unit/Action/HLR/LookupRequestTest.cs | 2 +- .../Unit/Action/HLR/LookupResponseTest.cs | 35 +++++++++++++++++++ .../Unit/Helper/StringToStreamHelper.cs | 16 +++++++++ 7 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 smsapiTests/Unit/Action/HLR/LookupResponseTest.cs create mode 100644 smsapiTests/Unit/Helper/StringToStreamHelper.cs diff --git a/examples/hlr/ListLookups.cs b/examples/hlr/ListLookups.cs index 1e493ae..2132f6e 100644 --- a/examples/hlr/ListLookups.cs +++ b/examples/hlr/ListLookups.cs @@ -16,7 +16,7 @@ Console.WriteLine($"MCC: {r.Country?.MCC}"); Console.WriteLine($"Network name: {r.Network?.Name}"); Console.WriteLine($"MNC: {r.Network?.MNC}"); - Console.WriteLine($"Cost: {r.Cost.Points}"); + Console.WriteLine($"Cost: {r.Cost}"); Console.WriteLine($"Sent at: {r.SentAt}"); Console.WriteLine($"Error code: {r.ErrorCode}"); diff --git a/smsapi/Api/Response/HLR/LookupResult.cs b/smsapi/Api/Response/HLR/LookupResult.cs index 34443e8..8994841 100644 --- a/smsapi/Api/Response/HLR/LookupResult.cs +++ b/smsapi/Api/Response/HLR/LookupResult.cs @@ -7,7 +7,7 @@ namespace SMSApi.Api.Response.HLR; public record struct LookupResult { - public readonly LookupCost Cost; + public readonly double Cost; public readonly Country? Country; @@ -25,16 +25,6 @@ public record struct LookupResult public readonly DateTime SentAt; } -public readonly record struct LookupCost -{ - public readonly double Points; - - public LookupCost(double points) - { - Points = points; - } -} - public readonly record struct Ported { [JsonProperty("ported")] public readonly IEnumerable PortedFrom; diff --git a/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs b/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs index 1edc62f..c68cd89 100644 --- a/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs +++ b/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs @@ -16,7 +16,7 @@ public static Dictionary Lookups( string @interface, Country? country, Network? network, - LookupCost cost, + double cost, Ported? ported, uint? errorCode, DateTime sentAt diff --git a/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs b/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs index b685ebf..9c381d3 100644 --- a/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs +++ b/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs @@ -5,12 +5,11 @@ using SMSApi.Api; using SMSApi.Api.Action; using SMSApi.Api.Response.Common.Telephony; -using SMSApi.Api.Response.HLR; using smsapiTests.Unit.Action.HLR.Fixture; using smsapiTests.Unit.Fixture; using smsapiTests.Unit.Helper; -namespace smsapiTests.Unit.Action.Blacklist; +namespace smsapiTests.Unit.Action.HLR; [TestClass] public class ListLookupsResponseTest @@ -39,7 +38,7 @@ public void list_lookups_with_result() var @interface = "api"; var country = new Country("Poland", 260); var network = new Network("T-Mobile", 3); - var cost = new LookupCost(1.08); + var cost = 1.08; var sentAt = DateTime.Now; var response = LookupsCollectionMother.Lookups( id, @@ -78,7 +77,7 @@ public void list_lookups_with_error() var id = "655B26893332330011B0B297"; var phoneNumber = "48500100100"; var @interface = "api"; - var cost = new LookupCost(1.08); + var cost = 1.08; var sentAt = DateTime.Now; var response = LookupsCollectionMother.Lookups( id, diff --git a/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs b/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs index 1f505c1..448b0dc 100644 --- a/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs +++ b/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SMSApi.Api.Action; -namespace smsapiTests.Unit.Action.Blacklist; +namespace smsapiTests.Unit.Action.HLR; [TestClass] public class LookupRequestTest diff --git a/smsapiTests/Unit/Action/HLR/LookupResponseTest.cs b/smsapiTests/Unit/Action/HLR/LookupResponseTest.cs new file mode 100644 index 0000000..6a57562 --- /dev/null +++ b/smsapiTests/Unit/Action/HLR/LookupResponseTest.cs @@ -0,0 +1,35 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.HLR; + +[TestClass] +public class LookupResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void successfully_request_lookup() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + "[]".ToHttpEntityStreamTask(), + HttpStatusCode.Accepted + ); + + Lookup().Execute(); + + Assert.IsTrue(true); + } + + private Lookup Lookup() + { + var action = new Lookup("48500500"); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Helper/StringToStreamHelper.cs b/smsapiTests/Unit/Helper/StringToStreamHelper.cs new file mode 100644 index 0000000..ad71e2a --- /dev/null +++ b/smsapiTests/Unit/Helper/StringToStreamHelper.cs @@ -0,0 +1,16 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace smsapiTests.Unit.Helper; + +public static class StringToStreamHelper +{ + public static Task ToHttpEntityStreamTask(this string @string) + { + var bytes = Encoding.UTF8.GetBytes(@string); + var stream = new MemoryStream(bytes); + + return Task.FromResult(stream); + } +} From 2294c43c051c25c9e8e13b2f993dbc3711786508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 3 Oct 2025 11:39:50 +0000 Subject: [PATCH 128/142] Form request tests --- smsapi/ProxyHTTP.cs | 15 +++-- .../Integration/FormDataRequestPayloadTest.cs | 67 +++++++++++++++++++ smsapiTests/Integration/RequestAssert.cs | 11 +++ .../RequestInterceptorMiddleware.cs | 4 ++ smsapiTests/Integration/RequestStorage.cs | 3 + smsapiTests/Unit/ProxyAssert.cs | 2 +- 6 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 smsapiTests/Integration/FormDataRequestPayloadTest.cs diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index 7396704..9cbe565 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -13,7 +13,7 @@ public class ProxyHTTP : Proxy { private readonly string baseUrl; private readonly HttpClient? httpClient; - private IClient authentication; + private IClient? authentication; public ProxyHTTP(string baseUrl, HttpClient? httpClient = null) { @@ -107,18 +107,21 @@ public async Task ExecuteAsync( throw new ProxyException("Failed to get response from " + uri, e); } } - + private HttpClient CreateClient() { var client = httpClient ?? new HttpClient(); - + client.BaseAddress = new Uri(baseUrl); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", authentication.GetClientAgent()); + + authentication?.Let( + auth => client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", auth.GetClientAgent()) + ); if (authentication == null) return client; - + var authHeader = authentication.DefaultRequestHeaders; - + client.DefaultRequestHeaders.Add(authHeader.Key, authHeader.Value); return client; diff --git a/smsapiTests/Integration/FormDataRequestPayloadTest.cs b/smsapiTests/Integration/FormDataRequestPayloadTest.cs new file mode 100644 index 0000000..88de7df --- /dev/null +++ b/smsapiTests/Integration/FormDataRequestPayloadTest.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; + +namespace smsapiTests.Integration; + +[TestClass] +public class FormDataRequestPayloadTest : IntegrationTestBase +{ + [TestMethod] + [DataRow(1, "1")] + [DataRow(true, "True")] + [DataRow(false, "False")] + [DataRow(null, "")] + public void convert_types_to_string(dynamic typeRepresentation, string stringRepresentation) + { + var action = GetAction(typeRepresentation); + + action.Execute(); + + AssertRequestContainsFormParameter(stringRepresentation); + } + + private void AssertRequestContainsFormParameter(string value) + { + RequestAssert.AssertContainsFormParameter("value", value); + } + + private AnyFormDataModifyingAction GetAction(dynamic value) + { + var action = new AnyFormDataModifyingAction(value); + action.Proxy(GetProxy()); + + return action; + } + + private class AnyFormDataModifyingAction : Action + { + private dynamic _value; + + public AnyFormDataModifyingAction(dynamic value) + { + _value = value; + } + + protected override RequestMethod Method => RequestMethod.POST; + + protected override ActionContentType ContentType => ActionContentType.FormWww; + + protected override string Uri() => ""; + + protected override (NameValueCollection, ISet>?) Values() + { + return ( + new NameValueCollection(), + new HashSet> + { + KeyValuePair.Create("value", _value) + } + ); + } + } + + private class Response; +} diff --git a/smsapiTests/Integration/RequestAssert.cs b/smsapiTests/Integration/RequestAssert.cs index 5ed356d..634e2f3 100644 --- a/smsapiTests/Integration/RequestAssert.cs +++ b/smsapiTests/Integration/RequestAssert.cs @@ -1,3 +1,5 @@ +using System; +using System.Text.RegularExpressions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace smsapiTests.Integration; @@ -30,4 +32,13 @@ public static void AsserPath(string path) Assert.IsTrue(pathEquals); } + + public static void AssertContainsFormParameter(string name, string value) + { + var containsParameter = RequestStorage.FormParameters.ContainsKey(name); + Assert.IsTrue(containsParameter, $"Request does not contains {name} parameter"); + + var actualValue = RequestStorage.FormParameters[name]; + Assert.AreEqual(value, actualValue, $"Actual value: {actualValue} ({actualValue.GetType()})"); + } } diff --git a/smsapiTests/Integration/RequestInterceptorMiddleware.cs b/smsapiTests/Integration/RequestInterceptorMiddleware.cs index 2a71bea..c44fec5 100644 --- a/smsapiTests/Integration/RequestInterceptorMiddleware.cs +++ b/smsapiTests/Integration/RequestInterceptorMiddleware.cs @@ -1,3 +1,6 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -17,6 +20,7 @@ public async Task InvokeAsync(HttpContext context) RequestStorage.Method = context.Request.Method; RequestStorage.AuthorizationHeader = context.Request.Headers.Authorization; RequestStorage.UserAgentHeader = context.Request.Headers.UserAgent; + RequestStorage.FormParameters = context.Request.Form.ToDictionary(k => k.Key, v => v.Value.ToString()); RequestStorage.Path = $"{context.Request.Scheme}://{context.Request.Host.Value}{context.Request.Path.Value}"; await _next(context); diff --git a/smsapiTests/Integration/RequestStorage.cs b/smsapiTests/Integration/RequestStorage.cs index fd94ef8..393eb75 100644 --- a/smsapiTests/Integration/RequestStorage.cs +++ b/smsapiTests/Integration/RequestStorage.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace smsapiTests.Integration; public static class RequestStorage @@ -5,5 +7,6 @@ public static class RequestStorage public static string AuthorizationHeader; public static string UserAgentHeader; public static string Path; + public static Dictionary FormParameters; public static string Method; } diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index 4d01049..efc9665 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -45,7 +45,7 @@ public ProxyAssert AssertParametersContain(string name, string value) Assert.IsTrue( proxy.Parameters.Contains(value: expectedParameter), - $"Expected {value}, actual value: {proxy.Parameters[name]}" + $"Expected {value} ({name.GetType()}), actual value: {proxy.Parameters[name]} ({proxy.Parameters[name]?.GetType()})" ); return this; From 2fa8fc8120b91c0c7271a9f2406f15df108611dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 3 Oct 2025 11:52:40 +0000 Subject: [PATCH 129/142] Form request tests --- smsapi/ProxyHTTP.cs | 6 ++---- smsapiTests/Unit/ProxyAssert.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index 9cbe565..c7e3d2f 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -114,12 +114,10 @@ private HttpClient CreateClient() client.BaseAddress = new Uri(baseUrl); - authentication?.Let( - auth => client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", auth.GetClientAgent()) - ); - if (authentication == null) return client; + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", authentication.GetClientAgent()); + var authHeader = authentication.DefaultRequestHeaders; client.DefaultRequestHeaders.Add(authHeader.Key, authHeader.Value); diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs index efc9665..10ddd84 100644 --- a/smsapiTests/Unit/ProxyAssert.cs +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -45,7 +45,7 @@ public ProxyAssert AssertParametersContain(string name, string value) Assert.IsTrue( proxy.Parameters.Contains(value: expectedParameter), - $"Expected {value} ({name.GetType()}), actual value: {proxy.Parameters[name]} ({proxy.Parameters[name]?.GetType()})" + $"Expected {value} ({value.GetType()}), actual value: {proxy.Parameters[name]} ({proxy.Parameters[name]?.GetType()})" ); return this; From fd7bd700541380eead8f043bb8b443f0d9815954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Fri, 3 Oct 2025 12:47:45 +0000 Subject: [PATCH 130/142] [Feature] - Change default sendername --- examples/sendernames/ChangeDefault.cs | 25 +++++++++++ .../Sendernames/ChangeDefaultSendername.cs | 24 ++++++++++ .../ChangeDefaultSendernameResult.cs | 7 +++ smsapi/Api/SendernamesFactory.cs | 32 ++++++++++++++ .../ChangeDefaultSendernameRequestTest.cs | 44 +++++++++++++++++++ .../ChangeDefaultSendernameResponseTest.cs | 35 +++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 examples/sendernames/ChangeDefault.cs create mode 100644 smsapi/Api/Action/Sendernames/ChangeDefaultSendername.cs create mode 100644 smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameRequestTest.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameResponseTest.cs diff --git a/examples/sendernames/ChangeDefault.cs b/examples/sendernames/ChangeDefault.cs new file mode 100644 index 0000000..00ec65a --- /dev/null +++ b/examples/sendernames/ChangeDefault.cs @@ -0,0 +1,25 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string newDefaultSender = "new sender2"; + +try +{ + features.Sendernames() + .ChangeDefault(newDefaultSender) + .Execute(); + + //default sendername is changed at this point +} +catch (NotFoundException) +{ + Console.WriteLine("Sender not found"); +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/smsapi/Api/Action/Sendernames/ChangeDefaultSendername.cs b/smsapi/Api/Action/Sendernames/ChangeDefaultSendername.cs new file mode 100644 index 0000000..eb07a0f --- /dev/null +++ b/smsapi/Api/Action/Sendernames/ChangeDefaultSendername.cs @@ -0,0 +1,24 @@ +using SMSApi.Api.Response.Sendernames; + +namespace SMSApi.Api.Action.Sendernames; + +public sealed class ChangeDefaultSendername : Action +{ + private string _sender; + + public ChangeDefaultSendername(string sender) + { + _sender = sender; + } + + protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override string Uri() + { + return $"sms/sendernames/{_sender}/commands/make_default"; + } +} diff --git a/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs new file mode 100644 index 0000000..70ed569 --- /dev/null +++ b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs @@ -0,0 +1,7 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Sendernames; + +public sealed class ChangeDefaultSendernameResult : IResponseCodeAwareResolver +{ +} diff --git a/smsapi/Api/SendernamesFactory.cs b/smsapi/Api/SendernamesFactory.cs index 23a9e3b..6213e2b 100644 --- a/smsapi/Api/SendernamesFactory.cs +++ b/smsapi/Api/SendernamesFactory.cs @@ -26,6 +26,38 @@ public ListSendernames List() return action; } + + public CreateSendername Create(string sender) + { + var action = new CreateSendername(sender); + action.Proxy(proxy); + + return action; + } + + public GetSendername Get(string sender) + { + var action = new GetSendername(sender); + action.Proxy(proxy); + + return action; + } + + public DeleteSendername Delete(string sender) + { + var action = new DeleteSendername(sender); + action.Proxy(proxy); + + return action; + } + + public ChangeDefaultSendername ChangeDefault(string sender) + { + var action = new ChangeDefaultSendername(sender); + action.Proxy(proxy); + + return action; + } } public static class SendernamesFeatureRegister diff --git a/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameRequestTest.cs b/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameRequestTest.cs new file mode 100644 index 0000000..ca783f4 --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameRequestTest.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class ChangeDefaultSendernameRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public ChangeDefaultSendernameRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + var sender = "any sender"; + + Change(sender); + + var encodedSender = Uri.EscapeDataString(sender); + _proxyAssert.AssertUriEquals($"sms/sendernames/{encodedSender}/commands/make_default"); + } + + [TestMethod] + public void request_method_is_post() + { + Change(); + + _proxyAssert.AssertRequestMethod(RequestMethod.POST); + } + + private void Change(string? sender = null) + { + var action = new ChangeDefaultSendername(sender ?? "any"); + action.Proxy(_spyProxy); + action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameResponseTest.cs b/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameResponseTest.cs new file mode 100644 index 0000000..440aa34 --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/ChangeDefaultSendernameResponseTest.cs @@ -0,0 +1,35 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class ChangeDefaultSendernameResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void successfully_change_default_sendername() + { + var sender = "any sender"; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + "".ToHttpEntityStreamTask(), + HttpStatusCode.NoContent + ); + + Change(sender); + + Assert.IsTrue(true); + } + + private void Change(string? sender = null) + { + var action = new ChangeDefaultSendername(sender ?? "any"); + action.Proxy(_proxyStub); + action.Execute(); + } +} From baa77215b9f7561c512972915eccef25b4205e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 6 Oct 2025 10:42:46 +0000 Subject: [PATCH 131/142] [Feature] - Create sendername --- examples/sendernames/Create.cs | 24 +++++++++ .../Action/Sendernames/CreateSendername.cs | 34 ++++++++++++ smsapi/Api/Response/Sendernames/Sendername.cs | 3 +- .../CreateSendernameRequestTest.cs | 50 ++++++++++++++++++ .../CreateSendernameResponseTest.cs | 52 +++++++++++++++++++ smsapiTests/Unit/Helper/RandomHelper.cs | 11 ++++ 6 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 examples/sendernames/Create.cs create mode 100644 smsapi/Api/Action/Sendernames/CreateSendername.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/CreateSendernameRequestTest.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/CreateSendernameResponseTest.cs create mode 100644 smsapiTests/Unit/Helper/RandomHelper.cs diff --git a/examples/sendernames/Create.cs b/examples/sendernames/Create.cs new file mode 100644 index 0000000..328db1d --- /dev/null +++ b/examples/sendernames/Create.cs @@ -0,0 +1,24 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string newSender = "new sender"; + +try +{ + var createdSendername = features.Sendernames() + .Create(newSender) + .Execute(); + + Console.WriteLine(createdSendername.Sender); + Console.WriteLine(createdSendername.Status); + Console.WriteLine(createdSendername.IsDefault); + Console.WriteLine(createdSendername.CreatedAt); +} +catch (ValidationException ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + Console.WriteLine(validationErrorsError.Message); +} diff --git a/smsapi/Api/Action/Sendernames/CreateSendername.cs b/smsapi/Api/Action/Sendernames/CreateSendername.cs new file mode 100644 index 0000000..3056315 --- /dev/null +++ b/smsapi/Api/Action/Sendernames/CreateSendername.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using SMSApi.Api.Response.Sendernames; + +namespace SMSApi.Api.Action.Sendernames; + +public sealed class CreateSendername : Action +{ + private readonly string _sender; + + public CreateSendername(string sender) + { + _sender = sender; + } + + protected override RequestMethod Method => RequestMethod.POST; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override string Uri() + { + return "sms/sendernames"; + } + + protected override (NameValueCollection, ISet>?) Values() + { + return ( + new NameValueCollection(), + new HashSet> { KeyValuePair.Create("sender", _sender) } + ); + } +} diff --git a/smsapi/Api/Response/Sendernames/Sendername.cs b/smsapi/Api/Response/Sendernames/Sendername.cs index 9e318a1..859d024 100644 --- a/smsapi/Api/Response/Sendernames/Sendername.cs +++ b/smsapi/Api/Response/Sendernames/Sendername.cs @@ -1,8 +1,9 @@ using System; +using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response.Sendernames; -public readonly record struct Sendername +public readonly record struct Sendername : IResponseCodeAwareResolver { public readonly DateTime CreatedAt; diff --git a/smsapiTests/Unit/Action/Sendernames/CreateSendernameRequestTest.cs b/smsapiTests/Unit/Action/Sendernames/CreateSendernameRequestTest.cs new file mode 100644 index 0000000..c6c3e6f --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/CreateSendernameRequestTest.cs @@ -0,0 +1,50 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class CreateSendernameRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public CreateSendernameRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + Create(); + + _proxyAssert.AssertUriEquals("sms/sendernames"); + } + + [TestMethod] + public void request_method_is_post() + { + Create(); + + _proxyAssert.AssertRequestMethod(RequestMethod.POST); + } + + [TestMethod] + public void request_contains_sender() + { + var sender = "any sender"; + + Create(sender); + + _proxyAssert.AssertParametersContain("sender", sender); + } + + private void Create(string? sender = null) + { + var action = new CreateSendername(sender ?? "any"); + action.Proxy(_spyProxy); + action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Sendernames/CreateSendernameResponseTest.cs b/smsapiTests/Unit/Action/Sendernames/CreateSendernameResponseTest.cs new file mode 100644 index 0000000..0c3a6b5 --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/CreateSendernameResponseTest.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; +using SMSApi.Api.Response.Sendernames; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class CreateSendernameResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void map_response_to_sendername() + { + var createdAt = "2018-11-08T09:36:53+01:00"; + var isDefault = new Random().NextBoolean(); + var sender = "any sender"; + var status = "any status"; + var response = new Dictionary + { + { "created_at", createdAt }, + { "is_default", isDefault }, + { "sender", sender }, + { "status", status }, + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + var createdSendername = Create(); + + Assert.AreEqual(DateTime.Parse(createdAt), createdSendername.CreatedAt); + Assert.AreEqual(isDefault, createdSendername.IsDefault); + Assert.AreEqual(sender, createdSendername.Sender); + Assert.AreEqual(status, createdSendername.Status); + } + + private Sendername Create() + { + var action = new CreateSendername("any"); + action.Proxy(_proxyStub); + + return action.Execute(); + } +} diff --git a/smsapiTests/Unit/Helper/RandomHelper.cs b/smsapiTests/Unit/Helper/RandomHelper.cs new file mode 100644 index 0000000..aec3693 --- /dev/null +++ b/smsapiTests/Unit/Helper/RandomHelper.cs @@ -0,0 +1,11 @@ +using System; + +namespace smsapiTests.Unit.Helper; + +public static class RandomHelper +{ + public static bool NextBoolean(this Random random) + { + return random.Next() > int.MaxValue / 2; + } +} From 1a922991f0dbf9729b7eef2fedd7094f2dd288ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 20 Oct 2025 14:31:57 +0000 Subject: [PATCH 132/142] [Feature] - Sendernames --- examples/sendernames/Delete.cs | 20 +++++++ examples/sendernames/Get.cs | 22 ++++++++ .../Action/Sendernames/DeleteSendername.cs | 24 +++++++++ .../Api/Action/Sendernames/GetSendername.cs | 22 ++++++++ .../ChangeDefaultSendernameResult.cs | 2 +- .../Sendernames/DeleteSendernameResult.cs | 7 +++ .../DeleteSendernameRequestTest.cs | 44 ++++++++++++++++ .../DeleteSendernameResponseTest.cs | 35 +++++++++++++ .../Sendernames/GetSendernameReponseTest.cs | 52 +++++++++++++++++++ .../Sendernames/GetSendernameRequestTest.cs | 44 ++++++++++++++++ 10 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 examples/sendernames/Delete.cs create mode 100644 examples/sendernames/Get.cs create mode 100644 smsapi/Api/Action/Sendernames/DeleteSendername.cs create mode 100644 smsapi/Api/Action/Sendernames/GetSendername.cs create mode 100644 smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/DeleteSendernameRequestTest.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/DeleteSendernameResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/GetSendernameReponseTest.cs create mode 100644 smsapiTests/Unit/Action/Sendernames/GetSendernameRequestTest.cs diff --git a/examples/sendernames/Delete.cs b/examples/sendernames/Delete.cs new file mode 100644 index 0000000..fdfc94f --- /dev/null +++ b/examples/sendernames/Delete.cs @@ -0,0 +1,20 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string senderToDelete = "sender"; + +try +{ + features.Sendernames() + .Delete(senderToDelete) + .Execute(); + + //sendername is deleted at this point +} +catch (NotFoundException) +{ + Console.WriteLine("Sender not found"); +} diff --git a/examples/sendernames/Get.cs b/examples/sendernames/Get.cs new file mode 100644 index 0000000..65ec0eb --- /dev/null +++ b/examples/sendernames/Get.cs @@ -0,0 +1,22 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +const string existingSender = "sender"; + +try +{ + var createdSendername = features.Sendernames() + .Get(existingSender) + .Execute(); + + Console.WriteLine(createdSendername.Sender); + Console.WriteLine(createdSendername.Status); + Console.WriteLine(createdSendername.IsDefault); + Console.WriteLine(createdSendername.CreatedAt); +} +catch (NotFoundException) +{ +} diff --git a/smsapi/Api/Action/Sendernames/DeleteSendername.cs b/smsapi/Api/Action/Sendernames/DeleteSendername.cs new file mode 100644 index 0000000..cdfc933 --- /dev/null +++ b/smsapi/Api/Action/Sendernames/DeleteSendername.cs @@ -0,0 +1,24 @@ +using SMSApi.Api.Response.Sendernames; + +namespace SMSApi.Api.Action.Sendernames; + +public sealed class DeleteSendername : Action +{ + private string _sender; + + public DeleteSendername(string sender) + { + _sender = sender; + } + + protected override RequestMethod Method => RequestMethod.DELETE; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override ActionContentType ContentType => ActionContentType.Json; + + protected override string Uri() + { + return $"sms/sendernames/{_sender}"; + } +} diff --git a/smsapi/Api/Action/Sendernames/GetSendername.cs b/smsapi/Api/Action/Sendernames/GetSendername.cs new file mode 100644 index 0000000..d0a1f3b --- /dev/null +++ b/smsapi/Api/Action/Sendernames/GetSendername.cs @@ -0,0 +1,22 @@ +using SMSApi.Api.Response.Sendernames; + +namespace SMSApi.Api.Action.Sendernames; + +public sealed class GetSendername : Action +{ + private string _sender; + + public GetSendername(string sender) + { + _sender = sender; + } + + protected override RequestMethod Method => RequestMethod.GET; + + protected override ApiType ApiType() => Action.ApiType.Rest; + + protected override string Uri() + { + return $"sms/sendernames/{_sender}"; + } +} diff --git a/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs index 70ed569..01d6454 100644 --- a/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs +++ b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs @@ -2,6 +2,6 @@ namespace SMSApi.Api.Response.Sendernames; -public sealed class ChangeDefaultSendernameResult : IResponseCodeAwareResolver +public sealed class DeleteSendernameResult : IResponseCodeAwareResolver { } diff --git a/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs b/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs new file mode 100644 index 0000000..70ed569 --- /dev/null +++ b/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs @@ -0,0 +1,7 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Sendernames; + +public sealed class ChangeDefaultSendernameResult : IResponseCodeAwareResolver +{ +} diff --git a/smsapiTests/Unit/Action/Sendernames/DeleteSendernameRequestTest.cs b/smsapiTests/Unit/Action/Sendernames/DeleteSendernameRequestTest.cs new file mode 100644 index 0000000..2ed0d06 --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/DeleteSendernameRequestTest.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class DeleteSendernameRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public DeleteSendernameRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + var sender = "any sender"; + + Delete(sender); + + var encodedSender = Uri.EscapeDataString(sender); + _proxyAssert.AssertUriEquals($"sms/sendernames/{encodedSender}"); + } + + [TestMethod] + public void request_method_is_delete() + { + Delete(); + + _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); + } + + private void Delete(string? sender = null) + { + var action = new DeleteSendername(sender ?? "any"); + action.Proxy(_spyProxy); + action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Sendernames/DeleteSendernameResponseTest.cs b/smsapiTests/Unit/Action/Sendernames/DeleteSendernameResponseTest.cs new file mode 100644 index 0000000..d43cf7a --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/DeleteSendernameResponseTest.cs @@ -0,0 +1,35 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class DeleteSendernameResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void smoke_delete() + { + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + "".ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + Delete(); + + Assert.IsTrue(true); + } + + private void Delete() + { + var action = new DeleteSendername("any"); + action.Proxy(_proxyStub); + + action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Sendernames/GetSendernameReponseTest.cs b/smsapiTests/Unit/Action/Sendernames/GetSendernameReponseTest.cs new file mode 100644 index 0000000..877e396 --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/GetSendernameReponseTest.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; +using SMSApi.Api.Response.Sendernames; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class GetSendernameReponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void map_response_to_sendername() + { + var createdAt = "2018-11-08T09:36:53+01:00"; + var isDefault = new Random().NextBoolean(); + var sender = "any sender"; + var status = "any status"; + var response = new Dictionary + { + { "created_at", createdAt }, + { "is_default", isDefault }, + { "sender", sender }, + { "status", status }, + }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.Created + ); + + var sendername = Get(); + + Assert.AreEqual(DateTime.Parse(createdAt), sendername.CreatedAt); + Assert.AreEqual(isDefault, sendername.IsDefault); + Assert.AreEqual(sender, sendername.Sender); + Assert.AreEqual(status, sendername.Status); + } + + private Sendername Get() + { + var action = new GetSendername("any"); + action.Proxy(_proxyStub); + + return action.Execute(); + } +} diff --git a/smsapiTests/Unit/Action/Sendernames/GetSendernameRequestTest.cs b/smsapiTests/Unit/Action/Sendernames/GetSendernameRequestTest.cs new file mode 100644 index 0000000..35d817e --- /dev/null +++ b/smsapiTests/Unit/Action/Sendernames/GetSendernameRequestTest.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action.Sendernames; + +namespace smsapiTests.Unit.Action.Sendernames; + +[TestClass] +public class GetSendernameRequestTest +{ + private readonly SpyProxy _spyProxy = new(); + private readonly ProxyAssert _proxyAssert; + + public GetSendernameRequestTest() + { + _proxyAssert = new ProxyAssert(_spyProxy); + } + + [TestMethod] + public void uri_is_valid() + { + var sender = "any sender"; + + Get(sender); + + var encodedSender = Uri.EscapeDataString(sender); + _proxyAssert.AssertUriEquals($"sms/sendernames/{encodedSender}"); + } + + [TestMethod] + public void request_method_is_get() + { + Get(); + + _proxyAssert.AssertRequestMethod(RequestMethod.GET); + } + + private void Get(string? sender = null) + { + var action = new GetSendername(sender ?? "any"); + action.Proxy(_spyProxy); + action.Execute(); + } +} From d582c3c13aa4a48b5e861bf87a311037f8549d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Mon, 20 Oct 2025 14:33:59 +0000 Subject: [PATCH 133/142] [Feature] - Sendernames --- .../Api/Response/Sendernames/ChangeDefaultSendernameResult.cs | 2 +- smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs index 01d6454..70ed569 100644 --- a/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs +++ b/smsapi/Api/Response/Sendernames/ChangeDefaultSendernameResult.cs @@ -2,6 +2,6 @@ namespace SMSApi.Api.Response.Sendernames; -public sealed class DeleteSendernameResult : IResponseCodeAwareResolver +public sealed class ChangeDefaultSendernameResult : IResponseCodeAwareResolver { } diff --git a/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs b/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs index 70ed569..01d6454 100644 --- a/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs +++ b/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs @@ -2,6 +2,6 @@ namespace SMSApi.Api.Response.Sendernames; -public sealed class ChangeDefaultSendernameResult : IResponseCodeAwareResolver +public sealed class DeleteSendernameResult : IResponseCodeAwareResolver { } From 9c79a2e1d5a86580fe2d69d524e045af52418c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 8 Jan 2026 01:49:24 +0000 Subject: [PATCH 134/142] Accept proxy URL without trailing slash --- .../Integration/IntegrationTestBase.cs | 19 ++++++++---- smsapiTests/Integration/ProxyPathTest.cs | 29 +++++++++++++++++++ smsapiTests/Integration/RequestAssert.cs | 10 +++---- .../RequestInterceptorMiddleware.cs | 6 ++-- smsapiTests/Integration/RequestStorage.cs | 1 + smsapiTests/Integration/SendActionHelper.cs | 3 +- 6 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 smsapiTests/Integration/ProxyPathTest.cs diff --git a/smsapiTests/Integration/IntegrationTestBase.cs b/smsapiTests/Integration/IntegrationTestBase.cs index e6dcabd..e24cc52 100644 --- a/smsapiTests/Integration/IntegrationTestBase.cs +++ b/smsapiTests/Integration/IntegrationTestBase.cs @@ -10,18 +10,25 @@ namespace smsapiTests.Integration; public abstract class IntegrationTestBase { + protected virtual bool AutostartServer => true; private static string _currentHost; - + [TestInitialize] public void InitializeServer() { - RunTestServer(); + if (!AutostartServer) return; + RunTestServer(FreeHost()); } - private static void RunTestServer() + protected void InitializeServer(string host) { - _currentHost = FreeHost(); - + RunTestServer(host); + } + + private static void RunTestServer(string host) + { + _currentHost = host; + new WebHostBuilder() .UseKestrel() .UseStartup(typeof(Program)) @@ -36,7 +43,7 @@ protected static ProxyHTTP GetProxy() return new ProxyHTTP(_currentHost); } - private static string FreeHost() + protected static string FreeHost() { TcpListener l = new TcpListener(IPAddress.Loopback, 0); l.Start(); diff --git a/smsapiTests/Integration/ProxyPathTest.cs b/smsapiTests/Integration/ProxyPathTest.cs new file mode 100644 index 0000000..026825d --- /dev/null +++ b/smsapiTests/Integration/ProxyPathTest.cs @@ -0,0 +1,29 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; + +namespace smsapiTests.Integration; + +[TestClass] +public class ProxyPathTest : IntegrationTestBase +{ + protected override bool AutostartServer => true; + + [TestMethod] + public void proxy_adds_trailing_slash() + { + var client = new ClientOAuth("any"); + var host = FreeHost(); + InitializeServer(host); + var smsFactory = new SMSFactory(client, GetProxy()); + + SendAnyMessage(smsFactory); + + var expectedUri = host + "/sms.do"; + RequestAssert.AsserRawPath(expectedUri); + } + + private static void SendAnyMessage(SMSFactory smsFactory) + { + SendActionHelper.SendAnySms(smsFactory); + } +} diff --git a/smsapiTests/Integration/RequestAssert.cs b/smsapiTests/Integration/RequestAssert.cs index 634e2f3..690e0ed 100644 --- a/smsapiTests/Integration/RequestAssert.cs +++ b/smsapiTests/Integration/RequestAssert.cs @@ -1,5 +1,3 @@ -using System; -using System.Text.RegularExpressions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace smsapiTests.Integration; @@ -24,13 +22,15 @@ public static void AssertContainsUserAgentHeader(string value) Assert.IsTrue(headerExists, $"Expected {value}, Found: {RequestStorage.UserAgentHeader}"); } - public static void AsserPath(string path) + public static void AsserRawPath(string path) { + Assert.IsNotNull(RequestStorage.Path, "Missing request path"); + var pathEquals = RequestStorage - .Path + .RawPath .Equals(path); - Assert.IsTrue(pathEquals); + Assert.IsTrue(pathEquals, "Found: " + RequestStorage.RawPath); } public static void AssertContainsFormParameter(string name, string value) diff --git a/smsapiTests/Integration/RequestInterceptorMiddleware.cs b/smsapiTests/Integration/RequestInterceptorMiddleware.cs index c44fec5..d1acc85 100644 --- a/smsapiTests/Integration/RequestInterceptorMiddleware.cs +++ b/smsapiTests/Integration/RequestInterceptorMiddleware.cs @@ -1,8 +1,7 @@ -using System.Collections.Immutable; using System.Linq; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; namespace smsapiTests.Integration; @@ -20,8 +19,9 @@ public async Task InvokeAsync(HttpContext context) RequestStorage.Method = context.Request.Method; RequestStorage.AuthorizationHeader = context.Request.Headers.Authorization; RequestStorage.UserAgentHeader = context.Request.Headers.UserAgent; - RequestStorage.FormParameters = context.Request.Form.ToDictionary(k => k.Key, v => v.Value.ToString()); RequestStorage.Path = $"{context.Request.Scheme}://{context.Request.Host.Value}{context.Request.Path.Value}"; + RequestStorage.RawPath = context.Request.GetDisplayUrl(); + RequestStorage.FormParameters = context.Request.Form.ToDictionary(k => k.Key, v => v.Value.ToString()); await _next(context); } diff --git a/smsapiTests/Integration/RequestStorage.cs b/smsapiTests/Integration/RequestStorage.cs index 393eb75..91b2aa9 100644 --- a/smsapiTests/Integration/RequestStorage.cs +++ b/smsapiTests/Integration/RequestStorage.cs @@ -7,6 +7,7 @@ public static class RequestStorage public static string AuthorizationHeader; public static string UserAgentHeader; public static string Path; + public static string RawPath; public static Dictionary FormParameters; public static string Method; } diff --git a/smsapiTests/Integration/SendActionHelper.cs b/smsapiTests/Integration/SendActionHelper.cs index 80db9fc..b949ecd 100644 --- a/smsapiTests/Integration/SendActionHelper.cs +++ b/smsapiTests/Integration/SendActionHelper.cs @@ -11,8 +11,9 @@ public static void SendAnySms(SMSFactory smsFactory) { smsFactory.ActionSend("48500100100", "any").Execute(); } - catch (MissingMethodException) + catch (MissingMethodException e) { + Console.WriteLine(@"Error sending message: " + e.Message); } } From f7304382c172bf626411cdebf9143d5691de9b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 8 Jan 2026 01:55:46 +0000 Subject: [PATCH 135/142] Accept proxy URL without trailing slash --- smsapiTests/Integration/IntegrationTestBase.cs | 8 ++++---- smsapiTests/Integration/ProxyPathTest.cs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/smsapiTests/Integration/IntegrationTestBase.cs b/smsapiTests/Integration/IntegrationTestBase.cs index e24cc52..e0e8789 100644 --- a/smsapiTests/Integration/IntegrationTestBase.cs +++ b/smsapiTests/Integration/IntegrationTestBase.cs @@ -11,7 +11,7 @@ namespace smsapiTests.Integration; public abstract class IntegrationTestBase { protected virtual bool AutostartServer => true; - private static string _currentHost; + protected static string CurrentHost; [TestInitialize] public void InitializeServer() @@ -27,12 +27,12 @@ protected void InitializeServer(string host) private static void RunTestServer(string host) { - _currentHost = host; + CurrentHost = host; new WebHostBuilder() .UseKestrel() .UseStartup(typeof(Program)) - .UseUrls(_currentHost) + .UseUrls(CurrentHost) .Configure(app => app.UseMiddleware()) .Build() .Start(); @@ -40,7 +40,7 @@ private static void RunTestServer(string host) protected static ProxyHTTP GetProxy() { - return new ProxyHTTP(_currentHost); + return new ProxyHTTP(CurrentHost); } protected static string FreeHost() diff --git a/smsapiTests/Integration/ProxyPathTest.cs b/smsapiTests/Integration/ProxyPathTest.cs index 026825d..a56740b 100644 --- a/smsapiTests/Integration/ProxyPathTest.cs +++ b/smsapiTests/Integration/ProxyPathTest.cs @@ -6,13 +6,14 @@ namespace smsapiTests.Integration; [TestClass] public class ProxyPathTest : IntegrationTestBase { - protected override bool AutostartServer => true; + protected override bool AutostartServer => false; [TestMethod] public void proxy_adds_trailing_slash() { var client = new ClientOAuth("any"); var host = FreeHost(); + Assert.IsFalse(host.EndsWith("/")); InitializeServer(host); var smsFactory = new SMSFactory(client, GetProxy()); From 50e4c8b3e6a335fd8ff667fa30528406d0bc2ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Thu, 8 Jan 2026 14:13:22 +0000 Subject: [PATCH 136/142] Accept proxy URL without trailing slash --- smsapi/ProxyHTTP.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smsapi/ProxyHTTP.cs b/smsapi/ProxyHTTP.cs index c7e3d2f..1eaaba9 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -17,7 +17,7 @@ public class ProxyHTTP : Proxy public ProxyHTTP(string baseUrl, HttpClient? httpClient = null) { - this.baseUrl = baseUrl; + this.baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"; this.httpClient = httpClient; } From 90f1ec5b47b075033fd3f4be9221506ffc1904e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 20 May 2026 12:25:55 +0200 Subject: [PATCH 137/142] Support .net10 --- smsapi/smsapi.csproj | 2 +- smsapiTests/smsapiTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smsapi/smsapi.csproj b/smsapi/smsapi.csproj index 77f44a0..ca488e0 100644 --- a/smsapi/smsapi.csproj +++ b/smsapi/smsapi.csproj @@ -16,7 +16,7 @@ README.md logo.jpg enable - net6.0;net7.0;net8.0;net9.0;netcoreapp3.1 + net6.0;net7.0;net8.0;net9.0;net10.0;netcoreapp3.1 SMSAPI.pl diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index dc47028..673c76d 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,7 +2,7 @@ false - net6.0;net7.0;net8.0;net9.0 + net6.0;net7.0;net8.0;net9.0;net10.0 3.0.0 12.0 enable From 5431d6ee6166b40a7229f885ae1625da8a53b7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81abno?= Date: Wed, 20 May 2026 12:27:36 +0200 Subject: [PATCH 138/142] Fix contacts response deserialization --- smsapi/Api/Response/Contact.cs | 2 + .../LegacyJsonResponseDeserializer.cs | 12 ++-- .../ResponseResolver/ErrorAwareResponse.cs | 17 +---- .../BaseJsonDeserializerTest.cs | 55 ++++++++++++++++ ...acyResponseDeserializationExceptionTest.cs | 16 +++++ .../Unit/Response/ErrorAwareResponseTest.cs | 63 +++++++++++++++++++ 6 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 smsapiTests/Unit/Response/ErrorAwareResponseTest.cs diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index f4fa1a4..c50d784 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -57,6 +57,7 @@ public Dictionary> HandleExceptionActions() } [Obsolete("use DateCreated instead")] + [JsonIgnore] public uint DateAdd { get @@ -69,6 +70,7 @@ public uint DateAdd public DateTime? DateCreated => dateCreated; [Obsolete("use DateUpdated instead")] + [JsonIgnore] public uint DateMod { get diff --git a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index ee99e33..e0ec376 100644 --- a/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs @@ -77,9 +77,11 @@ private void HandleError(HttpResponseEntity responseEntity, DeserializationRe * 1000 Akcja dostępna tylko dla użytkownika głównego * 1001 Nieprawidłowa akcja */ - private static bool IsClientError(dynamic code) + private static bool IsClientError(string? code) { - switch (code) + if (!int.TryParse(code, out var n)) return false; + + switch (n) { case 101: case 102: @@ -101,9 +103,11 @@ private static bool IsClientError(dynamic code) * 999 Wewnętrzny błąd systemu * 201 Wewnętrzny błąd systemu */ - private static bool IsHostError(dynamic code) + private static bool IsHostError(string? code) { - switch (code) + if (!int.TryParse(code, out var n)) return false; + + switch (n) { case 8: case 201: diff --git a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs index 70eb032..5781d30 100644 --- a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs +++ b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs @@ -6,24 +6,13 @@ public class ErrorAwareResponse : IResponseCodeAwareResolver { [JsonProperty("message")] public readonly string ErrorMessage; - [JsonProperty("error")] public readonly int? ErrorCode; - - // [JsonProperty("error")] - // private JsonElement? _errorCode - // { - // set => value?.Let(val => - // { - // ErrorCode = val.ValueKind == JsonValueKind.Number ? val.GetInt32() : val.GetString(); - // }); - // } + [JsonProperty("error")] public readonly string? ErrorCode; public bool IsError() { - if (ErrorCode == null) return false; - - // if (ErrorCode is string) return ErrorCode != ""; + if (string.IsNullOrEmpty(ErrorCode)) return false; - return (ErrorCode as int? ?? 0) != 0; + return ErrorCode != "0"; } public string GetErrorMessage() diff --git a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs index 654c6e5..f27ebd6 100644 --- a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs @@ -115,6 +115,32 @@ public void deserialize_with_custom_name() Assert.AreEqual("abc", result.Field); } + [TestMethod] + public void deserialize_ignores_property_marked_with_json_ignore_avoiding_name_collision() + { + var json = new Dictionary + { + { "value", 42 } + }; + + var result = Deserialize(json); + + Assert.AreEqual(42, result.Value); + } + + [TestMethod] + public void deserialize_serialization_helper_writes_back_to_private_field() + { + var json = new Dictionary + { + { "raw_value", 5 } + }; + + var result = Deserialize(json); + + Assert.AreEqual(10, result.DoubledValue); + } + [TestMethod] public void deserialize_readonly_record_struct() { @@ -184,4 +210,33 @@ private readonly record struct ReadonlyRecordStruct { public readonly string Field; } + + private class JsonIgnoreAvoidsNameCollision + { + [JsonIgnore] + public int LegacyValue => Value * 2; + + public int Value { get; private set; } + + [JsonProperty("value")] + private int? ValueSerializationHelper + { + get => Value; + set => Value = value ?? 0; + } + } + + private class SerializationHelperBackedProperty + { + private int _value; + + public int DoubledValue => _value * 2; + + [JsonProperty("raw_value")] + private int RawValueSerializationHelper + { + get => _value; + set => _value = value; + } + } } diff --git a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationExceptionTest.cs b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationExceptionTest.cs index 2ed07bf..6e8613e 100644 --- a/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationExceptionTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationExceptionTest.cs @@ -31,6 +31,22 @@ public void throw_client_exception(int errorCode) Assert.ThrowsException(execution); } + [TestMethod] + public void throw_action_exception_for_non_numeric_error_code() + { + var action = new TestAction(); + action.Proxy(_proxyStub); + Dictionary errorResponse = new() { { "error", "contact_not_found" }, { "message", "Cannot find contact" } }; + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + errorResponse.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var execution = () => action.Execute(); + + Assert.ThrowsException(execution); + } + [TestMethod] [DynamicData(nameof(HostErrorCodes), DynamicDataSourceType.Method)] public void throw_host_exception(int errorCode) diff --git a/smsapiTests/Unit/Response/ErrorAwareResponseTest.cs b/smsapiTests/Unit/Response/ErrorAwareResponseTest.cs new file mode 100644 index 0000000..e2cacce --- /dev/null +++ b/smsapiTests/Unit/Response/ErrorAwareResponseTest.cs @@ -0,0 +1,63 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Response.Deserialization; +using SMSApi.Api.Response.ResponseResolver; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Response; + +[TestClass] +public class ErrorAwareResponseTest +{ + private readonly BaseJsonDeserializer _deserializer = new(); + + [TestMethod] + public void deserializes_string_error_code() + { + var json = "{\"message\":\"Cannot find contact\",\"error\":\"contact_not_found\",\"code\":404}"; + + var result = Deserialize(json); + + Assert.AreEqual("contact_not_found", result.ErrorCode); + Assert.AreEqual("Cannot find contact", result.ErrorMessage); + Assert.IsTrue(result.IsError()); + } + + [TestMethod] + public void deserializes_numeric_error_code_as_string() + { + var json = "{\"message\":\"unauthorized\",\"error\":101}"; + + var result = Deserialize(json); + + Assert.AreEqual("101", result.ErrorCode); + Assert.IsTrue(result.IsError()); + } + + [TestMethod] + public void is_not_error_when_error_code_is_zero() + { + var json = "{\"error\":0}"; + + var result = Deserialize(json); + + Assert.IsFalse(result.IsError()); + } + + [TestMethod] + public void is_not_error_when_error_code_is_missing() + { + var json = "{\"message\":\"ok\"}"; + + var result = Deserialize(json); + + Assert.IsFalse(result.IsError()); + } + + private T Deserialize(string json) + { + var responseEntity = new HttpResponseEntity(json.ToHttpEntityStreamTask(), HttpStatusCode.OK); + return _deserializer.Deserialize(responseEntity).Result; + } +} From 97fc5ac9060257d421525a7ab83d353a0f50bece Mon Sep 17 00:00:00 2001 From: jakublabno Date: Wed, 20 May 2026 12:39:05 +0200 Subject: [PATCH 139/142] Fix contacts response deserialization --- .../Deserialization/BaseJsonDeserializerTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs index f27ebd6..a06dd47 100644 --- a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs @@ -213,16 +213,16 @@ private readonly record struct ReadonlyRecordStruct private class JsonIgnoreAvoidsNameCollision { - [JsonIgnore] - public int LegacyValue => Value * 2; + private int _backing; - public int Value { get; private set; } + [JsonIgnore] + public int Value => _backing; [JsonProperty("value")] - private int? ValueSerializationHelper + private int ValueSerializationHelper { - get => Value; - set => Value = value ?? 0; + get => _backing; + set => _backing = value; } } From 68abc334ffa8c10e0ae17a4db8de48405c4d3f50 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Wed, 20 May 2026 12:52:41 +0200 Subject: [PATCH 140/142] Fix contacts response deserialization --- smsapi/Api/Response/Contact.cs | 2 ++ .../Response/Deserialization/PrivateFieldsContractResolver.cs | 1 + smsapi/Api/Response/Group.cs | 2 ++ 3 files changed, 5 insertions(+) diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index c50d784..77d707a 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -67,6 +67,7 @@ public uint DateAdd } } + [JsonIgnore] public DateTime? DateCreated => dateCreated; [Obsolete("use DateUpdated instead")] @@ -80,6 +81,7 @@ public uint DateMod } } + [JsonIgnore] public DateTime? DateUpdated => dateUpdated; [JsonProperty("date_add")] diff --git a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs index c482e4c..2215c08 100644 --- a/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs +++ b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs @@ -17,6 +17,7 @@ public PrivateFieldsContractResolver() protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { var jsonProperties = base.CreateProperties(type, memberSerialization) + .Where(property => !property.Ignored) .GroupBy(property => property.UnderlyingName, StringComparer.OrdinalIgnoreCase) .Select(group => group.First()) .ToHashSet(); diff --git a/smsapi/Api/Response/Group.cs b/smsapi/Api/Response/Group.cs index d2091fb..4a097d7 100644 --- a/smsapi/Api/Response/Group.cs +++ b/smsapi/Api/Response/Group.cs @@ -22,8 +22,10 @@ public class Group : ErrorAwareResponse [JsonProperty("contacts_count")] public int? ContactsCount { get; private set; } + [JsonIgnore] public DateTime? DateCreated { get; private set; } + [JsonIgnore] public DateTime? DateUpdated { get; private set; } [JsonProperty("description")] From 97fd4f044d7e83d18bcc624a32f26d73eddac980 Mon Sep 17 00:00:00 2001 From: jakublabno Date: Wed, 20 May 2026 12:56:11 +0200 Subject: [PATCH 141/142] Fix collection deserialization --- smsapi/Api/Response/BasicCollection.cs | 3 +-- .../BaseJsonDeserializerTest.cs | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/smsapi/Api/Response/BasicCollection.cs b/smsapi/Api/Response/BasicCollection.cs index c90a3ea..139d529 100644 --- a/smsapi/Api/Response/BasicCollection.cs +++ b/smsapi/Api/Response/BasicCollection.cs @@ -24,8 +24,7 @@ public List Collection return collection; } - set - { } + set => collection = value; } [Obsolete("use Size instead")] diff --git a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs index a06dd47..802245d 100644 --- a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs +++ b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs @@ -141,6 +141,20 @@ public void deserialize_serialization_helper_writes_back_to_private_field() Assert.AreEqual(10, result.DoubledValue); } + [TestMethod] + public void deserialize_populates_property_with_lazy_initializing_getter_and_field_writing_setter() + { + var json = new Dictionary> + { + { "items", new List { "a", "b", "c" } } + }; + + var result = Deserialize(json); + + Assert.AreEqual(3, result.Items.Count); + CollectionAssert.AreEqual(new[] { "a", "b", "c" }, result.Items); + } + [TestMethod] public void deserialize_readonly_record_struct() { @@ -226,6 +240,18 @@ private int ValueSerializationHelper } } + private class LazyGetterFieldBackedCollection + { + private List _items; + + [JsonProperty("items")] + public List Items + { + get => _items ??= new List(); + set => _items = value; + } + } + private class SerializationHelperBackedProperty { private int _value; From 636396f1458d1c20dcdcf5b6cc348f2c56e9320d Mon Sep 17 00:00:00 2001 From: jakublabno Date: Tue, 9 Jun 2026 12:34:21 +0200 Subject: [PATCH 142/142] Fix collection response deserialization --- smsapi/Api/Response/BasicCollection.cs | 1 + .../Action/Contacts/ListFieldsResponseTest.cs | 69 +++++++++++++++++++ .../Action/Contacts/ListGroupsResponseTest.cs | 69 +++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 smsapiTests/Unit/Action/Contacts/ListFieldsResponseTest.cs create mode 100644 smsapiTests/Unit/Action/Contacts/ListGroupsResponseTest.cs diff --git a/smsapi/Api/Response/BasicCollection.cs b/smsapi/Api/Response/BasicCollection.cs index 139d529..0f090e9 100644 --- a/smsapi/Api/Response/BasicCollection.cs +++ b/smsapi/Api/Response/BasicCollection.cs @@ -12,6 +12,7 @@ public class BasicCollection : Countable, IResponseCodeAwareResolver [JsonProperty("size")] private int _size; + [JsonProperty("collection")] public List Collection { get diff --git a/smsapiTests/Unit/Action/Contacts/ListFieldsResponseTest.cs b/smsapiTests/Unit/Action/Contacts/ListFieldsResponseTest.cs new file mode 100644 index 0000000..75b4ce0 --- /dev/null +++ b/smsapiTests/Unit/Action/Contacts/ListFieldsResponseTest.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Contacts; + +[TestClass] +public class ListFieldsResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = CollectionMother.Empty(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_fields() + { + var response = CollectionMother.WithItems( + new Dictionary + { + { "id", "1" }, + { "name", "FieldA" }, + { "type", "TEXT" } + }, + new Dictionary + { + { "id", "2" }, + { "name", "FieldB" }, + { "type", "NUMBER" } + }); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(2, result.Size); + Assert.AreEqual(2, result.Collection.Count); + var firstField = result.Collection.First(); + Assert.AreEqual("1", firstField.Id); + Assert.AreEqual("FieldA", firstField.Name); + Assert.AreEqual("TEXT", firstField.Type); + } + + private ListFields GetList() + { + var action = new ListFields(); + action.Proxy(_proxyStub); + + return action; + } +} diff --git a/smsapiTests/Unit/Action/Contacts/ListGroupsResponseTest.cs b/smsapiTests/Unit/Action/Contacts/ListGroupsResponseTest.cs new file mode 100644 index 0000000..f3eb871 --- /dev/null +++ b/smsapiTests/Unit/Action/Contacts/ListGroupsResponseTest.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; +using SMSApi.Api.Action; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.Contacts; + +[TestClass] +public class ListGroupsResponseTest +{ + private readonly ProxyStub _proxyStub = new(); + + [TestMethod] + public void empty_list() + { + var response = CollectionMother.Empty(); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(0, result.Size); + } + + [TestMethod] + public void list_groups() + { + var response = CollectionMother.WithItems( + new Dictionary + { + { "id", "1" }, + { "name", "GroupA" }, + { "contacts_count", 5 } + }, + new Dictionary + { + { "id", "2" }, + { "name", "GroupB" }, + { "contacts_count", 0 } + }); + _proxyStub.SyncExecutionResponse = new HttpResponseEntity( + response.ToHttpEntityStreamTask(), + HttpStatusCode.OK + ); + + var result = GetList().Execute(); + + Assert.AreEqual(2, result.Size); + Assert.AreEqual(2, result.Collection.Count); + var firstGroup = result.Collection.First(); + Assert.AreEqual("1", firstGroup.Id); + Assert.AreEqual("GroupA", firstGroup.Name); + Assert.AreEqual(5, firstGroup.ContactsCount); + } + + private ListGroups GetList() + { + var action = new ListGroups(); + action.Proxy(_proxyStub); + + return action; + } +}