diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml new file mode 100644 index 0000000..4e39f5b --- /dev/null +++ b/.github/workflows/run-unit-tests.yml @@ -0,0 +1,51 @@ +name: Unit tests + +on: + push: + branches: + - 3.x.x-dev + pull_request: + branches: + - 3.x.x-dev + +jobs: + build-and-test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + dotnet-version: ['6.0', '7.0', '8.0', '9.0'] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup .NET 6 SDK + uses: actions/setup-dotnet@v3 + with: + 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: Setup .NET 9 SDKs + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0' + + - name: Install dependencies + run: dotnet restore + + - name: Build the solution + 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 }} 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); diff --git a/examples/blacklist/Add.cs b/examples/blacklist/Add.cs new file mode 100644 index 0000000..19a722c --- /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 ex) +{ + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } +} 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/examples/blacklist/Remove.cs b/examples/blacklist/Remove.cs new file mode 100644 index 0000000..d059325 --- /dev/null +++ b/examples/blacklist/Remove.cs @@ -0,0 +1,20 @@ +using SMSApi.Api; +using SMSApi.Api.Response.Blacklist.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 ex) +{ + System.Console.WriteLine(ex.Message); +} 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/examples/hlr/ListLookups.cs b/examples/hlr/ListLookups.cs new file mode 100644 index 0000000..2132f6e --- /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}"); + 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/examples/hlr/Lookup.cs b/examples/hlr/Lookup.cs new file mode 100644 index 0000000..bec0970 --- /dev/null +++ b/examples/hlr/Lookup.cs @@ -0,0 +1,23 @@ +using SMSApi.Api; +using smsapi.Api.Response.REST.Exception; + +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 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 f90bc9b..d79dec8 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(); @@ -23,11 +22,16 @@ } catch (ValidationException ex) { - var errors = ex.ValidationErrors; + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + 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 916463a..eb2000f 100644 --- a/examples/mfa/VerifyMFACode.cs +++ b/examples/mfa/VerifyMFACode.cs @@ -19,11 +19,24 @@ } catch (ValidationException ex) { - var errors = ex.ValidationErrors; + foreach (var validationErrorsError in ex.ValidationErrors.Errors) + { + Console.WriteLine(validationErrorsError.Message); + } } -catch (InvalidVerificationCodeException) +catch (InvalidVerificationCodeException ex) { + Console.WriteLine("Error: " + ex.Message); } -catch (ExpiredVerificationCodeException) +catch (ExpiredVerificationCodeException ex) { + Console.WriteLine("Error: " + ex.Message); +} +catch (TooManyRequestsException ex) +{ + Console.WriteLine("Error: " + ex.Message); +} +catch (ClientException ex) +{ + Console.WriteLine("Message: " + ex.Message); } 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..9904955 --- /dev/null +++ b/examples/optOut/Delete.cs @@ -0,0 +1,31 @@ +using SMSApi.Api; +using SMSApi.Api.Response.OptOut.Exception; + +var client = new ClientOAuth("token"); +var features = new Features(client); + +var optOutList = features.OptOut() + .List() + .Execute(); + +void DeleteOptOut(string optOutId) +{ + 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/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}"); 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}"); }); 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/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/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/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/examples/shortUrl/CreateFileLink.cs b/examples/shortUrl/CreateFileLink.cs new file mode 100644 index 0000000..0a287fc --- /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 FileInfo(""); + +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/CreateUrlLink.cs b/examples/shortUrl/CreateUrlLink.cs new file mode 100644 index 0000000..424001b --- /dev/null +++ b/examples/shortUrl/CreateUrlLink.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); +} diff --git a/examples/sms/SmsWithVmsFallback.cs b/examples/sms/SmsWithVmsFallback.cs new file mode 100644 index 0000000..2ffccb6 --- /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 = "4850010010"; +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); + }); +} 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) +{ +} 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/Action.cs b/smsapi/Api/Action/Action.cs index 2f8b9de..7ce51df 100644 --- a/smsapi/Api/Action/Action.cs +++ b/smsapi/Api/Action/Action.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -16,6 +18,8 @@ public abstract class Action protected abstract RequestMethod Method { get; } + protected virtual ActionContentType ContentType => ActionContentType.Json; + protected virtual ApiType ApiType() { return Action.ApiType.Legacy; @@ -24,18 +28,19 @@ protected virtual ApiType ApiType() public T Execute() { Validate(); - return ProcessResponse(_proxy.Execute(Uri(), 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(Uri(), 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() @@ -51,7 +56,9 @@ protected virtual T ResponseToObject(HttpResponseEntity data) //TODO get rid of new LegacyJsonResponseDeserializer(), new ValidationErrorsResolver(new BaseJsonDeserializer()), new TooManyRequestsErrorResolver(), - new AccessErrorResolver() + new AccessErrorResolver(), + new NotFoundErrorResolver(), + new HostErrorsResolver() ), Action.ApiType.Legacy => new LegacyJsonResponseDeserializer(), _ => throw new Exception("Unknown api type") @@ -70,9 +77,62 @@ protected virtual void Validate() { } - protected virtual NameValueCollection Values() + protected virtual (NameValueCollection, ISet>?) Values() + { + return (new NameValueCollection(), default); + } + + private string UriWithPagination() { - return new NameValueCollection(); + var uriBuilder = new UriBuilder + { + Path = Uri() + }; + + 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().Item1); + + Values().Item2?.ToList().ForEach(pair => + { + switch (pair.Value) + { + case string[] list: + { + foreach (var item in list) + { + var key = $"{pair.Key}[]"; + + if (Environment.Version.Major < 9) + key = HttpUtility.UrlEncode(key); + + query.Add(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(); } private T ProcessResponse(HttpResponseEntity responseEntity) @@ -80,11 +140,20 @@ private T ProcessResponse(HttpResponseEntity responseEntity) return ResponseToObject(responseEntity); } - private NameValueCollection GetValues() + private ISet> GetValues() { - var values = Values(); - return values.Count > 0 - ? new NameValueCollection { { "format", "json" }, values } - : HttpUtility.ParseQueryString(string.Empty); + var values = new HashSet> + { + 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))); + } + + return values; } } 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/ActionPaginationHelper.cs b/smsapi/Api/Action/ActionPaginationHelper.cs new file mode 100644 index 0000000..8d5bd27 --- /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, uint? limit, uint? 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.ToPathWithQuery(); + } +} diff --git a/smsapi/Api/Action/Blacklist/Add.cs b/smsapi/Api/Action/Blacklist/Add.cs new file mode 100644 index 0000000..0d85693 --- /dev/null +++ b/smsapi/Api/Action/Blacklist/Add.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using smsapi.Api.Response.Blacklist; + +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, ISet>?) Values() + { + var values = new NameValueCollection { { "phone_number", phoneNumber } }; + + if (withExpireAt != null) + values.Add("expire_at", withExpireAt.Value.ToString("O")); + + return (values, default); + } +} diff --git a/smsapi/Api/Action/Blacklist/List.cs b/smsapi/Api/Action/Blacklist/List.cs new file mode 100644 index 0000000..2fcf0d6 --- /dev/null +++ b/smsapi/Api/Action/Blacklist/List.cs @@ -0,0 +1,16 @@ +using SMSApi.Api.Response; +using smsapi.Api.Response.Blacklist; + +namespace SMSApi.Api.Action.Blacklist; + +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 uint? Limit { get; set; } + public uint? Offset { get; set; } +} diff --git a/smsapi/Api/Action/Blacklist/Remove.cs b/smsapi/Api/Action/Blacklist/Remove.cs new file mode 100644 index 0000000..6a952b2 --- /dev/null +++ b/smsapi/Api/Action/Blacklist/Remove.cs @@ -0,0 +1,19 @@ +using smsapi.Api.Response.Blacklist; + +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/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/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..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; @@ -17,6 +18,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) { @@ -77,7 +82,7 @@ protected override string Uri() return "contacts"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); @@ -126,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 e1bf428..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; @@ -9,6 +10,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) { @@ -27,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) @@ -40,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 4cc4fcf..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; @@ -10,6 +11,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) { @@ -34,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) @@ -44,7 +49,7 @@ protected override NameValueCollection Values() if (description != null) { - values.Add("desciption", description); + values.Add("description", description); } if (idx != null) @@ -52,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 7fb7797..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; @@ -13,6 +14,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) { @@ -48,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 { @@ -62,7 +67,7 @@ protected override NameValueCollection Values() values.Add("username", username); } - return values; + return (values, default); } } } 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..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; @@ -24,6 +25,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) { @@ -84,7 +89,7 @@ protected override string Uri() return "contacts/" + ContactId; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var values = new NameValueCollection(); @@ -133,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 d886403..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; @@ -16,6 +17,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) { @@ -28,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) @@ -36,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 b0d4125..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; @@ -17,6 +18,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) { @@ -41,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) @@ -59,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 9288f93..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; @@ -19,6 +20,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) { @@ -43,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/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..75d173c 100644 --- a/smsapi/Api/Action/Contacts/ListContacts.cs +++ b/smsapi/Api/Action/Contacts/ListContacts.cs @@ -1,24 +1,25 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action { - public class ListContacts : Action + public class ListContacts : Action, IPaginable { private DateTime? birthdayDate; private string email; private string firstName; private string gender; - private int? groupId; + private string? groupId; private string lastName; - private int? limit; - private int? offset; private string phoneNumber; private string search; protected override RequestMethod Method => RequestMethod.GET; + protected override ApiType ApiType() => Action.ApiType.Rest; + public ListContacts SetBirthdayDate(DateTime? birthdayDate) { this.birthdayDate = birthdayDate; @@ -44,6 +45,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; @@ -55,15 +62,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; } @@ -84,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) @@ -92,16 +105,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); @@ -124,7 +127,7 @@ protected override NameValueCollection Values() if (groupId != null) { - parameters.Add("group_id", groupId.Value.ToString()); + parameters.Add("group_id", groupId); } if (gender != null) @@ -137,7 +140,10 @@ protected override NameValueCollection Values() parameters.Add("birthday_date", birthdayDate.Value.ToString("yyyy-MM-dd")); } - return parameters; + return (parameters, default); } + + public uint? Limit { get; set; } + public uint? Offset { get; set; } } } 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..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; @@ -9,6 +10,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) { @@ -27,7 +30,7 @@ protected override string Uri() return "contacts/groups"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) Values() { var parameters = new NameValueCollection { @@ -44,7 +47,7 @@ protected override NameValueCollection Values() parameters.Add("name", name); } - return parameters; + return (parameters, default); } } } 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/Action/HLR/CheckNumber.cs b/smsapi/Api/Action/HLR/CheckNumber.cs index 305cee9..bb8d52d 100644 --- a/smsapi/Api/Action/HLR/CheckNumber.cs +++ b/smsapi/Api/Action/HLR/CheckNumber.cs @@ -1,8 +1,11 @@ -using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; using SMSApi.Api.Response; namespace SMSApi.Api.Action { + [Obsolete] public class HLRCheckNumber : Action { private string number; @@ -20,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/ListLookups.cs b/smsapi/Api/Action/HLR/ListLookups.cs new file mode 100644 index 0000000..f5a9eaf --- /dev/null +++ b/smsapi/Api/Action/HLR/ListLookups.cs @@ -0,0 +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, 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; } +} diff --git a/smsapi/Api/Action/HLR/Lookup.cs b/smsapi/Api/Action/HLR/Lookup.cs new file mode 100644 index 0000000..f3f01c2 --- /dev/null +++ b/smsapi/Api/Action/HLR/Lookup.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +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, ISet>?) Values() + { + return (new NameValueCollection { { "phone_number", _numberToCheck } }, default); + } +} diff --git a/smsapi/Api/Action/IPaginable.cs b/smsapi/Api/Action/IPaginable.cs new file mode 100644 index 0000000..fa8a808 --- /dev/null +++ b/smsapi/Api/Action/IPaginable.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Action; + +public interface IPaginable +{ + uint? Limit { get; set; } + + uint? Offset { get; set; } +} diff --git a/smsapi/Api/Action/MFA/CreateMFACode.cs b/smsapi/Api/Action/MFA/CreateMFACode.cs index e9c8bf4..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; @@ -5,35 +6,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; } @@ -48,19 +49,19 @@ protected override string Uri() return "mfa/codes"; } - protected override NameValueCollection Values() + protected override (NameValueCollection, ISet>?) 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; + 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 new file mode 100644 index 0000000..c34244a --- /dev/null +++ b/smsapi/Api/Action/OptOut/ChangeOptOutSettings.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +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, ISet>?) Values() + { + var values = new NameValueCollection(); + + _brandName?.Let(newName => values.Add("brand", newName)); + + return (values, default); + } +} diff --git a/smsapi/Api/Action/OptOut/DeleteOptOut.cs b/smsapi/Api/Action/OptOut/DeleteOptOut.cs new file mode 100644 index 0000000..37f7184 --- /dev/null +++ b/smsapi/Api/Action/OptOut/DeleteOptOut.cs @@ -0,0 +1,22 @@ +using SMSApi.Api.Response.OptOut; + +namespace SMSApi.Api.Action.OptOut; + +public sealed class DeleteOptOut : Action +{ + private readonly string _optOutId; + + public DeleteOptOut(string optOutId) + { + _optOutId = optOutId; + } + + 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/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/Action/OptOut/OptOutList.cs b/smsapi/Api/Action/OptOut/OptOutList.cs new file mode 100644 index 0000000..2bc40fc --- /dev/null +++ b/smsapi/Api/Action/OptOut/OptOutList.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +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, ISet>?) Values() + { + var values = new NameValueCollection(); + + _phoneNumber?.Let(number => values.Add("phone_number", number)); + + return (values, default); + } +} 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/Action/SMS/Delete.cs b/smsapi/Api/Action/SMS/Delete.cs index 32c97df..9dbd30a 100644 --- a/smsapi/Api/Action/SMS/Delete.cs +++ b/smsapi/Api/Action/SMS/Delete.cs @@ -1,31 +1,48 @@ -using System.Collections.Specialized; +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 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 } - }; - } + { "sch_del", string.Join(",", _ids.ToHashSet()) } + }, 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/SMS/Send.cs b/smsapi/Api/Action/SMS/Send.cs index 84814c8..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) - { - 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/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/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/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/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/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/Action/ShortUrl/CreateShortUrl.cs b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs new file mode 100644 index 0000000..269b518 --- /dev/null +++ b/smsapi/Api/Action/ShortUrl/CreateShortUrl.cs @@ -0,0 +1,100 @@ +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 FileInfo? _file; + + private readonly string _name; + private readonly string? _url; + + public CreateShortUrl(string name, string url) + { + _name = name; + _url = url; + } + + public CreateShortUrl(string name, FileInfo file) + { + _name = name; + _file = file; + } + + protected override RequestMethod Method => RequestMethod.POST; + protected override ActionContentType ContentType => ActionContentType.FormWww; + + 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) + ); + }); + + _file?.Let(_ => body.Add(("type", "FILE"))); + + return (new NameValueCollection(), body); + } + + protected override Dictionary Files() + { + var files = new Dictionary(); + + _file?.Let(file => files.Add(file.Name, file.OpenRead())); + + return files; + } +} 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/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/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/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/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/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/Action/Subusers/Creation/CreateSubuser.cs b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs new file mode 100644 index 0000000..354eb44 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/CreateSubuser.cs @@ -0,0 +1,85 @@ +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 }, + }), + ("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/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/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/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs new file mode 100644 index 0000000..7beb0f0 --- /dev/null +++ b/smsapi/Api/Action/Subusers/Creation/SubuserCredentials.cs @@ -0,0 +1,7 @@ +namespace SMSApi.Api.Action.Subusers.Creation; + +public readonly record struct SubuserCredentials(string Username, string Password) +{ + public readonly string Username = Username; + public readonly string Password = Password; +} 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/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/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/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; + } +} 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); } } } diff --git a/smsapi/Api/BlackListFactory.cs b/smsapi/Api/BlackListFactory.cs new file mode 100644 index 0000000..7fc56a2 --- /dev/null +++ b/smsapi/Api/BlackListFactory.cs @@ -0,0 +1,54 @@ +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 Add Add(string phoneNumber) + { + var service = new Add(phoneNumber); + service.Proxy(proxy); + + return service; + } + + public Remove Remove(string id) + { + var service = new Remove(id); + service.Proxy(proxy); + + return service; + } + + public RemoveAll RemoveAll() + { + var service = new RemoveAll(); + 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/Features.cs b/smsapi/Api/Features.cs index 502093d..c3d5a97 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 @@ -10,7 +12,13 @@ public Features(IClient client, ProxyAddress proxy = ProxyAddress.SmsApiIo) Proxy = new ProxyHTTP(proxy.GetUrl()); 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; diff --git a/smsapi/Api/HLRFactory.cs b/smsapi/Api/HLRFactory.cs index f25fd3d..d9bc064 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,24 @@ 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; + } + + public ListLookups ListLookups() + { + var action = new ListLookups(); + + action.Proxy(proxy); + + return action; + } } } diff --git a/smsapi/Api/OptOutFactory.cs b/smsapi/Api/OptOutFactory.cs new file mode 100644 index 0000000..44e1b0c --- /dev/null +++ b/smsapi/Api/OptOutFactory.cs @@ -0,0 +1,61 @@ +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 OptOutList List() + { + var action = new OptOutList(); + action.Proxy(proxy); + + return action; + } + + public GetOptOutSettings Settings() + { + var action = new GetOptOutSettings(); + action.Proxy(proxy); + + return action; + } + + public ChangeOptOutSettings ChangeSettings() + { + var action = new ChangeOptOutSettings(); + 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/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/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/BasicCollection.cs b/smsapi/Api/Response/BasicCollection.cs index b4da875..0f090e9 100644 --- a/smsapi/Api/Response/BasicCollection.cs +++ b/smsapi/Api/Response/BasicCollection.cs @@ -1,22 +1,18 @@ 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; + + [JsonProperty("size")] + private int _size; - [DataMember(Name = "size", IsRequired = false)] - protected int size; - - protected BasicCollection() - { } - + [JsonProperty("collection")] public List Collection { get @@ -29,31 +25,32 @@ public List Collection return collection; } - set - { } + set => collection = value; } [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/Blacklist/BlacklistRecord.cs b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs new file mode 100644 index 0000000..a878a7e --- /dev/null +++ b/smsapi/Api/Response/Blacklist/BlacklistRecord.cs @@ -0,0 +1,16 @@ +using System; +using Newtonsoft.Json; +using SMSApi.Api.Response.ResponseResolver; + +namespace smsapi.Api.Response.Blacklist; + +public record struct BlacklistRecord : IResponseCodeAwareResolver +{ + public readonly string Id; + + public readonly string PhoneNumber; + + [JsonProperty("created_at")] public readonly DateTime DateCreated; + + [JsonProperty("expire_at")] public readonly DateTime? DateExpired; +} diff --git a/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs b/smsapi/Api/Response/Blacklist/BlacklistRemovalResult.cs new file mode 100644 index 0000000..a97ea42 --- /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.Blacklist.Exception; +using SMSApi.Api.Response.ResponseResolver; + +namespace smsapi.Api.Response.Blacklist; + +[DataContract] +public class BlacklistRemovalResult : IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new Dictionary> + { + { 404, _ => throw new BlacklistRecordDoesNotExistException() } + }; + } +} diff --git a/smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs b/smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs new file mode 100644 index 0000000..e0da95f --- /dev/null +++ b/smsapi/Api/Response/Blacklist/Exception/BlacklistRecordDoesNotExistException.cs @@ -0,0 +1,8 @@ +namespace SMSApi.Api.Response.Blacklist.Exception; + +public class BlacklistRecordDoesNotExistException : ClientException +{ + public BlacklistRecordDoesNotExistException() : base("record does not exist", 404) + { + } +} 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 new file mode 100644 index 0000000..f8df4ae --- /dev/null +++ b/smsapi/Api/Response/Common/Telephony/Country.cs @@ -0,0 +1,13 @@ +namespace SMSApi.Api.Response.Common.Telephony; + +public readonly record struct Country +{ + public readonly string Name; + 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..a9b7f72 --- /dev/null +++ b/smsapi/Api/Response/Common/Telephony/Network.cs @@ -0,0 +1,13 @@ +namespace SMSApi.Api.Response.Common.Telephony; + +public readonly record struct Network +{ + public readonly string Name; + public readonly int MNC; + + public Network(string name, int mnc) + { + Name = name; + MNC = mnc; + } +} diff --git a/smsapi/Api/Response/Contact.cs b/smsapi/Api/Response/Contact.cs index 4c36644..77d707a 100644 --- a/smsapi/Api/Response/Contact.cs +++ b/smsapi/Api/Response/Contact.cs @@ -1,65 +1,63 @@ using System; -using System.Runtime.Serialization; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using smsapi.Api.Response.Contacts.Exception; using SMSApi.Api.Response.ResponseResolver; namespace SMSApi.Api.Response { - [DataContract] - public class Contact : ErrorAwareResponse + public class Contact : IResponseCodeAwareResolver { public const string FemaleGender = "female"; public const string MaleGender = "male"; 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() + { + return new Dictionary> + { + { 409, _ => throw new ContactAlreadyExistsException() } + }; + } [Obsolete("use DateCreated instead")] + [JsonIgnore] public uint DateAdd { get @@ -69,9 +67,11 @@ public uint DateAdd } } + [JsonIgnore] public DateTime? DateCreated => dateCreated; [Obsolete("use DateUpdated instead")] + [JsonIgnore] public uint DateMod { get @@ -81,22 +81,10 @@ public uint DateMod } } + [JsonIgnore] 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 @@ -107,14 +95,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 @@ -125,7 +113,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/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/Countable.cs b/smsapi/Api/Response/Countable.cs index d352b29..45ad967 100644 --- a/smsapi/Api/Response/Countable.cs +++ b/smsapi/Api/Response/Countable.cs @@ -1,22 +1,26 @@ -using System.Runtime.Serialization; +using Newtonsoft.Json; namespace SMSApi.Api.Response { - [DataContract] public class Countable { - private int count; + [JsonIgnore] + private int _count; + + public Countable() + { + } protected Countable(int count = 0) { - this.count = count; + _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/Response/Deserialization/BaseJsonDeserializer.cs b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs index aa75717..13d3d5d 100644 --- a/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs +++ b/smsapi/Api/Response/Deserialization/BaseJsonDeserializer.cs @@ -1,31 +1,36 @@ 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 && !responseEntity.IsEmptyContentCode) { - 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(); - } + data.Position = 0; + 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/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/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/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/LegacyJsonResponseDeserializer.cs b/smsapi/Api/Response/Deserialization/LegacyJsonResponseDeserializer.cs index ea08844..e0ec376 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; @@ -22,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(); @@ -45,8 +43,8 @@ private void HandleError(HttpResponseEntity responseEntity, DeserializationRe try { var error = _baseJsonDeserializer.Deserialize(responseEntity).Result; - - if (!error.IsError()) return; + + if (!error!.IsError()) return; if (IsHostError(error.ErrorCode)) { @@ -79,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(int code) + private static bool IsClientError(string? code) { - switch (code) + if (!int.TryParse(code, out var n)) return false; + + switch (n) { case 101: case 102: @@ -103,9 +103,11 @@ 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(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/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/Deserialization/PrivateFieldsContractResolver.cs b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs new file mode 100644 index 0000000..2215c08 --- /dev/null +++ b/smsapi/Api/Response/Deserialization/PrivateFieldsContractResolver.cs @@ -0,0 +1,81 @@ +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 +{ + public PrivateFieldsContractResolver() + { + NamingStrategy = new SnakeCaseNamingStrategy(); + } + + 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(); + + AddReadonlyMembers(type, memberSerialization, 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); + + jsonProperty.Writable = member switch + { + PropertyInfo propertyInfo when HasPrivateSetter(propertyInfo) => true, + FieldInfo { IsInitOnly: true } => true, + _ => jsonProperty.Writable + }; + + return jsonProperty; + } + + private static IEnumerable GetPublicReadonlyFields(Type type) + { + return type.GetFields(BindingFlags.Public | BindingFlags.Instance) + .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..febc2fc 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 readonly record struct ValidationErrors { - [DataMember(Name = "errors")] public readonly IEnumerable Errors; + [JsonProperty("errors")] + public readonly IEnumerable Errors; } - - [DataContract] - public readonly struct ValidationError + + public readonly record struct 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/smsapi/Api/Response/Field.cs b/smsapi/Api/Response/Field.cs index bfa9824..ac5813c 100644 --- a/smsapi/Api/Response/Field.cs +++ b/smsapi/Api/Response/Field.cs @@ -1,23 +1,19 @@ -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"; 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/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() - { } } } diff --git a/smsapi/Api/Response/Group.cs b/smsapi/Api/Response/Group.cs index ec6a3a2..4a097d7 100644 --- a/smsapi/Api/Response/Group.cs +++ b/smsapi/Api/Response/Group.cs @@ -2,42 +2,37 @@ 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; - private Group() - { } - - [DataMember(Name = "contacts_count", IsRequired = false)] - public int ContactsCount { get; private set; } + [JsonProperty("contacts_count")] + public int? ContactsCount { get; private set; } + [JsonIgnore] public DateTime? DateCreated { get; private set; } + [JsonIgnore] 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; @@ -45,7 +40,7 @@ public string Info } [Obsolete("use ContactsCount instead")] - [DataMember(Name = "numbers_count", IsRequired = false)] + [JsonProperty("numbers_count")] public uint NumbersCount { get => (uint)ContactsCount; @@ -65,14 +60,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 dd333cb..da384bf 100644 --- a/smsapi/Api/Response/GroupPermission.cs +++ b/smsapi/Api/Response/GroupPermission.cs @@ -4,21 +4,16 @@ namespace SMSApi.Api.Response { [DataContract] - public class GroupPermission : ErrorAwareResponse + 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 new file mode 100644 index 0000000..8994841 --- /dev/null +++ b/smsapi/Api/Response/HLR/LookupResult.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using SMSApi.Api.Response.Common.Telephony; + +namespace SMSApi.Api.Response.HLR; + +public record struct LookupResult +{ + public readonly double Cost; + + public readonly Country? Country; + + 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; +} + +public readonly record struct Ported +{ + [JsonProperty("ported")] public readonly IEnumerable PortedFrom; + + public Ported(IEnumerable portedFrom) + { + PortedFrom = portedFrom; + } +} + +public readonly record struct MCC +{ + public readonly int Mcc; + + public MCC(int mcc) + { + Mcc = mcc; + } +} \ No newline at end of file diff --git a/smsapi/Api/Response/HLR/SingleCheckResult.cs b/smsapi/Api/Response/HLR/SingleCheckResult.cs new file mode 100644 index 0000000..7947b52 --- /dev/null +++ b/smsapi/Api/Response/HLR/SingleCheckResult.cs @@ -0,0 +1,5 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.HLR; + +public readonly record struct SingleCheckResult : IResponseCodeAwareResolver; 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/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/OptOut.cs b/smsapi/Api/Response/OptOut/OptOut.cs new file mode 100644 index 0000000..4a86e75 --- /dev/null +++ b/smsapi/Api/Response/OptOut/OptOut.cs @@ -0,0 +1,12 @@ +using System; + +namespace SMSApi.Api.Response.OptOut; + +public sealed class OptOut +{ + public readonly string Id; + + public readonly string PhoneNumber; + + public readonly DateTime CreationTime; +} 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/smsapi/Api/Response/OptOut/OptOutSettings.cs b/smsapi/Api/Response/OptOut/OptOutSettings.cs new file mode 100644 index 0000000..62ef6c2 --- /dev/null +++ b/smsapi/Api/Response/OptOut/OptOutSettings.cs @@ -0,0 +1,8 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.OptOut; + +public sealed class OptOutSettings : IResponseCodeAwareResolver +{ + 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 b0bfe42..34029d9 100644 --- a/smsapi/Api/Response/Profile/Prices/PriceResponse.cs +++ b/smsapi/Api/Response/Profile/Prices/PriceResponse.cs @@ -1,34 +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; + + public readonly string Type; } -[DataContract] 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; + public readonly float Amount; + public readonly string Currency; } diff --git a/smsapi/Api/Response/Profile/Profile.cs b/smsapi/Api/Response/Profile/Profile.cs new file mode 100644 index 0000000..3ad32aa --- /dev/null +++ b/smsapi/Api/Response/Profile/Profile.cs @@ -0,0 +1,20 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Profile; + +public sealed class Profile : IResponseCodeAwareResolver +{ + public readonly string Name; + + public readonly string Username; + + public readonly string Email; + + public readonly string PhoneNumber; + + public readonly string UserType; + + public readonly double Points; + + public readonly string PaymentType; +} 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) + { + } +} 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/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs index e007bc7..5781d30 100644 --- a/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs +++ b/smsapi/Api/Response/ResponseResolver/ErrorAwareResponse.cs @@ -1,17 +1,22 @@ -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: IErrorResponse + [JsonProperty("message")] public readonly string ErrorMessage; + + [JsonProperty("error")] public readonly string? ErrorCode; + + public bool IsError() { - [DataMember(Name = "error", IsRequired = false)] - public readonly int ErrorCode; + if (string.IsNullOrEmpty(ErrorCode)) return false; - [DataMember(Name = "message", IsRequired = false)] - public readonly string ErrorMessage; - - public bool IsError() => ErrorCode != 0; - public string GetErrorMessage() => ErrorMessage; + return ErrorCode != "0"; + } + + public string GetErrorMessage() + { + 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/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/Response/Sendernames/DeleteSendernameResult.cs b/smsapi/Api/Response/Sendernames/DeleteSendernameResult.cs new file mode 100644 index 0000000..01d6454 --- /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 DeleteSendernameResult : IResponseCodeAwareResolver +{ +} diff --git a/smsapi/Api/Response/Sendernames/Sendername.cs b/smsapi/Api/Response/Sendernames/Sendername.cs new file mode 100644 index 0000000..859d024 --- /dev/null +++ b/smsapi/Api/Response/Sendernames/Sendername.cs @@ -0,0 +1,15 @@ +using System; +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Sendernames; + +public readonly record struct Sendername : IResponseCodeAwareResolver +{ + public readonly DateTime CreatedAt; + + public readonly bool IsDefault; + + public readonly string Sender; + + 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/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 new file mode 100644 index 0000000..d75ef0b --- /dev/null +++ b/smsapi/Api/Response/ShortUrl/ShortLink.cs @@ -0,0 +1,43 @@ +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: IResponseCodeAwareResolver +{ + public Dictionary> HandleExceptionActions() + { + return new() + { + { 409, _ => throw new ShortUrlWithNameAlreadyExistsException() }, + }; + } + + 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/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/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/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/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/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/Response/Subusers/SubuserDetails.cs b/smsapi/Api/Response/Subusers/SubuserDetails.cs new file mode 100644 index 0000000..d7fec8a --- /dev/null +++ b/smsapi/Api/Response/Subusers/SubuserDetails.cs @@ -0,0 +1,29 @@ +using SMSApi.Api.Response.ResponseResolver; + +namespace SMSApi.Api.Response.Subusers; + +public readonly record struct SubuserDetails : IResponseCodeAwareResolver +{ + public readonly bool Active; + + public readonly string Description; + + public readonly string Id; + + public readonly UserPoints Points; + + public readonly string Username; +} + +public readonly record struct UserPoints +{ + public readonly double FromAccount; + + public readonly double 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/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/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..6213e2b --- /dev/null +++ b/smsapi/Api/SendernamesFactory.cs @@ -0,0 +1,69 @@ +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 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 +{ + public static SendernamesFactory Sendernames(this Features features) + { + return new SendernamesFactory(features.Client, features.Proxy); + } +} diff --git a/smsapi/Api/ShortUrlFactory.cs b/smsapi/Api/ShortUrlFactory.cs new file mode 100644 index 0000000..857da47 --- /dev/null +++ b/smsapi/Api/ShortUrlFactory.cs @@ -0,0 +1,94 @@ +using System.IO; +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 CreateShortUrl Create(string name, string uri) + { + var action = new CreateShortUrl(name, uri); + action.Proxy(proxy); + + return action; + } + + public CreateShortUrl Create(string name, FileInfo file) + { + var action = new CreateShortUrl(name, file); + action.Proxy(proxy); + + return action; + } + + public GetShortUrl GetShortUrl(string id) + { + var action = new GetShortUrl(id); + action.Proxy(proxy); + + return action; + } + + public UpdateShortUrl UpdateShortUrl(string id) + { + var action = new UpdateShortUrl(id); + action.Proxy(proxy); + + return action; + } + + public DeleteShortUrl DeleteShortUrl(string id) + { + var action = new DeleteShortUrl(id); + action.Proxy(proxy); + + return action; + } + + public ListShortUrlClicks ListClicks() + { + var action = new ListShortUrlClicks(); + action.Proxy(proxy); + + return action; + } + + public ListShortUrlClicksGroupedByDevice ListClicksGroupedByDeviceType(params string[] linkId) + { + var action = new ListShortUrlClicksGroupedByDevice(linkId); + 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/smsapi/Api/SubUsersFactory.cs b/smsapi/Api/SubUsersFactory.cs new file mode 100644 index 0000000..c6755a5 --- /dev/null +++ b/smsapi/Api/SubUsersFactory.cs @@ -0,0 +1,75 @@ +using SMSApi.Api.Action.Subusers; +using SMSApi.Api.Action.Subusers.Creation; + +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 CreateSubuser Create(SubuserCredentials credentials) + { + var action = new CreateSubuser(credentials); + + action.Proxy(proxy); + + return action; + } + + public GetSubuser Get(string userId) + { + var action = new GetSubuser(userId); + + action.Proxy(proxy); + + return action; + } + + public DeleteSubuser Delete(string userId) + { + var action = new DeleteSubuser(userId); + + action.Proxy(proxy); + + return action; + } + + public EditSubuser Edit(string userId) + { + var action = new EditSubuser(userId); + + 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/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/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; diff --git a/smsapi/NativeHttpClientHelper.cs b/smsapi/NativeHttpClientHelper.cs index 3ba0350..44c1401 100644 --- a/smsapi/NativeHttpClientHelper.cs +++ b/smsapi/NativeHttpClientHelper.cs @@ -1,76 +1,88 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net.Http; +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; + + switch (method) { - HttpContent httpContent; - - 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(postResponse.Content.ReadAsStreamAsync(), postResponse.StatusCode); - case RequestMethod.PUT: - httpContent = ConvertNameValueCollectionToHttpContent(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(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); - default: - throw new ArgumentOutOfRangeException(nameof(method), method, null); - } + case RequestMethod.GET: + var getResponse = await httpClient.GetAsync(uri, 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 = 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(deleteResult.Content.ReadAsStreamAsync(), deleteResult.StatusCode); + default: + throw new ArgumentOutOfRangeException(nameof(method), method, null); } + } - private static HttpContent ConvertNameValueCollectionToHttpContent( - NameValueCollection collection, - Dictionary files = null - ) - { - var contentCollectionKeys = collection.AllKeys; + private static HttpContent ConvertRequestDataToHttpContent( + ActionContentType contentType, + ISet> collection, + Dictionary files = null + ) + { + var collectionDictionary = collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - var contentCollection = contentCollectionKeys - .Select(key => new KeyValuePair(key, collection[key])) - .ToList(); - var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); + if (contentType == ActionContentType.Json) + return new StringContent(JsonSerializer.Serialize(collectionDictionary), Encoding.UTF8, "application/json"); - if (files == null) return formUrlEncodedContent; + var contentCollection = collectionDictionary.Keys + .Select(key => new KeyValuePair(key, collectionDictionary[key]?.ToString())) + .ToList(); - var multipartContent = new MultipartFormDataContent(); + var formUrlEncodedContent = new FormUrlEncodedContent(contentCollection); - foreach (var keyValuePair in contentCollection) - multipartContent.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); + if (files == null || files.Count == 0) return formUrlEncodedContent; - files - .ToList() - .ForEach(pair => multipartContent.Add(new StreamContent(pair.Value), "file", pair.Key)); + var streamContent = new StreamContent(files.Values.First()); + var filename = files.Keys.First(); + var encodedFilename = Uri.EscapeDataString(filename); - return multipartContent; - } + streamContent.Headers.TryAddWithoutValidation("Content-Disposition", + $"form-data; name=\"file\"; filename=\"{filename}\"; filename*=utf-8''{encodedFilename}"); + + var content = new MultipartFormDataContent + { + streamContent + }; + + foreach (var keyValuePair in collection) content.Add(new StringContent(keyValuePair.Value?.ToString()), keyValuePair.Key); + + return content; } } diff --git a/smsapi/OperationsHelper.cs b/smsapi/OperationsHelper.cs new file mode 100644 index 0000000..f37d013 --- /dev/null +++ b/smsapi/OperationsHelper.cs @@ -0,0 +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) + { + 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)); + } + } +} diff --git a/smsapi/Proxy.cs b/smsapi/Proxy.cs index 693593b..284d159 100644 --- a/smsapi/Proxy.cs +++ b/smsapi/Proxy.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; +using SMSApi.Api.Action; namespace SMSApi.Api { @@ -11,40 +11,46 @@ public interface Proxy void Authentication(IClient client); 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 ); Task ExecuteAsync( + ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default ); Task ExecuteAsync( + ActionContentType contentType, string uri, - NameValueCollection data, + ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default 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 b09decf..1eaaba9 100644 --- a/smsapi/ProxyHTTP.cs +++ b/smsapi/ProxyHTTP.cs @@ -1,22 +1,24 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using SMSApi.Api.Action; namespace SMSApi.Api { public class ProxyHTTP : Proxy { private readonly string baseUrl; - private IClient authentication; + private readonly HttpClient? httpClient; + private IClient? authentication; - public ProxyHTTP(string baseUrl) + public ProxyHTTP(string baseUrl, HttpClient? httpClient = null) { - this.baseUrl = baseUrl; + this.baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/"; + this.httpClient = httpClient; } public void Authentication(IClient client) @@ -24,23 +26,25 @@ public void Authentication(IClient client) authentication = client; } - public HttpResponseEntity Execute(string uri, NameValueCollection data, RequestMethod method) + public HttpResponseEntity Execute(ActionContentType contentType, string uri, ISet> 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, + ISet> 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, + ISet> data, Dictionary files, RequestMethod method) { @@ -50,38 +54,41 @@ public HttpResponseEntity Execute( try { - return client.SendRequest(method, uri, data, files).Result; + return client.SendRequest(contentType, method, uri, data, files).Result; } - catch (System.Exception e) + catch (Exception e) { throw new ProxyException("Failed to get response from " + uri, e); } } public async Task ExecuteAsync( + ActionContentType contentType, string uri, - NameValueCollection data, + ISet> 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, + ISet> data, Stream file, RequestMethod method, 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, + ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default @@ -93,24 +100,26 @@ public async Task ExecuteAsync( try { - return await client.SendRequest(method, uri, data, files, cancellationToken); + return await client.SendRequest(contentType, method, uri, data, files, cancellationToken); } - catch (System.Exception e) + catch (Exception e) { throw new ProxyException("Failed to get response from " + uri, e); } } - + private HttpClient CreateClient() { - var client = new HttpClient(); + var client = httpClient ?? new HttpClient(); + client.BaseAddress = new Uri(baseUrl); - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", authentication.GetClientAgent()); if (authentication == null) return client; - + + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", authentication.GetClientAgent()); + var authHeader = authentication.DefaultRequestHeaders; - + client.DefaultRequestHeaders.Add(authHeader.Key, authHeader.Value); return client; 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 { diff --git a/smsapi/smsapi.csproj b/smsapi/smsapi.csproj index e26208e..ca488e0 100644 --- a/smsapi/smsapi.csproj +++ b/smsapi/smsapi.csproj @@ -3,10 +3,9 @@ 8.0.30703 2.0 - netcoreapp3.1;net5.0;net6.0;net7.0 false false - 9.0 + 10 SMSAPI SMSAPI SMSAPI @@ -16,6 +15,8 @@ SMSAPI README.md logo.jpg + enable + net6.0;net7.0;net8.0;net9.0;net10.0;netcoreapp3.1 SMSAPI.pl @@ -26,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;netcoreapp3.1 True @@ -55,4 +55,12 @@ + + + ..\..\..\..\.nuget\packages\newtonsoft.json\10.0.3\lib\netstandard1.3\Newtonsoft.Json.dll + + + + + 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 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/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/IntegrationTestBase.cs b/smsapiTests/Integration/IntegrationTestBase.cs index e6dcabd..e0e8789 100644 --- a/smsapiTests/Integration/IntegrationTestBase.cs +++ b/smsapiTests/Integration/IntegrationTestBase.cs @@ -10,22 +10,29 @@ namespace smsapiTests.Integration; public abstract class IntegrationTestBase { - private static string _currentHost; - + protected virtual bool AutostartServer => true; + protected 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)) - .UseUrls(_currentHost) + .UseUrls(CurrentHost) .Configure(app => app.UseMiddleware()) .Build() .Start(); @@ -33,10 +40,10 @@ private static void RunTestServer() protected static ProxyHTTP GetProxy() { - return new ProxyHTTP(_currentHost); + 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..a56740b --- /dev/null +++ b/smsapiTests/Integration/ProxyPathTest.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMSApi.Api; + +namespace smsapiTests.Integration; + +[TestClass] +public class ProxyPathTest : IntegrationTestBase +{ + 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()); + + 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 5ed356d..690e0ed 100644 --- a/smsapiTests/Integration/RequestAssert.cs +++ b/smsapiTests/Integration/RequestAssert.cs @@ -22,12 +22,23 @@ 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) + { + 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..d1acc85 100644 --- a/smsapiTests/Integration/RequestInterceptorMiddleware.cs +++ b/smsapiTests/Integration/RequestInterceptorMiddleware.cs @@ -1,5 +1,7 @@ +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; namespace smsapiTests.Integration; @@ -18,6 +20,8 @@ public async Task InvokeAsync(HttpContext context) RequestStorage.AuthorizationHeader = context.Request.Headers.Authorization; RequestStorage.UserAgentHeader = context.Request.Headers.UserAgent; 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 fd94ef8..91b2aa9 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,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); } } 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/Unit/Action/ActionPaginationTest.cs b/smsapiTests/Unit/Action/ActionPaginationTest.cs new file mode 100644 index 0000000..8cdd1e1 --- /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 = 10u; + 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 = 10u; + 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 = 5u; + var offset = 10u; + 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 uint? Limit { get; set; } + public uint? Offset { get; set; } + } + + private class Response{} +} 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; + } +} 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/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/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..02749f6 --- /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.Blacklist.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/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; + } +} diff --git a/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs b/smsapiTests/Unit/Action/HLR/Fixture/LookupsCollectionMother.cs new file mode 100644 index 0000000..c68cd89 --- /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, + double 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..9c381d3 --- /dev/null +++ b/smsapiTests/Unit/Action/HLR/ListLookupsResponseTest.cs @@ -0,0 +1,120 @@ +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 smsapiTests.Unit.Action.HLR.Fixture; +using smsapiTests.Unit.Fixture; +using smsapiTests.Unit.Helper; + +namespace smsapiTests.Unit.Action.HLR; + +[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 = 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 = 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 diff --git a/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs b/smsapiTests/Unit/Action/HLR/LookupRequestTest.cs new file mode 100644 index 0000000..448b0dc --- /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.HLR; + +[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; + } +} 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/Action/MFA/CreateMFACodeTest.cs b/smsapiTests/Unit/Action/MFA/CreateMFACodeTest.cs index 1312fee..ed2cca2 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,21 +31,21 @@ 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] - public void create_as_fast() + public void create_without_priority() { var create = CreateMfaCodeAction(GetAnyPhoneNumber()) - .AsFast(); + .WithoutPriority(); create.Execute(); - AssertParametersContain("fast", "1"); + _proxyAssert.AssertParametersContain("fast", "0"); } [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/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; + } +} diff --git a/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs b/smsapiTests/Unit/Action/OptOut/DeleteOptOutResponseTest.cs new file mode 100644 index 0000000..288535a --- /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("any"); + 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..8b9f713 --- /dev/null +++ b/smsapiTests/Unit/Action/OptOut/DeleteOptOutTest.cs @@ -0,0 +1,45 @@ +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 = AnyId(); + + CreateOptOutDelete(optOutId).Execute(); + + _proxyAssert.AssertUriEquals($"opt_outs/{optOutId}"); + } + + [TestMethod] + public void valid_method() + { + CreateOptOutDelete(AnyId()).Execute(); + + _proxyAssert.AssertRequestMethod(RequestMethod.DELETE); + } + + private DeleteOptOut CreateOptOutDelete(string optOutId) + { + var action = new DeleteOptOut(optOutId); + action.Proxy(_spyProxy); + + return action; + } + + private static string AnyId() => "5A5359173738303F2F95B7E2"; +} 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; + } +} 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; + } +} diff --git a/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs new file mode 100644 index 0000000..ef39955 --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/GetProfileResponseTest.cs @@ -0,0 +1,60 @@ +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 name = "fancy name"; + var username = "fancy_username"; + var email = "any@any.pl"; + var phoneNumber = "48500100100"; + var userType = "native"; + var points = 500.25d; + var paymentType = "prepaid"; + + var response = new Dictionary + { + { "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(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; + } +} 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; + } +} 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..252847c --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/Prices/Fixture/PricesCollectionMother.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using smsapiTests.Unit.Fixture; + +namespace smsapiTests.Unit.Action.Profile.Prices.Fixture; + +public static class PricesCollectionMother +{ + public static Dictionary EmptyCollection() => CollectionMother.Empty(); + + public static Dictionary SinglePrice( + float amount, + string currency, + string countryName, + int mcc, + string networkName, + int mnc, + string type + ) + { + return new Dictionary + { + { + "collection", new List + { + new Dictionary + { + { "type", type }, + { + "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..8e87169 --- /dev/null +++ b/smsapiTests/Unit/Action/Profile/Prices/GetPricesTest.cs @@ -0,0 +1,76 @@ +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 type = "hlr"; + + var response = PricesCollectionMother.SinglePrice( + amount, + currency, + countryName, + mcc, + networkName, + mnc, + type + ); + _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); + Assert.AreEqual(type, firstResult.Type); + } + + private GetPrices GetPrices() + { + var action = new GetPrices(); + action.Proxy(_proxyStub); + + return action; + } +} 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/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 90% rename from smsapiTests/Unit/SMS/SMSSendTest.cs rename to smsapiTests/Unit/Action/SMS/SMSSendTest.cs index 7989f38..3a53a67 100644 --- a/smsapiTests/Unit/SMS/SMSSendTest.cs +++ b/smsapiTests/Unit/Action/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 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(); + } +} 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/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(); + } +} diff --git a/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs new file mode 100644 index 0000000..81fa7dd --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/CreateShortUrlTest.cs @@ -0,0 +1,136 @@ +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 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(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")] + [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, FileInfo 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..8f64a69 --- /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_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 = 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://example.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/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; + } +} 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/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..e1accaa --- /dev/null +++ b/smsapiTests/Unit/Action/ShortUrl/ListShortUrlClicksGroupedByDeviceTest.cs @@ -0,0 +1,54 @@ +using System; +using System.Web; +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%5b%5d=1&links%5b%5d=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; + } +} 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/Action/ShortUrl/ShortUrlListResponseTest.cs b/smsapiTests/Unit/Action/ShortUrl/ShortUrlListResponseTest.cs new file mode 100644 index 0000000..b0d68c9 --- /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_short_urls() + { + 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; + } +} 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; + } +} 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..3852589 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/CreateSubuserTest.cs @@ -0,0 +1,160 @@ +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 credentials = new SubuserCredentials(username, password); + + CreateSubuser(credentials).Execute(); + + var expectedCredentials = new Dictionary + { + { "username", username }, + { "password", password }, + }; + _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/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 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; + } +} diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs new file mode 100644 index 0000000..cce5619 --- /dev/null +++ b/smsapiTests/Unit/Action/Subusers/Fixture/SubuersCollectionMother.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using SMSApi.Api.Response.Subusers; +using smsapiTests.Unit.Fixture; + +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 CollectionMother.WithItems(new Dictionary + { + { "id", id }, + { "username", username }, + { "active", active }, + { "description", description }, + { + "points", new Dictionary + { + { "from_account", userPoints.FromAccount }, + { "per_month", userPoints.PerMonth }, + } + } + }); + } +} \ No newline at end of file diff --git a/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs b/smsapiTests/Unit/Action/Subusers/Fixture/SubuserCredentialsMother.cs new file mode 100644 index 0000000..cdcec31 --- /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"); + } +} 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; + } +} 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; + } +} 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 + } + }; + } +} diff --git a/smsapiTests/Unit/Fixture/ProxyStub.cs b/smsapiTests/Unit/Fixture/ProxyStub.cs index ef9264a..84ea947 100644 --- a/smsapiTests/Unit/Fixture/ProxyStub.cs +++ b/smsapiTests/Unit/Fixture/ProxyStub.cs @@ -1,9 +1,10 @@ +using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Threading; using System.Threading.Tasks; using SMSApi.Api; +using SMSApi.Api.Action; namespace smsapiTests.Unit.Fixture; @@ -13,36 +14,36 @@ public class ProxyStub : 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, ISet> data, RequestMethod method) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } - public HttpResponseEntity Execute(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(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(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(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(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/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()); } 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; + } +} 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); + } +} diff --git a/smsapiTests/Unit/ProxyAssert.cs b/smsapiTests/Unit/ProxyAssert.cs new file mode 100644 index 0000000..10ddd84 --- /dev/null +++ b/smsapiTests/Unit/ProxyAssert.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +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), + $"expected: {uri}, got: {proxy.RequestedUri}" + ); + } + + public void AssertNoParameters() + { + var parametersCount = proxy.Parameters.Count; + + Assert.IsTrue(parametersCount == 0, $"Parameters expected to be empty, {parametersCount} found"); + } + + public ProxyAssert AssertParametersCount(int expectedCount) + { + Assert.AreEqual( + expectedCount, + proxy.Parameters.Count + ); + + return this; + } + + public ProxyAssert AssertParametersContain(string name, string value) + { + var expectedParameter = new KeyValuePair(name, value); + + Assert.IsTrue( + proxy.Parameters.Contains(value: expectedParameter), + $"Expected {value} ({value.GetType()}), actual value: {proxy.Parameters[name]} ({proxy.Parameters[name]?.GetType()})" + ); + + return this; + } + + public void AssertFileAttached(string name, Stream file) + { + Assert.IsTrue( + proxy.Files.Contains(value: KeyValuePair.Create(name, new StreamReader(file).ReadToEnd())), + "Not attached file found" + ); + } + + 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( + proxy.Parameters.ContainsKey(name), + $"Key not expected {name}" + ); + } +} diff --git a/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs new file mode 100644 index 0000000..802245d --- /dev/null +++ b/smsapiTests/Unit/Response/Deserialization/BaseJsonDeserializerTest.cs @@ -0,0 +1,268 @@ +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_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_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() + { + 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; + } + + private class JsonIgnoreAvoidsNameCollision + { + private int _backing; + + [JsonIgnore] + public int Value => _backing; + + [JsonProperty("value")] + private int ValueSerializationHelper + { + get => _backing; + set => _backing = value; + } + } + + private class LazyGetterFieldBackedCollection + { + private List _items; + + [JsonProperty("items")] + public List Items + { + get => _items ??= new List(); + set => _items = value; + } + } + + 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/Deserialization/LegacyResponseDeserializationTest.cs b/smsapiTests/Unit/Response/Deserialization/LegacyResponseDeserializationTest.cs index e844bd1..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 @@ -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..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 @@ -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() { 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 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; + } +} diff --git a/smsapiTests/Unit/SpyProxy.cs b/smsapiTests/Unit/SpyProxy.cs index f93e406..bcab892 100644 --- a/smsapiTests/Unit/SpyProxy.cs +++ b/smsapiTests/Unit/SpyProxy.cs @@ -1,86 +1,104 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using SMSApi.Api; +using SMSApi.Api.Action; namespace smsapiTests.Unit; public class SpyProxy : Proxy { public string RequestedUri { get; private set; } - - public Dictionary Parameters { get; } = new(); + + public RequestMethod RequestMethod { get; private set; } + + public Dictionary Parameters { get; } = new(); + public ICollection> Files { get; } = new List>(); 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, ISet> data, RequestMethod method) { RequestedUri = uri; SetParameters(data); + RequestMethod = method; + 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, ISet> data, Stream file, RequestMethod method) { RequestedUri = uri; SetParameters(data); + RequestMethod = method; + Files.Add(KeyValuePair.Create("", new StreamReader(file).ReadToEnd())); + file.Position = 0; 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, ISet> data, Dictionary files, RequestMethod method) { RequestedUri = uri; SetParameters(data); + RequestMethod = method; + foreach (var file in files) + { + 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); } - public Task ExecuteAsync(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); + RequestMethod = method; 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, ISet> data, Stream file, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); + RequestMethod = method; 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, ISet> data, Dictionary files, RequestMethod method, CancellationToken cancellationToken = default) { RequestedUri = uri; SetParameters(data); + RequestMethod = method; 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 } } diff --git a/smsapiTests/smsapiTests.csproj b/smsapiTests/smsapiTests.csproj index b984ac6..673c76d 100644 --- a/smsapiTests/smsapiTests.csproj +++ b/smsapiTests/smsapiTests.csproj @@ -2,24 +2,20 @@ false - net7.0 + net6.0;net7.0;net8.0;net9.0;net10.0 3.0.0 + 12.0 + enable - - - - - - + + + - - -