diff --git a/README.md b/README.md index 67fbfa5b..5a675546 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ``` ## ⌛ Progress - -![Server & Client - 20 / 288](https://img.shields.io/badge/Server_&_Client-20%20%2F%20288-red?style=for-the-badge) + +![Server & Client - 21 / 288](https://img.shields.io/badge/Server_&_Client-21%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 - 18 / 93](https://img.shields.io/badge/Client-18%20%2F%2093-red?style=for-the-badge) + +![Client - 19 / 93](https://img.shields.io/badge/Client-19%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 - 20 / 52](https://img.shields.io/badge/Account-20%20%2F%2052-yellow?style=for-the-badge) + +![Account - 21 / 52](https://img.shields.io/badge/Account-21%20%2F%2052-yellow?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -167,7 +167,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Delete Identity](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteIdentity) | ⬛ | ❌ | | [Create JWT](https://appwrite.io/docs/references/1.5.x/client-rest/account#createJWT) | ✅ | ❌ | | [List Logs](https://appwrite.io/docs/references/1.5.x/client-rest/account#listLogs) | ✅ | ❌ | -| [Update MFA](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMFA) | ⬛ | ❌ | +| [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) | ⬛ | ❌ | diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index cb355951..9a6ef10c 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -329,4 +329,21 @@ public async Task> VerifyAuthenticator(VerifyAuthenticatorR return e.GetExceptionResponse(); } } + + /// + public async Task> UpdateMfa(UpdateMfaRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.UpdateMfa(Session, 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 ab244046..13f01319 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -157,4 +157,11 @@ public interface IAccountClient /// Type of authenticator /// The User Task> VerifyAuthenticator(VerifyAuthenticatorRequest request, string type = "totp"); + + /// + /// Enable or disable MFA on an account + /// + /// The request content + /// The user + Task> UpdateMfa(UpdateMfaRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index d3aac654..d4533158 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -62,4 +62,7 @@ internal interface IAccountApi : IBaseApi [Put("/account/mfa/authenticators/{type}")] Task> VerifyAuthenticator([Header("x-appwrite-session")] string? session, string type, VerifyAuthenticatorRequest request); + + [Patch("/account/mfa")] + Task> UpdateMfa([Header("x-appwrite-session")] string? session, UpdateMfaRequest request); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 49b95ab2..41e9c111 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -21,9 +21,9 @@ public async Task Run(string[] args) _client.SetSession(_session); //var response = await _client.Account.AddAuthenticator(); - var response = await _client.Account.VerifyAuthenticator(new Shared.Requests.VerifyAuthenticatorRequest + var response = await _client.Account.UpdateMfa(new Shared.Requests.UpdateMfaRequest { - Otp = "623850" + MfaEnabled = false }); Console.WriteLine(response.Result.Match( diff --git a/src/PinguApps.Appwrite.Shared/Requests/UpdateMfaRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/UpdateMfaRequest.cs new file mode 100644 index 00000000..3290ecc8 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/UpdateMfaRequest.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; + +/// +/// The request for updating Mfa +/// +public class UpdateMfaRequest : BaseRequest +{ + /// + /// Enable or disable MFA + /// + [JsonPropertyName("mfa")] + public bool MfaEnabled { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdateMfaRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdateMfaRequestValidator.cs new file mode 100644 index 00000000..8cacd018 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/UpdateMfaRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class UpdateMfaRequestValidator : AbstractValidator +{ + public UpdateMfaRequestValidator() + { + RuleFor(x => x.MfaEnabled).NotNull(); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdateMfa.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdateMfa.cs new file mode 100644 index 00000000..885abe12 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.UpdateMfa.cs @@ -0,0 +1,80 @@ +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 UpdateMfa_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateMfaRequest() + { + MfaEnabled = true + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/mfa") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.UserResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.UpdateMfa(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateMfa_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateMfaRequest() + { + MfaEnabled = true + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/mfa") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.UpdateMfa(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateMfa_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateMfaRequest() + { + MfaEnabled = true + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{Constants.Endpoint}/account/mfa") + .ExpectedHeaders(true) + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.UpdateMfa(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.Shared.Tests/Requests/UpdateMfaRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdateMfaRequestTests.cs new file mode 100644 index 00000000..ce37cb0c --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/UpdateMfaRequestTests.cs @@ -0,0 +1,46 @@ +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class UpdateMfaRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateMfaRequest(); + + // Assert + Assert.False(request.MfaEnabled); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var request = new UpdateMfaRequest(); + + // Act + request.MfaEnabled = true; + + // Assert + Assert.True(request.MfaEnabled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsValid_WithValidData_ReturnsTrue(bool mfaEnabled) + { + // Arrange + var request = new UpdateMfaRequest + { + MfaEnabled = mfaEnabled + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } +}