From 954d6fb7f854fb8b5df36113d1019410991f457f Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 00:48:33 +0100 Subject: [PATCH 1/8] Added support for non generic AppwriteResult for when there is no content returned --- .../AppwriteResult.cs | 51 +++++++++++++++---- .../Requests/DeleteAuthenticatorRequest.cs | 22 ++++++++ .../DeleteAuthenticatorRequestValidator.cs | 11 ++++ .../AppwriteResultTests.cs | 4 +- 4 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/Requests/DeleteAuthenticatorRequest.cs create mode 100644 src/PinguApps.Appwrite.Shared/Requests/Validators/DeleteAuthenticatorRequestValidator.cs diff --git a/src/PinguApps.Appwrite.Shared/AppwriteResult.cs b/src/PinguApps.Appwrite.Shared/AppwriteResult.cs index d0a04fc5..82dbe106 100644 --- a/src/PinguApps.Appwrite.Shared/AppwriteResult.cs +++ b/src/PinguApps.Appwrite.Shared/AppwriteResult.cs @@ -1,40 +1,73 @@ using OneOf; +using OneOf.Types; namespace PinguApps.Appwrite.Shared; /// /// The result of all API calls /// -/// the type of response expected on success -public class AppwriteResult +public class AppwriteResult { - public AppwriteResult(OneOf result) + public AppwriteResult(OneOf result) { Result = result; } + protected AppwriteResult() + { + + } + /// - /// The result of making the API call. Can be , or depending on what happened + /// The result of making the API call. Can be , or depending on what happened /// - public OneOf Result { get; } + public OneOf Result { get; } /// /// Indicates the API call was successful /// - public bool Success => Result.IsT0; + public virtual bool Success => Result.IsT0; /// /// Indicates there is an error /// - public bool IsError => Result.IsT1 || Result.IsT2; + public virtual bool IsError => Result.IsT1 || Result.IsT2; /// /// Indicates that there was an error thrown within Appwrite /// - public bool IsAppwriteError => Result.IsT1; + public virtual bool IsAppwriteError => Result.IsT1; /// /// Indicates that there was an error thrown within the SDK /// - public bool IsInternalError => Result.IsT2; + public virtual bool IsInternalError => Result.IsT2; +} + + +/// +/// the type of response expected on success +public class AppwriteResult : AppwriteResult +{ + public AppwriteResult(OneOf result) + { + Result = result; + } + + /// + /// /// The result of making the API call. Can be , or depending on what happened + /// + public new OneOf Result { get; } + + /// + public override bool Success => Result.IsT0; + + /// + public override bool IsError => Result.IsT1 || Result.IsT2; + + /// + public override bool IsAppwriteError => Result.IsT1; + + /// + public override bool IsInternalError => Result.IsT2; } diff --git a/src/PinguApps.Appwrite.Shared/Requests/DeleteAuthenticatorRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/DeleteAuthenticatorRequest.cs new file mode 100644 index 00000000..21fd9b78 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/DeleteAuthenticatorRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; + +/// +/// The request for deleting an authenticator +/// +public class DeleteAuthenticatorRequest : BaseRequest +{ + /// + /// Type of authenticator + /// + [JsonIgnore] + public string Type { get; set; } = "totp"; + + /// + /// Valid verification token + /// + [JsonPropertyName("otp")] + public string Otp { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/DeleteAuthenticatorRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/DeleteAuthenticatorRequestValidator.cs new file mode 100644 index 00000000..c5e71830 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/DeleteAuthenticatorRequestValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class DeleteAuthenticatorRequestValidator : AbstractValidator +{ + public DeleteAuthenticatorRequestValidator() + { + RuleFor(x => x.Type).NotEmpty(); + RuleFor(x => x.Otp).NotEmpty(); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs index bfb6d3f1..150c6e1b 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs @@ -13,7 +13,7 @@ public void Constructor_WithTResult_SuccessIsTrue() Assert.False(result.IsError); Assert.False(result.IsAppwriteError); Assert.False(result.IsInternalError); - Assert.True(result.Result.AsT0 == "Success"); + Assert.Equal("Success", result.Result.AsT0); } [Fact] @@ -25,6 +25,7 @@ public void Constructor_WithAppwriteError_IsAppwriteErrorIsTrue() Assert.True(result.IsError); Assert.True(result.IsAppwriteError); Assert.False(result.IsInternalError); + Assert.Equal("Message", result.Result.AsT1.Message); } [Fact] @@ -36,5 +37,6 @@ public void Constructor_WithInternalError_IsInternalErrorIsTrue() Assert.True(result.IsError); Assert.False(result.IsAppwriteError); Assert.True(result.IsInternalError); + Assert.Equal("Message", result.Result.AsT2.Message); } } From 4b180d5690b45b25272fb9868902f930f2c93844 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 00:59:22 +0100 Subject: [PATCH 2/8] Implemented Delete Authenticator --- .../Clients/AccountClient.cs | 17 ++++++++++++++ .../Clients/IAccountClient.cs | 8 +++++++ .../Internals/IAccountApi.cs | 3 +++ .../Utils/ResponseUtils.cs | 23 +++++++++++++++++++ src/PinguApps.Appwrite.Playground/App.cs | 5 ++-- 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index 9a6ef10c..fb880448 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -346,4 +346,21 @@ public async Task> UpdateMfa(UpdateMfaRequest request) return e.GetExceptionResponse(); } } + + /// + public async Task DeleteAuthenticator(DeleteAuthenticatorRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.DeleteAuthenticator(Session, request.Type, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } } diff --git a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs index fa549def..b4a4faa8 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -166,4 +166,12 @@ public interface IAccountClient /// The request content /// The user Task> UpdateMfa(UpdateMfaRequest request); + + /// + /// Delete an authenticator for a user by ID + /// Appwrite Docs + /// + /// The request content + /// The result + Task DeleteAuthenticator(DeleteAuthenticatorRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index d4533158..f31f5aac 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -65,4 +65,7 @@ internal interface IAccountApi : IBaseApi [Patch("/account/mfa")] Task> UpdateMfa([Header("x-appwrite-session")] string? session, UpdateMfaRequest request); + + [Delete("/account/mfa/authenticators/{type}")] + Task DeleteAuthenticator([Header("x-appwrite-session")] string? session, string type, [Body] DeleteAuthenticatorRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs b/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs index ecfeacc4..2980a6bd 100644 --- a/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs +++ b/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs @@ -1,11 +1,29 @@ using System; using System.Text.Json; +using OneOf.Types; using PinguApps.Appwrite.Shared; using Refit; namespace PinguApps.Appwrite.Client.Utils; internal static class ResponseUtils { + internal static AppwriteResult GetApiResponse(this IApiResponse result) + { + if (result.IsSuccessStatusCode) + { + return new AppwriteResult(new Success()); + } + + if (result.Error?.Content is null || string.IsNullOrEmpty(result.Error.Content)) + { + throw new Exception("Unknown error encountered."); + } + + var error = JsonSerializer.Deserialize(result.Error.Content); + + return new AppwriteResult(error!); + } + internal static AppwriteResult GetApiResponse(this IApiResponse result) { if (result.IsSuccessStatusCode) @@ -28,6 +46,11 @@ internal static AppwriteResult GetApiResponse(this IApiResponse result) return new AppwriteResult(error!); } + internal static AppwriteResult GetExceptionResponse(this Exception e) + { + return new AppwriteResult(new InternalError(e.Message)); + } + internal static AppwriteResult GetExceptionResponse(this Exception e) { return new AppwriteResult(new InternalError(e.Message)); diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 41e9c111..e103434b 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -21,9 +21,10 @@ public async Task Run(string[] args) _client.SetSession(_session); //var response = await _client.Account.AddAuthenticator(); - var response = await _client.Account.UpdateMfa(new Shared.Requests.UpdateMfaRequest + var response = await _client.Account.DeleteAuthenticator(new Shared.Requests.DeleteAuthenticatorRequest { - MfaEnabled = false + Type = "totp", + Otp = "413526" }); Console.WriteLine(response.Result.Match( From fd19dbffe22b188b6d5927aaccb1e3f815b96196 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:12:15 +0100 Subject: [PATCH 3/8] Changed how we are handling text input such as type, so that we get better validation on it in future --- .../Clients/AccountClient.cs | 10 ++++---- .../Clients/IAccountClient.cs | 4 ++-- .../Requests/AddAuthenticatorRequest.cs | 12 ++++++++++ .../Requests/BaseRequest.cs | 9 ++++++++ .../AddAuthenticatorRequestValidator.cs | 10 ++++++++ .../VerifyAuthenticatorRequestValidator.cs | 1 + .../Requests/VerifyAuthenticatorRequest.cs | 9 ++++++++ .../AccountClientTests.AddAuthenticator.cs | 23 ++++++++++++++----- .../AccountClientTests.VerifyAuthenticator.cs | 5 ++-- 9 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/Requests/AddAuthenticatorRequest.cs create mode 100644 src/PinguApps.Appwrite.Shared/Requests/Validators/AddAuthenticatorRequestValidator.cs diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index fb880448..4ef2e742 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -299,11 +299,13 @@ public async Task> ListLogs(List? queries = null } /// - public async Task> AddAuthenticator(string type = "totp") + public async Task> AddAuthenticator(AddAuthenticatorRequest request) { try { - var result = await _accountApi.AddAuthenticator(Session, type); + request.Validate(true); + + var result = await _accountApi.AddAuthenticator(Session, request.Type); return result.GetApiResponse(); } @@ -314,13 +316,13 @@ public async Task> AddAuthenticator(string type = "totp" } /// - public async Task> VerifyAuthenticator(VerifyAuthenticatorRequest request, string type = "totp") + public async Task> VerifyAuthenticator(VerifyAuthenticatorRequest request) { try { request.Validate(true); - var result = await _accountApi.VerifyAuthenticator(Session, type, request); + var result = await _accountApi.VerifyAuthenticator(Session, request.Type, request); return result.GetApiResponse(); } diff --git a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs index b4a4faa8..e4c12988 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -148,7 +148,7 @@ public interface IAccountClient /// /// Type of authenticator. Must be `totp` /// The MfaType - Task> AddAuthenticator(string type = "totp"); + Task> AddAuthenticator(AddAuthenticatorRequest request); /// /// Verify an authenticator app after adding it using . @@ -157,7 +157,7 @@ public interface IAccountClient /// The request content /// Type of authenticator /// The User - Task> VerifyAuthenticator(VerifyAuthenticatorRequest request, string type = "totp"); + Task> VerifyAuthenticator(VerifyAuthenticatorRequest request); /// /// Enable or disable MFA on an account diff --git a/src/PinguApps.Appwrite.Shared/Requests/AddAuthenticatorRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/AddAuthenticatorRequest.cs new file mode 100644 index 00000000..0514306a --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/AddAuthenticatorRequest.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; +public class AddAuthenticatorRequest : BaseRequest +{ + /// + /// Type of authenticator. Must be `totp` + /// + [JsonIgnore] + public string Type { get; set; } = "totp"; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/BaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/BaseRequest.cs index 858a9d7b..f6dc8df1 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/BaseRequest.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/BaseRequest.cs @@ -6,8 +6,17 @@ public abstract class BaseRequest where TRequest : class where TValidator : IValidator, new() { + /// + /// True if the request object passes all validation + /// + /// Whether the request object is valid public bool IsValid() => Validate().IsValid; + /// + /// Attempts to validate the request object + /// + /// If true, throws an exception on failure + /// The result, showing any errors if applicable public ValidationResult Validate(bool throwOnFailures = false) { var validator = new TValidator(); diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/AddAuthenticatorRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/AddAuthenticatorRequestValidator.cs new file mode 100644 index 00000000..b9a5a200 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/AddAuthenticatorRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class AddAuthenticatorRequestValidator : AbstractValidator +{ + public AddAuthenticatorRequestValidator() + { + RuleFor(x => x.Type).NotEmpty(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/VerifyAuthenticatorRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/VerifyAuthenticatorRequestValidator.cs index ebcc168b..2561daa9 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/Validators/VerifyAuthenticatorRequestValidator.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/VerifyAuthenticatorRequestValidator.cs @@ -6,5 +6,6 @@ public class VerifyAuthenticatorRequestValidator : AbstractValidator x.Otp).NotEmpty(); + RuleFor(x => x.Type).NotEmpty(); } } diff --git a/src/PinguApps.Appwrite.Shared/Requests/VerifyAuthenticatorRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/VerifyAuthenticatorRequest.cs index 6f335680..ab33b59b 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/VerifyAuthenticatorRequest.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/VerifyAuthenticatorRequest.cs @@ -4,6 +4,15 @@ namespace PinguApps.Appwrite.Shared.Requests; public class VerifyAuthenticatorRequest : BaseRequest { + /// + /// Valid verification token + /// [JsonPropertyName("otp")] public string Otp { get; set; } = string.Empty; + + /// + /// Type of authenticator + /// + [JsonIgnore] + public string Type { get; set; } = "totp"; } diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs index 47b12a33..d358f5c6 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.AddAuthenticator.cs @@ -1,4 +1,5 @@ using System.Net; +using PinguApps.Appwrite.Shared.Requests; using PinguApps.Appwrite.Shared.Tests; using RichardSzalay.MockHttp; @@ -9,6 +10,8 @@ public partial class AccountClientTests public async Task AddAuthenticator_ShouldReturnSuccess_WhenApiCallSucceeds() { // Arrange + var request = new AddAuthenticatorRequest(); + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") .ExpectedHeaders(true) .Respond(Constants.AppJson, Constants.MfaTypeResponse); @@ -16,7 +19,7 @@ public async Task AddAuthenticator_ShouldReturnSuccess_WhenApiCallSucceeds() _appwriteClient.SetSession(Constants.Session); // Act - var result = await _appwriteClient.Account.AddAuthenticator(); + var result = await _appwriteClient.Account.AddAuthenticator(request); // Assert Assert.True(result.Success); @@ -27,19 +30,23 @@ public async Task AddAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUsed( { // Arrange var type = "newAuth"; + var request = new AddAuthenticatorRequest() + { + Type = type + }; var requestUri = $"{Constants.Endpoint}/account/mfa/authenticators/{type}"; - var request = _mockHttp.Expect(HttpMethod.Post, requestUri) + var mockRequest = _mockHttp.Expect(HttpMethod.Post, requestUri) .ExpectedHeaders(true) .Respond(Constants.AppJson, Constants.MfaTypeResponse); _appwriteClient.SetSession(Constants.Session); // Act - var result = await _appwriteClient.Account.AddAuthenticator(type); + var result = await _appwriteClient.Account.AddAuthenticator(request); // Assert _mockHttp.VerifyNoOutstandingExpectation(); - var matches = _mockHttp.GetMatchCount(request); + var matches = _mockHttp.GetMatchCount(mockRequest); Assert.Equal(1, matches); } @@ -47,6 +54,8 @@ public async Task AddAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUsed( public async Task AddAuthenticator_ShouldHandleException_WhenApiCallFails() { // Arrange + var request = new AddAuthenticatorRequest(); + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") .ExpectedHeaders(true) .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); @@ -54,7 +63,7 @@ public async Task AddAuthenticator_ShouldHandleException_WhenApiCallFails() _appwriteClient.SetSession(Constants.Session); // Act - var result = await _appwriteClient.Account.AddAuthenticator(); + var result = await _appwriteClient.Account.AddAuthenticator(request); // Assert Assert.True(result.IsError); @@ -65,6 +74,8 @@ public async Task AddAuthenticator_ShouldHandleException_WhenApiCallFails() public async Task AddAuthenticator_ShouldReturnErrorResponse_WhenExceptionOccurs() { // Arrange + var request = new AddAuthenticatorRequest(); + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/mfa/authenticators/totp") .ExpectedHeaders(true) .Throw(new HttpRequestException("An error occurred")); @@ -72,7 +83,7 @@ public async Task AddAuthenticator_ShouldReturnErrorResponse_WhenExceptionOccurs _appwriteClient.SetSession(Constants.Session); // Act - var result = await _appwriteClient.Account.AddAuthenticator(); + var result = await _appwriteClient.Account.AddAuthenticator(request); // Assert Assert.False(result.Success); diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.VerifyAuthenticator.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.VerifyAuthenticator.cs index 7efca464..c10a34cc 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.VerifyAuthenticator.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.VerifyAuthenticator.cs @@ -36,7 +36,8 @@ public async Task VerifyAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUs var requestUri = $"{Constants.Endpoint}/account/mfa/authenticators/{type}"; var requestBody = new VerifyAuthenticatorRequest { - Otp = "123456" + Otp = "123456", + Type = type }; var request = _mockHttp.Expect(HttpMethod.Put, requestUri) .WithJsonContent(requestBody) @@ -46,7 +47,7 @@ public async Task VerifyAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUs _appwriteClient.SetSession(Constants.Session); // Act - var result = await _appwriteClient.Account.VerifyAuthenticator(requestBody, type); + var result = await _appwriteClient.Account.VerifyAuthenticator(requestBody); // Assert _mockHttp.VerifyNoOutstandingExpectation(); From f0c69ffe4ffbde354c189c9758faab1f172d19a0 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:26:10 +0100 Subject: [PATCH 4/8] Added shared tests to cover all new code --- .../AppwriteResultTests.cs | 41 ++++++- .../Requests/AddAuthenticatorRequestTests.cs | 95 ++++++++++++++++ .../DeleteAuthenticatorRequestTests.cs | 103 ++++++++++++++++++ 3 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Requests/AddAuthenticatorRequestTests.cs create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Requests/DeleteAuthenticatorRequestTests.cs diff --git a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs index 150c6e1b..6e5cb80d 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs @@ -1,4 +1,5 @@ using OneOf; +using OneOf.Types; namespace PinguApps.Appwrite.Shared.Tests; @@ -6,6 +7,42 @@ public class AppwriteResultTests { [Fact] public void Constructor_WithTResult_SuccessIsTrue() + { + var result = new AppwriteResult(OneOf.FromT0(new Success())); + + Assert.True(result.Success); + Assert.False(result.IsError); + Assert.False(result.IsAppwriteError); + Assert.False(result.IsInternalError); + Assert.IsType(result.Result.AsT0); + } + + [Fact] + public void Constructor_WithAppwriteError_IsAppwriteErrorIsTrue() + { + var result = new AppwriteResult(OneOf.FromT1(new AppwriteError("Message", 500, "Type", "Version"))); + + Assert.False(result.Success); + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + Assert.False(result.IsInternalError); + Assert.Equal("Message", result.Result.AsT1.Message); + } + + [Fact] + public void Constructor_WithInternalError_IsInternalErrorIsTrue() + { + var result = new AppwriteResult(OneOf.FromT2(new InternalError("Message"))); + + Assert.False(result.Success); + Assert.True(result.IsError); + Assert.False(result.IsAppwriteError); + Assert.True(result.IsInternalError); + Assert.Equal("Message", result.Result.AsT2.Message); + } + + [Fact] + public void GenericConstructor_WithTResult_SuccessIsTrue() { var result = new AppwriteResult(OneOf.FromT0("Success")); @@ -17,7 +54,7 @@ public void Constructor_WithTResult_SuccessIsTrue() } [Fact] - public void Constructor_WithAppwriteError_IsAppwriteErrorIsTrue() + public void GenericConstructor_WithAppwriteError_IsAppwriteErrorIsTrue() { var result = new AppwriteResult(OneOf.FromT1(new AppwriteError("Message", 500, "Type", "Version"))); @@ -29,7 +66,7 @@ public void Constructor_WithAppwriteError_IsAppwriteErrorIsTrue() } [Fact] - public void Constructor_WithInternalError_IsInternalErrorIsTrue() + public void GenericConstructor_WithInternalError_IsInternalErrorIsTrue() { var result = new AppwriteResult(OneOf.FromT2(new InternalError("Message"))); diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/AddAuthenticatorRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/AddAuthenticatorRequestTests.cs new file mode 100644 index 00000000..96ef335a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/AddAuthenticatorRequestTests.cs @@ -0,0 +1,95 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class AddAuthenticatorRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new AddAuthenticatorRequest(); + + // Assert + Assert.Equal("totp", request.Type); + } + + [Theory] + [InlineData("A string")] + [InlineData("123456")] + [InlineData("A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. ")] + public void Properties_CanBeSet(string type) + { + // Arrange + var request = new AddAuthenticatorRequest(); + + // Act + request.Type = type; + + // Assert + Assert.Equal(type, request.Type); + } + + [Fact] + public void IsValid_WithValidInputs_ReturnsTrue() + { + // Arrange + var request = new AddAuthenticatorRequest + { + Type = "abcd" + }; + + // Act + bool isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void IsValid_WithInvalidInputs_ReturnsFalse(string? type) + { + // Arrange + var request = new AddAuthenticatorRequest + { + Type = type! + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new AddAuthenticatorRequest + { + Type = null! + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new AddAuthenticatorRequest + { + Type = null! + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/DeleteAuthenticatorRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/DeleteAuthenticatorRequestTests.cs new file mode 100644 index 00000000..c5bf4f88 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/DeleteAuthenticatorRequestTests.cs @@ -0,0 +1,103 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class DeleteAuthenticatorRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new DeleteAuthenticatorRequest(); + + // Assert + Assert.Equal("totp", request.Type); + Assert.Equal(string.Empty, request.Otp); + } + + [Theory] + [InlineData("A string", "A string")] + [InlineData("123456", "123456")] + [InlineData("A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. ", "A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. A much longer string. ")] + public void Properties_CanBeSet(string type, string otp) + { + // Arrange + var request = new DeleteAuthenticatorRequest(); + + // Act + request.Type = type; + request.Otp = otp; + + // Assert + Assert.Equal(type, request.Type); + } + + [Fact] + public void IsValid_WithValidInputs_ReturnsTrue() + { + // Arrange + var request = new DeleteAuthenticatorRequest + { + Type = "abcd", + Otp = "123456" + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("", "123456")] + [InlineData(null, "123456")] + [InlineData("abcd", "")] + [InlineData("abcd", null)] + public void IsValid_WithInvalidInputs_ReturnsFalse(string? type, string? otp) + { + // Arrange + var request = new DeleteAuthenticatorRequest + { + Type = type!, + Otp = otp! + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new DeleteAuthenticatorRequest + { + Type = null!, + Otp = null! + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new DeleteAuthenticatorRequest + { + Type = null!, + Otp = null! + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} From 1afad7bb5feeb6f0c9bf68ba15454ccc36e4b497 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:39:52 +0100 Subject: [PATCH 5/8] Added all client tests for new code --- .../AccountClientTests.DeleteAuthenticator.cs | 107 ++++++++++++++++++ .../Utils/ResponseUtilsTests.cs | 77 ++++++++++++- 2 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.DeleteAuthenticator.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.DeleteAuthenticator.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.DeleteAuthenticator.cs new file mode 100644 index 00000000..0498c263 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.DeleteAuthenticator.cs @@ -0,0 +1,107 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + [Fact] + public async Task DeleteAuthenticator_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteAuthenticatorRequest() + { + Otp = "123456" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.MfaTypeResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.DeleteAuthenticator(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteAuthenticator_ShouldHitDifferentEndpoint_WhenNewTypeIsUsed() + { + // Arrange + var type = "newAuth"; + var request = new DeleteAuthenticatorRequest() + { + Type = type, + Otp = "123456" + }; + var requestUri = $"{Constants.Endpoint}/account/mfa/authenticators/{type}"; + var mockRequest = _mockHttp.Expect(HttpMethod.Delete, requestUri) + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.MfaTypeResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.DeleteAuthenticator(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + var matches = _mockHttp.GetMatchCount(mockRequest); + Assert.Equal(1, matches); + } + + [Fact] + public async Task DeleteAuthenticator_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteAuthenticatorRequest() + { + Otp = "123456" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.DeleteAuthenticator(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteAuthenticator_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteAuthenticatorRequest() + { + Otp = "123456" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{Constants.Endpoint}/account/mfa/authenticators/totp") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.DeleteAuthenticator(request); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs index 31167695..09f138a9 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs @@ -1,5 +1,6 @@ using System.Net; using Moq; +using OneOf.Types; using PinguApps.Appwrite.Client.Utils; using PinguApps.Appwrite.Shared.Tests; using Refit; @@ -7,8 +8,63 @@ namespace PinguApps.Appwrite.Client.Tests.Utils; public class ResponseUtilsTests { + [Fact] public void GetApiResponse_Success_ReturnsContent() + { + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.True(result.Success); + Assert.IsType(result.Result.AsT0); + } + + [Fact] + public async Task GetApiResponse_Failure_ReturnsError() + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(Constants.AppwriteError) + }; + var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, response, new RefitSettings()); + + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns(exception); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.False(result.Success); + Assert.True(result.IsAppwriteError); + Assert.True(result.Result.IsT1); + } + + [Fact] + public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() + { + var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, new HttpResponseMessage(HttpStatusCode.InternalServerError), new RefitSettings()); + + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns(exception); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + + [Fact] + public void GetApiResponse_FailureButNullError_ThrowsException() + { + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns((ApiException)null!); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + + [Fact] + public void GenericGetApiResponse_Success_ReturnsContent() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); @@ -21,7 +77,7 @@ public void GetApiResponse_Success_ReturnsContent() } [Fact] - public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() + public void GenericGetApiResponse_SuccessButNullContent_ReturnsInternalError() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); @@ -34,7 +90,7 @@ public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() } [Fact] - public async Task GetApiResponse_Failure_ReturnsError() + public async Task GenericGetApiResponse_Failure_ReturnsError() { var response = new HttpResponseMessage(HttpStatusCode.InternalServerError) { @@ -54,7 +110,7 @@ public async Task GetApiResponse_Failure_ReturnsError() } [Fact] - public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() + public async Task GenericGetApiResponse_FailureButNullErrorContent_ThrowsException() { var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, new HttpResponseMessage(HttpStatusCode.InternalServerError), new RefitSettings()); @@ -66,7 +122,7 @@ public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() } [Fact] - public void GetApiResponse_FailureButNullError_ThrowsException() + public void GenericGetApiResponse_FailureButNullError_ThrowsException() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); @@ -80,6 +136,19 @@ public void GetExceptionResponse_ReturnsInternalError() { var exception = new Exception("Test exception"); + var result = exception.GetExceptionResponse(); + + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.True(result.Result.IsT2); + Assert.Equal("Test exception", result.Result.AsT2.Message); + } + + [Fact] + public void GenericGetExceptionResponse_ReturnsInternalError() + { + var exception = new Exception("Test exception"); + var result = exception.GetExceptionResponse(); Assert.False(result.Success); From 5ec12d98b296810f2a9009fd0862769f80334134 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:42:41 +0100 Subject: [PATCH 6/8] Added utils for server to match client and added server tests --- .../Utils/ResponseUtils.cs | 23 +++++ .../Utils/ResponseUtilsTests.cs | 1 - .../Utils/ResponseUtilsTests.cs | 84 ++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs b/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs index a31c654a..1e7c3920 100644 --- a/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs +++ b/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs @@ -1,11 +1,29 @@ using System; using System.Text.Json; +using OneOf.Types; using PinguApps.Appwrite.Shared; using Refit; namespace PinguApps.Appwrite.Server.Utils; internal static class ResponseUtils { + internal static AppwriteResult GetApiResponse(this IApiResponse result) + { + if (result.IsSuccessStatusCode) + { + return new AppwriteResult(new Success()); + } + + if (result.Error?.Content is null || string.IsNullOrEmpty(result.Error.Content)) + { + throw new Exception("Unknown error encountered."); + } + + var error = JsonSerializer.Deserialize(result.Error.Content); + + return new AppwriteResult(error!); + } + internal static AppwriteResult GetApiResponse(this IApiResponse result) { if (result.IsSuccessStatusCode) @@ -28,6 +46,11 @@ internal static AppwriteResult GetApiResponse(this IApiResponse result) return new AppwriteResult(error!); } + internal static AppwriteResult GetExceptionResponse(this Exception e) + { + return new AppwriteResult(new InternalError(e.Message)); + } + internal static AppwriteResult GetExceptionResponse(this Exception e) { return new AppwriteResult(new InternalError(e.Message)); diff --git a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs index 09f138a9..07924728 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs @@ -8,7 +8,6 @@ namespace PinguApps.Appwrite.Client.Tests.Utils; public class ResponseUtilsTests { - [Fact] public void GetApiResponse_Success_ReturnsContent() { diff --git a/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs index 5ddd5ad2..0cb55ad9 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs +++ b/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs @@ -1,5 +1,6 @@ using System.Net; using Moq; +using OneOf.Types; using PinguApps.Appwrite.Server.Utils; using PinguApps.Appwrite.Shared.Tests; using Refit; @@ -9,6 +10,60 @@ public class ResponseUtilsTests { [Fact] public void GetApiResponse_Success_ReturnsContent() + { + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.True(result.Success); + Assert.IsType(result.Result.AsT0); + } + + [Fact] + public async Task GetApiResponse_Failure_ReturnsError() + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(Constants.AppwriteError) + }; + var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, response, new RefitSettings()); + + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns(exception); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.False(result.Success); + Assert.True(result.IsAppwriteError); + Assert.True(result.Result.IsT1); + } + + [Fact] + public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() + { + var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, new HttpResponseMessage(HttpStatusCode.InternalServerError), new RefitSettings()); + + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns(exception); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + + [Fact] + public void GetApiResponse_FailureButNullError_ThrowsException() + { + var mockApiResponse = new Mock(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns((ApiException)null!); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + + [Fact] + public void GenericGetApiResponse_Success_ReturnsContent() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); @@ -21,7 +76,7 @@ public void GetApiResponse_Success_ReturnsContent() } [Fact] - public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() + public void GenericGetApiResponse_SuccessButNullContent_ReturnsInternalError() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); @@ -34,7 +89,7 @@ public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() } [Fact] - public async Task GetApiResponse_Failure_ReturnsError() + public async Task GenericGetApiResponse_Failure_ReturnsError() { var response = new HttpResponseMessage(HttpStatusCode.InternalServerError) { @@ -54,7 +109,7 @@ public async Task GetApiResponse_Failure_ReturnsError() } [Fact] - public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() + public async Task GenericGetApiResponse_FailureButNullErrorContent_ThrowsException() { var exception = await ApiException.Create(new HttpRequestMessage(), HttpMethod.Get, new HttpResponseMessage(HttpStatusCode.InternalServerError), new RefitSettings()); @@ -65,11 +120,34 @@ public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); } + [Fact] + public void GenericGetApiResponse_FailureButNullError_ThrowsException() + { + var mockApiResponse = new Mock>(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns((ApiException)null!); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + [Fact] public void GetExceptionResponse_ReturnsInternalError() { var exception = new Exception("Test exception"); + var result = exception.GetExceptionResponse(); + + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.True(result.Result.IsT2); + Assert.Equal("Test exception", result.Result.AsT2.Message); + } + + [Fact] + public void GenericGetExceptionResponse_ReturnsInternalError() + { + var exception = new Exception("Test exception"); + var result = exception.GetExceptionResponse(); Assert.False(result.Success); From 2dbfda89d9b6d5d37483522e576688a1d4debe88 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:43:53 +0100 Subject: [PATCH 7/8] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5a675546..f5ca3125 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ``` ## ⌛ Progress - -![Server & Client - 21 / 288](https://img.shields.io/badge/Server_&_Client-21%20%2F%20288-red?style=for-the-badge) + +![Server & Client - 22 / 288](https://img.shields.io/badge/Server_&_Client-22%20%2F%20288-red?style=for-the-badge) ![Server - 2 / 195](https://img.shields.io/badge/Server-2%20%2F%20195-red?style=for-the-badge) - -![Client - 19 / 93](https://img.shields.io/badge/Client-19%20%2F%2093-red?style=for-the-badge) + +![Client - 20 / 93](https://img.shields.io/badge/Client-20%20%2F%2093-red?style=for-the-badge) ### 🔑 Key | Icon | Definition | @@ -155,8 +155,8 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account - -![Account - 21 / 52](https://img.shields.io/badge/Account-21%20%2F%2052-yellow?style=for-the-badge) + +![Account - 22 / 52](https://img.shields.io/badge/Account-22%20%2F%2052-yellow?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -170,7 +170,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Update MFA](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMFA) | ✅ | ❌ | | [Add Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaAuthenticator) | ✅ | ❌ | | [Verify Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMfaAuthenticator) | ✅ | ❌ | -| [Delete Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteMfaAuthenticator) | ⬛ | ❌ | +| [Delete Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteMfaAuthenticator) | ✅ | ❌ | | [Create 2FA Challenge](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaChallenge) | ⬛ | ❌ | | [Create MFA Challenge (confirmation)](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMfaChallenge) | ⬛ | ❌ | | [List Factors](https://appwrite.io/docs/references/1.5.x/client-rest/account#listMfaFactors) | ⬛ | ❌ | From 344d026942b8fe001513d075a66cbd0dbde68eba Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Fri, 9 Aug 2024 01:51:42 +0100 Subject: [PATCH 8/8] Fixed CodeFactor issues manually --- .../AppwriteResult.cs | 29 ------------------ .../AppwriteResultOfT.cs | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/AppwriteResultOfT.cs diff --git a/src/PinguApps.Appwrite.Shared/AppwriteResult.cs b/src/PinguApps.Appwrite.Shared/AppwriteResult.cs index 82dbe106..08e77070 100644 --- a/src/PinguApps.Appwrite.Shared/AppwriteResult.cs +++ b/src/PinguApps.Appwrite.Shared/AppwriteResult.cs @@ -15,7 +15,6 @@ public AppwriteResult(OneOf result) protected AppwriteResult() { - } /// @@ -43,31 +42,3 @@ protected AppwriteResult() /// public virtual bool IsInternalError => Result.IsT2; } - - -/// -/// the type of response expected on success -public class AppwriteResult : AppwriteResult -{ - public AppwriteResult(OneOf result) - { - Result = result; - } - - /// - /// /// The result of making the API call. Can be , or depending on what happened - /// - public new OneOf Result { get; } - - /// - public override bool Success => Result.IsT0; - - /// - public override bool IsError => Result.IsT1 || Result.IsT2; - - /// - public override bool IsAppwriteError => Result.IsT1; - - /// - public override bool IsInternalError => Result.IsT2; -} diff --git a/src/PinguApps.Appwrite.Shared/AppwriteResultOfT.cs b/src/PinguApps.Appwrite.Shared/AppwriteResultOfT.cs new file mode 100644 index 00000000..95c2cff7 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/AppwriteResultOfT.cs @@ -0,0 +1,30 @@ +using OneOf; + +namespace PinguApps.Appwrite.Shared; + +/// +/// the type of response expected on success +public class AppwriteResult : AppwriteResult +{ + public AppwriteResult(OneOf result) + { + Result = result; + } + + /// + /// /// The result of making the API call. Can be , or depending on what happened + /// + public new OneOf Result { get; } + + /// + public override bool Success => Result.IsT0; + + /// + public override bool IsError => Result.IsT1 || Result.IsT2; + + /// + public override bool IsAppwriteError => Result.IsT1; + + /// + public override bool IsInternalError => Result.IsT2; +}