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
-
-
-