From e604d1b17c8ea90b9db019f23379e0c494c20b85 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 11 Aug 2024 15:24:41 +0100 Subject: [PATCH 1/4] Implemented create email password session --- .../Clients/AccountClient.cs | 17 ++++++++++++++ .../Clients/IAccountClient.cs | 9 ++++++++ .../Internals/IAccountApi.cs | 3 +++ src/PinguApps.Appwrite.Playground/App.cs | 6 ++++- .../CreateEmailPasswordSessionRequest.cs | 22 +++++++++++++++++++ ...ateEmailPasswordSessionRequestValidator.cs | 11 ++++++++++ 6 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/PinguApps.Appwrite.Shared/Requests/CreateEmailPasswordSessionRequest.cs create mode 100644 src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailPasswordSessionRequestValidator.cs diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index e141309e..9f70f532 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -544,4 +544,21 @@ public async Task> CreateAnonymousSession() return e.GetExceptionResponse(); } } + + /// + public async Task> CreateEmailPasswordSession(CreateEmailPasswordSessionRequest request) + { + try + { + request.Validate(true); + + var result = await _accountApi.CreateEmailPasswordSession(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 51fa5b4b..5bda3445 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -256,4 +256,13 @@ public interface IAccountClient /// /// The Session Task> CreateAnonymousSession(); + + /// + /// Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. + /// A user is limited to 10 active sessions at a time by default. Learn more about session limits. + /// Appwrite Docs + /// + /// The request + /// The Session + Task> CreateEmailPasswordSession(CreateEmailPasswordSessionRequest request); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index cc2b81a2..5f2142c6 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -101,4 +101,7 @@ internal interface IAccountApi : IBaseApi [Post("/account/sessions/anonymous")] Task> CreateAnonymousSession(); + + [Post("/account/sessions/email")] + Task> CreateEmailPasswordSession(CreateEmailPasswordSessionRequest request); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 6c1bdd6b..eeae6a35 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -22,7 +22,11 @@ public async Task Run(string[] args) Console.WriteLine(_client.Session); - var response = await _client.Account.CreateAnonymousSession(); + var response = await _client.Account.CreateEmailPasswordSession(new Shared.Requests.CreateEmailPasswordSessionRequest + { + Email = "pingu@example.com", + Password = "password" + }); Console.WriteLine(response.Result.Match( account => account.ToString(), diff --git a/src/PinguApps.Appwrite.Shared/Requests/CreateEmailPasswordSessionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/CreateEmailPasswordSessionRequest.cs new file mode 100644 index 00000000..6ba87829 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/CreateEmailPasswordSessionRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests; + +/// +/// The request for creating an email password session +/// +public class CreateEmailPasswordSessionRequest : BaseRequest +{ + /// + /// User email + /// + [JsonPropertyName("email")] + public string Email { get; set; } = string.Empty; + + /// + /// User password. Must be at least 8 chars + /// + [JsonPropertyName("password")] + public string Password { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailPasswordSessionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailPasswordSessionRequestValidator.cs new file mode 100644 index 00000000..34c7cd9d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Validators/CreateEmailPasswordSessionRequestValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Validators; +public class CreateEmailPasswordSessionRequestValidator : AbstractValidator +{ + public CreateEmailPasswordSessionRequestValidator() + { + RuleFor(x => x.Email).NotEmpty().EmailAddress(); + RuleFor(x => x.Password).NotEmpty().MinimumLength(8).MaximumLength(256); + } +} From 222e30b757c1c7712467d13e707958726b731a37 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 11 Aug 2024 15:30:51 +0100 Subject: [PATCH 2/4] added shared tests --- .../CreateEmailPasswordSessionRequestTests.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailPasswordSessionRequestTests.cs diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailPasswordSessionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailPasswordSessionRequestTests.cs new file mode 100644 index 00000000..8df631ba --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateEmailPasswordSessionRequestTests.cs @@ -0,0 +1,107 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class CreateEmailPasswordSessionRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateEmailPasswordSessionRequest(); + + // Assert + Assert.Equal(string.Empty, request.Email); + Assert.Equal(string.Empty, request.Password); + } + + [Theory] + [InlineData("test@example.com", "password123")] + [InlineData("another@example.com", "diffPassword")] + public void Properties_CanBeSet(string email, string password) + { + // Arrange + var request = new CreateEmailPasswordSessionRequest(); + + // Act + request.Email = email; + request.Password = password; + + // Assert + Assert.Equal(email, request.Email); + Assert.Equal(password, request.Password); + } + + [Theory] + [InlineData("pingu@example.com", "Password")] + public void IsValid_WithValidData_ReturnsTrue(string email, string password) + { + // Arrange + var request = new CreateEmailPasswordSessionRequest + { + Email = email, + Password = password + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null, "Password")] + [InlineData("", "Password")] + [InlineData("not an email", "Password")] + [InlineData("pingu@example.com", null)] + [InlineData("pingu@example.com", "")] + [InlineData("pingu@example.com", "short")] + [InlineData("pingu@example.com", "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 IsValid_WithInvalidData_ReturnsFalse(string? email, string? password) + { + // Arrange + var request = new CreateEmailPasswordSessionRequest + { + Email = email!, + Password = password! + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateEmailPasswordSessionRequest + { + Email = "not an email", + Password = "short" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateEmailPasswordSessionRequest + { + Email = "not an email", + Password = "short" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} From 6cbbb1bf3934eff083f0c0ed8809094f6c572252 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 11 Aug 2024 15:34:04 +0100 Subject: [PATCH 3/4] added client tests --- ...tClientTests.CreateEmailPasswordSession.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailPasswordSession.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailPasswordSession.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailPasswordSession.cs new file mode 100644 index 00000000..c237f09d --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.CreateEmailPasswordSession.cs @@ -0,0 +1,77 @@ +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 CreateEmailPasswordSession_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateEmailPasswordSessionRequest() + { + Email = "email@example.com", + Password = "Password" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/sessions/email") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.SessionResponse); + + // Act + var result = await _appwriteClient.Account.CreateEmailPasswordSession(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateEmailPasswordSession_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateEmailPasswordSessionRequest() + { + Email = "email@example.com", + Password = "Password" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/sessions/email") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + // Act + var result = await _appwriteClient.Account.CreateEmailPasswordSession(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateEmailPasswordSession_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateEmailPasswordSessionRequest() + { + Email = "email@example.com", + Password = "Password" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account/sessions/email") + .ExpectedHeaders() + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Account.CreateEmailPasswordSession(request); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} From d38786a67bae954877e57dbf6c8528bb428c5aa5 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Sun, 11 Aug 2024 15:35:22 +0100 Subject: [PATCH 4/4] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5cd9fea4..5cce3df8 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,14 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ``` ## ⌛ Progress - -![Server & Client - 33 / 288](https://img.shields.io/badge/Server_&_Client-33%20%2F%20288-red?style=for-the-badge) + +![Server & Client - 34 / 288](https://img.shields.io/badge/Server_&_Client-34%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 - 31 / 93](https://img.shields.io/badge/Client-31%20%2F%2093-red?style=for-the-badge) + +![Client - 32 / 93](https://img.shields.io/badge/Client-32%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 - 33 / 52](https://img.shields.io/badge/Account-33%20%2F%2052-yellow?style=for-the-badge) + +![Account - 34 / 52](https://img.shields.io/badge/Account-34%20%2F%2052-yellow?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -187,7 +187,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [List Sessions](https://appwrite.io/docs/references/1.5.x/client-rest/account#listSessions) | ✅ | ❌ | | [Delete Sessions](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteSessions) | ✅ | ❌ | | [Create Anonymous Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createAnonymousSession) | ✅ | ❌ | -| [Create Email Password Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createEmailPasswordSession) | ⬛ | ❌ | +| [Create Email Password Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createEmailPasswordSession) | ✅ | ❌ | | [Update Magic URL Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMagicURLSession) | ⬛ | ❌ | | [Create OAuth2 Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#createOAuth2Session) | ⬛ | ❌ | | [Update Phone Session](https://appwrite.io/docs/references/1.5.x/client-rest/account#updatePhoneSession) | ⬛ | ❌ |