diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 4188aa9e..bd15bba9 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -52,6 +52,7 @@ jobs: tag: "${{ github.run_number }}_${{ github.run_id }}" customSettings: "" toolpath: "reportgeneratortool" + assemblyfilters: "-PinguApps.Appwrite.Shared.Tests" - name: Upload Combined Coverage XML uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4 diff --git a/.gitignore b/.gitignore index 144ef7ca..d934889f 100644 --- a/.gitignore +++ b/.gitignore @@ -398,3 +398,4 @@ FodyWeavers.xsd *.sln.iml **/*appsettings*.json +**/coverage-report/** diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 00000000..331cc769 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,22 @@ + + + + + + + + + + + <_Parameter1>%(InternalsVisibleTo.Identity) + + + + + + + <_Parameter1>$(AssemblyName)%(InternalsVisibleToSuffix.Identity) + + + + diff --git a/PinguApps.Appwrite.sln b/PinguApps.Appwrite.sln index 6ef20be7..02e9cf76 100644 --- a/PinguApps.Appwrite.sln +++ b/PinguApps.Appwrite.sln @@ -19,7 +19,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PinguApps.Appwrite.Server.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PinguApps.Appwrite.Client.Tests", "tests\PinguApps.Appwrite.Client.Tests\PinguApps.Appwrite.Client.Tests.csproj", "{71C1431E-8098-4B9B-9CFA-A49F77242C79}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PinguApps.Appwrite.Tests.Shared", "tests\PinguApps.Appwrite.Tests.Shared\PinguApps.Appwrite.Tests.Shared.csproj", "{5AED345C-1E3E-4397-807C-8EFC22B786FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PinguApps.Appwrite.Shared.Tests", "tests\PinguApps.Appwrite.Shared.Tests\PinguApps.Appwrite.Shared.Tests.csproj", "{5AED345C-1E3E-4397-807C-8EFC22B786FA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs b/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs index 01fbc0d1..ecfeacc4 100644 --- a/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs +++ b/src/PinguApps.Appwrite.Client/Utils/ResponseUtils.cs @@ -18,7 +18,7 @@ internal static AppwriteResult GetApiResponse(this IApiResponse result) return new AppwriteResult(result.Content); } - if (result.Error?.Content is null) + if (result.Error?.Content is null || string.IsNullOrEmpty(result.Error.Content)) { throw new Exception("Unknown error encountered."); } diff --git a/src/PinguApps.Appwrite.Common/Responses/HashOptions.cs b/src/PinguApps.Appwrite.Common/Responses/HashOptions.cs new file mode 100644 index 00000000..9b68903f --- /dev/null +++ b/src/PinguApps.Appwrite.Common/Responses/HashOptions.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; +public record HashOptions( + [property: JsonPropertyName("type")] string Type, + [property: JsonPropertyName("memoryCost")] long MemoryCost, + [property: JsonPropertyName("timeCost")] int TimeCost, + [property: JsonPropertyName("threads")] int Threads +); diff --git a/src/PinguApps.Appwrite.Common/Responses/User.cs b/src/PinguApps.Appwrite.Common/Responses/User.cs index 882cbe44..1d1affee 100644 --- a/src/PinguApps.Appwrite.Common/Responses/User.cs +++ b/src/PinguApps.Appwrite.Common/Responses/User.cs @@ -8,6 +8,9 @@ public record User( [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("password")] string? Password, + [property: JsonPropertyName("hash")] string? Hash, + [property: JsonPropertyName("hashOptions")] HashOptions? HashOptions, [property: JsonPropertyName("registration")] DateTime Registration, [property: JsonPropertyName("status")] bool Status, [property: JsonPropertyName("labels")] IReadOnlyList Labels, diff --git a/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs b/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs index 727a0015..a31c654a 100644 --- a/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs +++ b/src/PinguApps.Appwrite.Server/Utils/ResponseUtils.cs @@ -18,7 +18,7 @@ internal static AppwriteResult GetApiResponse(this IApiResponse result) return new AppwriteResult(result.Content); } - if (result.Error?.Content is null) + if (result.Error?.Content is null || string.IsNullOrEmpty(result.Error.Content)) { throw new Exception("Unknown error encountered."); } diff --git a/tests/PinguApps.Appwrite.Client.Tests/AccountTests.cs b/tests/PinguApps.Appwrite.Client.Tests/AccountTests.cs deleted file mode 100644 index 4bd271d6..00000000 --- a/tests/PinguApps.Appwrite.Client.Tests/AccountTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Net; -using Microsoft.Extensions.DependencyInjection; -using PinguApps.Appwrite.Shared.Requests; -using PinguApps.Appwrite.Tests.Shared; -using Refit; -using RichardSzalay.MockHttp; - -namespace PinguApps.Appwrite.Client.Tests; -public class AccountTests -{ - private readonly MockHttpMessageHandler _mockHttp; - private readonly IAppwriteClient _appwriteClient; - - public AccountTests() - { - _mockHttp = new MockHttpMessageHandler(); - var services = new ServiceCollection(); - - services.AddAppwriteClientForServer("PROJECT_ID", Constants.Endpoint, new RefitSettings - { - HttpMessageHandlerFactory = () => _mockHttp - }); - - var serviceProvider = services.BuildServiceProvider(); - - _appwriteClient = serviceProvider.GetRequiredService(); - } - - [Fact] - public async Task Get_ShouldReturnSuccess_WhenApiCallSucceeds() - { - // Arrange - _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account") - .ExpectedHeaders(true) - .Respond(Constants.AppJson, Constants.UserResponse); - - _appwriteClient.SetSession(Constants.Session); - - // Act - var result = await _appwriteClient.Account.Get(); - - // Assert - Assert.True(result.Success); - } - - [Fact] - public async Task Get_ShouldHandleException_WhenApiCallFails() - { - // Arrange - _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account") - .ExpectedHeaders(true) - .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); - - _appwriteClient.SetSession(Constants.Session); - - // Act - var result = await _appwriteClient.Account.Get(); - - // Assert - Assert.True(result.IsError); - Assert.True(result.IsAppwriteError); - } - - [Fact] - public async Task Create_ShouldReturnSuccess_WhenApiCallSucceeds() - { - // Arrange - var request = new CreateAccountRequest() - { - Email = "email@example.com", - Password = "password", - Name = "name" - }; - - _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") - .ExpectedHeaders() - .WithJsonContent(request) - .Respond(Constants.AppJson, Constants.UserResponse); - - // Act - var result = await _appwriteClient.Account.Create(request); - - // Assert - Assert.True(result.Success); - } - - [Fact] - public async Task Create_ShouldHandleException_WhenApiCallFails() - { - // Arrange - var request = new CreateAccountRequest() - { - Email = "email@example.com", - Password = "password", - Name = "name" - }; - - _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") - .ExpectedHeaders() - .WithJsonContent(request) - .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); - - // Act - var result = await _appwriteClient.Account.Create(request); - - // Assert - Assert.True(result.IsError); - Assert.True(result.IsAppwriteError); - } -} - -public static class AccountTestsExtensions -{ - public static MockedRequest ExpectedHeaders(this MockedRequest request, bool addSessionHeaders = false) - { - var req = request - .WithHeaders("x-appwrite-project", Constants.ProjectId) - .WithHeaders("x-sdk-name", Constants.SdkName) - .WithHeaders("x-sdk-platform", "client") - .WithHeaders("x-sdk-language", Constants.SdkLanguage) - .WithHeaders("x-sdk-version", Constants.SdkVersion) - .WithHeaders("x-appwrite-response-format", Constants.AppwriteResponseFormat); - - if (addSessionHeaders) - return req.ExpectSessionHeaders(); - - return req; - } - - public static MockedRequest ExpectSessionHeaders(this MockedRequest request) - { - return request - .WithHeaders("x-appwrite-session", Constants.Session); - } -} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.Create.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.Create.cs new file mode 100644 index 00000000..c8e57e36 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.Create.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 Create_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateAccountRequest() + { + Email = "email@example.com", + Password = "password", + Name = "name" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(Constants.AppJson, Constants.UserResponse); + + // Act + var result = await _appwriteClient.Account.Create(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task Create_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateAccountRequest() + { + Email = "email@example.com", + Password = "password", + Name = "name" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") + .ExpectedHeaders() + .WithJsonContent(request) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + // Act + var result = await _appwriteClient.Account.Create(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task Create_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateAccountRequest() + { + Email = "email@example.com", + Password = "password", + Name = "name" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") + .ExpectedHeaders() + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Account.Create(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/Clients/Account/AccountClientTests.Get.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.Get.cs new file mode 100644 index 00000000..ff31f349 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.Get.cs @@ -0,0 +1,61 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + [Fact] + public async Task Get_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account") + .ExpectedHeaders(true) + .Respond(Constants.AppJson, Constants.UserResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.Get(); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task Get_ShouldHandleException_WhenApiCallFails() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account") + .ExpectedHeaders(true) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.Get(); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task Get_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account") + .ExpectedHeaders(true) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.Get(); + + // 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/Clients/Account/AccountClientTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs new file mode 100644 index 00000000..5b13235b --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Shared.Tests; +using Refit; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + private readonly MockHttpMessageHandler _mockHttp; + private readonly IAppwriteClient _appwriteClient; + + public AccountClientTests() + { + _mockHttp = new MockHttpMessageHandler(); + var services = new ServiceCollection(); + + services.AddAppwriteClientForServer("PROJECT_ID", Constants.Endpoint, new RefitSettings + { + HttpMessageHandlerFactory = () => _mockHttp + }); + + var serviceProvider = services.BuildServiceProvider(); + + _appwriteClient = serviceProvider.GetRequiredService(); + } +} + +public static class AccountTestsExtensions +{ + public static MockedRequest ExpectedHeaders(this MockedRequest request, bool addSessionHeaders = false) + { + var req = request + .WithHeaders("x-appwrite-project", Constants.ProjectId) + .WithHeaders("x-sdk-name", Constants.SdkName) + .WithHeaders("x-sdk-platform", "client") + .WithHeaders("x-sdk-language", Constants.SdkLanguage) + .WithHeaders("x-sdk-version", Constants.SdkVersion) + .WithHeaders("x-appwrite-response-format", Constants.AppwriteResponseFormat); + + if (addSessionHeaders) + return req.ExpectSessionHeaders(); + + return req; + } + + public static MockedRequest ExpectSessionHeaders(this MockedRequest request) + { + return request + .WithHeaders("x-appwrite-session", Constants.Session); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs new file mode 100644 index 00000000..a9b0f41e --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs @@ -0,0 +1,64 @@ +using Moq; +using PinguApps.Appwrite.Client.Clients; +using PinguApps.Appwrite.Shared.Tests; + +namespace PinguApps.Appwrite.Client.Tests.Clients; +public class AppwriteClientTests +{ + [Fact] + public void Constructor_SetsAccountClient() + { + // Arrange + var mockAccountClient = new Mock(); + + // Act + var appwriteClient = new AppwriteClient(mockAccountClient.Object); + + // Assert + Assert.Equal(mockAccountClient.Object, appwriteClient.Account); + } + + [Fact] + public void Session_InitiallyNull_ReturnsNull() + { + // Arrange + var mockAccountClient = new Mock(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object); + + // Act + var session = appwriteClient.Session; + + // Assert + Assert.Null(session); + } + + [Fact] + public void SetSession_UpdatesSession() + { + // Arrange + var mockAccountClient = new Mock(); + mockAccountClient.As(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object); + + // Act + appwriteClient.SetSession(Constants.Session); + + // Assert + Assert.Equal(Constants.Session, appwriteClient.Session); + } + + [Fact] + public void SetSession_UpdatesSessionInAccountClient() + { + // Arrange + var mockAccountClient = new Mock(); + mockAccountClient.As(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object); + + // Act + appwriteClient.SetSession(Constants.Session); + + // Assert + mockAccountClient.As().Verify(a => a.UpdateSession(Constants.Session), Times.Once); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Handlers/HeaderHandlerTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Handlers/HeaderHandlerTests.cs new file mode 100644 index 00000000..89259ef9 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Handlers/HeaderHandlerTests.cs @@ -0,0 +1,42 @@ +using Moq; +using Moq.Protected; +using PinguApps.Appwrite.Client.Handlers; +using PinguApps.Appwrite.Shared.Tests; + +namespace PinguApps.Appwrite.Client.Tests.Handlers; +public class HeaderHandlerTests +{ + [Fact] + public async Task SendAsync_AddsRequiredHeaders() + { + // Arrange + var mockInnerHandler = new Mock(); + mockInnerHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage()) + .Verifiable(); + + var headerHandler = new HeaderHandler(Constants.ProjectId) + { + InnerHandler = mockInnerHandler.Object + }; + var httpClient = new HttpClient(headerHandler); + + // Act + await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + mockInnerHandler.Protected().Verify( + "SendAsync", + Times.Once(), + ItExpr.Is(req => + req.Headers.Contains("x-appwrite-project") && + req.Headers.GetValues("x-appwrite-project").Contains(Constants.ProjectId)), + ItExpr.IsAny() + ); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj b/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj index 3ff0562d..e23ead07 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj +++ b/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -19,7 +23,7 @@ - + diff --git a/tests/PinguApps.Appwrite.Client.Tests/ServiceCollectionExtensionsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/ServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000..1f93b2cb --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Client.Handlers; +using PinguApps.Appwrite.Client.Internals; +using PinguApps.Appwrite.Shared.Tests; + +namespace PinguApps.Appwrite.Client.Tests; +public class ServiceCollectionExtensionsTests +{ + [Fact] + public void AddAppwriteClient_RegistersExpectedServices() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddAppwriteClient(Constants.ProjectId, Constants.Endpoint); + + // Assert + var provider = services.BuildServiceProvider(); + + // Assert HeaderHandler is registered + var headerHandler = provider.GetService(); + Assert.NotNull(headerHandler); + + // Assert IAccountApi is registered and configured + var accountApi = provider.GetService(); + Assert.NotNull(accountApi); + + // Assert services are registered + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + } + + [Fact] + public void AddAppwriteClientForServer_RegistersExpectedServicesWithTransientLifetime() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddAppwriteClientForServer(Constants.ProjectId, Constants.Endpoint); + + // Assert + var provider = services.BuildServiceProvider(); + + // Assert HeaderHandler is registered + var headerHandler = provider.GetService(); + Assert.NotNull(headerHandler); + + // Assert IAccountApi is registered and configured + var accountApi = provider.GetService(); + Assert.NotNull(accountApi); + + // Assert services are registered with Transient lifetime + var accountClientServiceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IAccountClient)); + Assert.NotNull(accountClientServiceDescriptor); + Assert.Equal(ServiceLifetime.Transient, accountClientServiceDescriptor.Lifetime); + + var appwriteClientServiceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IAppwriteClient)); + Assert.NotNull(appwriteClientServiceDescriptor); + Assert.Equal(ServiceLifetime.Transient, appwriteClientServiceDescriptor.Lifetime); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs new file mode 100644 index 00000000..a7663bc4 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs @@ -0,0 +1,80 @@ +using System.Net; +using Moq; +using PinguApps.Appwrite.Client.Utils; +using PinguApps.Appwrite.Shared.Tests; +using Refit; + +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); + mockApiResponse.SetupGet(r => r.Content).Returns("Success"); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.True(result.Success); + Assert.Equal("Success", result.Result); + } + + [Fact] + public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() + { + var mockApiResponse = new Mock>(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); + mockApiResponse.SetupGet(r => r.Content).Returns((string?)null); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.False(result.Success); + Assert.True(result.Result.IsT2); + } + + [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 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); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/test.ps1 b/tests/PinguApps.Appwrite.Client.Tests/test.ps1 new file mode 100644 index 00000000..1e7a9698 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/test.ps1 @@ -0,0 +1,15 @@ +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + +# Check if the dotnet-reportgenerator-globaltool is installed +$toolInstalled = dotnet tool list -g | Select-String -Pattern "dotnet-reportgenerator-globaltool" + +if (-not $toolInstalled) { + # Install the dotnet-reportgenerator-globaltool globally + dotnet tool install -g dotnet-reportgenerator-globaltool +} + +# Generate the report +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Client + +# Open the generated report in the default browser +Start-Process "coverage-report/index.html" diff --git a/tests/PinguApps.Appwrite.Server.Tests/Handlers/HeaderHandlerTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Handlers/HeaderHandlerTests.cs new file mode 100644 index 00000000..9be219e4 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Handlers/HeaderHandlerTests.cs @@ -0,0 +1,44 @@ +using Moq; +using Moq.Protected; +using PinguApps.Appwrite.Server.Handlers; +using PinguApps.Appwrite.Shared.Tests; + +namespace PinguApps.Appwrite.Server.Tests.Handlers; +public class HeaderHandlerTests +{ + [Fact] + public async Task SendAsync_AddsRequiredHeaders() + { + // Arrange + var mockInnerHandler = new Mock(); + mockInnerHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage()) + .Verifiable(); + + var headerHandler = new HeaderHandler(Constants.ProjectId, Constants.ApiKey) + { + InnerHandler = mockInnerHandler.Object + }; + var httpClient = new HttpClient(headerHandler); + + // Act + await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + mockInnerHandler.Protected().Verify( + "SendAsync", + Times.Once(), + ItExpr.Is(req => + req.Headers.Contains("x-appwrite-project") && + req.Headers.GetValues("x-appwrite-project").Contains(Constants.ProjectId) && + req.Headers.Contains("x-appwrite-key") && + req.Headers.GetValues("x-appwrite-key").Contains(Constants.ApiKey)), + ItExpr.IsAny() + ); + } +} diff --git a/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj b/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj index 9587cc39..21e09583 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj +++ b/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -20,7 +24,7 @@ - + diff --git a/tests/PinguApps.Appwrite.Server.Tests/AccountTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.Create.cs similarity index 51% rename from tests/PinguApps.Appwrite.Server.Tests/AccountTests.cs rename to tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.Create.cs index 52a673f1..cd78a877 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/AccountTests.cs +++ b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.Create.cs @@ -1,32 +1,11 @@ using System.Net; -using Microsoft.Extensions.DependencyInjection; -using PinguApps.Appwrite.Server.Servers; using PinguApps.Appwrite.Shared.Requests; -using PinguApps.Appwrite.Tests.Shared; -using Refit; +using PinguApps.Appwrite.Shared.Tests; using RichardSzalay.MockHttp; -namespace PinguApps.Appwrite.Server.Tests; -public class AccountTests +namespace PinguApps.Appwrite.Server.Tests.Servers.Account; +public partial class AccountServerTests { - private readonly MockHttpMessageHandler _mockHttp; - private readonly IAppwriteServer _appwriteServer; - - public AccountTests() - { - _mockHttp = new MockHttpMessageHandler(); - var services = new ServiceCollection(); - - services.AddAppwriteServer(Constants.ProjectId, Constants.ApiKey, Constants.Endpoint, new RefitSettings - { - HttpMessageHandlerFactory = () => _mockHttp - }); - - var serviceProvider = services.BuildServiceProvider(); - - _appwriteServer = serviceProvider.GetRequiredService(); - } - [Fact] public async Task Create_ShouldReturnSuccess_WhenApiCallSucceeds() { @@ -73,19 +52,29 @@ public async Task Create_ShouldHandleException_WhenApiCallFails() Assert.True(result.IsError); Assert.True(result.IsAppwriteError); } -} -public static class AccountTestsExtensions -{ - public static MockedRequest ExpectedHeaders(this MockedRequest request) + [Fact] + public async Task Create_ShouldReturnErrorResponse_WhenExceptionOccurs() { - return request - .WithHeaders("x-appwrite-project", Constants.ProjectId) - .WithHeaders("x-appwrite-key", Constants.ApiKey) - .WithHeaders("x-sdk-name", Constants.SdkName) - .WithHeaders("x-sdk-platform", "server") - .WithHeaders("x-sdk-language", Constants.SdkLanguage) - .WithHeaders("x-sdk-version", Constants.SdkVersion) - .WithHeaders("x-appwrite-response-format", Constants.AppwriteResponseFormat); + // Arrange + var request = new CreateAccountRequest() + { + Email = "email@example.com", + Password = "password", + Name = "name" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{Constants.Endpoint}/account") + .ExpectedHeaders() + .WithJsonContent(request) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteServer.Account.Create(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.Server.Tests/Servers/Account/AccountServerTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.cs new file mode 100644 index 00000000..48df8da0 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Servers/Account/AccountServerTests.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Server.Servers; +using PinguApps.Appwrite.Shared.Tests; +using Refit; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Servers.Account; +public partial class AccountServerTests +{ + private readonly MockHttpMessageHandler _mockHttp; + private readonly IAppwriteServer _appwriteServer; + + public AccountServerTests() + { + _mockHttp = new MockHttpMessageHandler(); + var services = new ServiceCollection(); + + services.AddAppwriteServer(Constants.ProjectId, Constants.ApiKey, Constants.Endpoint, new RefitSettings + { + HttpMessageHandlerFactory = () => _mockHttp + }); + + var serviceProvider = services.BuildServiceProvider(); + + _appwriteServer = serviceProvider.GetRequiredService(); + } +} + +public static class AccountTestsExtensions +{ + public static MockedRequest ExpectedHeaders(this MockedRequest request) + { + return request + .WithHeaders("x-appwrite-project", Constants.ProjectId) + .WithHeaders("x-appwrite-key", Constants.ApiKey) + .WithHeaders("x-sdk-name", Constants.SdkName) + .WithHeaders("x-sdk-platform", "server") + .WithHeaders("x-sdk-language", Constants.SdkLanguage) + .WithHeaders("x-sdk-version", Constants.SdkVersion) + .WithHeaders("x-appwrite-response-format", Constants.AppwriteResponseFormat); + } +} diff --git a/tests/PinguApps.Appwrite.Server.Tests/Servers/AppwriteServerTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Servers/AppwriteServerTests.cs new file mode 100644 index 00000000..05bbba79 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Servers/AppwriteServerTests.cs @@ -0,0 +1,17 @@ +using Moq; +using PinguApps.Appwrite.Server.Servers; + +namespace PinguApps.Appwrite.Server.Tests.Servers; +public class AppwriteServerTests +{ + [Fact] + public void Constructor_AssignsAccountServerCorrectly() + { + // Arrange + var mockAccountServer = new Mock(); + // Act + var appwriteServer = new AppwriteServer(mockAccountServer.Object); + // Assert + Assert.Equal(mockAccountServer.Object, appwriteServer.Account); + } +} diff --git a/tests/PinguApps.Appwrite.Server.Tests/ServiceCollectionExtensionsTests.cs b/tests/PinguApps.Appwrite.Server.Tests/ServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000..e578c769 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Server.Handlers; +using PinguApps.Appwrite.Server.Internals; +using PinguApps.Appwrite.Server.Servers; +using PinguApps.Appwrite.Shared.Tests; + +namespace PinguApps.Appwrite.Server.Tests; +public class ServiceCollectionExtensionsTests +{ + [Fact] + public void AddAppwriteServer_RegistersExpectedServices() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddAppwriteServer(Constants.ProjectId, Constants.ApiKey, Constants.Endpoint); + + // Assert + var provider = services.BuildServiceProvider(); + + // Assert HeaderHandler is registered + var headerHandler = provider.GetService(); + Assert.NotNull(headerHandler); + + // Assert IAccountApi is registered and configured + var accountApi = provider.GetService(); + Assert.NotNull(accountApi); + + // Assert services are registered + Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); + } + + // Additional tests can be added to verify different configurations or overloads of AddAppwriteServer +} diff --git a/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs new file mode 100644 index 00000000..5ddd5ad2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Utils/ResponseUtilsTests.cs @@ -0,0 +1,80 @@ +using System.Net; +using Moq; +using PinguApps.Appwrite.Server.Utils; +using PinguApps.Appwrite.Shared.Tests; +using Refit; + +namespace PinguApps.Appwrite.Server.Tests.Utils; +public class ResponseUtilsTests +{ + [Fact] + public void GetApiResponse_Success_ReturnsContent() + { + var mockApiResponse = new Mock>(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); + mockApiResponse.SetupGet(r => r.Content).Returns("Success"); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.True(result.Success); + Assert.Equal("Success", result.Result); + } + + [Fact] + public void GetApiResponse_SuccessButNullContent_ReturnsInternalError() + { + var mockApiResponse = new Mock>(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(true); + mockApiResponse.SetupGet(r => r.Content).Returns((string?)null); + + var result = mockApiResponse.Object.GetApiResponse(); + + Assert.False(result.Success); + Assert.True(result.Result.IsT2); + } + + [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 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); + } +} diff --git a/tests/PinguApps.Appwrite.Server.Tests/test.ps1 b/tests/PinguApps.Appwrite.Server.Tests/test.ps1 new file mode 100644 index 00000000..12457080 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/test.ps1 @@ -0,0 +1,15 @@ +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + +# Check if the dotnet-reportgenerator-globaltool is installed +$toolInstalled = dotnet tool list -g | Select-String -Pattern "dotnet-reportgenerator-globaltool" + +if (-not $toolInstalled) { + # Install the dotnet-reportgenerator-globaltool globally + dotnet tool install -g dotnet-reportgenerator-globaltool +} + +# Generate the report +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Server + +# Open the generated report in the default browser +Start-Process "coverage-report/index.html" diff --git a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteErrorTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteErrorTests.cs new file mode 100644 index 00000000..0747dd81 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteErrorTests.cs @@ -0,0 +1,22 @@ +namespace PinguApps.Appwrite.Shared.Tests; +public class AppwriteErrorTests +{ + [Fact] + public void AppwriteError_Constructor_ShouldSetPropertiesCorrectly() + { + // Arrange + var expectedMessage = "An error occurred"; + var expectedCode = 400; + var expectedType = "BadRequest"; + var expectedVersion = "v1"; + + // Act + var error = new AppwriteError(expectedMessage, expectedCode, expectedType, expectedVersion); + + // Assert + Assert.Equal(expectedMessage, error.Message); + Assert.Equal(expectedCode, error.Code); + Assert.Equal(expectedType, error.Type); + Assert.Equal(expectedVersion, error.Version); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs new file mode 100644 index 00000000..bfb6d3f1 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/AppwriteResultTests.cs @@ -0,0 +1,40 @@ +using OneOf; + +namespace PinguApps.Appwrite.Shared.Tests; + +public class AppwriteResultTests +{ + [Fact] + public void Constructor_WithTResult_SuccessIsTrue() + { + var result = new AppwriteResult(OneOf.FromT0("Success")); + + Assert.True(result.Success); + Assert.False(result.IsError); + Assert.False(result.IsAppwriteError); + Assert.False(result.IsInternalError); + Assert.True(result.Result.AsT0 == "Success"); + } + + [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); + } + + [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); + } +} diff --git a/tests/PinguApps.Appwrite.Tests.Shared/Constants.cs b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs similarity index 95% rename from tests/PinguApps.Appwrite.Tests.Shared/Constants.cs rename to tests/PinguApps.Appwrite.Shared.Tests/Constants.cs index 56be3181..1fc5715f 100644 --- a/tests/PinguApps.Appwrite.Tests.Shared/Constants.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs @@ -1,4 +1,4 @@ -namespace PinguApps.Appwrite.Tests.Shared; +namespace PinguApps.Appwrite.Shared.Tests; public static class Constants { @@ -54,7 +54,9 @@ public static class Constants "emailVerification": true, "phoneVerification": true, "mfa": true, - "prefs": {}, + "prefs": { + "pref1": "val1" + }, "targets": [ { "$id": "259125845563242502", diff --git a/tests/PinguApps.Appwrite.Shared.Tests/InternalErrorTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/InternalErrorTests.cs new file mode 100644 index 00000000..0fe7d0fa --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/InternalErrorTests.cs @@ -0,0 +1,16 @@ +namespace PinguApps.Appwrite.Shared.Tests; +public class InternalErrorTests +{ + [Fact] + public void Constructor_AssignsMessage() + { + // Arrange + var expectedMessage = "An error occurred"; + + // Act + var internalError = new InternalError(expectedMessage); + + // Assert + Assert.Equal(expectedMessage, internalError.Message); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj b/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj new file mode 100644 index 00000000..d2fa0703 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateAccountRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateAccountRequestTests.cs new file mode 100644 index 00000000..9f92b18f --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/CreateAccountRequestTests.cs @@ -0,0 +1,38 @@ +using PinguApps.Appwrite.Shared.Requests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests; +public class CreateAccountRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateAccountRequest(); + + // Assert + Assert.NotNull(request.UserId); + Assert.NotEmpty(request.UserId); + Assert.Equal(string.Empty, request.Email); + Assert.Equal(string.Empty, request.Password); + Assert.Null(request.Name); + } + + [Theory] + [InlineData("test@example.com", "password123", "Test User")] + [InlineData("another@example.com", "diffPassword", null)] + public void Properties_CanBeSet(string email, string password, string? name) + { + // Arrange + var request = new CreateAccountRequest(); + + // Act + request.Email = email; + request.Password = password; + request.Name = name; + + // Assert + Assert.Equal(email, request.Email); + Assert.Equal(password, request.Password); + Assert.Equal(name, request.Name); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/UserTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/UserTests.cs new file mode 100644 index 00000000..f5f9845c --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/UserTests.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class UserTests +{ + [Fact] + public void User_Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var id = "testId"; + var createdAt = DateTime.UtcNow; + var updatedAt = DateTime.UtcNow; + var name = "Test User"; + var password = ""; + var hash = ""; + var hashOptions = new HashOptions("type", 6, 5, 4); + var registration = DateTime.UtcNow; + var status = true; + var labels = new List { "label1", "label2" }; + var passwordUpdate = DateTime.UtcNow; + var email = "test@example.com"; + var phone = "1234567890"; + var emailVerification = true; + var phoneVerification = false; + var mfa = true; + var prefs = new Dictionary { { "theme", "dark" } }; + var targets = new List { new Target("259125845563242502", DateTime.UtcNow, DateTime.UtcNow, "Aegon apple token", "259125845563242502", "259125845563242502", TargetProviderType.Email, "token") }; + var accessedAt = DateTime.UtcNow; + + // Act + var user = new User(id, createdAt, updatedAt, name, password, hash, hashOptions, registration, status, labels, passwordUpdate, + email, phone, emailVerification, phoneVerification, mfa, prefs, targets, accessedAt); + + // Assert + Assert.Equal(id, user.Id); + Assert.Equal(createdAt, user.CreatedAt); + Assert.Equal(updatedAt, user.UpdatedAt); + Assert.Equal(name, user.Name); + Assert.Equal(registration, user.Registration); + Assert.Equal(status, user.Status); + Assert.Equal(labels, user.Labels); + Assert.Equal(passwordUpdate, user.PasswordUpdate); + Assert.Equal(email, user.Email); + Assert.Equal(phone, user.Phone); + Assert.Equal(emailVerification, user.EmailVerification); + Assert.Equal(phoneVerification, user.PhoneVerification); + Assert.Equal(mfa, user.Mfa); + Assert.Equal(prefs, user.Prefs); + Assert.Equal(targets, user.Targets); + Assert.Equal(accessedAt, user.AccessedAt); + } + + [Fact] + public void User_CanBeDeserialized_FromJson() + { + // Act + var user = JsonSerializer.Deserialize(Constants.UserResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(user); + Assert.Equal("5e5ea5c16897e", user.Id); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.UpdatedAt.ToUniversalTime()); + Assert.Equal("John Doe", user.Name); + Assert.NotNull(user.Password); + Assert.Equal("$argon2id$v=19$m=2048,t=4,p=3$aUZjLnliVWRINmFNTWMudg$5S+x+7uA31xFnrHFT47yFwcJeaP0w92L/4LdgrVRXxE", user.Password); + Assert.NotNull(user.Hash); + Assert.Equal("argon2", user.Hash); + Assert.NotNull(user.HashOptions); + Assert.Equal("argon2", user.HashOptions.Type); + Assert.Equal(65536, user.HashOptions.MemoryCost); + Assert.Equal(4, user.HashOptions.TimeCost); + Assert.Equal(3, user.HashOptions.Threads); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.Registration.ToUniversalTime()); + Assert.True(user.Status); + Assert.Contains("vip", user.Labels); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.PasswordUpdate.ToUniversalTime()); + Assert.Equal("john@appwrite.io", user.Email); + Assert.Equal("+4930901820", user.Phone); + Assert.True(user.EmailVerification); + Assert.True(user.PhoneVerification); + Assert.True(user.Mfa); + Assert.Contains("pref1", user.Prefs); + Assert.Equal("val1", user.Prefs["pref1"]); + Assert.Single(user.Targets); + Assert.Equal("259125845563242502", user.Targets[0].Id); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.Targets[0].CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.Targets[0].UpdatedAt.ToUniversalTime()); + Assert.Equal("Aegon apple token", user.Targets[0].Name); + Assert.Equal("259125845563242502", user.Targets[0].UserId); + Assert.Equal("259125845563242502", user.Targets[0].ProviderId); + Assert.Equal(TargetProviderType.Email, user.Targets[0].ProviderType); + Assert.Equal("token", user.Targets[0].Identifier); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), user.AccessedAt.ToUniversalTime()); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Utils/IdUtilsTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Utils/IdUtilsTests.cs new file mode 100644 index 00000000..687e3be4 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Utils/IdUtilsTests.cs @@ -0,0 +1,64 @@ +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Utils; +public class IdUtilsTests +{ + [Fact] + public void GetHexTimestamp_Returns_ValidHexFormat() + { + // Act + var hexTimestamp = IdUtils.GetHexTimestamp(); + + // Assert + Assert.Matches("^[a-fA-F0-9]+$", hexTimestamp); + } + + [Fact] + public void GetHexTimestamp_Returns_ExpectedLength() + { + // Act + var hexTimestamp = IdUtils.GetHexTimestamp(); + + // Assert + // Assuming the length is the hex representation of seconds (at least 5 chars) plus 5 chars of milliseconds + Assert.True(hexTimestamp.Length >= 10); + } + + [Theory] + [InlineData(5)] + [InlineData(7)] + [InlineData(10)] + public void GenerateUniqueId_WithPadding_Returns_CorrectLength(int padding) + { + // Act + var uniqueId = IdUtils.GenerateUniqueId(padding); + + // Assert + // Length of the base ID plus the padding + var expectedLength = IdUtils.GetHexTimestamp().Length + padding; + Assert.Equal(expectedLength, uniqueId.Length); + } + + [Fact] + public void GenerateUniqueId_WithoutPadding_Returns_DefaultLength() + { + // Act + var uniqueId = IdUtils.GenerateUniqueId(); + + // Assert + // Default padding is 7 + var expectedLength = IdUtils.GetHexTimestamp().Length + 7; + Assert.Equal(expectedLength, uniqueId.Length); + } + + [Fact] + public void GenerateUniqueId_Returns_UniqueValues() + { + // Act + var id1 = IdUtils.GenerateUniqueId(); + var id2 = IdUtils.GenerateUniqueId(); + + // Assert + Assert.NotEqual(id1, id2); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 b/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 new file mode 100644 index 00000000..57b4af39 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 @@ -0,0 +1,15 @@ +dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + +# Check if the dotnet-reportgenerator-globaltool is installed +$toolInstalled = dotnet tool list -g | Select-String -Pattern "dotnet-reportgenerator-globaltool" + +if (-not $toolInstalled) { + # Install the dotnet-reportgenerator-globaltool globally + dotnet tool install -g dotnet-reportgenerator-globaltool +} + +# Generate the report +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Shared + +# Open the generated report in the default browser +Start-Process "coverage-report/index.html" diff --git a/tests/PinguApps.Appwrite.Tests.Shared/PinguApps.Appwrite.Tests.Shared.csproj b/tests/PinguApps.Appwrite.Tests.Shared/PinguApps.Appwrite.Tests.Shared.csproj deleted file mode 100644 index fa71b7ae..00000000 --- a/tests/PinguApps.Appwrite.Tests.Shared/PinguApps.Appwrite.Tests.Shared.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - -