diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b03c8142..6b05ac36 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,7 @@ - [ ] `feature` - [ ] `bug` - [ ] `docs` +- [ ] `security` - [ ] `meta` - [ ] `patch` - [ ] `minor` diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 39566e76..c3591cf4 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -4,6 +4,9 @@ change-template: "- [#$NUMBER] $TITLE - Thanks to @$AUTHOR!" exclude-labels: - automated categories: + - title: "🔒 Security" + labels: + - "security" - title: "🚀 Features" labels: - "feature" diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 1a64ce40..1ec4fddb 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -25,7 +25,7 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Nuget Cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} @@ -42,7 +42,7 @@ jobs: run: dotnet test -c Release --no-build --verbosity normal --logger trx --collect:"XPlat Code Coverage" - name: Combine Coverage Reports - uses: danielpalme/ReportGenerator-GitHub-Action@62f9e70ab348d56eee76d446b4db903a85ab0ea8 # v5.3.11 + uses: danielpalme/ReportGenerator-GitHub-Action@810356ce07a94200154301fb73d878e327b2dd58 # v5.4.1 with: reports: "**/*.cobertura.xml" targetdir: "${{ github.workspace }}" diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 271d5e64..60464879 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -30,7 +30,7 @@ jobs: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: "TODO to Issue" - uses: "alstr/todo-to-issue-action@53d8a86de9f2224b24c6a9009a8a1f56d4e1324d" # v5 + uses: "alstr/todo-to-issue-action@f357c404fe00f5a17cd96696eaf6f8ce5fd19cf7" # v5 with: INSERT_ISSUE_URLS: true AUTO_ASSIGN: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3b84a8fc..e5610f10 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -20,3 +20,4 @@ jobs: close-issue-message: 'Closing issue as it has been marked as stale for 3 days.' close-pr-message: 'Closing PR as it has been marked as stale for 3 days.' exempt-all-milestones: true + exempt-pr-labels: 'dependencies' diff --git a/README.md b/README.md index 332e94d7..8700f46a 100644 --- a/README.md +++ b/README.md @@ -141,11 +141,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ## ⌛ Progress -![Server & Client - 125 / 317](https://img.shields.io/badge/Server_&_Client-125%20%2F%20317-gold?style=for-the-badge) +![Server & Client - 172 / 317](https://img.shields.io/badge/Server_&_Client-172%20%2F%20317-gold?style=for-the-badge) -![Server - 65 / 224](https://img.shields.io/badge/Server-65%20%2F%20224-red?style=for-the-badge) +![Server - 107 / 224](https://img.shields.io/badge/Server-107%20%2F%20224-gold?style=for-the-badge) -![Client - 60 / 93](https://img.shields.io/badge/Client-60%20%2F%2093-gold?style=for-the-badge) +![Client - 65 / 93](https://img.shields.io/badge/Client-65%20%2F%2093-forestgreen?style=for-the-badge) ### 🔑 Key | Icon | Definition | @@ -275,52 +275,52 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [Update Preferences](https://appwrite.io/docs/references/1.6.x/client-rest/teams#updatePrefs) | ✅ | ✅ | ### Databases -![Databases - 0 / 47](https://img.shields.io/badge/Databases-0%20%2F%2047-red?style=for-the-badge) +![Databases - 47 / 47](https://img.shields.io/badge/Databases-47%20%2F%2047-blue?style=for-the-badge) | Endpoint | Client | Server | |:-:|:-:|:-:| -| [List Databases](https://appwrite.io/docs/references/1.6.x/server-rest/databases#list) | ❌ | ⬛ | -| [Create Databases](https://appwrite.io/docs/references/1.6.x/server-rest/databases#create) | ❌ | ⬛ | -| [Get Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#get) | ❌ | ⬛ | -| [Update Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#update) | ❌ | ⬛ | -| [Delete Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#delete) | ❌ | ⬛ | -| [List Collections](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listCollections) | ❌ | ⬛ | -| [Create Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createCollection) | ❌ | ⬛ | -| [Get Collections](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getCollection) | ❌ | ⬛ | -| [Update Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateCollection) | ❌ | ⬛ | -| [Delete Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteCollection) | ❌ | ⬛ | -| [List Attributes](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listAttributes) | ❌ | ⬛ | -| [Create Boolean Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createBooleanAttribute) | ❌ | ⬛ | -| [Update Boolean Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateBooleanAttribute) | ❌ | ⬛ | -| [Create Datatime Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createDatetimeAttribute) | ❌ | ⬛ | -| [Update Datetime Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateDatetimeAttribute) | ❌ | ⬛ | -| [Create Email Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createEmailAttribute) | ❌ | ⬛ | -| [Update Email Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateEmailAttribute) | ❌ | ⬛ | -| [Create Enum Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createEnumAttribute) | ❌ | ⬛ | -| [Update Enum Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateEnumAttribute) | ❌ | ⬛ | -| [Create Float Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createFloatAttribute) | ❌ | ⬛ | -| [Update Float Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateFloatAttribute) | ❌ | ⬛ | -| [Create Integer Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIntegerAttribute) | ❌ | ⬛ | -| [Update Integer attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateIntegerAttribute) | ❌ | ⬛ | -| [Create IP Address Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIpAttribute) | ❌ | ⬛ | -| [Update IP Address Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateIpAttribute) | ❌ | ⬛ | -| [Create Relationship Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createRelationshipAttribute) | ❌ | ⬛ | -| [Create String Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createStringAttribute) | ❌ | ⬛ | -| [Update String Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateStringAttribute) | ❌ | ⬛ | -| [Create URL Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createUrlAttribute) | ❌ | ⬛ | -| [Update URL Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateUrlAttribute) | ❌ | ⬛ | -| [Get Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getAttribute) | ❌ | ⬛ | -| [Delete Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteAttribute) | ❌ | ⬛ | -| [Update Relationship Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateRelationshipAttribute) | ❌ | ⬛ | -| [List Documents](https://appwrite.io/docs/references/1.6.x/client-rest/databases#listDocuments) | ⬛ | ⬛ | -| [Create Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#createDocument) | ⬛ | ⬛ | -| [Get Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#getDocument) | ⬛ | ⬛ | -| [Update Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#updateDocument) | ⬛ | ⬛ | -| [Delete Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#deleteDocument) | ⬛ | ⬛ | -| [List Indexes](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listIndexes) | ❌ | ⬛ | -| [Create Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIndex) | ❌ | ⬛ | -| [Get Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getIndex) | ❌ | ⬛ | -| [Delete Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteIndex) | ❌ | ⬛ | +| [List Databases](https://appwrite.io/docs/references/1.6.x/server-rest/databases#list) | ❌ | ✅ | +| [Create Databases](https://appwrite.io/docs/references/1.6.x/server-rest/databases#create) | ❌ | ✅ | +| [Get Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#get) | ❌ | ✅ | +| [Update Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#update) | ❌ | ✅ | +| [Delete Database](https://appwrite.io/docs/references/1.6.x/server-rest/databases#delete) | ❌ | ✅ | +| [List Collections](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listCollections) | ❌ | ✅ | +| [Create Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createCollection) | ❌ | ✅ | +| [Get Collections](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getCollection) | ❌ | ✅ | +| [Update Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateCollection) | ❌ | ✅ | +| [Delete Collection](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteCollection) | ❌ | ✅ | +| [List Attributes](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listAttributes) | ❌ | ✅ | +| [Create Boolean Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createBooleanAttribute) | ❌ | ✅ | +| [Update Boolean Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateBooleanAttribute) | ❌ | ✅ | +| [Create Datetime Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createDatetimeAttribute) | ❌ | ✅ | +| [Update Datetime Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateDatetimeAttribute) | ❌ | ✅ | +| [Create Email Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createEmailAttribute) | ❌ | ✅ | +| [Update Email Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateEmailAttribute) | ❌ | ✅ | +| [Create Enum Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createEnumAttribute) | ❌ | ✅ | +| [Update Enum Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateEnumAttribute) | ❌ | ✅ | +| [Create Float Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createFloatAttribute) | ❌ | ✅ | +| [Update Float Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateFloatAttribute) | ❌ | ✅ | +| [Create Integer Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIntegerAttribute) | ❌ | ✅ | +| [Update Integer attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateIntegerAttribute) | ❌ | ✅ | +| [Create IP Address Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIpAttribute) | ❌ | ✅ | +| [Update IP Address Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateIpAttribute) | ❌ | ✅ | +| [Create Relationship Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createRelationshipAttribute) | ❌ | ✅ | +| [Create String Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createStringAttribute) | ❌ | ✅ | +| [Update String Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateStringAttribute) | ❌ | ✅ | +| [Create URL Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createUrlAttribute) | ❌ | ✅ | +| [Update URL Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateUrlAttribute) | ❌ | ✅ | +| [Get Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getAttribute) | ❌ | ✅ | +| [Delete Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteAttribute) | ❌ | ✅ | +| [Update Relationship Attribute](https://appwrite.io/docs/references/1.6.x/server-rest/databases#updateRelationshipAttribute) | ❌ | ✅ | +| [List Documents](https://appwrite.io/docs/references/1.6.x/client-rest/databases#listDocuments) | ✅ | ✅ | +| [Create Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#createDocument) | ✅ | ✅ | +| [Get Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#getDocument) | ✅ | ✅ | +| [Update Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#updateDocument) | ✅ | ✅ | +| [Delete Document](https://appwrite.io/docs/references/1.6.x/client-rest/databases#deleteDocument) | ✅ | ✅ | +| [List Indexes](https://appwrite.io/docs/references/1.6.x/server-rest/databases#listIndexes) | ❌ | ✅ | +| [Create Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#createIndex) | ❌ | ✅ | +| [Get Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#getIndex) | ❌ | ✅ | +| [Delete Index](https://appwrite.io/docs/references/1.6.x/server-rest/databases#deleteIndex) | ❌ | ✅ | ### Storage ![storage - 0 / 21](https://img.shields.io/badge/Storage-0%20%2F%2021-red?style=for-the-badge) diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index 581bd728..eabb67dc 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Client.Clients; using PinguApps.Appwrite.Client.Internals; using PinguApps.Appwrite.Client.Utils; @@ -17,9 +16,9 @@ public class AccountClient : SessionAwareClientBase, IAccountClient private readonly IAccountApi _accountApi; private readonly Config _config; - public AccountClient(IServiceProvider services, Config config) + internal AccountClient(IAccountApi accountApi, Config config) { - _accountApi = services.GetRequiredService(); + _accountApi = accountApi; _config = config; } diff --git a/src/PinguApps.Appwrite.Client/Clients/AppwriteClient.cs b/src/PinguApps.Appwrite.Client/Clients/AppwriteClient.cs index 447f6176..2d4d0f31 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AppwriteClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AppwriteClient.cs @@ -7,12 +7,18 @@ public class AppwriteClient : IAppwriteClient, ISessionAware { /// public IAccountClient Account { get; } + + /// public ITeamsClient Teams { get; } - public AppwriteClient(IAccountClient accountClient, ITeamsClient teams) + /// + public IDatabasesClient Databases { get; } + + public AppwriteClient(IAccountClient accountClient, ITeamsClient teams, IDatabasesClient databasesClient) { Account = accountClient; Teams = teams; + Databases = databasesClient; } string? ISessionAware.Session { get; set; } @@ -36,5 +42,6 @@ public void SetSession(string? session) (this as ISessionAware).UpdateSession(session); (Account as ISessionAware)!.UpdateSession(session); (Teams as ISessionAware)!.UpdateSession(session); + (Databases as ISessionAware)!.UpdateSession(session); } } diff --git a/src/PinguApps.Appwrite.Client/Clients/DatabasesClient.cs b/src/PinguApps.Appwrite.Client/Clients/DatabasesClient.cs new file mode 100644 index 00000000..ba81c4df --- /dev/null +++ b/src/PinguApps.Appwrite.Client/Clients/DatabasesClient.cs @@ -0,0 +1,105 @@ +using System; +using System.Threading.Tasks; +using PinguApps.Appwrite.Client.Internals; +using PinguApps.Appwrite.Client.Utils; +using PinguApps.Appwrite.Shared; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Client.Clients; + +/// +public class DatabasesClient : SessionAwareClientBase, IDatabasesClient +{ + private readonly IDatabasesApi _databasesApi; + + internal DatabasesClient(IDatabasesApi databasesApi) + { + _databasesApi = databasesApi; + } + + /// + public async Task> ListDocuments(ListDocumentsRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListDocuments(GetCurrentSession(), request.DatabaseId, request.CollectionId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateDocument(CreateDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateDocument(GetCurrentSession(), request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteDocument(DeleteDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteDocument(GetCurrentSession(), request.DatabaseId, request.CollectionId, request.DocumentId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetDocument(GetDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetDocument(GetCurrentSession(), request.DatabaseId, request.CollectionId, request.DocumentId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateDocument(UpdateDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateDocument(GetCurrentSession(), request.DatabaseId, request.CollectionId, request.DocumentId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } +} diff --git a/src/PinguApps.Appwrite.Client/Clients/IAppwriteClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAppwriteClient.cs index c212a5b3..94bb5b18 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAppwriteClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAppwriteClient.cs @@ -22,8 +22,23 @@ public interface IAppwriteClient void SetSession(string? session); /// - /// The sessio of the currently logged in user + /// The session of the currently logged in user /// string? Session { get; } + + /// + /// The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources, such as database documents or storage files. + /// Each user who creates a team becomes the team owner and can delegate the ownership role by inviting a new team member. Only team owners can invite new users to their team. + /// Appwrite Docs + /// ITeamsClient Teams { get; } + + /// + /// The Databases service allows you to create structured collections of documents, query and filter lists of documents, and manage an advanced set of read and write access permissions. + /// All data returned by the Databases service are represented as structured JSON documents. + /// The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by collection attributes. The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure. + /// Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (any). You can learn more about how Appwrite handles permissions and access control. + /// Appwrite Docs + /// + IDatabasesClient Databases { get; } } diff --git a/src/PinguApps.Appwrite.Client/Clients/IDatabasesClient.cs b/src/PinguApps.Appwrite.Client/Clients/IDatabasesClient.cs new file mode 100644 index 00000000..bf5797fc --- /dev/null +++ b/src/PinguApps.Appwrite.Client/Clients/IDatabasesClient.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using PinguApps.Appwrite.Shared; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Client.Clients; + +/// +/// The Databases service allows you to create structured collections of documents, query and filter lists of documents, and manage an advanced set of read and write access permissions. +/// All data returned by the Databases service are represented as structured JSON documents. +/// The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by collection attributes. The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure. +/// Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (any). You can learn more about how Appwrite handles permissions and access control. +/// Appwrite Docs +/// +public interface IDatabasesClient +{ + /// + /// Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + /// Appwrite Docs + /// + /// The request content + /// The documents list + Task> ListDocuments(ListDocumentsRequest request); + + /// + /// Create a new Document. Before using this route, you should create a new collection resource using either a server integration API or directly from your database console. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> CreateDocument(CreateDocumentRequest request); + + /// + /// Delete a document by its unique ID. + /// Appwrite Docs + /// + /// The request content + /// 204 Success Code + Task DeleteDocument(DeleteDocumentRequest request); + + /// + /// Get a document by its unique ID. This endpoint response returns a JSON object with the document data. You can return select columns by passing in a Select query. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> GetDocument(GetDocumentRequest request); + + /// + /// Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> UpdateDocument(UpdateDocumentRequest request); +} diff --git a/src/PinguApps.Appwrite.Client/Clients/ITeamsClient.cs b/src/PinguApps.Appwrite.Client/Clients/ITeamsClient.cs index 03a52a93..3c88139d 100644 --- a/src/PinguApps.Appwrite.Client/Clients/ITeamsClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/ITeamsClient.cs @@ -9,6 +9,7 @@ namespace PinguApps.Appwrite.Client.Clients; /// /// The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources, such as database documents or storage files. /// Each user who creates a team becomes the team owner and can delegate the ownership role by inviting a new team member. Only team owners can invite new users to their team. +/// Appwrite Docs /// public interface ITeamsClient { diff --git a/src/PinguApps.Appwrite.Client/Clients/TeamsClient.cs b/src/PinguApps.Appwrite.Client/Clients/TeamsClient.cs index a076aebb..9a6864df 100644 --- a/src/PinguApps.Appwrite.Client/Clients/TeamsClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/TeamsClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Client.Internals; using PinguApps.Appwrite.Client.Utils; using PinguApps.Appwrite.Shared; @@ -17,9 +16,9 @@ public class TeamsClient : SessionAwareClientBase, ITeamsClient private readonly ITeamsApi _teamsApi; private readonly Config _config; - public TeamsClient(IServiceProvider services, Config config) + internal TeamsClient(ITeamsApi teamsApi, Config config) { - _teamsApi = services.GetRequiredService(); + _teamsApi = teamsApi; _config = config; } diff --git a/src/PinguApps.Appwrite.Client/Internals/IDatabasesApi.cs b/src/PinguApps.Appwrite.Client/Internals/IDatabasesApi.cs new file mode 100644 index 00000000..cc723517 --- /dev/null +++ b/src/PinguApps.Appwrite.Client/Internals/IDatabasesApi.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; +using Refit; + +namespace PinguApps.Appwrite.Client.Internals; +internal interface IDatabasesApi : IBaseApi +{ + [Get("/databases/{databaseId}/collections/{collectionId}/documents")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListDocuments([Header("x-appwrite-session")] string? session, string databaseId, string collectionId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Post("/databases/{databaseId}/collections/{collectionId}/documents")] + Task> CreateDocument([Header("x-appwrite-session")] string? session, string databaseId, string collectionId, CreateDocumentRequest request); + + [Delete("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + Task DeleteDocument([Header("x-appwrite-session")] string? session, string databaseId, string collectionId, string documentId); + + [Get("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> GetDocument([Header("x-appwrite-session")] string? session, string databaseId, string collectionId, string documentId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Patch("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + Task> UpdateDocument([Header("x-appwrite-session")] string? session, string databaseId, string collectionId, string documentId, UpdateDocumentRequest request); +} diff --git a/src/PinguApps.Appwrite.Client/PinguApps.Appwrite.Client.csproj b/src/PinguApps.Appwrite.Client/PinguApps.Appwrite.Client.csproj index 22ff2101..d6d68fd8 100644 --- a/src/PinguApps.Appwrite.Client/PinguApps.Appwrite.Client.csproj +++ b/src/PinguApps.Appwrite.Client/PinguApps.Appwrite.Client.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs b/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs index 4e03823d..8c7d39c2 100644 --- a/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs +++ b/src/PinguApps.Appwrite.Client/ServiceCollectionExtensions.cs @@ -44,8 +44,28 @@ public static IServiceCollection AddAppwriteClient(this IServiceCollection servi .AddHttpMessageHandler() .AddHttpMessageHandler(); - services.AddSingleton(); - services.AddSingleton(); + services.AddRefitClient(customRefitSettings) + .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint)) + .AddHttpMessageHandler() + .AddHttpMessageHandler(); + + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new AccountClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new TeamsClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + return new DatabasesClient(api); + }); services.AddSingleton(); services.AddSingleton(x => new Lazy(() => x.GetRequiredService())); @@ -77,8 +97,28 @@ public static IServiceCollection AddAppwriteClientForServer(this IServiceCollect .AddHttpMessageHandler() .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler); - services.AddSingleton(); - services.AddSingleton(); + services.AddRefitClient(customRefitSettings) + .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint)) + .AddHttpMessageHandler() + .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler); + + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new AccountClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new TeamsClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + return new DatabasesClient(api); + }); services.AddSingleton(); return services; diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 1d229a3d..6bcb8df0 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -1,5 +1,6 @@ -using Microsoft.Extensions.Configuration; -using PinguApps.Appwrite.Shared.Requests.Teams; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Configuration; +using PinguApps.Appwrite.Shared.Requests.Databases; namespace PinguApps.Appwrite.Playground; internal class App @@ -15,33 +16,44 @@ public App(Client.IAppwriteClient client, Server.Clients.IAppwriteClient server, _session = config.GetValue("Session"); } - public async Task Run(string[] args) + private class Rec { - _client.SetSession(_session); - - var request = new UpdatePreferencesRequest() - { - TeamId = "67142b78001c379958cb", - Preferences = new Dictionary() - { - {"key", "value"} - } - }; + [JsonPropertyName("test")] + public string Test { get; set; } = string.Empty; - //var clientResponse = await _client.Teams.UpdatePreferences(request); + [JsonPropertyName("boolAttribute")] + public bool BoolAttribute { get; set; } + } - //Console.WriteLine(clientResponse.Result.Match( - // result => result.ToString(), - // appwriteError => appwriteError.Message, - // internalError => internalError.Message)); + public async Task Run(string[] args) + { + var before = new Rec { Test = "test", BoolAttribute = false }; + var after = new Rec { Test = "test", BoolAttribute = true }; - Console.WriteLine("############################################################################"); + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId("67541a2800221703e717") + .WithCollectionId("67541a37001514b81821") + .WithDocumentId("67541af9000055e59e59") + .WithChanges(before, after) + .Build(); - var serverResponse = await _server.Teams.UpdatePreferences(request); + var serverResponse = await _server.Databases.UpdateDocument(request); Console.WriteLine(serverResponse.Result.Match( result => result.ToString(), appwriteError => appwriteError.Message, internalError => internalError.Message)); + + Console.WriteLine("###############################################################################"); + + //Console.ReadKey(); + //request.Data["test"] = "Client Update"; + + //var clientResponse = await _client.Databases.UpdateDocument(request); + + //Console.WriteLine(clientResponse.Result.Match( + // result => result.ToString(), + // appwriteError => appwriteError.Message, + // internalError => internalError.Message)); } } diff --git a/src/PinguApps.Appwrite.Playground/PinguApps.Appwrite.Playground.csproj b/src/PinguApps.Appwrite.Playground/PinguApps.Appwrite.Playground.csproj index 3b09ab5e..9c725139 100644 --- a/src/PinguApps.Appwrite.Playground/PinguApps.Appwrite.Playground.csproj +++ b/src/PinguApps.Appwrite.Playground/PinguApps.Appwrite.Playground.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/PinguApps.Appwrite.Server/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Server/Clients/AccountClient.cs index 40efa473..f2847a6e 100644 --- a/src/PinguApps.Appwrite.Server/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/AccountClient.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Server.Internals; using PinguApps.Appwrite.Server.Utils; using PinguApps.Appwrite.Shared; @@ -16,9 +15,9 @@ public class AccountClient : IAccountClient private readonly Config _config; - public AccountClient(IServiceProvider services, Config config) + internal AccountClient(IAccountApi accountApi, Config config) { - _accountApi = services.GetRequiredService(); + _accountApi = accountApi; _config = config; } diff --git a/src/PinguApps.Appwrite.Server/Clients/AppwriteClient.cs b/src/PinguApps.Appwrite.Server/Clients/AppwriteClient.cs index 0906935e..ffe3eb27 100644 --- a/src/PinguApps.Appwrite.Server/Clients/AppwriteClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/AppwriteClient.cs @@ -3,14 +3,23 @@ /// public class AppwriteClient : IAppwriteClient { + /// public IAccountClient Account { get; } + + /// public IUsersClient Users { get; } + + /// public ITeamsClient Teams { get; } - public AppwriteClient(IAccountClient accountClient, IUsersClient usersClient, ITeamsClient teamsClient) + /// + public IDatabasesClient Databases { get; } + + public AppwriteClient(IAccountClient accountClient, IUsersClient usersClient, ITeamsClient teamsClient, IDatabasesClient databasesClient) { Account = accountClient; Users = usersClient; Teams = teamsClient; + Databases = databasesClient; } } diff --git a/src/PinguApps.Appwrite.Server/Clients/DatabasesClient.cs b/src/PinguApps.Appwrite.Server/Clients/DatabasesClient.cs new file mode 100644 index 00000000..1a47a2ca --- /dev/null +++ b/src/PinguApps.Appwrite.Server/Clients/DatabasesClient.cs @@ -0,0 +1,736 @@ +using System; +using System.Threading.Tasks; +using PinguApps.Appwrite.Server.Internals; +using PinguApps.Appwrite.Server.Utils; +using PinguApps.Appwrite.Shared; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Server.Clients; + +/// +public class DatabasesClient : IDatabasesClient +{ + private readonly IDatabasesApi _databasesApi; + + internal DatabasesClient(IDatabasesApi databasesApi) + { + _databasesApi = databasesApi; + } + + /// + public async Task> ListDatabases(ListDatabasesRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListDatabases(RequestUtils.GetQueryStrings(request.Queries), request.Search); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateDatabase(CreateDatabaseRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateDatabase(request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteDatabase(DeleteDatabaseRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteDatabase(request.DatabaseId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetDatabase(GetDatabaseRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetDatabase(request.DatabaseId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateDatabase(UpdateDatabaseRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateDatabase(request.DatabaseId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> ListCollections(ListCollectionsRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListCollections(request.DatabaseId, RequestUtils.GetQueryStrings(request.Queries), request.Search); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateCollection(CreateCollectionRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateCollection(request.DatabaseId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteCollection(DeleteCollectionRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteCollection(request.DatabaseId, request.CollectionId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetCollection(GetCollectionRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetCollection(request.DatabaseId, request.CollectionId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateCollection(UpdateCollectionRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateCollection(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> ListAttributes(ListAttributesRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListAttributes(request.DatabaseId, request.CollectionId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateBooleanAttribute(CreateBooleanAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateBooleanAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateBooleanAttribute(UpdateBooleanAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateBooleanAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateDatetimeAttribute(CreateDatetimeAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateDatetimeAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateDatetimeAttribute(UpdateDatetimeAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateDatetimeAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateEmailAttribute(CreateEmailAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateEmailAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateEmailAttribute(UpdateEmailAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateEmailAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateEnumAttribute(CreateEnumAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateEnumAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateEnumAttribute(UpdateEnumAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateEnumAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateFloatAttribute(CreateFloatAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateFloatAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateFloatAttribute(UpdateFloatAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateFloatAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateIntegerAttribute(CreateIntegerAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateIntegerAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateIntegerAttribute(UpdateIntegerAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateIntegerAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateIpAttribute(CreateIPAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateIpAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateIpAttribute(UpdateIPAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateIpAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateRelationshipAttribute(CreateRelationshipAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateRelationshipAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateStringAttribute(CreateStringAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateStringAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateStringAttribute(UpdateStringAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateStringAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateUrlAttribute(CreateURLAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateUrlAttribute(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateUrlAttribute(UpdateURLAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateUrlAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteAttribute(DeleteAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteAttribute(request.DatabaseId, request.CollectionId, request.Key); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetAttribute(GetAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetAttribute(request.DatabaseId, request.CollectionId, request.Key); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateRelationshipAttribute(UpdateRelationshipAttributeRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateRelationshipAttribute(request.DatabaseId, request.CollectionId, request.Key, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> ListDocuments(ListDocumentsRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListDocuments(request.DatabaseId, request.CollectionId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateDocument(CreateDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateDocument(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteDocument(DeleteDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteDocument(request.DatabaseId, request.CollectionId, request.DocumentId); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetDocument(GetDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetDocument(request.DatabaseId, request.CollectionId, request.DocumentId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> UpdateDocument(UpdateDocumentRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.UpdateDocument(request.DatabaseId, request.CollectionId, request.DocumentId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> ListIndexes(ListIndexesRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.ListIndexes(request.DatabaseId, request.CollectionId, RequestUtils.GetQueryStrings(request.Queries)); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> CreateIndex(CreateIndexRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.CreateIndex(request.DatabaseId, request.CollectionId, request); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task DeleteIndex(DeleteIndexRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.DeleteIndex(request.DatabaseId, request.CollectionId, request.Key); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } + + /// + public async Task> GetIndex(GetIndexRequest request) + { + try + { + request.Validate(true); + + var result = await _databasesApi.GetIndex(request.DatabaseId, request.CollectionId, request.Key); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } +} diff --git a/src/PinguApps.Appwrite.Server/Clients/IAppwriteClient.cs b/src/PinguApps.Appwrite.Server/Clients/IAppwriteClient.cs index 3be2226e..881d5c5d 100644 --- a/src/PinguApps.Appwrite.Server/Clients/IAppwriteClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/IAppwriteClient.cs @@ -18,5 +18,20 @@ public interface IAppwriteClient /// Appwrite Docs /// IUsersClient Users { get; } + + /// + /// The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources, such as database documents or storage files. + /// Each user who creates a team becomes the team owner and can delegate the ownership role by inviting a new team member. Only team owners can invite new users to their team. + /// Appwrite Docs + /// ITeamsClient Teams { get; } + + /// + /// The Databases service allows you to create structured collections of documents, query and filter lists of documents, and manage an advanced set of read and write access permissions. + /// All data returned by the Databases service are represented as structured JSON documents. + /// The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by collection attributes. The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure. + /// Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (any). You can learn more about how Appwrite handles permissions and access control. + /// Appwrite Docs + /// + IDatabasesClient Databases { get; } } diff --git a/src/PinguApps.Appwrite.Server/Clients/IDatabasesClient.cs b/src/PinguApps.Appwrite.Server/Clients/IDatabasesClient.cs new file mode 100644 index 00000000..9cc432c9 --- /dev/null +++ b/src/PinguApps.Appwrite.Server/Clients/IDatabasesClient.cs @@ -0,0 +1,355 @@ +using System.Threading.Tasks; +using PinguApps.Appwrite.Shared; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Server.Clients; + +/// +/// The Databases service allows you to create structured collections of documents, query and filter lists of documents, and manage an advanced set of read and write access permissions. +/// All data returned by the Databases service are represented as structured JSON documents. +/// The Databases service can contain multiple databases, each database can contain multiple collections. A collection is a group of similarly structured documents. The accepted structure of documents is defined by collection attributes. The collection attributes help you ensure all your user-submitted data is validated and stored according to the collection structure. +/// Using Appwrite permissions architecture, you can assign read or write access to each collection or document in your project for either a specific user, team, user role, or even grant it with public access (any). You can learn more about how Appwrite handles permissions and access control. +/// Appwrite Docs +/// +public interface IDatabasesClient +{ + /// + /// Get a list of all databases from the current Appwrite project. You can use the search parameter to filter your results. + /// Appwrite Docs + /// + /// The request content + /// The databases list + Task> ListDatabases(ListDatabasesRequest request); + + /// + /// Create a new Database. + /// Appwrite Docs + /// + /// The request content + /// The database + Task> CreateDatabase(CreateDatabaseRequest request); + + /// + /// Delete a database by its unique ID. Only API keys with with databases.write scope can delete a database. + /// Appwrite Docs + /// + /// The request content + /// 200 Success Response + Task DeleteDatabase(DeleteDatabaseRequest request); + + /// + /// Get a database by its unique ID. This endpoint response returns a JSON object with the database metadata. + /// Appwrite Docs + /// + /// The request content + /// The database + Task> GetDatabase(GetDatabaseRequest request); + + /// + /// Update a database by its unique ID. + /// Appwrite Docs + /// + /// The request content + /// The database + Task> UpdateDatabase(UpdateDatabaseRequest request); + + /// + /// Get a list of all collections that belong to the provided databaseId. You can use the search parameter to filter your results. + /// Appwrite Docs + /// + /// The request content + /// The collections list + Task> ListCollections(ListCollectionsRequest request); + + /// + /// Create a new Collection. Before using this route, you should create a new database resource using either or directly from your database console. + /// Appwrite Docs + /// + /// The request content + /// The collection + Task> CreateCollection(CreateCollectionRequest request); + + /// + /// Delete a collection by its unique ID. Only users with write permissions have access to delete this resource. + /// Appwrite Docs + /// + /// The request content + /// 204 Success Response + Task DeleteCollection(DeleteCollectionRequest request); + + /// + /// Get a collection by its unique ID. This endpoint response returns a JSON object with the collection metadata. + /// Appwrite Docs + /// + /// The request content + /// The collection + Task> GetCollection(GetCollectionRequest request); + + /// + /// Update a collection by its unique ID. + /// Appwrite Docs + /// + /// The request content + /// The collection + Task> UpdateCollection(UpdateCollectionRequest request); + + /// + /// List attributes in the collection. + /// Appwrite Docs + /// + /// The request content + /// The attributes list + Task> ListAttributes(ListAttributesRequest request); + + /// + /// Create a boolean attribute. + /// Appwrite Docs + /// + /// The request content + /// The boolean attribute + Task> CreateBooleanAttribute(CreateBooleanAttributeRequest request); + + /// + /// Update a boolean attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The boolean attribute + Task> UpdateBooleanAttribute(UpdateBooleanAttributeRequest request); + + /// + /// Create a date time attribute according to the ISO 8601 standard. + /// Appwrite Docs + /// + /// The request content + /// The datetime attribute + Task> CreateDatetimeAttribute(CreateDatetimeAttributeRequest request); + + /// + /// Update a date time attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The datetime attribute + Task> UpdateDatetimeAttribute(UpdateDatetimeAttributeRequest request); + + /// + /// Create an email attribute. + /// Appwrite Docs + /// + /// The request content + /// The email attribute + Task> CreateEmailAttribute(CreateEmailAttributeRequest request); + + /// + /// Update an email attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The email attribute + Task> UpdateEmailAttribute(UpdateEmailAttributeRequest request); + + /// + /// Create an enumeration attribute. The elements param acts as a white-list of accepted values for this attribute. + /// Appwrite Docs + /// + /// The request content + /// The enum attribute + Task> CreateEnumAttribute(CreateEnumAttributeRequest request); + + /// + /// Update an enumeration attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The enum attribute + Task> UpdateEnumAttribute(UpdateEnumAttributeRequest request); + + /// + /// Create a float attribute. Optionally, minimum and maximum values can be provided. + /// Appwrite Docs + /// + /// The request content + /// The float attribute + Task> CreateFloatAttribute(CreateFloatAttributeRequest request); + + /// + /// Update a float attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The float attribute + Task> UpdateFloatAttribute(UpdateFloatAttributeRequest request); + + /// + /// Create an integer attribute. Optionally, minimum and maximum values can be provided. + /// Appwrite Docs + /// + /// The request content + /// The integer attribute + Task> CreateIntegerAttribute(CreateIntegerAttributeRequest request); + + /// + /// Update an integer attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The integer attribute + Task> UpdateIntegerAttribute(UpdateIntegerAttributeRequest request); + + /// + /// Create an IP attribute. + /// Appwrite Docs + /// + /// The request content + /// The ip address attribute + Task> CreateIpAttribute(CreateIPAttributeRequest request); + + /// + /// Update an IP attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The ip address attribute + Task> UpdateIpAttribute(UpdateIPAttributeRequest request); + + /// + /// Create relationship attribute. Learn more about relationship attributes. + /// Appwrite Docs + /// + /// The request content + /// The relationship attribute + Task> CreateRelationshipAttribute(CreateRelationshipAttributeRequest request); + + /// + /// Create a string attribute. + /// Appwrite Docs + /// + /// The request content + /// The string attribute + Task> CreateStringAttribute(CreateStringAttributeRequest request); + + /// + /// Update a string attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The string attribute + Task> UpdateStringAttribute(UpdateStringAttributeRequest request); + + /// + /// Create a URL attribute. + /// Appwrite Docs + /// + /// The request content + /// The url attribute + Task> CreateUrlAttribute(CreateURLAttributeRequest request); + + /// + /// Update a URL attribute. Changing the default value will not update already existing documents. + /// Appwrite Docs + /// + /// The request content + /// The url attribute + Task> UpdateUrlAttribute(UpdateURLAttributeRequest request); + + /// + /// Deletes an attribute. + /// Appwrite Docs + /// + /// The request content + /// 204 success code + Task DeleteAttribute(DeleteAttributeRequest request); + + /// + /// Get attribute by ID. + /// Appwrite Docs + /// + /// The request content + /// The attribute + Task> GetAttribute(GetAttributeRequest request); + + /// + /// Update relationship attribute. Learn more about relationship attributes. + /// Appwrite Docs + /// + /// The request content + /// The relationship attribute + Task> UpdateRelationshipAttribute(UpdateRelationshipAttributeRequest request); + + /// + /// Get a list of all the user's documents in a given collection. You can use the query params to filter your results. + /// Appwrite Docs + /// + /// The request content + /// The documents list + Task> ListDocuments(ListDocumentsRequest request); + + /// + /// Create a new Document. Before using this route, you should create a new collection resource using either or directly from your database console. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> CreateDocument(CreateDocumentRequest request); + + /// + /// Delete a document by its unique ID. + /// Appwrite Docs + /// + /// The request content + /// 204 success code + Task DeleteDocument(DeleteDocumentRequest request); + + /// + /// Get a document by its unique ID. This endpoint response returns a JSON object with the document data. You can return select columns by passing in a Select query. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> GetDocument(GetDocumentRequest request); + + /// + /// Update a document by its unique ID. Using the patch method you can pass only specific fields that will get updated. + /// Appwrite Docs + /// + /// The request content + /// The document + Task> UpdateDocument(UpdateDocumentRequest request); + + /// + /// List indexes in the collection. + /// Appwrite Docs + /// + /// The request content + /// The indexes list + Task> ListIndexes(ListIndexesRequest request); + + /// + /// Creates an index on the attributes listed. Your index should include all the attributes you will query in a single request. Attributes can be , and . + /// Appwrite Docs + /// + /// The request content + /// The index + Task> CreateIndex(CreateIndexRequest request); + + /// + /// Delete an index. + /// Appwrite Docs + /// + /// The request content + /// 204 success code + Task DeleteIndex(DeleteIndexRequest request); + + /// + /// Get index by ID. + /// Appwrite Docs + /// + /// The request content + /// The index + Task> GetIndex(GetIndexRequest request); +} diff --git a/src/PinguApps.Appwrite.Server/Clients/ITeamsClient.cs b/src/PinguApps.Appwrite.Server/Clients/ITeamsClient.cs index f21c3b30..e469d974 100644 --- a/src/PinguApps.Appwrite.Server/Clients/ITeamsClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/ITeamsClient.cs @@ -9,6 +9,7 @@ namespace PinguApps.Appwrite.Server.Clients; /// /// The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources, such as database documents or storage files. /// Each user who creates a team becomes the team owner and can delegate the ownership role by inviting a new team member. Only team owners can invite new users to their team. +/// Appwrite Docs /// public interface ITeamsClient { diff --git a/src/PinguApps.Appwrite.Server/Clients/TeamsClient.cs b/src/PinguApps.Appwrite.Server/Clients/TeamsClient.cs index c23acbab..b5786391 100644 --- a/src/PinguApps.Appwrite.Server/Clients/TeamsClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/TeamsClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Server.Internals; using PinguApps.Appwrite.Server.Utils; using PinguApps.Appwrite.Shared; @@ -17,9 +16,9 @@ public class TeamsClient : ITeamsClient private readonly ITeamsApi _teamsApi; private readonly Config _config; - public TeamsClient(IServiceProvider services, Config config) + internal TeamsClient(ITeamsApi teamsApi, Config config) { - _teamsApi = services.GetRequiredService(); + _teamsApi = teamsApi; _config = config; } diff --git a/src/PinguApps.Appwrite.Server/Clients/UsersClient.cs b/src/PinguApps.Appwrite.Server/Clients/UsersClient.cs index 7822d28e..c0c1f7f5 100644 --- a/src/PinguApps.Appwrite.Server/Clients/UsersClient.cs +++ b/src/PinguApps.Appwrite.Server/Clients/UsersClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Server.Internals; using PinguApps.Appwrite.Server.Utils; using PinguApps.Appwrite.Shared; @@ -16,9 +15,9 @@ public class UsersClient : IUsersClient private readonly IUsersApi _usersApi; private readonly Config _config; - public UsersClient(IServiceProvider services, Config config) + internal UsersClient(IUsersApi usersApi, Config config) { - _usersApi = services.GetRequiredService(); + _usersApi = usersApi; _config = config; } diff --git a/src/PinguApps.Appwrite.Server/Internals/IDatabasesApi.cs b/src/PinguApps.Appwrite.Server/Internals/IDatabasesApi.cs new file mode 100644 index 00000000..90c5711f --- /dev/null +++ b/src/PinguApps.Appwrite.Server/Internals/IDatabasesApi.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Responses; +using Refit; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Server.Internals; +internal interface IDatabasesApi : IBaseApi +{ + // Database Operations + [Get("/databases")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListDatabases([Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries, [AliasAs("search")] string? search); + + [Post("/databases")] + Task> CreateDatabase(CreateDatabaseRequest request); + + [Delete("/databases/{databaseId}")] + Task DeleteDatabase(string databaseId); + + [Get("/databases/{databaseId}")] + Task> GetDatabase(string databaseId); + + [Put("/databases/{databaseId}")] + Task> UpdateDatabase(string databaseId, UpdateDatabaseRequest request); + + // Collection Operations + [Get("/databases/{databaseId}/collections")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListCollections(string databaseId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries, [AliasAs("search")] string? search); + + [Post("/databases/{databaseId}/collections")] + Task> CreateCollection(string databaseId, CreateCollectionRequest request); + + [Delete("/databases/{databaseId}/collections/{collectionId}")] + Task DeleteCollection(string databaseId, string collectionId); + + [Get("/databases/{databaseId}/collections/{collectionId}")] + Task> GetCollection(string databaseId, string collectionId); + + [Put("/databases/{databaseId}/collections/{collectionId}")] + Task> UpdateCollection(string databaseId, string collectionId, UpdateCollectionRequest request); + + // Attribute Operations + [Get("/databases/{databaseId}/collections/{collectionId}/attributes")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListAttributes(string databaseId, string collectionId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/boolean")] + Task> CreateBooleanAttribute(string databaseId, string collectionId, CreateBooleanAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/boolean/{key}")] + Task> UpdateBooleanAttribute(string databaseId, string collectionId, string key, UpdateBooleanAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/datetime")] + Task> CreateDatetimeAttribute(string databaseId, string collectionId, CreateDatetimeAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/datetime/{key}")] + Task> UpdateDatetimeAttribute(string databaseId, string collectionId, string key, UpdateDatetimeAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/email")] + Task> CreateEmailAttribute(string databaseId, string collectionId, CreateEmailAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/email/{key}")] + Task> UpdateEmailAttribute(string databaseId, string collectionId, string key, UpdateEmailAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/enum")] + Task> CreateEnumAttribute(string databaseId, string collectionId, CreateEnumAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/enum/{key}")] + Task> UpdateEnumAttribute(string databaseId, string collectionId, string key, UpdateEnumAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/float")] + Task> CreateFloatAttribute(string databaseId, string collectionId, CreateFloatAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/float/{key}")] + Task> UpdateFloatAttribute(string databaseId, string collectionId, string key, UpdateFloatAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/integer")] + Task> CreateIntegerAttribute(string databaseId, string collectionId, CreateIntegerAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/integer/{key}")] + Task> UpdateIntegerAttribute(string databaseId, string collectionId, string key, UpdateIntegerAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/ip")] + Task> CreateIpAttribute(string databaseId, string collectionId, CreateIPAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/ip/{key}")] + Task> UpdateIpAttribute(string databaseId, string collectionId, string key, UpdateIPAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/relationship")] + Task> CreateRelationshipAttribute(string databaseId, string collectionId, CreateRelationshipAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/string")] + Task> CreateStringAttribute(string databaseId, string collectionId, CreateStringAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/string/{key}")] + Task> UpdateStringAttribute(string databaseId, string collectionId, string key, UpdateStringAttributeRequest request); + + [Post("/databases/{databaseId}/collections/{collectionId}/attributes/url")] + Task> CreateUrlAttribute(string databaseId, string collectionId, CreateURLAttributeRequest request); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/url/{key}")] + Task> UpdateUrlAttribute(string databaseId, string collectionId, string key, UpdateURLAttributeRequest request); + + [Delete("/databases/{databaseId}/collections/{collectionId}/attributes/{key}")] + Task DeleteAttribute(string databaseId, string collectionId, string key); + + [Get("/databases/{databaseId}/collections/{collectionId}/attributes/{key}")] + Task> GetAttribute(string databaseId, string collectionId, string key); + + [Patch("/databases/{databaseId}/collections/{collectionId}/attributes/{key}/relationship")] + Task> UpdateRelationshipAttribute(string databaseId, string collectionId, string key, UpdateRelationshipAttributeRequest request); + + // Document Operations + [Get("/databases/{databaseId}/collections/{collectionId}/documents")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListDocuments(string databaseId, string collectionId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Post("/databases/{databaseId}/collections/{collectionId}/documents")] + Task> CreateDocument(string databaseId, string collectionId, CreateDocumentRequest request); + + [Delete("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + Task DeleteDocument(string databaseId, string collectionId, string documentId); + + [Get("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> GetDocument(string databaseId, string collectionId, string documentId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Patch("/databases/{databaseId}/collections/{collectionId}/documents/{documentId}")] + Task> UpdateDocument(string databaseId, string collectionId, string documentId, UpdateDocumentRequest request); + + // Index Operations + [Get("/databases/{databaseId}/collections/{collectionId}/indexes")] + [QueryUriFormat(UriFormat.Unescaped)] + Task> ListIndexes(string databaseId, string collectionId, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); + + [Post("/databases/{databaseId}/collections/{collectionId}/indexes")] + Task> CreateIndex(string databaseId, string collectionId, CreateIndexRequest request); + + [Delete("/databases/{databaseId}/collections/{collectionId}/indexes/{key}")] + Task DeleteIndex(string databaseId, string collectionId, string key); + + [Get("/databases/{databaseId}/collections/{collectionId}/indexes/{key}")] + Task> GetIndex(string databaseId, string collectionId, string key); +} diff --git a/src/PinguApps.Appwrite.Server/PinguApps.Appwrite.Server.csproj b/src/PinguApps.Appwrite.Server/PinguApps.Appwrite.Server.csproj index a562a11b..38c36209 100644 --- a/src/PinguApps.Appwrite.Server/PinguApps.Appwrite.Server.csproj +++ b/src/PinguApps.Appwrite.Server/PinguApps.Appwrite.Server.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs b/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs index fe1d9419..c1f39084 100644 --- a/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs +++ b/src/PinguApps.Appwrite.Server/ServiceCollectionExtensions.cs @@ -49,9 +49,34 @@ public static IServiceCollection AddAppwriteServer(this IServiceCollection servi .AddHttpMessageHandler() .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddRefitClient(customRefitSettings) + .ConfigureHttpClient(x => ConfigureHttpClient(x, endpoint)) + .AddHttpMessageHandler() + .ConfigurePrimaryHttpMessageHandler(ConfigurePrimaryHttpMessageHandler); + + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new AccountClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new UsersClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + var config = sp.GetRequiredService(); + return new TeamsClient(api, config); + }); + services.AddSingleton(sp => + { + var api = sp.GetRequiredService(); + return new DatabasesClient(api); + }); services.AddSingleton(); return services; diff --git a/src/PinguApps.Appwrite.Shared/Constants.cs b/src/PinguApps.Appwrite.Shared/Constants.cs index 552fe9fd..b6fffe82 100644 --- a/src/PinguApps.Appwrite.Shared/Constants.cs +++ b/src/PinguApps.Appwrite.Shared/Constants.cs @@ -1,5 +1,5 @@ namespace PinguApps.Appwrite.Shared; public static class Constants { - public const string Version = "0.3.0"; + public const string Version = "0.4.0"; } diff --git a/src/PinguApps.Appwrite.Shared/Converters/AlwaysWriteNullableDateTimeConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/AlwaysWriteNullableDateTimeConverter.cs new file mode 100644 index 00000000..3201890f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/AlwaysWriteNullableDateTimeConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Text.Json; + +namespace PinguApps.Appwrite.Shared.Converters; +public class AlwaysWriteNullableDateTimeConverter : NullableDateTimeConverter +{ + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + } + else + { + base.Write(writer, value, options); + } + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/AttributeJsonConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/AttributeJsonConverter.cs new file mode 100644 index 00000000..118e385e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/AttributeJsonConverter.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; + +namespace PinguApps.Appwrite.Shared.Converters; +public class AttributeJsonConverter : JsonConverter +{ + public override Attribute? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var jsonDoc = JsonDocument.ParseValue(ref reader); + + var jsonObject = jsonDoc.RootElement; + + var attributeType = ResolveAttributeType(jsonObject); + + return DeserializeAttribute(jsonObject, attributeType, options); + } + + private static Type ResolveAttributeType(JsonElement jsonObject) + { + var type = GetRequiredTypeProperty(jsonObject); + var format = GetOptionalFormatProperty(jsonObject); + + return DetermineAttributeType(type, format, jsonObject); + } + + private static string GetRequiredTypeProperty(JsonElement jsonObject) + { + if (!jsonObject.TryGetProperty("type", out var typeProperty)) + { + throw new JsonException("Missing `Type` property"); + } + + return typeProperty.GetString()!; + } + + private static string? GetOptionalFormatProperty(JsonElement jsonObject) + { + jsonObject.TryGetProperty("format", out var formatProperty); + + return formatProperty.ValueKind is JsonValueKind.String ? formatProperty.GetString() : null; + } + + private static Type DetermineAttributeType(string type, string? format, JsonElement jsonObject) + { + return type switch + { + "boolean" => typeof(AttributeBoolean), + "integer" => typeof(AttributeInteger), + "double" => typeof(AttributeFloat), + "datetime" => typeof(AttributeDatetime), + "string" => ResolveStringAttributeType(format, jsonObject), + _ => throw new JsonException($"Unknown type: {type}") + }; + } + + private static Type ResolveStringAttributeType(string? format, JsonElement jsonObject) + { + return format switch + { + "email" => typeof(AttributeEmail), + "url" => typeof(AttributeUrl), + "ip" => typeof(AttributeIp), + "enum" => typeof(AttributeEnum), + null or "" => ResolveBasicStringAttributeType(jsonObject), + _ => throw new JsonException($"Unknown format: {format}") + }; + } + + private static Type ResolveBasicStringAttributeType(JsonElement jsonObject) + { + return jsonObject.TryGetProperty("relatedCollection", out _) ? typeof(AttributeRelationship) : typeof(AttributeString); + } + + private static Attribute? DeserializeAttribute(JsonElement jsonObject, Type attributeType, JsonSerializerOptions options) + { + return (Attribute?)JsonSerializer.Deserialize(jsonObject.GetRawText(), attributeType, options); + } + + public override void Write(Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/AttributeListJsonConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/AttributeListJsonConverter.cs new file mode 100644 index 00000000..8661fd31 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/AttributeListJsonConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; + +namespace PinguApps.Appwrite.Shared.Converters; +public class AttributeListJsonConverter : JsonConverter> +{ + private readonly AttributeJsonConverter _attributeConverter = new(); + + public override IReadOnlyList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var attributes = new List(); + + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array"); + } + + reader.Read(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + var attribute = _attributeConverter.Read(ref reader, typeof(Attribute), options); + + if (attribute is not null) + { + attributes.Add(attribute); + } + + reader.Read(); + } + + return attributes; + } + + public override void Write(Utf8JsonWriter writer, IReadOnlyList value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var attribute in value) + { + _attributeConverter.Write(writer, attribute, options); + } + + writer.WriteEndArray(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/CamelCaseEnumConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/CamelCaseEnumConverter.cs new file mode 100644 index 00000000..03fe804b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/CamelCaseEnumConverter.cs @@ -0,0 +1,10 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Converters; +public class CamelCaseEnumConverter : JsonStringEnumConverter +{ + public CamelCaseEnumConverter() : base(JsonNamingPolicy.CamelCase, false) + { + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/DocumentConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/DocumentConverter.cs new file mode 100644 index 00000000..f7e40cac --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/DocumentConverter.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Converters; +public class DocumentConverter : JsonConverter +{ + public override Document? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? id = null; + string? collectionId = null; + string? databaseId = null; + DateTime? createdAt = null; + DateTime? updatedAt = null; + List? permissions = null; + var data = new Dictionary(); + + var dateTimeConverter = new MultiFormatDateTimeConverter(); + var permissionListConverter = new PermissionListConverter(); + + if (reader.TokenType is not JsonTokenType.StartObject) + { + throw new JsonException("Expected StartObject token"); + } + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndObject) + { + break; + } + + var propertyName = reader.GetString()!; + + reader.Read(); + + switch (propertyName) + { + case "$id": + id = reader.GetString(); + break; + case "$collectionId": + collectionId = reader.GetString(); + break; + case "$databaseId": + databaseId = reader.GetString(); + break; + case "$createdAt": + createdAt = dateTimeConverter.Read(ref reader, typeof(DateTime), options); + break; + case "$updatedAt": + updatedAt = dateTimeConverter.Read(ref reader, typeof(DateTime), options); + break; + case "$permissions": + permissions = permissionListConverter.Read(ref reader, typeof(List), options); + break; + default: + var value = ReadValue(ref reader, options); + data[propertyName] = value; + break; + } + } + + if (id is null) + { + throw new JsonException("Unable to find a value for Id"); + } + + if (collectionId is null) + { + throw new JsonException("Unable to find a value for CollectionId"); + } + + if (databaseId is null) + { + throw new JsonException("Unable to find a value for DatabaseId"); + } + + if (permissions is null) + { + throw new JsonException("Unable to find a value for Permissions"); + } + + return new Document(id, collectionId, databaseId, createdAt, updatedAt, permissions, data); + } + + internal object? ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + var str = reader.GetString(); + + if ((DateTime.TryParse(str, out var dateTime))) + { + return dateTime; + } + return str; + + case JsonTokenType.Number: + if (reader.TryGetInt64(out var longValue)) + { + return longValue; + } + return reader.GetSingle(); + + case JsonTokenType.True: + case JsonTokenType.False: + return reader.GetBoolean(); + + case JsonTokenType.Null: + return null; + + case JsonTokenType.StartArray: + return ReadArray(ref reader, options); + + case JsonTokenType.StartObject: + return ReadObject(ref reader, options); + + default: + throw new JsonException($"Unsupported token type: {reader.TokenType}"); + } + } + + private IReadOnlyCollection ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var list = new List(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndArray) + { + break; + } + + var item = ReadValue(ref reader, options); + list.Add(item); + } + + return list; + } + + private Dictionary ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var dict = new Dictionary(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndObject) + { + break; + } + + var propertyName = reader.GetString()!; + + reader.Read(); + + var value = ReadValue(ref reader, options); + + dict[propertyName] = value; + } + + return dict; + } + + public override void Write(Utf8JsonWriter writer, Document value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + // Write known properties + writer.WriteString("$id", value.Id); + writer.WriteString("$collectionId", value.CollectionId); + writer.WriteString("$databaseId", value.DatabaseId); + + // Use MultiFormatDateTimeConverter for DateTime properties + var dateTimeConverter = new NullableDateTimeConverter(); + + writer.WritePropertyName("$createdAt"); + dateTimeConverter.Write(writer, value.CreatedAt, options); + + writer.WritePropertyName("$updatedAt"); + dateTimeConverter.Write(writer, value.UpdatedAt, options); + + writer.WritePropertyName("$permissions"); + JsonSerializer.Serialize(writer, value.Permissions, options); + + // Write dynamic properties from the Data dictionary + foreach (var kvp in value.Data) + { + writer.WritePropertyName(kvp.Key); + WriteValue(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + } + + private void WriteValue(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + // Handle null values + if (value is null) + { + writer.WriteNullValue(); + return; + } + + // Determine the type of the value and write accordingly + switch (value) + { + case string s: + writer.WriteStringValue(s); + break; + case int i: + writer.WriteNumberValue(i); + break; + case long l: + writer.WriteNumberValue(l); + break; + case float f: + writer.WriteNumberValue(f); + break; + case double d: + writer.WriteNumberValue(d); + break; + case decimal dec: + writer.WriteNumberValue(dec); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case DateTime dt: + var dateTimeConverter = new MultiFormatDateTimeConverter(); + dateTimeConverter.Write(writer, dt, options); + break; + case IReadOnlyList list: + writer.WriteStartArray(); + foreach (var item in list) + { + WriteValue(writer, item, options); + } + writer.WriteEndArray(); + break; + case Dictionary dict: + writer.WriteStartObject(); + foreach (var kvp in dict) + { + writer.WritePropertyName(kvp.Key); + WriteValue(writer, kvp.Value, options); + } + writer.WriteEndObject(); + break; + default: + // Fallback to default serialization + JsonSerializer.Serialize(writer, value, value.GetType(), options); + break; + } + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverter.cs new file mode 100644 index 00000000..3aa27074 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverter.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Converters; + +public class DocumentGenericConverter : JsonConverter> + where TData : class, new() +{ + private class DocumentFields + { + public string? Id { get; set; } + public string? CollectionId { get; set; } + public string? DatabaseId { get; set; } + public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public List? Permissions { get; set; } + public Dictionary DataProperties { get; set; } = new(); + } + + public override Document Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + ValidateStartObject(ref reader); + + var documentFields = ReadDocumentFields(ref reader, options); + + ValidateRequiredFields(documentFields); + + var data = DeserializeCustomData(documentFields.DataProperties, options); + + return new Document(documentFields.Id!, documentFields.CollectionId!, documentFields.DatabaseId!, documentFields.CreatedAt, + documentFields.UpdatedAt, documentFields.Permissions!, data); + } + + private static void ValidateStartObject(ref Utf8JsonReader reader) + { + if (reader.TokenType is not JsonTokenType.StartObject) + { + throw new JsonException("Expected StartObject token"); + } + } + + private DocumentFields ReadDocumentFields(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var dateTimeConverter = new MultiFormatDateTimeConverter(); + var permissionListConverter = new PermissionListConverter(); + var fields = new DocumentFields(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndObject) + { + break; + } + + var propertyName = reader.GetString()!; + reader.Read(); + + ProcessProperty(ref reader, propertyName, fields, dateTimeConverter, permissionListConverter, options); + } + + return fields; + } + + private static void ProcessProperty(ref Utf8JsonReader reader, string propertyName, DocumentFields fields, + MultiFormatDateTimeConverter dateTimeConverter, PermissionListConverter permissionListConverter, JsonSerializerOptions options) + { + switch (propertyName) + { + case "$id": + fields.Id = reader.GetString(); + break; + case "$collectionId": + fields.CollectionId = reader.GetString(); + break; + case "$databaseId": + fields.DatabaseId = reader.GetString(); + break; + case "$createdAt": + fields.CreatedAt = dateTimeConverter.Read(ref reader, typeof(DateTime), options); + break; + case "$updatedAt": + fields.UpdatedAt = dateTimeConverter.Read(ref reader, typeof(DateTime), options); + break; + case "$permissions": + fields.Permissions = permissionListConverter.Read(ref reader, typeof(List), options); + break; + default: + var value = ReadValue(ref reader, options); + fields.DataProperties[propertyName] = value; + break; + } + } + + internal static object? ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + var str = reader.GetString(); + + if ((DateTime.TryParse(str, out var dateTime))) + { + return dateTime; + } + return str; + + case JsonTokenType.Number: + if (reader.TryGetInt64(out var longValue)) + { + return longValue; + } + return reader.GetSingle(); + + case JsonTokenType.True: + case JsonTokenType.False: + return reader.GetBoolean(); + + case JsonTokenType.Null: + return null; + + case JsonTokenType.StartArray: + return ReadArray(ref reader, options); + + case JsonTokenType.StartObject: + return ReadObject(ref reader, options); + + default: + throw new JsonException($"Unsupported token type: {reader.TokenType}"); + } + } + + private static IReadOnlyCollection ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var list = new List(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndArray) + { + break; + } + + var item = ReadValue(ref reader, options); + list.Add(item); + } + + return list; + } + + private static Dictionary ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var dict = new Dictionary(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndObject) + { + break; + } + + var propertyName = reader.GetString()!; + + reader.Read(); + + var value = ReadValue(ref reader, options); + + dict[propertyName] = value; + } + + return dict; + } + + private static void ValidateRequiredFields(DocumentFields fields) + { + if (fields.Id is null) + throw new JsonException("Unable to find a value for Id"); + + if (fields.CollectionId is null) + throw new JsonException("Unable to find a value for CollectionId"); + + if (fields.DatabaseId is null) + throw new JsonException("Unable to find a value for DatabaseId"); + + if (fields.Permissions is null) + throw new JsonException("Unable to find a value for Permissions"); + } + + private static TData DeserializeCustomData(Dictionary dataProperties, JsonSerializerOptions options) + { + var dataJson = JsonSerializer.Serialize(dataProperties, options); + return JsonSerializer.Deserialize(dataJson, options) ?? new TData(); + } + + public override void Write(Utf8JsonWriter writer, Document value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteString("$id", value.Id); + writer.WriteString("$collectionId", value.CollectionId); + writer.WriteString("$databaseId", value.DatabaseId); + + // Use MultiFormatDateTimeConverter for DateTime properties + var dateTimeConverter = new NullableDateTimeConverter(); + + writer.WritePropertyName("$createdAt"); + dateTimeConverter.Write(writer, value.CreatedAt, options); + + writer.WritePropertyName("$updatedAt"); + dateTimeConverter.Write(writer, value.UpdatedAt, options); + + writer.WritePropertyName("$permissions"); + JsonSerializer.Serialize(writer, value.Permissions, options); + + // Serialize the Data property + if (value.Data is not null) + { + var dataProperties = JsonSerializer.SerializeToElement(value.Data, options); + foreach (var property in dataProperties.EnumerateObject()) + { + writer.WritePropertyName(property.Name); + WriteValue(writer, property.Value, options); + } + } + + writer.WriteEndObject(); + } + + internal void WriteValue(Utf8JsonWriter writer, JsonElement element, JsonSerializerOptions options) + { + var dateTimeConverter = new MultiFormatDateTimeConverter(); + + switch (element.ValueKind) + { + case JsonValueKind.String: + var stringValue = element.GetString(); + if (DateTime.TryParse(stringValue, out var dateTimeValue)) + { + // Write DateTime using the MultiFormatDateTimeConverter + dateTimeConverter.Write(writer, dateTimeValue, options); + } + else + { + writer.WriteStringValue(stringValue); + } + break; + case JsonValueKind.Number: + if (element.TryGetInt32(out var intValue)) + writer.WriteNumberValue(intValue); + else if (element.TryGetInt64(out var longValue)) + writer.WriteNumberValue(longValue); + else if (element.TryGetDouble(out var doubleValue)) + writer.WriteNumberValue(doubleValue); + break; + case JsonValueKind.True: + case JsonValueKind.False: + writer.WriteBooleanValue(element.GetBoolean()); + break; + case JsonValueKind.Null: + writer.WriteNullValue(); + break; + case JsonValueKind.Array: + writer.WriteStartArray(); + foreach (var item in element.EnumerateArray()) + { + WriteValue(writer, item, options); + } + writer.WriteEndArray(); + break; + case JsonValueKind.Object: + writer.WriteStartObject(); + foreach (var property in element.EnumerateObject()) + { + writer.WritePropertyName(property.Name); + WriteValue(writer, property.Value, options); + } + writer.WriteEndObject(); + break; + case JsonValueKind.Undefined: + throw new JsonException("Cannot serialize undefined JsonElement"); + } + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverterFactory.cs b/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverterFactory.cs new file mode 100644 index 00000000..da178a5f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/DocumentGenericConverterFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Converters; +public class DocumentGenericConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(Type typeToConvert) + { + // Ensure the type is a generic type of Document<> + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(Document<>); + } + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + // Extract the TData type from Doocument + Type dataType = typeToConvert.GetGenericArguments()[0]; + + // Create a specific generic converter for Doocument + var converterType = typeof(DocumentGenericConverter<>).MakeGenericType(dataType); + return (JsonConverter?)Activator.CreateInstance(converterType); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/DocumentListConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/DocumentListConverter.cs new file mode 100644 index 00000000..3fc58de1 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/DocumentListConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Converters; +public class DocumentListConverter : JsonConverter> +{ + private readonly DocumentConverter _documentConverter = new(); + + public override IReadOnlyList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var documents = new List(); + + if (reader.TokenType is not JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array"); + } + + reader.Read(); + + while (reader.TokenType is not JsonTokenType.EndArray) + { + var document = _documentConverter.Read(ref reader, typeof(Document), options); + + if (document is not null) + { + documents.Add(document); + } + + reader.Read(); + } + + return documents; + } + + public override void Write(Utf8JsonWriter writer, IReadOnlyList value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var document in value) + { + _documentConverter.Write(writer, document, options); + } + + writer.WriteEndArray(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/IgnoreSdkExcludedPropertiesConverterFactory.cs b/src/PinguApps.Appwrite.Shared/Converters/IgnoreSdkExcludedPropertiesConverterFactory.cs index 766cefa4..046c2a50 100644 --- a/src/PinguApps.Appwrite.Shared/Converters/IgnoreSdkExcludedPropertiesConverterFactory.cs +++ b/src/PinguApps.Appwrite.Shared/Converters/IgnoreSdkExcludedPropertiesConverterFactory.cs @@ -86,7 +86,11 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions var propValue = prop.GetValue(value); - if (propValue is null && options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull) + // Check for JsonIgnoreAttribute with Never condition + var jsonIgnoreAttr = prop.GetCustomAttribute(); + var shouldIncludeNull = jsonIgnoreAttr?.Condition == JsonIgnoreCondition.Never; + + if (propValue is null && !shouldIncludeNull && options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull) continue; writer.WritePropertyName(jsonPropertyName); diff --git a/src/PinguApps.Appwrite.Shared/Converters/MultiFormatDateTimeConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/MultiFormatDateTimeConverter.cs index 83f2cfca..a6e2c04f 100644 --- a/src/PinguApps.Appwrite.Shared/Converters/MultiFormatDateTimeConverter.cs +++ b/src/PinguApps.Appwrite.Shared/Converters/MultiFormatDateTimeConverter.cs @@ -11,7 +11,7 @@ namespace PinguApps.Appwrite.Shared.Converters; public class MultiFormatDateTimeConverter : JsonConverter { private readonly string[] _formats = [ - "yyyy-MM-ddTHH:mm:ss.fffK", + "yyyy-MM-ddTHH:mm:ss.fffzzz", "yyyy-MM-dd HH:mm:ss.fff" ]; @@ -35,6 +35,6 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToString(_formats[0])); // Use the first format for serialization + writer.WriteStringValue(value.ToUniversalTime().ToString(_formats[0])); // Use the first format for serialization } } diff --git a/src/PinguApps.Appwrite.Shared/Converters/NullableDateTimeConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/NullableDateTimeConverter.cs index 2719aeca..86df73fa 100644 --- a/src/PinguApps.Appwrite.Shared/Converters/NullableDateTimeConverter.cs +++ b/src/PinguApps.Appwrite.Shared/Converters/NullableDateTimeConverter.cs @@ -3,8 +3,10 @@ using System.Text.Json.Serialization; namespace PinguApps.Appwrite.Shared.Converters; -internal class NullableDateTimeConverter : JsonConverter +public class NullableDateTimeConverter : JsonConverter { + private readonly MultiFormatDateTimeConverter _dateTimeConverter = new(); + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) @@ -15,12 +17,7 @@ internal class NullableDateTimeConverter : JsonConverter return null; } - if (DateTime.TryParse(stringValue, out var dateTime)) - { - return dateTime; - } - - throw new JsonException($"Unable to parse '{stringValue}' to DateTime."); + return _dateTimeConverter.Read(ref reader, typeof(DateTime), options); } throw new JsonException("Unexpected token type."); @@ -30,7 +27,7 @@ public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerialize { if (value.HasValue) { - writer.WriteStringValue(value.Value.ToString("o")); + _dateTimeConverter.Write(writer, value.Value, options); } } } diff --git a/src/PinguApps.Appwrite.Shared/Converters/PermissionJsonConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/PermissionJsonConverter.cs new file mode 100644 index 00000000..004c1cbc --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/PermissionJsonConverter.cs @@ -0,0 +1,157 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Converters; +public class PermissionJsonConverter : JsonConverter +{ + public override Permission? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + ValidateTokenType(ref reader); + + var value = reader.GetString()!; + + var (permissionType, roleString) = ParsePermissionString(value); + var permissionBuilder = CreatePermissionBuilder(permissionType); + + return ResolveRole(permissionBuilder, roleString); + } + + private static void ValidateTokenType(ref Utf8JsonReader reader) + { + if (reader.TokenType is not JsonTokenType.String) + { + throw new JsonException("Expected string value for Permission"); + } + } + + private static (string permissionType, string roleString) ParsePermissionString(string value) + { + var openParenIndex = value.IndexOf('('); + var closeParenIndex = value.LastIndexOf(')'); + + if (openParenIndex == -1 || closeParenIndex == -1) + { + throw new JsonException("Invalid Permission format"); + } + + var permissionType = value[..openParenIndex].ToLower(); + // Remove the quotes from the role string + var roleString = value[(openParenIndex + 2)..(closeParenIndex - 1)]; + + return (permissionType, roleString); + } + + private static Permission.PermissionBuilder CreatePermissionBuilder(string permissionType) + { + return permissionType switch + { + "read" => Permission.Read(), + "write" => Permission.Write(), + "create" => Permission.Create(), + "update" => Permission.Update(), + "delete" => Permission.Delete(), + _ => throw new JsonException($"Unknown permission type: {permissionType}") + }; + } + + private static Permission ResolveRole(Permission.PermissionBuilder builder, string roleString) + { + if (IsSimpleRole(roleString, builder, out var simplePermission)) + { + return simplePermission; + } + + return ResolveComplexRole(builder, roleString); + } + + private static bool IsSimpleRole(string roleString, Permission.PermissionBuilder builder, [NotNullWhen(true)] out Permission? permission) + { + permission = roleString switch + { + "any" => builder.Any(), + "users" => builder.Users(), + "guests" => builder.Guests(), + _ => null + }; + + return permission != null; + } + + private static Permission ResolveComplexRole(Permission.PermissionBuilder builder, string roleString) + { + return roleString switch + { + var s when s.StartsWith("user:") => ParseUserRole(builder, s[5..]), + var s when s.StartsWith("users/") => ParseUsersRole(builder, s[6..]), + var s when s.StartsWith("team:") => ParseTeamRole(builder, s[5..]), + var s when s.StartsWith("member:") => builder.Member(s[7..]), + var s when s.StartsWith("label:") => builder.Label(s[6..]), + _ => throw new JsonException($"Unknown role format: {roleString}") + }; + } + + private static Permission ParseUserRole(Permission.PermissionBuilder builder, string value) + { + var parts = value.Split('/'); + return parts.Length == 2 + ? builder.User(parts[0], Enum.Parse(parts[1], true)) + : builder.User(parts[0]); + } + + private static Permission ParseUsersRole(Permission.PermissionBuilder builder, string value) + { + return builder.Users(Enum.Parse(value, true)); + } + + private static Permission ParseTeamRole(Permission.PermissionBuilder builder, string value) + { + var parts = value.Split('/'); + return parts.Length == 2 + ? builder.Team(parts[0], parts[1]) + : builder.Team(parts[0]); + } + + public override void Write(Utf8JsonWriter writer, Permission value, JsonSerializerOptions options) + { + var roleString = value.RoleType switch + { + RoleType.Any => "any", + RoleType.User => FormatUserRole(value), + RoleType.Users => FormatUsersRole(value), + RoleType.Guests => "guests", + RoleType.Team => FormatTeamRole(value), + RoleType.Member => $"member:{value.Id}", + RoleType.Label => $"label:{value.Label}", + _ => throw new JsonException($"Unknown role type: {value.RoleType}") + }; + + var permissionString = value.PermissionType.ToString().ToLower() + $"(\"{roleString}\")"; + writer.WriteStringValue(permissionString); + } + + private static string FormatUserRole(Permission permission) + { + return permission.Status.HasValue + ? $"user:{permission.Id}/{permission.Status.ToString()!.ToLower()}" + : $"user:{permission.Id}"; + } + + private static string FormatUsersRole(Permission permission) + { + return permission.Status.HasValue + ? $"users/{permission.Status.ToString()!.ToLower()}" + : "users"; + } + + private static string FormatTeamRole(Permission permission) + { + return string.IsNullOrEmpty(permission.TeamRole) + ? $"team:{permission.Id}" + : $"team:{permission.Id}/{permission.TeamRole}"; + } +} + diff --git a/src/PinguApps.Appwrite.Shared/Converters/PermissionListConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/PermissionListConverter.cs new file mode 100644 index 00000000..4db2ef6f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/PermissionListConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Converters; +public class PermissionListConverter : JsonConverter> +{ + private readonly PermissionJsonConverter _permissionConverter = new(); + + public override List? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType is not JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array"); + } + + var permissions = new List(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndArray) + { + break; + } + + var permission = _permissionConverter.Read(ref reader, typeof(Permission), options); + + if (permission is not null) + { + permissions.Add(permission); + } + } + + return permissions; + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var permission in value) + { + _permissionConverter.Write(writer, permission, options); + } + + writer.WriteEndArray(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/PermissionReadOnlyListConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/PermissionReadOnlyListConverter.cs new file mode 100644 index 00000000..e69ce127 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/PermissionReadOnlyListConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Converters; +public class PermissionReadOnlyListConverter : JsonConverter> +{ + private readonly PermissionJsonConverter _permissionConverter = new(); + + public override IReadOnlyList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType is not JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array"); + } + + var permissions = new List(); + + while (reader.Read()) + { + if (reader.TokenType is JsonTokenType.EndArray) + { + break; + } + + var permission = _permissionConverter.Read(ref reader, typeof(Permission), options); + + if (permission is not null) + { + permissions.Add(permission); + } + } + + return permissions.AsReadOnly(); + } + + public override void Write(Utf8JsonWriter writer, IReadOnlyList value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var permission in value) + { + _permissionConverter.Write(writer, permission, options); + } + + writer.WriteEndArray(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumConverter.cs new file mode 100644 index 00000000..7b45732e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Converters; +public class UpperCaseEnumConverter : JsonConverter +{ + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsEnum; + } + + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return Enum.Parse(typeToConvert, value, true); + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString().ToUpper()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumListConverter.cs b/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumListConverter.cs new file mode 100644 index 00000000..7a6371eb --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Converters/UpperCaseEnumListConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Converters; +public class UpperCaseEnumListConverter : JsonConverter> where T : Enum +{ + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType is not JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array"); + } + + var list = new List(); + reader.Read(); + + while (reader.TokenType is not JsonTokenType.EndArray) + { + var value = reader.GetString(); + list.Add((T)Enum.Parse(typeof(T), value, true)); + + reader.Read(); + } + + return list; + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var item in value) + { + writer.WriteStringValue(item.ToString().ToUpper()); + } + writer.WriteEndArray(); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/DatabaseElementStatus.cs b/src/PinguApps.Appwrite.Shared/Enums/DatabaseElementStatus.cs new file mode 100644 index 00000000..4833a2ed --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/DatabaseElementStatus.cs @@ -0,0 +1,39 @@ +using System.Runtime.Serialization; + +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// An Appwrite Status of an Attribute +/// +public enum DatabaseElementStatus +{ + /// + /// Available + /// + [EnumMember(Value = "available")] + Available, + + /// + /// Processing + /// + [EnumMember(Value = "processing")] + Processing, + + /// + /// Deleting + /// + [EnumMember(Value = "deleting")] + Deleting, + + /// + /// Stuck + /// + [EnumMember(Value = "stuck")] + Stuck, + + /// + /// Failed + /// + [EnumMember(Value = "failed")] + Failed +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/IndexType.cs b/src/PinguApps.Appwrite.Shared/Enums/IndexType.cs new file mode 100644 index 00000000..0349b501 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/IndexType.cs @@ -0,0 +1,20 @@ +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// An Appwrite IndexType enum for Collection Indexes +/// +public enum IndexType +{ + /// + /// Key + /// + Key, + /// + /// Unique + /// + Unique, + /// + /// Fulltext + /// + Fulltext +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/OnDelete.cs b/src/PinguApps.Appwrite.Shared/Enums/OnDelete.cs new file mode 100644 index 00000000..33786ae4 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/OnDelete.cs @@ -0,0 +1,27 @@ +using System.Runtime.Serialization; + +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// An Appwrite OnDelete enum for Relationship Attributes +/// +public enum OnDelete +{ + /// + /// Restrict + /// + [EnumMember(Value = "restrict")] + Restrict, + + /// + /// Cascade + /// + [EnumMember(Value = "cascade")] + Cascade, + + /// + /// Set null + /// + [EnumMember(Value = "setNull")] + SetNull +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/PermissionType.cs b/src/PinguApps.Appwrite.Shared/Enums/PermissionType.cs new file mode 100644 index 00000000..f8e5932c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/PermissionType.cs @@ -0,0 +1,24 @@ +namespace PinguApps.Appwrite.Shared.Enums; +public enum PermissionType +{ + /// + /// Access to read a resource + /// + Read, + /// + /// Alias to grant create, update, and delete access for collections and buckets and update and delete access for documents and files + /// + Write, + /// + /// Access to create new resources. Does not apply to files or documents. Applying this type of access to files or documents results in an error + /// + Create, + /// + /// Access to change a resource, but not remove or create new resources. Does not apply to functions + /// + Update, + /// + /// Access to remove a resource. Does not apply to functions + /// + Delete +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/RelationType.cs b/src/PinguApps.Appwrite.Shared/Enums/RelationType.cs new file mode 100644 index 00000000..1273820b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/RelationType.cs @@ -0,0 +1,33 @@ +using System.Runtime.Serialization; + +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// An Appwrite rerlation type for relationship attributes +/// +public enum RelationType +{ + /// + /// One to one + /// + [EnumMember(Value = "oneToOne")] + OneToOne, + + /// + /// One to many + /// + [EnumMember(Value = "oneToMany")] + OneToMany, + + /// + /// Many to one + /// + [EnumMember(Value = "manyToOne")] + ManyToOne, + + /// + /// Many to many + /// + [EnumMember(Value = "manyToMany")] + ManyToMany +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/RelationshipSide.cs b/src/PinguApps.Appwrite.Shared/Enums/RelationshipSide.cs new file mode 100644 index 00000000..7654aeaa --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/RelationshipSide.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; + +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// An Appwrite Side enum for Relationship Attributes +/// +public enum RelationshipSide +{ + /// + /// Parent + /// + [EnumMember(Value = "parent")] + Parent, + + /// + /// Child + /// + [EnumMember(Value = "child")] + Child +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/RoleStatus.cs b/src/PinguApps.Appwrite.Shared/Enums/RoleStatus.cs new file mode 100644 index 00000000..d059eb5f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/RoleStatus.cs @@ -0,0 +1,12 @@ +namespace PinguApps.Appwrite.Shared.Enums; +public enum RoleStatus +{ + /// + /// Verified users + /// + Verified, + /// + /// Unverified users + /// + Unverified +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/RoleType.cs b/src/PinguApps.Appwrite.Shared/Enums/RoleType.cs new file mode 100644 index 00000000..1c5308c2 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/RoleType.cs @@ -0,0 +1,32 @@ +namespace PinguApps.Appwrite.Shared.Enums; +public enum RoleType +{ + /// + /// Grants access to anyone + /// + Any, + /// + /// Grants access to a specific user by user ID. You can optionally pass the verified or unverified string to target specific types of users + /// + User, + /// + /// Grants access to any authenticated or anonymous user. You can optionally pass the verified or unverified string to target specific types of users + /// + Users, + /// + /// Grants access to any guest user without a session. Authenticated users don't have access to this role + /// + Guests, + /// + /// Grants access to any member who possesses a specific role in a team. To gain access to this permission, the user must be a member of the specific team and have the given role assigned to them. Team roles can be assigned when inviting a user to become a team member + /// + Team, + /// + /// Grants access to a specific member of a team. When the member is removed from the team, they will no longer have access + /// + Member, + /// + /// Grants access to all accounts with a specific label ID. Once the label is removed from the user, they will no longer have access + /// + Label +} diff --git a/src/PinguApps.Appwrite.Shared/Enums/SortDirection.cs b/src/PinguApps.Appwrite.Shared/Enums/SortDirection.cs new file mode 100644 index 00000000..38bbcd98 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Enums/SortDirection.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Enums; + +/// +/// Indicates the sort direction +/// +[JsonConverter(typeof(UpperCaseEnumConverter))] +public enum SortDirection +{ + Asc, + Desc +} diff --git a/src/PinguApps.Appwrite.Shared/PinguApps.Appwrite.Shared.csproj b/src/PinguApps.Appwrite.Shared/PinguApps.Appwrite.Shared.csproj index 5700ebe1..4dbf2dcd 100644 --- a/src/PinguApps.Appwrite.Shared/PinguApps.Appwrite.Shared.csproj +++ b/src/PinguApps.Appwrite.Shared/PinguApps.Appwrite.Shared.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/src/PinguApps.Appwrite.Shared/Requests/Account/Create2faChallengeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Account/Create2faChallengeRequest.cs index 78cdc679..43ac1d85 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/Account/Create2faChallengeRequest.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/Account/Create2faChallengeRequest.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; using PinguApps.Appwrite.Shared.Enums; using PinguApps.Appwrite.Shared.Requests.Account.Validators; @@ -18,6 +19,6 @@ public class Create2faChallengeRequest : BaseRequest /// [JsonPropertyName("factor")] - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(CamelCaseEnumConverter))] public SecondFactor Factor { get; set; } } diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateAttributeBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateAttributeBaseRequest.cs new file mode 100644 index 00000000..e24d8cae --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateAttributeBaseRequest.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request plus everything required to create an attribute (minus attribute specific properties) +/// +/// The request type +/// The request validator type +public abstract class CreateAttributeBaseRequest : DatabaseCollectionIdBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Attribute Key + /// + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// + /// Is attribute required? + /// + [JsonPropertyName("required")] + public bool Required { get; set; } + + /// + /// Is attribute an array? + /// + [JsonPropertyName("array")] + public bool Array { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateBooleanAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateBooleanAttributeRequest.cs new file mode 100644 index 00000000..5b62d34d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateBooleanAttributeRequest.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a boolean attribute +/// +public class CreateBooleanAttributeRequest : CreateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + public bool? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateCollectionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateCollectionRequest.cs new file mode 100644 index 00000000..8bbb2eeb --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateCollectionRequest.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request for creating a collection +/// +public class CreateCollectionRequest : DatabaseIdBaseRequest +{ + /// + /// Unique Id. Choose a custom ID or generate a random ID with ID.unique(). Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars + /// + [JsonPropertyName("collectionId")] + public string CollectionId { get; set; } = IdUtils.GenerateUniqueId(); + + /// + /// Collection name. Max length: 128 chars + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// An array of permissions strings. By default, no user is granted with any permissions. Learn more about permissions. + /// + [JsonPropertyName("permissions")] + [JsonConverter(typeof(PermissionListConverter))] + public List Permissions { get; set; } = []; + + /// + /// Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. Learn more about permissions. + /// + [JsonPropertyName("documentSecurity")] + public bool DocumentSecurity { get; set; } + + /// + /// Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled. + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatabaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatabaseRequest.cs new file mode 100644 index 00000000..ac3bfdf7 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatabaseRequest.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a database +/// +public class CreateDatabaseRequest : BaseRequest +{ + /// + /// Unique Id. Choose a custom ID or generate a random ID with ID.unique(). Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars + /// + [JsonPropertyName("databaseId")] + public string DatabaseId { get; set; } = IdUtils.GenerateUniqueId(); + + /// + /// Database name. Max length: 128 chars + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Is the database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatetimeAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatetimeAttributeRequest.cs new file mode 100644 index 00000000..c27ea29f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDatetimeAttributeRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a datetime attribute +/// +public class CreateDatetimeAttributeRequest : CreateAttributeBaseRequest +{ + /// + /// Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonConverter(typeof(NullableDateTimeConverter))] + public DateTime? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequest.cs new file mode 100644 index 00000000..2038f46d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequest.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a document. To instantiate, use +/// +public class CreateDocumentRequest : CreateDocumentRequest> +{ + internal CreateDocumentRequest() { } + + /// + /// Creates a new builder for creating a document request + /// + public static ICreateDocumentRequestBuilder CreateBuilder() => new CreateDocumentRequestBuilder(); +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestBuilder.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestBuilder.cs new file mode 100644 index 00000000..09a5e2ac --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestBuilder.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Utils; +using static PinguApps.Appwrite.Shared.Requests.Databases.ICreateDocumentRequestBuilder; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; +internal class CreateDocumentRequestBuilder : ICreateDocumentRequestBuilder +{ + private readonly CreateDocumentRequest _request = new(); + private readonly Dictionary _data = []; + + private static readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + private static readonly ConcurrentDictionary _propertyCache = new(); + + public ICreateDocumentRequestBuilder WithDatabaseId(string databaseId) + { + _request.DatabaseId = databaseId; + return this; + } + + public ICreateDocumentRequestBuilder WithCollectionId(string collectionId) + { + _request.CollectionId = collectionId; + return this; + } + + public ICreateDocumentRequestBuilder WithDocumentId(string documentId) + { + _request.DocumentId = documentId; + return this; + } + + public ICreateDocumentRequestBuilder WithPermissions(List permissions) + { + _request.Permissions = permissions; + return this; + } + + public ICreateDocumentRequestBuilder AddPermission(Permission permission) + { + _request.Permissions.Add(permission); + return this; + } + + public ICreateDocumentRequestBuilder AddField(string name, object? value) + { + _data[name] = value; + return this; + } + + public ICreateDocumentRequestBuilder WithData(T? data, Action? options = null) where T : class + { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + + var withDataOptions = new WithDataOptions(); + options?.Invoke(withDataOptions); + + var properties = _propertyCache.GetOrAdd(typeof(T), x => x.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + + foreach (var property in properties) + { + if (!withDataOptions.ShouldIncludeProperty(property)) + { + continue; + } + + var jsonPropertyName = GetJsonPropertyName(property); + + var value = property.GetValue(data); + if (value is not null || !withDataOptions.IgnoreNullValues) + { + AddField(jsonPropertyName, value); + } + } + + return this; + } + + [ExcludeFromCodeCoverage] + private static string GetJsonPropertyName(PropertyInfo property) + { + return property.GetCustomAttribute()?.Name + ?? _jsonOptions.PropertyNamingPolicy?.ConvertName(property.Name) + ?? property.Name; + } + + public CreateDocumentRequest Build() + { + _request.Data = _data; + return _request; + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestGeneric.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestGeneric.cs new file mode 100644 index 00000000..3eee0bbc --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateDocumentRequestGeneric.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a new document +/// +/// The type of the document data +public class CreateDocumentRequest : DatabaseCollectionIdBaseRequest, CreateDocumentRequestValidator> + where TData : class +{ + /// + /// Document ID. Choose a custom ID or generate a random ID with ID.unique(). Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars + /// + [JsonPropertyName("documentId")] + public string DocumentId { get; set; } = IdUtils.GenerateUniqueId(); + + /// + /// Document data - Provide your own type to match your database schema, or build this up using + /// + [JsonPropertyName("data")] + public TData Data { get; set; } = default!; + + /// + /// An array of permissions strings. By default, only the current user is granted all permissions. Learn more about permissions. + /// + [JsonPropertyName("permissions")] + [JsonConverter(typeof(PermissionListConverter))] + public List Permissions { get; set; } = []; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEmailAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEmailAttributeRequest.cs new file mode 100644 index 00000000..c6330eda --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEmailAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create an email attribute +/// +public class CreateEmailAttributeRequest : CreateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEnumAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEnumAttributeRequest.cs new file mode 100644 index 00000000..a1588a3c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateEnumAttributeRequest.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create an enum attribute +/// +public class CreateEnumAttributeRequest : CreateStringAttributeBaseRequest +{ + /// + /// Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of 100 elements are allowed, each 255 characters long + /// + [JsonPropertyName("elements")] + public List Elements { get; set; } = []; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateFloatAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateFloatAttributeRequest.cs new file mode 100644 index 00000000..83061828 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateFloatAttributeRequest.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a float attribute +/// +public class CreateFloatAttributeRequest : CreateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + public double? Default { get; set; } + + /// + /// Minimum value to enforce on new documents + /// + [JsonPropertyName("min")] + public double? Min { get; set; } + + /// + /// Maximum value to enforce on new documents + /// + [JsonPropertyName("max")] + public double? Max { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIPAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIPAttributeRequest.cs new file mode 100644 index 00000000..1878192e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIPAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create an IP Address attribute +/// +public class CreateIPAttributeRequest : CreateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIndexRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIndexRequest.cs new file mode 100644 index 00000000..429b5011 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIndexRequest.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create an index on a collection +/// +public class CreateIndexRequest : DatabaseCollectionIdBaseRequest +{ + /// + /// Index Key + /// + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// + /// Index type + /// + [JsonPropertyName("type")] + [JsonConverter(typeof(CamelCaseEnumConverter))] + public IndexType IndexType { get; set; } + + /// + /// Array of attributes (referenced via their Key) to index. Maximum of 100 attributes are allowed, each 32 characters long. Must match quantity of + /// + [JsonPropertyName("attributes")] + public List Attributes { get; set; } = []; + + /// + /// Array of index orders. Maximum of 100 orders are allowed. Must match quantity of + /// + [JsonPropertyName("orders")] + [JsonConverter(typeof(UpperCaseEnumListConverter))] + public List Orders { get; set; } = []; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIntegerAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIntegerAttributeRequest.cs new file mode 100644 index 00000000..e4fe056e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateIntegerAttributeRequest.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create an integer attribute +/// +public class CreateIntegerAttributeRequest : CreateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + public long? Default { get; set; } + + /// + /// Minimum value to enforce on new documents + /// + [JsonPropertyName("min")] + public long? Min { get; set; } + + /// + /// Maximum value to enforce on new documents + /// + [JsonPropertyName("max")] + public long? Max { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateRelationshipAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateRelationshipAttributeRequest.cs new file mode 100644 index 00000000..caac3b69 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateRelationshipAttributeRequest.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a realtionship attribute +/// +public class CreateRelationshipAttributeRequest : DatabaseCollectionIdBaseRequest +{ + /// + /// Related Collection ID. You can create a new collection using the Database service server integration. + /// + [JsonPropertyName("relatedCollectionId")] + public string RelatedCollectionId { get; set; } = string.Empty; + + /// + /// Relation type + /// + [JsonPropertyName("type")] + [JsonConverter(typeof(CamelCaseEnumConverter))] + public RelationType Type { get; set; } + + /// + /// Is Two Way? + /// + [JsonPropertyName("twoWay")] + public bool TwoWay { get; set; } + + /// + /// Attribute Key + /// + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// + /// Two Way Attribute Key + /// + [JsonPropertyName("twoWayKey")] + public string TwoWayKey { get; set; } = IdUtils.GenerateUniqueId(); + + /// + /// Constraints option + /// + [JsonPropertyName("onDelete")] + [JsonConverter(typeof(CamelCaseEnumConverter))] + public OnDelete OnDelete { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeBaseRequest.cs new file mode 100644 index 00000000..afad736c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeBaseRequest.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request for any string based attribute +/// +/// The request type +/// The request validator type +public abstract class CreateStringAttributeBaseRequest : CreateAttributeBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + public string? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeRequest.cs new file mode 100644 index 00000000..529e14d4 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateStringAttributeRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a string attribute +/// +public class CreateStringAttributeRequest : CreateStringAttributeBaseRequest +{ + /// + /// Attribute size for text attributes, in number of characters + /// + [JsonPropertyName("size")] + public int Size { get; set; } + + /// + /// Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried + /// + [JsonPropertyName("encrypt")] + public bool Encrypt { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateURLAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateURLAttributeRequest.cs new file mode 100644 index 00000000..78829e75 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/CreateURLAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to create a URL attribute +/// +public class CreateURLAttributeRequest : CreateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionDocumentIdBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionDocumentIdBaseRequest.cs new file mode 100644 index 00000000..bf4f77c4 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionDocumentIdBaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using FluentValidation; +using PinguApps.Appwrite.Shared.Attributes; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request but also containing DatabaseId, CollectionId and DocumentId +/// +/// The request type +/// The request validator type +public abstract class DatabaseCollectionDocumentIdBaseRequest : DatabaseCollectionIdBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Document ID + /// + [JsonPropertyName("documentId")] + [SdkExclude] + public string DocumentId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequest.cs new file mode 100644 index 00000000..08178874 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using FluentValidation; +using PinguApps.Appwrite.Shared.Attributes; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request but also containing DatabaseId, CollectionId and Key for attributes +/// +/// The request type +/// The request validator type +public abstract class DatabaseCollectionIdAttributeKeyBaseRequest : DatabaseCollectionIdBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Attribute Key + /// + [JsonPropertyName("key")] + [SdkExclude] + public string Key { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdBaseRequest.cs new file mode 100644 index 00000000..5015bcef --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdBaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using FluentValidation; +using PinguApps.Appwrite.Shared.Attributes; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request but also containing DatabaseId and CollectionId +/// +/// The request type +/// The request validator type +public abstract class DatabaseCollectionIdBaseRequest : DatabaseIdBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Collection ID + /// + [JsonPropertyName("collectionId")] + [SdkExclude] + public string CollectionId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequest.cs new file mode 100644 index 00000000..2476a5cd --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using FluentValidation; +using PinguApps.Appwrite.Shared.Attributes; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request but also containing DatabaseId, CollectionId and Key for indexes +/// +/// The request type +/// The request validator type +public abstract class DatabaseCollectionIdIndexKeyBaseRequest : DatabaseCollectionIdBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Index Key + /// + [JsonPropertyName("key")] + [SdkExclude] + public string Key { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseIdBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseIdBaseRequest.cs new file mode 100644 index 00000000..af7e639e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DatabaseIdBaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using FluentValidation; +using PinguApps.Appwrite.Shared.Attributes; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request but also containing DatabaseId +/// +/// The request type +/// The request validator type +public abstract class DatabaseIdBaseRequest : BaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Database ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string DatabaseId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteAttributeRequest.cs new file mode 100644 index 00000000..51782c94 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to delete an attribute +/// +public class DeleteAttributeRequest : DatabaseCollectionIdAttributeKeyBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteCollectionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteCollectionRequest.cs new file mode 100644 index 00000000..df46face --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteCollectionRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to delete a collection +/// +public class DeleteCollectionRequest : DatabaseCollectionIdBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDatabaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDatabaseRequest.cs new file mode 100644 index 00000000..ab0b3772 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDatabaseRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to delete a database +/// +public class DeleteDatabaseRequest : DatabaseIdBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDocumentRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDocumentRequest.cs new file mode 100644 index 00000000..089a17c7 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteDocumentRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to delete a document +/// +public class DeleteDocumentRequest : DatabaseCollectionDocumentIdBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteIndexRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteIndexRequest.cs new file mode 100644 index 00000000..8e973808 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/DeleteIndexRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to delete an index +/// +public class DeleteIndexRequest : DatabaseCollectionIdIndexKeyBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/GetAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetAttributeRequest.cs new file mode 100644 index 00000000..820e65f8 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to get an attribute +/// +public class GetAttributeRequest : DatabaseCollectionIdAttributeKeyBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/GetCollectionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetCollectionRequest.cs new file mode 100644 index 00000000..4e25af2a --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetCollectionRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to get a collection +/// +public class GetCollectionRequest : DatabaseCollectionIdBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDatabaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDatabaseRequest.cs new file mode 100644 index 00000000..f93ae85d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDatabaseRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to get a database +/// +public class GetDatabaseRequest : DatabaseIdBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDocumentRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDocumentRequest.cs new file mode 100644 index 00000000..924f2055 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetDocumentRequest.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Attributes; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to get a document +/// +public class GetDocumentRequest : DatabaseCollectionDocumentIdBaseRequest +{ + /// + /// Array of query strings generated using the Query class provided by the SDK. Learn more about queries. Maximum of 100 queries are allowed, each 4096 characters long. + /// + [JsonPropertyName("queries")] + [SdkExclude] + public List? Queries { get; set; } = null; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/GetIndexRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetIndexRequest.cs new file mode 100644 index 00000000..2454b087 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/GetIndexRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to get an index +/// +public class GetIndexRequest : DatabaseCollectionIdIndexKeyBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ICreateDocumentRequestBuilder.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ICreateDocumentRequestBuilder.cs new file mode 100644 index 00000000..e0a73873 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ICreateDocumentRequestBuilder.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// Builder interface for creating document requests +/// +public interface ICreateDocumentRequestBuilder +{ + /// + /// Sets the database identifier + /// + ICreateDocumentRequestBuilder WithDatabaseId(string databaseId); + + /// + /// Sets the collection identifier + /// + ICreateDocumentRequestBuilder WithCollectionId(string collectionId); + + /// + /// Sets the document identifier + /// + ICreateDocumentRequestBuilder WithDocumentId(string documentId); + + /// + /// Sets the document permissions + /// + ICreateDocumentRequestBuilder WithPermissions(List permissions); + + /// + /// Adds a permission for the document + /// + ICreateDocumentRequestBuilder AddPermission(Permission permission); + + /// + /// Adds a field to the document data + /// + ICreateDocumentRequestBuilder AddField(string name, object? value); + + /// + /// Builds the document request + /// + CreateDocumentRequest Build(); + + /// + /// Adds your given data do the document data + /// + /// The type of your data object + /// The data + /// Options + ICreateDocumentRequestBuilder WithData(T? data, Action? options = null) where T : class; + + /// + /// Options for adding data to the document + /// + public class WithDataOptions + { + /// + /// Whether to ignore null values + /// + public bool IgnoreNullValues { get; set; } = true; + + /// + /// A filter for properties + /// + public Func? PropertyFilter { get; set; } + + internal bool ShouldIncludeProperty(PropertyInfo property) + { + if (property.GetCustomAttribute() != null) + { + return false; + } + + return PropertyFilter?.Invoke(property) ?? true; + } + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/IUpdateDocumentRequestBuilder.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/IUpdateDocumentRequestBuilder.cs new file mode 100644 index 00000000..23133576 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/IUpdateDocumentRequestBuilder.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; +/// +/// Builder interface for creating document requests +/// +public interface IUpdateDocumentRequestBuilder +{ + /// + /// Sets the database identifier + /// + IUpdateDocumentRequestBuilder WithDatabaseId(string databaseId); + + /// + /// Sets the collection identifier + /// + IUpdateDocumentRequestBuilder WithCollectionId(string collectionId); + + /// + /// Sets the document identifier + /// + IUpdateDocumentRequestBuilder WithDocumentId(string documentId); + + /// + /// Sets the document permissions + /// + IUpdateDocumentRequestBuilder WithPermissions(List permissions); + + /// + /// Adds a permission for the document + /// + IUpdateDocumentRequestBuilder AddPermission(Permission permission); + + /// + /// Adds a field to the document data + /// + IUpdateDocumentRequestBuilder AddField(string name, object? value); + + /// + /// Builds the document request + /// + UpdateDocumentRequest Build(); + + /// + /// Compares a before and after snapshop of an object, and adds any changed values to the document data + /// + /// The model type + /// The values before any modifications + /// The values after modifications + /// + IUpdateDocumentRequestBuilder WithChanges(T before, T after) where T : class; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ListAttributesRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListAttributesRequest.cs new file mode 100644 index 00000000..094658e7 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListAttributesRequest.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Attributes; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to list attributes of a collection +/// +public class ListAttributesRequest : QueryBaseRequest +{ + /// + /// Database ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string DatabaseId { get; set; } = string.Empty; + + /// + /// Collection ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string CollectionId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ListCollectionsRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListCollectionsRequest.cs new file mode 100644 index 00000000..ff7307c2 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListCollectionsRequest.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Attributes; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to list collections in a database +/// +public class ListCollectionsRequest : QuerySearchBaseRequest +{ + /// + /// Database ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string DatabaseId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDatabasesRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDatabasesRequest.cs new file mode 100644 index 00000000..da3db278 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDatabasesRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request for listing databases +/// +public class ListDatabasesRequest : QuerySearchBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDocumentsRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDocumentsRequest.cs new file mode 100644 index 00000000..db1c4d85 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListDocumentsRequest.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Attributes; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to list documents +/// +public class ListDocumentsRequest : QueryBaseRequest +{ + /// + /// Database ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string DatabaseId { get; set; } = string.Empty; + + /// + /// Collection ID + /// + [JsonPropertyName("databaseId")] + [SdkExclude] + public string CollectionId { get; set; } = string.Empty; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/ListIndexesRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListIndexesRequest.cs new file mode 100644 index 00000000..4161d9e5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/ListIndexesRequest.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Attributes; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to list indexes of a collection +/// +public class ListIndexesRequest : DatabaseCollectionIdBaseRequest +{ + /// + /// Array of query strings generated using the Query class provided by the SDK. Learn more about queries. Maximum of 100 queries are allowed, each 4096 characters long. + /// + [JsonPropertyName("queries")] + [SdkExclude] + public List? Queries { get; set; } = null; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateAttributeBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateAttributeBaseRequest.cs new file mode 100644 index 00000000..f1f93a24 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateAttributeBaseRequest.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request plus everything required to update an attribute (minus attribute specific properties) +/// +/// The request type +/// The request validator type +public abstract class UpdateAttributeBaseRequest : DatabaseCollectionIdAttributeKeyBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Is attribute required? + /// + [JsonPropertyName("required")] + public bool Required { get; set; } + + /// + /// New attribute key + /// + [JsonPropertyName("newKey")] + public string? NewKey { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateBooleanAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateBooleanAttributeRequest.cs new file mode 100644 index 00000000..500d2ec1 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateBooleanAttributeRequest.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a boolean attribute +/// +public class UpdateBooleanAttributeRequest : UpdateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public bool? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateCollectionRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateCollectionRequest.cs new file mode 100644 index 00000000..fdf57972 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateCollectionRequest.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a collection +/// +public class UpdateCollectionRequest : DatabaseCollectionIdBaseRequest +{ + /// + /// Collection name. Max length: 128 chars + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// An array of permissions strings. By default, no user is granted with any permissions. Learn more about permissions. + /// + [JsonPropertyName("permissions")] + [JsonConverter(typeof(PermissionListConverter))] + public List Permissions { get; set; } = []; + + /// + /// Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. Learn more about permissions. + /// + [JsonPropertyName("documentSecurity")] + public bool DocumentSecurity { get; set; } + + /// + /// Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled. + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatabaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatabaseRequest.cs new file mode 100644 index 00000000..27e18038 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatabaseRequest.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a database +/// +public class UpdateDatabaseRequest : DatabaseIdBaseRequest +{ + /// + /// Database name. Max length: 128 chars + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Is database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled + /// + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatetimeAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatetimeAttributeRequest.cs new file mode 100644 index 00000000..40688fe7 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDatetimeAttributeRequest.cs @@ -0,0 +1,20 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a datetime attribute +/// +public class UpdateDatetimeAttributeRequest : UpdateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [JsonConverter(typeof(AlwaysWriteNullableDateTimeConverter))] + public DateTime? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequest.cs new file mode 100644 index 00000000..ff940f18 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequest.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a document. Can only be created with +/// +public class UpdateDocumentRequest : DatabaseCollectionDocumentIdBaseRequest +{ + internal UpdateDocumentRequest() { } + + /// + /// Document data. Include only attribute and value pairs to be updated. Build this up using + /// + [JsonPropertyName("data")] + public Dictionary Data { get; set; } = []; + + /// + /// An array of permissions strings. By default, the current permissions are inherited. Learn more about permissions. + /// + [JsonPropertyName("permissions")] + [JsonConverter(typeof(PermissionListConverter))] + public List Permissions { get; set; } = []; + + /// + /// Creates a new builder for creating a document request + /// + public static IUpdateDocumentRequestBuilder CreateBuilder() => new UpdateDocumentRequestBuilder(); +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequestBuilder.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequestBuilder.cs new file mode 100644 index 00000000..f89d195d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateDocumentRequestBuilder.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +internal class UpdateDocumentRequestBuilder : IUpdateDocumentRequestBuilder +{ + private readonly UpdateDocumentRequest _request = new(); + private readonly Dictionary _data = []; + + public IUpdateDocumentRequestBuilder WithDatabaseId(string databaseId) + { + _request.DatabaseId = databaseId; + return this; + } + + public IUpdateDocumentRequestBuilder WithCollectionId(string collectionId) + { + _request.CollectionId = collectionId; + return this; + } + + public IUpdateDocumentRequestBuilder WithDocumentId(string documentId) + { + _request.DocumentId = documentId; + return this; + } + + public IUpdateDocumentRequestBuilder WithPermissions(List permissions) + { + _request.Permissions = permissions; + return this; + } + + public IUpdateDocumentRequestBuilder AddPermission(Permission permission) + { + _request.Permissions.Add(permission); + return this; + } + + public IUpdateDocumentRequestBuilder AddField(string name, object? value) + { + _data[name] = value; + return this; + } + + public IUpdateDocumentRequestBuilder WithChanges(T before, T after) where T : class + { + if (before is null) + { + throw new ArgumentNullException(nameof(before)); + } + if (after is null) + { + throw new ArgumentNullException(nameof(after)); + } + + var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in properties) + { + if (!property.CanRead) continue; + + var beforeValue = property.GetValue(before); + var afterValue = property.GetValue(after); + + if (!AreValuesEqual(beforeValue, afterValue)) + { + var jsonPropertyName = GetJsonPropertyName(property); + AddField(jsonPropertyName, afterValue); + } + } + + return this; + } + + public UpdateDocumentRequest Build() + { + _request.Data = _data; + return _request; + } + + private static string GetJsonPropertyName(PropertyInfo property) + { + var jsonPropertyAttribute = property.GetCustomAttribute(); + + return jsonPropertyAttribute?.Name ?? property.Name; + } + + private static bool AreValuesEqual(object? value1, object? value2) + { + if (ReferenceEquals(value1, value2)) + { + return true; + } + + if (value1 is null || value2 is null) + { + return false; + } + + if (value1 is IEnumerable enumerable1 && value2 is IEnumerable enumerable2) + { + return enumerable1.SequenceEqual(enumerable2); + } + + return value1.Equals(value2); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEmailAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEmailAttributeRequest.cs new file mode 100644 index 00000000..31496c9c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEmailAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update an email attribute +/// +public class UpdateEmailAttributeRequest : UpdateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEnumAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEnumAttributeRequest.cs new file mode 100644 index 00000000..96337ce3 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateEnumAttributeRequest.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update an enum attribute +/// +public class UpdateEnumAttributeRequest : UpdateStringAttributeBaseRequest +{ + /// + /// Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of 100 elements are allowed, each 255 characters long + /// + [JsonPropertyName("elements")] + public List Elements { get; set; } = []; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateFloatAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateFloatAttributeRequest.cs new file mode 100644 index 00000000..bcc7f799 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateFloatAttributeRequest.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a float attribute +/// +public class UpdateFloatAttributeRequest : UpdateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public double? Default { get; set; } + + /// + /// Minimum value to enforce on new documents + /// + [JsonPropertyName("min")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public double Min { get; set; } = double.MinValue; + + /// + /// Maximum value to enforce on new documents + /// + [JsonPropertyName("max")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public double Max { get; set; } = double.MaxValue; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIPAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIPAttributeRequest.cs new file mode 100644 index 00000000..d4d92938 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIPAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The requset to update an up address attribute +/// +public class UpdateIPAttributeRequest : UpdateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIntegerAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIntegerAttributeRequest.cs new file mode 100644 index 00000000..0e86313b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateIntegerAttributeRequest.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update integer attributes +/// +public class UpdateIntegerAttributeRequest : UpdateAttributeBaseRequest +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long? Default { get; set; } + + /// + /// Minimum value to enforce on new documents + /// + [JsonPropertyName("min")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long Min { get; set; } = long.MinValue; + + /// + /// Maximum value to enforce on new documents + /// + [JsonPropertyName("max")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long Max { get; set; } = long.MaxValue; +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateRelationshipAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateRelationshipAttributeRequest.cs new file mode 100644 index 00000000..30e26701 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateRelationshipAttributeRequest.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a relationship attribute +/// +public class UpdateRelationshipAttributeRequest : DatabaseCollectionIdAttributeKeyBaseRequest +{ + /// + /// New attribute key + /// + [JsonPropertyName("newKey")] + public string? NewKey { get; set; } + + /// + /// Constraints option + /// + [JsonPropertyName("onDelete")] + [JsonConverter(typeof(CamelCaseEnumConverter))] + public OnDelete? OnDelete { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeBaseRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeBaseRequest.cs new file mode 100644 index 00000000..8093d219 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeBaseRequest.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The base request for any string based attribute +/// +/// The request type +/// The request validator type +public abstract class UpdateStringAttributeBaseRequest : UpdateAttributeBaseRequest + where TRequest : class + where TValidator : IValidator, new() +{ + /// + /// Default value for attribute when not provided. Cannot be set when attribute is required + /// + [JsonPropertyName("default")] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string? Default { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeRequest.cs new file mode 100644 index 00000000..43335b51 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateStringAttributeRequest.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a string attribute +/// +public class UpdateStringAttributeRequest : UpdateStringAttributeBaseRequest +{ + /// + /// Attribute size for text attributes, in number of characters + /// + [JsonPropertyName("size")] + public int? Size { get; set; } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateURLAttributeRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateURLAttributeRequest.cs new file mode 100644 index 00000000..a7bdb77b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/UpdateURLAttributeRequest.cs @@ -0,0 +1,10 @@ +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases; + +/// +/// The request to update a URL attribute +/// +public class UpdateURLAttributeRequest : UpdateStringAttributeBaseRequest +{ +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateAttributeBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateAttributeBaseRequestValidator.cs new file mode 100644 index 00000000..8158299f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateAttributeBaseRequestValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateAttributeBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public CreateAttributeBaseRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Key) + .NotEmpty() + .WithMessage("Key is required."); + + RuleFor(x => x.Required) + .NotNull() + .WithMessage("Required is required."); + + RuleFor(x => x.Array) + .NotNull() + .WithMessage("Array is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateBooleanAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateBooleanAttributeRequestValidator.cs new file mode 100644 index 00000000..0942ea01 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateBooleanAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateBooleanAttributeRequestValidator : AbstractValidator +{ + public CreateBooleanAttributeRequestValidator() + { + Include(new CreateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateCollectionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateCollectionRequestValidator.cs new file mode 100644 index 00000000..14db8147 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateCollectionRequestValidator.cs @@ -0,0 +1,34 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateCollectionRequestValidator : AbstractValidator +{ + public CreateCollectionRequestValidator() + { + Include(new DatabaseIdBaseRequestValidator()); + + RuleFor(x => x.CollectionId) + .NotEmpty() + .WithMessage("CollectionId is required.") + .Matches("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$") + .WithMessage("CollectionId can only contain a-z, A-Z, 0-9, period, hyphen, and underscore, and can't start with a special character. Max length is 36 characters."); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Name is required.") + .MaximumLength(128) + .WithMessage("Name cannot exceed 128 characters."); + + RuleFor(x => x.Permissions) + .NotNull() + .WithMessage("Permissions cannot be null."); + + RuleFor(x => x.DocumentSecurity) + .NotNull() + .WithMessage("DocumentSecurity is required."); + + RuleFor(x => x.Enabled) + .NotNull() + .WithMessage("Enabled is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatabaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatabaseRequestValidator.cs new file mode 100644 index 00000000..fda45843 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatabaseRequestValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateDatabaseRequestValidator : AbstractValidator +{ + public CreateDatabaseRequestValidator() + { + RuleFor(x => x.DatabaseId) + .NotEmpty() + .WithMessage("DatabaseId is required.") + .Matches("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$") + .WithMessage("DatabaseId can only contain a-z, A-Z, 0-9, period, hyphen, and underscore, and can't start with a special character. Max length is 36 chars."); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Name is required.") + .MaximumLength(128) + .WithMessage("Name can have a maximum length of 128 characters."); + + RuleFor(x => x.Enabled) + .NotNull() + .WithMessage("Enabled is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatetimeAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatetimeAttributeRequestValidator.cs new file mode 100644 index 00000000..a66892a0 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDatetimeAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateDatetimeAttributeRequestValidator : AbstractValidator +{ + public CreateDatetimeAttributeRequestValidator() + { + Include(new CreateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue == null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDocumentRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDocumentRequestValidator.cs new file mode 100644 index 00000000..d0e97244 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateDocumentRequestValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateDocumentRequestValidator : AbstractValidator> + where TData : class +{ + public CreateDocumentRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator, CreateDocumentRequestValidator>()); + + RuleFor(x => x.DocumentId) + .NotEmpty() + .WithMessage("DocumentId is required.") + .Matches("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$") + .WithMessage("DocumentId can only contain a-z, A-Z, 0-9, period, hyphen, and underscore, and can't start with a special character. Max length is 36 characters."); + + RuleFor(x => x.Data) + .NotNull() + .WithMessage("Data is required."); + + RuleFor(x => x.Permissions) + .NotNull() + .WithMessage("Permissions cannot be null."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEmailAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEmailAttributeRequestValidator.cs new file mode 100644 index 00000000..8cfa714e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEmailAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateEmailAttributeRequestValidator : AbstractValidator +{ + public CreateEmailAttributeRequestValidator() + { + Include(new CreateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .EmailAddress() + .WithMessage("Default should be formatted as an email address"); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEnumAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEnumAttributeRequestValidator.cs new file mode 100644 index 00000000..ef769e6e --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateEnumAttributeRequestValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateEnumAttributeRequestValidator : AbstractValidator +{ + public CreateEnumAttributeRequestValidator() + { + Include(new CreateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Elements) + .NotNull() + .WithMessage("Elements is required.") + .Must(x => x.Count > 0) + .WithMessage("At least one element must be specified.") + .Must(x => x.Count <= 100) + .WithMessage("A maximum of 100 elements are allowed.") + .ForEach(x => + { + x + .NotEmpty() + .WithMessage("Element cannot be empty.") + .MaximumLength(255) + .WithMessage("Element cannot be longer than 255 characters"); + }); + + RuleFor(x => x.Default) + .Must((x, defaultValue) => defaultValue is null || x.Elements.Contains(defaultValue)) + .WithMessage("Default must either be null or match an existing element."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateFloatAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateFloatAttributeRequestValidator.cs new file mode 100644 index 00000000..5fae7361 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateFloatAttributeRequestValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateFloatAttributeRequestValidator : AbstractValidator +{ + public CreateFloatAttributeRequestValidator() + { + Include(new CreateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required.") + .Must((request, defaultValue) => (request.Min is null || defaultValue is null) || defaultValue >= request.Min) + .WithMessage("Default cannot be a smaller value than Min.") + .Must((request, defaultValue) => (request.Max is null || defaultValue is null) || defaultValue <= request.Max) + .WithMessage("Default cannot be a larger value than Max."); + + RuleFor(x => x.Max) + .Must((request, max) => (request.Min is null || max is null) || max >= request.Min) + .WithMessage("Max can not be a lower value than Min."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIPAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIPAttributeRequestValidator.cs new file mode 100644 index 00000000..b36dc73d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIPAttributeRequestValidator.cs @@ -0,0 +1,35 @@ +using System.Net; +using System.Net.Sockets; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateIPAttributeRequestValidator : AbstractValidator +{ + public CreateIPAttributeRequestValidator() + { + Include(new CreateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .NotEmpty() + .When(x => x.Default is not null, ApplyConditionTo.CurrentValidator) + .WithMessage("Default must not be an empty string.") + .Must(BeValidIpAddress) + .WithMessage("Default is not a valid IP Address format."); + } + + private bool BeValidIpAddress(string? ipAddress) + { + if (string.IsNullOrEmpty(ipAddress)) + return true; + + // Try parsing as IP address (supports both IPv4 and IPv6) + if (IPAddress.TryParse(ipAddress, out var parsedIp)) + { + // Accept both IPv4 and IPv6 + return parsedIp.AddressFamily == AddressFamily.InterNetwork || + parsedIp.AddressFamily == AddressFamily.InterNetworkV6; + } + + return false; + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIndexRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIndexRequestValidator.cs new file mode 100644 index 00000000..2ef1694b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIndexRequestValidator.cs @@ -0,0 +1,33 @@ +using System.Linq; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateIndexRequestValidator : AbstractValidator +{ + public CreateIndexRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Key) + .NotEmpty() + .WithMessage("Key is required"); + + RuleFor(x => x.IndexType) + .IsInEnum() + .WithMessage("IndexType must be within the enums range"); + + RuleFor(x => x.Attributes) + .NotNull() + .Must(x => x.Count <= 100) + .WithMessage("Maximum of 100 attributes are allowed") + .Must(x => x.All(attr => attr.Length <= 32)) + .WithMessage("Each attribute must be 32 characters or less"); + + RuleFor(x => x.Orders) + .NotNull() + .Must(x => x.Count <= 100) + .WithMessage("Maximum of 100 orders are allowed") + .Must((model, orders) => orders.Count == model.Attributes.Count) + .WithMessage("Number of orders must match number of attributes"); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIntegerAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIntegerAttributeRequestValidator.cs new file mode 100644 index 00000000..d957b02f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateIntegerAttributeRequestValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateIntegerAttributeRequestValidator : AbstractValidator +{ + public CreateIntegerAttributeRequestValidator() + { + Include(new CreateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required.") + .Must((request, defaultValue) => (request.Min is null || defaultValue is null) || defaultValue >= request.Min) + .WithMessage("Default cannot be a smaller value than Min.") + .Must((request, defaultValue) => (request.Max is null || defaultValue is null) || defaultValue <= request.Max) + .WithMessage("Default cannot be a larger value than Max."); + + RuleFor(x => x.Max) + .Must((request, max) => (request.Min is null || max is null) || max >= request.Min) + .WithMessage("Max can not be a lower value than Min."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateRelationshipAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateRelationshipAttributeRequestValidator.cs new file mode 100644 index 00000000..a437cf4f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateRelationshipAttributeRequestValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateRelationshipAttributeRequestValidator : AbstractValidator +{ + public CreateRelationshipAttributeRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.RelatedCollectionId) + .NotEmpty() + .WithMessage("RelatedCollectionId is required."); + + RuleFor(x => x.Type) + .IsInEnum() + .WithMessage("Type must be a valid RelationType."); + + RuleFor(x => x.Key) + .NotEmpty() + .WithMessage("Key is required."); + + RuleFor(x => x.TwoWayKey) + .NotEmpty() + .WithMessage("TwoWayKey is required."); + + RuleFor(x => x.OnDelete) + .IsInEnum() + .WithMessage("OnDelete must be a valid OnDelete value."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeBaseRequestValidator.cs new file mode 100644 index 00000000..7e30d2c2 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateStringAttributeBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public CreateStringAttributeBaseRequestValidator() + { + Include(new CreateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeRequestValidator.cs new file mode 100644 index 00000000..5a021cb2 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateStringAttributeRequestValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateStringAttributeRequestValidator : AbstractValidator +{ + public CreateStringAttributeRequestValidator() + { + Include(new CreateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Size) + .GreaterThan(0) + .WithMessage("Size should be greater than 0") + .LessThan(1_073_741_825) + .WithMessage("Size should be less than 1,073,741,825"); + + RuleFor(x => x.Encrypt) + .NotNull() + .WithMessage("Encrypt should not be null"); + + RuleFor(x => x.Default) + .Length(x => 0, x => x.Size) + .When(x => x.Default is not null) + .WithMessage("Default should be between {MinLength} and {MaxLength} characters long"); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateURLAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateURLAttributeRequestValidator.cs new file mode 100644 index 00000000..d237aa5c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/CreateURLAttributeRequestValidator.cs @@ -0,0 +1,16 @@ +using System; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class CreateURLAttributeRequestValidator : AbstractValidator +{ + public CreateURLAttributeRequestValidator() + { + Include(new CreateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must(x => Uri.TryCreate(x, UriKind.Absolute, out _)) + .WithMessage("Default should be formatted as a URL") + .When(x => x.Default is not null); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionDocumentIdBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionDocumentIdBaseRequestValidator.cs new file mode 100644 index 00000000..015fb9e0 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionDocumentIdBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DatabaseCollectionDocumentIdBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public DatabaseCollectionDocumentIdBaseRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.DocumentId) + .NotEmpty() + .WithMessage("DocumentId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdAttributeKeyBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdAttributeKeyBaseRequestValidator.cs new file mode 100644 index 00000000..03b53d62 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdAttributeKeyBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DatabaseCollectionIdAttributeKeyBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public DatabaseCollectionIdAttributeKeyBaseRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Key) + .NotEmpty() + .WithMessage("Key is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdBaseRequestValidator.cs new file mode 100644 index 00000000..c2f6e249 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DatabaseCollectionIdBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public DatabaseCollectionIdBaseRequestValidator() + { + Include(new DatabaseIdBaseRequestValidator()); + + RuleFor(x => x.CollectionId) + .NotEmpty() + .WithMessage("CollectionId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdIndexKeyBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdIndexKeyBaseRequestValidator.cs new file mode 100644 index 00000000..c2572b79 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseCollectionIdIndexKeyBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DatabaseCollectionIdIndexKeyBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public DatabaseCollectionIdIndexKeyBaseRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Key) + .NotEmpty() + .WithMessage("Key is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseIdBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseIdBaseRequestValidator.cs new file mode 100644 index 00000000..04435525 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DatabaseIdBaseRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DatabaseIdBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public DatabaseIdBaseRequestValidator() + { + RuleFor(x => x.DatabaseId) + .NotEmpty() + .WithMessage("DatabaseId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteAttributeRequestValidator.cs new file mode 100644 index 00000000..7aa373e3 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteAttributeRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DeleteAttributeRequestValidator : AbstractValidator +{ + public DeleteAttributeRequestValidator() + { + Include(new DatabaseCollectionIdAttributeKeyBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteCollectionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteCollectionRequestValidator.cs new file mode 100644 index 00000000..1626b62a --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteCollectionRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DeleteCollectionRequestValidator : AbstractValidator +{ + public DeleteCollectionRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDatabaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDatabaseRequestValidator.cs new file mode 100644 index 00000000..9573792d --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDatabaseRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DeleteDatabaseRequestValidator : AbstractValidator +{ + public DeleteDatabaseRequestValidator() + { + Include(new DatabaseIdBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDocumentRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDocumentRequestValidator.cs new file mode 100644 index 00000000..88bcb83f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteDocumentRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DeleteDocumentRequestValidator : AbstractValidator +{ + public DeleteDocumentRequestValidator() + { + Include(new DatabaseCollectionDocumentIdBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteIndexRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteIndexRequestValidator.cs new file mode 100644 index 00000000..e438fe9b --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/DeleteIndexRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class DeleteIndexRequestValidator : AbstractValidator +{ + public DeleteIndexRequestValidator() + { + Include(new DatabaseCollectionIdIndexKeyBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetAttributeRequestValidator.cs new file mode 100644 index 00000000..36fcc0a3 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetAttributeRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class GetAttributeRequestValidator : AbstractValidator +{ + public GetAttributeRequestValidator() + { + Include(new DatabaseCollectionIdAttributeKeyBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetCollectionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetCollectionRequestValidator.cs new file mode 100644 index 00000000..2ee73033 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetCollectionRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class GetCollectionRequestValidator : AbstractValidator +{ + public GetCollectionRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDatabaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDatabaseRequestValidator.cs new file mode 100644 index 00000000..9c3d4cca --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDatabaseRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class GetDatabaseRequestValidator : AbstractValidator +{ + public GetDatabaseRequestValidator() + { + Include(new DatabaseIdBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDocumentRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDocumentRequestValidator.cs new file mode 100644 index 00000000..531b0a4c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetDocumentRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class GetDocumentRequestValidator : AbstractValidator +{ + public GetDocumentRequestValidator() + { + Include(new DatabaseCollectionDocumentIdBaseRequestValidator()); + + RuleFor(x => x.Queries) + .Must(queries => queries is null || queries.Count <= 100) + .WithMessage("A maximum of 100 queries are allowed.") + .ForEach(query => query.Must(q => q.GetQueryString().Length <= 4096) + .WithMessage("Each query can be a maximum of 4096 characters long.")); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetIndexRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetIndexRequestValidator.cs new file mode 100644 index 00000000..25264c83 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/GetIndexRequestValidator.cs @@ -0,0 +1,10 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class GetIndexRequestValidator : AbstractValidator +{ + public GetIndexRequestValidator() + { + Include(new DatabaseCollectionIdIndexKeyBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListAttributesRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListAttributesRequestValidator.cs new file mode 100644 index 00000000..bcaa0520 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListAttributesRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class ListAttributesRequestValidator : AbstractValidator +{ + public ListAttributesRequestValidator() + { + Include(new QueryBaseRequestValidator()); + + RuleFor(x => x.DatabaseId) + .NotEmpty() + .WithMessage("DatabaseId is required."); + + RuleFor(x => x.CollectionId) + .NotEmpty() + .WithMessage("CollectionId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListCollectionsRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListCollectionsRequestValidator.cs new file mode 100644 index 00000000..7d99d1b0 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListCollectionsRequestValidator.cs @@ -0,0 +1,15 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class ListCollectionsRequestValidator : AbstractValidator +{ + public ListCollectionsRequestValidator() + { + Include(new QuerySearchBaseRequestValidator()); + + RuleFor(x => x.DatabaseId) + .NotEmpty() + .WithMessage("DatabaseId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDatabasesRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDatabasesRequestValidator.cs new file mode 100644 index 00000000..2075bd56 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDatabasesRequestValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class ListDatabasesRequestValidator : AbstractValidator +{ + public ListDatabasesRequestValidator() + { + Include(new QuerySearchBaseRequestValidator()); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDocumentsRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDocumentsRequestValidator.cs new file mode 100644 index 00000000..07e71438 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListDocumentsRequestValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Validators; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class ListDocumentsRequestValidator : AbstractValidator +{ + public ListDocumentsRequestValidator() + { + Include(new QueryBaseRequestValidator()); + + RuleFor(x => x.DatabaseId) + .NotEmpty() + .WithMessage("DatabaseId is required."); + + RuleFor(x => x.CollectionId) + .NotEmpty() + .WithMessage("CollectionId is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListIndexesRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListIndexesRequestValidator.cs new file mode 100644 index 00000000..00acc4b5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/ListIndexesRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class ListIndexesRequestValidator : AbstractValidator +{ + public ListIndexesRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Queries) + .Must(queries => queries is null || queries.Count <= 100) + .WithMessage("A maximum of 100 queries are allowed.") + .ForEach(query => query.Must(q => q.GetQueryString().Length <= 4096) + .WithMessage("Each query can be a maximum of 4096 characters long.")); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateAttributeBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateAttributeBaseRequestValidator.cs new file mode 100644 index 00000000..0abe3582 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateAttributeBaseRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateAttributeBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public UpdateAttributeBaseRequestValidator() + { + Include(new DatabaseCollectionIdAttributeKeyBaseRequestValidator()); + + RuleFor(x => x.Required) + .NotNull() + .WithMessage("Required is required."); + + RuleFor(x => x.NewKey) + .NotEmpty() + .When(x => x.NewKey is not null) + .WithMessage("NewKey must either be null or a non empty string"); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateBooleanAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateBooleanAttributeRequestValidator.cs new file mode 100644 index 00000000..bf430807 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateBooleanAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateBooleanAttributeRequestValidator : AbstractValidator +{ + public UpdateBooleanAttributeRequestValidator() + { + Include(new UpdateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue == null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateCollectionRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateCollectionRequestValidator.cs new file mode 100644 index 00000000..0b6908f4 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateCollectionRequestValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateCollectionRequestValidator : AbstractValidator +{ + public UpdateCollectionRequestValidator() + { + Include(new DatabaseCollectionIdBaseRequestValidator()); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Name is required.") + .MaximumLength(128) + .WithMessage("Name cannot exceed 128 characters."); + + RuleFor(x => x.Permissions) + .NotNull() + .WithMessage("Permissions cannot be null."); + + RuleFor(x => x.DocumentSecurity) + .NotNull() + .WithMessage("DocumentSecurity is required."); + + RuleFor(x => x.Enabled) + .NotNull() + .WithMessage("Enabled is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatabaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatabaseRequestValidator.cs new file mode 100644 index 00000000..485d8132 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatabaseRequestValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateDatabaseRequestValidator : AbstractValidator +{ + public UpdateDatabaseRequestValidator() + { + Include(new DatabaseIdBaseRequestValidator()); + + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage("Name is required.") + .MaximumLength(128) + .WithMessage("Name cannot exceed 128 characters."); + + RuleFor(x => x.Enabled) + .NotNull() + .WithMessage("Enabled is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatetimeAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatetimeAttributeRequestValidator.cs new file mode 100644 index 00000000..d2c1d4da --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDatetimeAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateDatetimeAttributeRequestValidator : AbstractValidator +{ + public UpdateDatetimeAttributeRequestValidator() + { + Include(new UpdateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue == null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDocumentRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDocumentRequestValidator.cs new file mode 100644 index 00000000..bf1a5baa --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateDocumentRequestValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateDocumentRequestValidator : AbstractValidator +{ + public UpdateDocumentRequestValidator() + { + Include(new DatabaseCollectionDocumentIdBaseRequestValidator()); + + RuleFor(x => x.Data) + .NotNull() + .WithMessage("Data is required."); + + RuleFor(x => x.Permissions) + .NotNull() + .WithMessage("Permissions cannot be null."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEmailAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEmailAttributeRequestValidator.cs new file mode 100644 index 00000000..b9591e1c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEmailAttributeRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateEmailAttributeRequestValidator : AbstractValidator +{ + public UpdateEmailAttributeRequestValidator() + { + Include(new UpdateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .EmailAddress() + .WithMessage("Default should be formatted as an email address"); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEnumAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEnumAttributeRequestValidator.cs new file mode 100644 index 00000000..43c07f92 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateEnumAttributeRequestValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateEnumAttributeRequestValidator : AbstractValidator +{ + public UpdateEnumAttributeRequestValidator() + { + Include(new UpdateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Elements) + .NotNull() + .WithMessage("Elements is required.") + .Must(x => x.Count > 0) + .WithMessage("At least one element must be specified.") + .Must(x => x.Count <= 100) + .WithMessage("A maximum of 100 elements are allowed.") + .ForEach(x => + { + x + .NotEmpty() + .WithMessage("Element cannot be empty.") + .MaximumLength(255) + .WithMessage("Element cannot be longer than 255 characters"); + }); + + RuleFor(x => x.Default) + .Must((x, defaultValue) => defaultValue is null || x.Elements.Contains(defaultValue)) + .WithMessage("Default must either be null or match an existing element."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateFloatAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateFloatAttributeRequestValidator.cs new file mode 100644 index 00000000..647885a5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateFloatAttributeRequestValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateFloatAttributeRequestValidator : AbstractValidator +{ + public UpdateFloatAttributeRequestValidator() + { + Include(new UpdateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required.") + .Must((request, defaultValue) => defaultValue is null || defaultValue >= request.Min) + .WithMessage("Default cannot be a smaller value than Min.") + .Must((request, defaultValue) => defaultValue is null || defaultValue <= request.Max) + .WithMessage("Default cannot be a larger value than Max."); + + RuleFor(x => x.Max) + .Must((request, max) => max >= request.Min) + .WithMessage("Max can not be a lower value than Min."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIPAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIPAttributeRequestValidator.cs new file mode 100644 index 00000000..02ce1009 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIPAttributeRequestValidator.cs @@ -0,0 +1,35 @@ +using System.Net; +using System.Net.Sockets; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateIPAttributeRequestValidator : AbstractValidator +{ + public UpdateIPAttributeRequestValidator() + { + Include(new UpdateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .NotEmpty() + .When(x => x.Default is not null, ApplyConditionTo.CurrentValidator) + .WithMessage("Default must not be an empty string.") + .Must(BeValidIpAddress) + .WithMessage("Default is not a valid IP Address format."); + } + + private bool BeValidIpAddress(string? ipAddress) + { + if (string.IsNullOrEmpty(ipAddress)) + return true; + + // Try parsing as IP address (supports both IPv4 and IPv6) + if (IPAddress.TryParse(ipAddress, out var parsedIp)) + { + // Accept both IPv4 and IPv6 + return parsedIp.AddressFamily == AddressFamily.InterNetwork || + parsedIp.AddressFamily == AddressFamily.InterNetworkV6; + } + + return false; + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIntegerAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIntegerAttributeRequestValidator.cs new file mode 100644 index 00000000..684c14d3 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateIntegerAttributeRequestValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateIntegerAttributeRequestValidator : AbstractValidator +{ + public UpdateIntegerAttributeRequestValidator() + { + Include(new UpdateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required.") + .Must((request, defaultValue) => defaultValue is null || defaultValue >= request.Min) + .WithMessage("Default cannot be a smaller value than Min.") + .Must((request, defaultValue) => defaultValue is null || defaultValue <= request.Max) + .WithMessage("Default cannot be a larger value than Max."); + + RuleFor(x => x.Max) + .Must((request, max) => max >= request.Min) + .WithMessage("Max can not be a lower value than Min."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateRelationshipAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateRelationshipAttributeRequestValidator.cs new file mode 100644 index 00000000..753faf80 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateRelationshipAttributeRequestValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateRelationshipAttributeRequestValidator : AbstractValidator +{ + public UpdateRelationshipAttributeRequestValidator() + { + Include(new DatabaseCollectionIdAttributeKeyBaseRequestValidator()); + + RuleFor(x => x.NewKey) + .NotEmpty() + .When(x => x.NewKey is not null) + .WithMessage("NewKey must either be null or a non empty string"); + + RuleFor(x => x.OnDelete) + .IsInEnum() + .When(x => x.OnDelete is not null) + .WithMessage("OnDelete must be a valid OnDelete value."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeBaseRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeBaseRequestValidator.cs new file mode 100644 index 00000000..7ecedabd --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeBaseRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateStringAttributeBaseRequestValidator : AbstractValidator> + where TRequest : class + where TValidator : IValidator, new() +{ + public UpdateStringAttributeBaseRequestValidator() + { + Include(new UpdateAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must((request, defaultValue) => !request.Required || defaultValue is null) + .WithMessage("Default value cannot be set when attribute is required."); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeRequestValidator.cs new file mode 100644 index 00000000..7b5d1c6f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateStringAttributeRequestValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateStringAttributeRequestValidator : AbstractValidator +{ + public UpdateStringAttributeRequestValidator() + { + Include(new UpdateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Size) + .GreaterThan(0) + .WithMessage("Size should be greater than 0") + .LessThan(1_073_741_825) + .WithMessage("Size should be less than 1,073,741,825") + .When(x => x.Size is not null); + + RuleFor(x => x.Default) + .Length(x => 0, x => x.Size!.Value) + .When(x => x.Default is not null) + .WithMessage("Default should be between {MinLength} and {MaxLength} characters long") + .When(x => x.Size is not null && x.Default is not null); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateURLAttributeRequestValidator.cs b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateURLAttributeRequestValidator.cs new file mode 100644 index 00000000..7ce156b4 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Requests/Databases/Validators/UpdateURLAttributeRequestValidator.cs @@ -0,0 +1,16 @@ +using System; +using FluentValidation; + +namespace PinguApps.Appwrite.Shared.Requests.Databases.Validators; +public class UpdateURLAttributeRequestValidator : AbstractValidator +{ + public UpdateURLAttributeRequestValidator() + { + Include(new UpdateStringAttributeBaseRequestValidator()); + + RuleFor(x => x.Default) + .Must(x => Uri.TryCreate(x, UriKind.Absolute, out _)) + .WithMessage("Default should be formatted as a URL") + .When(x => x.Default is not null); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Requests/Users/CreateUserTargetRequest.cs b/src/PinguApps.Appwrite.Shared/Requests/Users/CreateUserTargetRequest.cs index b4e48df0..1c277bc8 100644 --- a/src/PinguApps.Appwrite.Shared/Requests/Users/CreateUserTargetRequest.cs +++ b/src/PinguApps.Appwrite.Shared/Requests/Users/CreateUserTargetRequest.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; using PinguApps.Appwrite.Shared.Enums; using PinguApps.Appwrite.Shared.Requests.Users.Validators; using PinguApps.Appwrite.Shared.Utils; @@ -20,7 +21,7 @@ public class CreateUserTargetRequest : UserIdBaseRequest, , /// [JsonPropertyName("providerType")] - [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonConverter(typeof(CamelCaseEnumConverter))] public TargetProviderType ProviderType { get; set; } /// diff --git a/src/PinguApps.Appwrite.Shared/Responses/Attribute.cs b/src/PinguApps.Appwrite.Shared/Responses/Attribute.cs new file mode 100644 index 00000000..d8cb83c1 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Attribute.cs @@ -0,0 +1,45 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite base Attribute object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +[JsonConverter(typeof(AttributeJsonConverter))] +public abstract record Attribute( + [property: JsonPropertyName("key")] string Key, + [property: JsonPropertyName("type")] string Type, + [property: JsonPropertyName("status"), JsonConverter(typeof(CamelCaseEnumConverter))] DatabaseElementStatus Status, + [property: JsonPropertyName("error")] string? Error, + [property: JsonPropertyName("required")] bool Required, + [property: JsonPropertyName("array")] bool Array, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt +) +{ + /// + /// Useful for iterating a mixed list of Attributes using the Visitor pattern + /// + /// Your own implementation of + public abstract void Accept(IAttributeVisitor visitor); + + /// + /// Useful for iterating a mixed list of Attributes using the Visitor pattern + /// + /// The return type + /// Your own implementation of + /// T + public abstract T Accept(IAttributeVisitor visitor); +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeBoolean.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeBoolean.cs new file mode 100644 index 00000000..f79d81f2 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeBoolean.cs @@ -0,0 +1,44 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeBoolean object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeBoolean( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("default")] bool? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeDatetime.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeDatetime.cs new file mode 100644 index 00000000..12043bac --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeDatetime.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeDatetime object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// ISO 8601 format +/// Default value for attribute when not provided. Only null is optional +public record AttributeDatetime( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("format")] string Format, + [property: JsonPropertyName("default"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeEmail.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeEmail.cs new file mode 100644 index 00000000..47a97101 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeEmail.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeEmail object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// String format +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeEmail( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("format")] string Format, + [property: JsonPropertyName("default")] string? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeEnum.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeEnum.cs new file mode 100644 index 00000000..c5b012da --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeEnum.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeEnum object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// Array of elements in enumerated type +/// String format +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeEnum( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("elements")] IReadOnlyList Elements, + [property: JsonPropertyName("format")] string Format, + [property: JsonPropertyName("default")] string? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeFloat.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeFloat.cs new file mode 100644 index 00000000..2fb3cfa5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeFloat.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeFloat object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// Minimum value to enforce for new documents +/// Maximum value to enforce for new documents +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeFloat( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("min")] double? Min, + [property: JsonPropertyName("max")] double? Max, + [property: JsonPropertyName("default")] double? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeInteger.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeInteger.cs new file mode 100644 index 00000000..e00a90db --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeInteger.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeInteger object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// Minimum value to enforce for new documents +/// Maximum value to enforce for new documents +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeInteger( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("min")] long? Min, + [property: JsonPropertyName("max")] long? Max, + [property: JsonPropertyName("default")] long? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeIp.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeIp.cs new file mode 100644 index 00000000..3f122235 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeIp.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeIp object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// String format +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeIp( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("format")] string Format, + [property: JsonPropertyName("default")] string? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeRelationship.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeRelationship.cs new file mode 100644 index 00000000..7b591748 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeRelationship.cs @@ -0,0 +1,55 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeRelationship object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// The ID of the related collection +/// The type of the relationship +/// Is the relationship two-way? +/// The key of the two-way relationship +/// How deleting the parent document will propagate to child documents +/// Whether this is the parent or child side of the relationship +public record AttributeRelationship( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("relatedCollection")] string RelatedCollection, + [property: JsonPropertyName("relationType"), JsonConverter(typeof(CamelCaseEnumConverter))] RelationType RelationType, + [property: JsonPropertyName("twoWay")] bool TwoWay, + [property: JsonPropertyName("twoWayKey")] string TwoWayKey, + [property: JsonPropertyName("onDelete"), JsonConverter(typeof(CamelCaseEnumConverter))] OnDelete OnDelete, + [property: JsonPropertyName("side"), JsonConverter(typeof(CamelCaseEnumConverter))] RelationshipSide Side +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeString.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeString.cs new file mode 100644 index 00000000..a352ee25 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeString.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeString object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// Attribute size +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeString( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("size")] int Size, + [property: JsonPropertyName("default")] string? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributeUrl.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributeUrl.cs new file mode 100644 index 00000000..60d2d085 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributeUrl.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite AttributeUrl object +/// +/// Attribute Key +/// Attribute type +/// Attribute status +/// Error message. Displays error generated on failure of creating or deleting an attribute +/// Is attribute required? +/// Is attribute an array? +/// Attribute creation date in ISO 8601 format +/// Attribute update date in ISO 8601 format +/// String format +/// Default value for attribute when not provided. Cannot be set when attribute is required +public record AttributeUrl( + string Key, + string Type, + DatabaseElementStatus Status, + string? Error, + bool Required, + bool Array, + DateTime CreatedAt, + DateTime UpdatedAt, + + [property: JsonPropertyName("format")] string Format, + [property: JsonPropertyName("default")] string? Default +) : Attribute(Key, Type, Status, Error, Required, Array, CreatedAt, UpdatedAt) +{ + /// + public override void Accept(IAttributeVisitor visitor) + { + visitor.Visit(this); + } + + /// + public override T Accept(IAttributeVisitor visitor) + { + return visitor.Visit(this); + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/AttributesList.cs b/src/PinguApps.Appwrite.Shared/Responses/AttributesList.cs new file mode 100644 index 00000000..ce621dd6 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/AttributesList.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Responses; +public record AttributesList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("attributes"), JsonConverter(typeof(AttributeListJsonConverter))] IReadOnlyList Attributes +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Collection.cs b/src/PinguApps.Appwrite.Shared/Responses/Collection.cs new file mode 100644 index 00000000..5e2d0b3c --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Collection.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Collection object +/// +/// Collection ID +/// Collection creation date in ISO 8601 format +/// Collection update date in ISO 8601 format +/// Collection permissions. Learn more about permissions +/// Database ID +/// Collection name +/// Collection enabled. Can be 'enabled' or 'disabled'. When disabled, the collection is inaccessible to users, but remains accessible to Server SDKs using API keys +/// Whether document-level permissions are enabled. Learn more about permissions +/// Collection attributes. Can be one of: , , , , , , , , , +/// Collection indexes +public record Collection( + [property: JsonPropertyName("$id")] string Id, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, + [property: JsonPropertyName("$permissions"), JsonConverter(typeof(PermissionReadOnlyListConverter))] IReadOnlyList Permissions, + [property: JsonPropertyName("databaseId")] string DatabaseId, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("enabled")] bool Enabled, + [property: JsonPropertyName("documentSecurity")] bool DocumentSecurity, + [property: JsonPropertyName("attributes"), JsonConverter(typeof(AttributeListJsonConverter))] IReadOnlyList Attributes, + [property: JsonPropertyName("indexes")] IReadOnlyList Indexes +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/CollectionsList.cs b/src/PinguApps.Appwrite.Shared/Responses/CollectionsList.cs new file mode 100644 index 00000000..baf8f1b3 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/CollectionsList.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite CollectionsList object +/// +/// Total number of collections documents that matched your query +/// List of collections +public record CollectionsList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("collections")] IReadOnlyList Collections +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Database.cs b/src/PinguApps.Appwrite.Shared/Responses/Database.cs new file mode 100644 index 00000000..8a8dc752 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Database.cs @@ -0,0 +1,21 @@ +using System; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Database object +/// +/// Database ID +/// Database Name +/// Database creation date in ISO 8601 format +/// Database update date in ISO 8601 format +/// If database is enabled. Can be 'enabled' or 'disabled'. When disabled, the database is inaccessible to users, but remains accessible to Server SDKs using API keys +public record Database( + [property: JsonPropertyName("$id")] string Id, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, + [property: JsonPropertyName("enabled")] bool Enabled +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/DatabasesList.cs b/src/PinguApps.Appwrite.Shared/Responses/DatabasesList.cs new file mode 100644 index 00000000..ed18fca5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/DatabasesList.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Databases List object +/// +/// Total number of databases documents that matched your query +/// List of databases. Can be one of: +public record DatabasesList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("databases")] IReadOnlyList Databases +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Document.cs b/src/PinguApps.Appwrite.Shared/Responses/Document.cs new file mode 100644 index 00000000..ad18c6c5 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Document.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Document object +/// +/// Document ID +/// Collection ID +/// Database ID +/// Document creation date in ISO 8601 format +/// Document update date in ISO 8601 format +/// Document permissions. Learn more about permissions +/// Document data +[JsonConverter(typeof(DocumentConverter))] +public record Document( + string Id, + string CollectionId, + string DatabaseId, + DateTime? CreatedAt, + DateTime? UpdatedAt, + IReadOnlyList Permissions, + [property: JsonExtensionData] Dictionary Data +) : DocumentBase(Id, CollectionId, DatabaseId, CreatedAt, UpdatedAt, Permissions) +{ + /// + /// Extract document data by key + /// + /// The attribute key + /// The attribute + public object? this[string key] => Data.ContainsKey(key) ? Data[key] : null; + + /// + /// Get the value of a given attribute + /// + /// The type of the attribute value + /// The attribute key + /// The attribute value + public T GetValue(string key) + { + if (Data.TryGetValue(key, out var value)) + { + if (value is T tValue) + { + return tValue; + } + + throw new InvalidCastException($"Value for '{key}' is not of type {typeof(T)}."); + } + throw new KeyNotFoundException($"Key '{key}' not found."); + } + + // TODO: Write tests for this method + /// + /// Try get the value of a given attribute + /// + /// The value type + /// The attribute key + /// The value of this attribute + /// Whether the attribute value could be successfully retrieved or not + public bool TryGetValue(string key, [NotNullWhen(true)] out T? value) + { + value = default; + + if (Data.TryGetValue(key, out var storedValue)) + { + if (storedValue is T tValue) + { + value = tValue; + return true; + } + } + + return false; + } +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/DocumentBase.cs b/src/PinguApps.Appwrite.Shared/Responses/DocumentBase.cs new file mode 100644 index 00000000..d48a5395 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/DocumentBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Document object +/// +/// Document ID +/// Collection ID +/// Database ID +/// Document creation date in ISO 8601 format +/// Document update date in ISO 8601 format +/// Document permissions. Learn more about permissions +[JsonConverter(typeof(DocumentConverter))] +public abstract record DocumentBase( + [property: JsonPropertyName("$id")] string Id, + [property: JsonPropertyName("$collectionId")] string CollectionId, + [property: JsonPropertyName("$databaseId")] string DatabaseId, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? UpdatedAt, + [property: JsonPropertyName("$permissions"), JsonConverter(typeof(PermissionReadOnlyListConverter))] IReadOnlyList Permissions +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/DocumentGeneric.cs b/src/PinguApps.Appwrite.Shared/Responses/DocumentGeneric.cs new file mode 100644 index 00000000..09dbe6be --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/DocumentGeneric.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Document object +/// +/// Document ID +/// Collection ID +/// Database ID +/// Document creation date in ISO 8601 format +/// Document update date in ISO 8601 format +/// Document permissions. Learn more about permissions +/// Document data +[JsonConverter(typeof(DocumentGenericConverterFactory))] +public record Document( + string Id, + string CollectionId, + string DatabaseId, + DateTime? CreatedAt, + DateTime? UpdatedAt, + IReadOnlyList Permissions, + TData Data +) : DocumentBase(Id, CollectionId, DatabaseId, CreatedAt, UpdatedAt, Permissions) + where TData : class, new(); diff --git a/src/PinguApps.Appwrite.Shared/Responses/DocumentsList.cs b/src/PinguApps.Appwrite.Shared/Responses/DocumentsList.cs new file mode 100644 index 00000000..bd1afea9 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/DocumentsList.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite DocumentsList attribute +/// +/// Total number of documents documents that matched your query +/// List of documents +public record DocumentsList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("documents"), JsonConverter(typeof(DocumentListConverter))] IReadOnlyList Documents +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/IdentityModel.cs b/src/PinguApps.Appwrite.Shared/Responses/IdentityModel.cs index e39ad3f3..21a89cf8 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/IdentityModel.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/IdentityModel.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; namespace PinguApps.Appwrite.Shared.Responses; @@ -18,13 +19,13 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Identity Provider Refresh Token public record IdentityModel( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, [property: JsonPropertyName("userId")] string UserId, [property: JsonPropertyName("provider")] string Provider, [property: JsonPropertyName("providerUid")] string ProviderUid, [property: JsonPropertyName("providerEmail")] string ProviderEmail, [property: JsonPropertyName("providerAccessToken")] string ProviderAccessToken, - [property: JsonPropertyName("providerAccessTokenExpiry")] DateTime ProviderAccessTokenExpiry, + [property: JsonPropertyName("providerAccessTokenExpiry"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime ProviderAccessTokenExpiry, [property: JsonPropertyName("providerRefreshToken")] string ProviderRefreshToken ); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Index.cs b/src/PinguApps.Appwrite.Shared/Responses/Index.cs new file mode 100644 index 00000000..166820b6 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Index.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Index object +/// +/// Index Key +/// Index Type +/// Index status. Possible values: available, processing, deleting, stuck, or failed +/// Error message. Displays error generated on failure of creating or deleting an index +/// Index attributes +/// Index orders +/// Index creation date in ISO 8601 format +/// Index update date in ISO 8601 format +public record Index( + [property: JsonPropertyName("key")] string Key, + [property: JsonPropertyName("type"), JsonConverter(typeof(CamelCaseEnumConverter))] IndexType Type, + [property: JsonPropertyName("status"), JsonConverter(typeof(CamelCaseEnumConverter))] DatabaseElementStatus Status, + [property: JsonPropertyName("error")] string? Error, + [property: JsonPropertyName("attributes")] IReadOnlyList Attributes, + [property: JsonPropertyName("orders")] IReadOnlyList Orders, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/IndexesList.cs b/src/PinguApps.Appwrite.Shared/Responses/IndexesList.cs new file mode 100644 index 00000000..a683b802 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/IndexesList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; +public record IndexesList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("indexes")] IReadOnlyList Indexes +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitor.cs b/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitor.cs new file mode 100644 index 00000000..dfa3c86a --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitor.cs @@ -0,0 +1,14 @@ +namespace PinguApps.Appwrite.Shared.Responses.Interfaces; +public interface IAttributeVisitor +{ + void Visit(AttributeBoolean attribute); + void Visit(AttributeInteger attribute); + void Visit(AttributeFloat attribute); + void Visit(AttributeString attribute); + void Visit(AttributeEmail attribute); + void Visit(AttributeUrl attribute); + void Visit(AttributeIp attribute); + void Visit(AttributeDatetime attribute); + void Visit(AttributeEnum attribute); + void Visit(AttributeRelationship attribute); +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitorGeneric.cs b/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitorGeneric.cs new file mode 100644 index 00000000..da55110f --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/Interfaces/IAttributeVisitorGeneric.cs @@ -0,0 +1,14 @@ +namespace PinguApps.Appwrite.Shared.Responses.Interfaces; +public interface IAttributeVisitor +{ + T Visit(AttributeBoolean attribute); + T Visit(AttributeInteger attribute); + T Visit(AttributeFloat attribute); + T Visit(AttributeString attribute); + T Visit(AttributeEmail attribute); + T Visit(AttributeUrl attribute); + T Visit(AttributeIp attribute); + T Visit(AttributeDatetime attribute); + T Visit(AttributeEnum attribute); + T Visit(AttributeRelationship attribute); +} diff --git a/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs b/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs index 1de05284..8102ac5e 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; namespace PinguApps.Appwrite.Shared.Responses; @@ -34,7 +35,7 @@ public record LogModel( [property: JsonPropertyName("userName")] string UserName, [property: JsonPropertyName("mode")] string Mode, [property: JsonPropertyName("ip")] string Ip, - [property: JsonPropertyName("time")] DateTime Time, + [property: JsonPropertyName("time"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime Time, [property: JsonPropertyName("osCode")] string OsCode, [property: JsonPropertyName("osName")] string OsName, [property: JsonPropertyName("osVersion")] string OsVersion, diff --git a/src/PinguApps.Appwrite.Shared/Responses/Membership.cs b/src/PinguApps.Appwrite.Shared/Responses/Membership.cs index 196d61d6..5d531410 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/Membership.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/Membership.cs @@ -23,14 +23,14 @@ namespace PinguApps.Appwrite.Shared.Responses; /// User list of roles public record Membership( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, [property: JsonPropertyName("userId")] string UserId, [property: JsonPropertyName("userName")] string UserName, [property: JsonPropertyName("userEmail")] string UserEmail, [property: JsonPropertyName("teamId")] string TeamId, [property: JsonPropertyName("teamName")] string TeamName, - [property: JsonPropertyName("invited")] DateTime Invited, + [property: JsonPropertyName("invited"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime Invited, [property: JsonPropertyName("joined"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? Joined, [property: JsonPropertyName("confirm")] bool Confirm, [property: JsonPropertyName("mfa")] bool Mfa, diff --git a/src/PinguApps.Appwrite.Shared/Responses/MfaChallenge.cs b/src/PinguApps.Appwrite.Shared/Responses/MfaChallenge.cs index 0faf14c4..be5264ba 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/MfaChallenge.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/MfaChallenge.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; namespace PinguApps.Appwrite.Shared.Responses; @@ -12,7 +13,7 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Token expiration date in ISO 8601 format public record MfaChallenge( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, [property: JsonPropertyName("userId")] string UserId, - [property: JsonPropertyName("expire")] DateTime Expire + [property: JsonPropertyName("expire"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime Expire ); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Session.cs b/src/PinguApps.Appwrite.Shared/Responses/Session.cs index d6375563..e607a9a3 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/Session.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/Session.cs @@ -39,8 +39,8 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Most recent date in ISO 8601 format when the session successfully passed MFA challenge public record Session( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, [property: JsonPropertyName("userId")] string UserId, [property: JsonPropertyName("expire"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime ExpiresAt, [property: JsonPropertyName("provider")] string Provider, diff --git a/src/PinguApps.Appwrite.Shared/Responses/Target.cs b/src/PinguApps.Appwrite.Shared/Responses/Target.cs index 05200ac5..a5847a68 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/Target.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/Target.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; using PinguApps.Appwrite.Shared.Enums; namespace PinguApps.Appwrite.Shared.Responses; @@ -16,11 +17,11 @@ namespace PinguApps.Appwrite.Shared.Responses; /// The target identifier public record Target( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("userId")] string UserId, [property: JsonPropertyName("providerId")] string? ProviderId, - [property: JsonPropertyName("providerType"), JsonConverter(typeof(JsonStringEnumConverter))] TargetProviderType ProviderType, + [property: JsonPropertyName("providerType"), JsonConverter(typeof(CamelCaseEnumConverter))] TargetProviderType ProviderType, [property: JsonPropertyName("identifier")] string Identifier ); diff --git a/src/PinguApps.Appwrite.Shared/Responses/Team.cs b/src/PinguApps.Appwrite.Shared/Responses/Team.cs index f67644e9..3bbc6e97 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/Team.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/Team.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; namespace PinguApps.Appwrite.Shared.Responses; @@ -15,8 +16,8 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Team preferences as a key-value object public record Team( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime UpdatedAt, [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("total")] int Total, [property: JsonPropertyName("prefs")] IReadOnlyDictionary Prefs diff --git a/src/PinguApps.Appwrite.Shared/Responses/Token.cs b/src/PinguApps.Appwrite.Shared/Responses/Token.cs index e1f9cf38..465231a0 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/Token.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/Token.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; namespace PinguApps.Appwrite.Shared.Responses; @@ -14,9 +15,9 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Security phrase of a token. Empty if security phrase was not requested when creating a token. It includes randomly generated phrase which is also sent in the external resource such as email public record Token( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, [property: JsonPropertyName("userId")] string UserId, [property: JsonPropertyName("secret")] string Secret, - [property: JsonPropertyName("expire")] DateTime ExpiresAt, + [property: JsonPropertyName("expire"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime ExpiresAt, [property: JsonPropertyName("phrase")] string Phrase ); diff --git a/src/PinguApps.Appwrite.Shared/Responses/User.cs b/src/PinguApps.Appwrite.Shared/Responses/User.cs index 6aa2d82d..97ebbc6c 100644 --- a/src/PinguApps.Appwrite.Shared/Responses/User.cs +++ b/src/PinguApps.Appwrite.Shared/Responses/User.cs @@ -30,13 +30,13 @@ namespace PinguApps.Appwrite.Shared.Responses; /// Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours public record User( [property: JsonPropertyName("$id")] string Id, - [property: JsonPropertyName("$createdAt")] DateTime CreatedAt, - [property: JsonPropertyName("$updatedAt")] DateTime UpdatedAt, + [property: JsonPropertyName("$createdAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime CreatedAt, + [property: JsonPropertyName("$updatedAt"), JsonConverter(typeof(MultiFormatDateTimeConverter))] 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("registration"), JsonConverter(typeof(MultiFormatDateTimeConverter))] DateTime Registration, [property: JsonPropertyName("status")] bool Status, [property: JsonPropertyName("labels")] IReadOnlyList Labels, [property: JsonPropertyName("passwordUpdate"), JsonConverter(typeof(NullableDateTimeConverter))] DateTime? PasswordUpdate, diff --git a/src/PinguApps.Appwrite.Shared/Utils/Permission.cs b/src/PinguApps.Appwrite.Shared/Utils/Permission.cs new file mode 100644 index 00000000..17f11b86 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Utils/Permission.cs @@ -0,0 +1,139 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; + +namespace PinguApps.Appwrite.Shared.Utils; + +/// +/// Helper class to create permission definitions +/// +[JsonConverter(typeof(PermissionJsonConverter))] +public class Permission +{ + /// + /// The type of the permission - What access is being granted to the role(s) + /// + public PermissionType PermissionType { get; private set; } + + /// + /// The type of role + /// + public RoleType RoleType { get; private set; } + + /// + /// The Id of the team, user or member + /// + public string? Id { get; private set; } + + /// + /// The verification status of a user group + /// + public RoleStatus? Status { get; private set; } + + /// + /// The role of team members + /// + public string? TeamRole { get; private set; } + + /// + /// The user label + /// + public string? Label { get; private set; } + + // Private constructor - can only be created through the builder + private Permission(PermissionType permissionType, RoleType roleType, string? id = null, RoleStatus? status = null, + string? teamRole = null, string? label = null) + { + PermissionType = permissionType; + RoleType = roleType; + Id = id; + Status = status; + TeamRole = teamRole; + Label = label; + } + + /// + /// Access to read a resource + /// + public static PermissionBuilder Read() => new(PermissionType.Read); + + /// + /// Alias to grant create, update, and delete access for collections and buckets and update and delete access for documents and files + /// + public static PermissionBuilder Write() => new(PermissionType.Write); + + /// + /// Access to create new resources. Does not apply to files or documents. Applying this type of access to files or documents results in an error + /// + public static PermissionBuilder Create() => new(PermissionType.Create); + + /// + /// Access to change a resource, but not remove or create new resources. Does not apply to functions + /// + public static PermissionBuilder Update() => new(PermissionType.Update); + + /// + /// Access to remove a resource. Does not apply to functions + /// + public static PermissionBuilder Delete() => new(PermissionType.Delete); + + public class PermissionBuilder + { + private readonly PermissionType _permissionType; + + internal PermissionBuilder(PermissionType permissionType) + { + _permissionType = permissionType; + } + + /// + /// Grants access to anyone + /// + public Permission Any() => new(_permissionType, RoleType.Any); + + /// + /// Grants access to any authenticated or anonymous user + /// + public Permission Users() => new(_permissionType, RoleType.Users); + + /// + /// Grants access to any authenticated or anonymous user. You can optionally pass the verified or unverified string to target specific types of users + /// + public Permission Users(RoleStatus status) => new(_permissionType, RoleType.Users, status: status); + + /// + /// Grants access to a specific user by user ID + /// + public Permission User(string userId) => new(_permissionType, RoleType.User, id: userId); + + /// + /// Grants access to a specific user by user ID. You can optionally pass the verified or unverified string to target specific types of users + /// + public Permission User(string userId, RoleStatus status) => new(_permissionType, RoleType.User, id: userId, status: status); + + /// + /// Grants access to any guest user without a session. Authenticated users don't have access to this role + /// + public Permission Guests() => new(_permissionType, RoleType.Guests); + + /// + /// Grants access to any member of the specific team. To gain access to this permission, the user must be the team creator (owner), or receive and accept an invitation to join this team + /// + public Permission Team(string teamId) => new(_permissionType, RoleType.Team, id: teamId); + + /// + /// Grants access to any member who possesses a specific role in a team. To gain access to this permission, the user must be a member of the specific team and have the given role assigned to them. Team roles can be assigned when inviting a user to become a team member + /// + public Permission Team(string teamId, string teamRole) => new(_permissionType, RoleType.Team, id: teamId, teamRole: teamRole); + + /// + /// Grants access to a specific member of a team. When the member is removed from the team, they will no longer have access + /// + public Permission Member(string memberId) => new(_permissionType, RoleType.Member, id: memberId); + + /// + /// Grants access to all accounts with a specific label ID. Once the label is removed from the user, they will no longer have access. Learn more about labels. + /// + public Permission Label(string label) => new(_permissionType, RoleType.Label, label: label); + } +} diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs index 6b41629f..1221fce2 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.cs @@ -46,9 +46,7 @@ public void SetSession_UpdatesSession() // Arrange var sc = new ServiceCollection(); var mockAccountApi = new Mock(); - sc.AddSingleton(mockAccountApi.Object); - var sp = sc.BuildServiceProvider(); - var accountClient = new AccountClient(sp, new Config(TestConstants.Endpoint, TestConstants.ProjectId)); + var accountClient = new AccountClient(mockAccountApi.Object, new Config(TestConstants.Endpoint, TestConstants.ProjectId)); var sessionAware = accountClient as ISessionAware; // Act diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs index 780e65c0..af231641 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/AppwriteClientTests.cs @@ -11,13 +11,15 @@ public void Constructor_SetsAccountClient() // Arrange var mockAccountClient = new Mock(); var mockTeamsClient = new Mock(); + var mockDatabasesClient = new Mock(); // Act - var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object); + var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object, mockDatabasesClient.Object); // Assert Assert.Equal(mockAccountClient.Object, appwriteClient.Account); Assert.Equal(mockTeamsClient.Object, appwriteClient.Teams); + Assert.Equal(mockDatabasesClient.Object, appwriteClient.Databases); } [Fact] @@ -26,7 +28,8 @@ public void Session_InitiallyNull_ReturnsNull() // Arrange var mockAccountClient = new Mock(); var mockTeamsClient = new Mock(); - var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object); + var mockDatabasesClient = new Mock(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object, mockDatabasesClient.Object); // Act var session = appwriteClient.Session; @@ -41,9 +44,11 @@ public void SetSession_UpdatesSession() // Arrange var mockAccountClient = new Mock(); var mockTeamsClient = new Mock(); + var mockDatabasesClient = new Mock(); mockAccountClient.As(); mockTeamsClient.As(); - var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object); + mockDatabasesClient.As(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object, mockDatabasesClient.Object); // Act appwriteClient.SetSession(TestConstants.Session); @@ -58,9 +63,11 @@ public void SetSession_UpdatesSessionInAccountClient() // Arrange var mockAccountClient = new Mock(); var mockTeamsClient = new Mock(); + var mockDatabasesClient = new Mock(); mockAccountClient.As(); mockTeamsClient.As(); - var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object); + mockDatabasesClient.As(); + var appwriteClient = new AppwriteClient(mockAccountClient.Object, mockTeamsClient.Object, mockDatabasesClient.Object); // Act appwriteClient.SetSession(TestConstants.Session); diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.CreateDocument.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.CreateDocument.cs new file mode 100644 index 00000000..1f44e84f --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.CreateDocument.cs @@ -0,0 +1,102 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateDocument_ShouldIncludeSessionHeaders_WhenProvided() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + _appwriteClient.SetSession(TestConstants.Session); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task CreateDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(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/Databases/DatabasesClientTests.DeleteDocument.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.DeleteDocument.cs new file mode 100644 index 00000000..ec2df9df --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.DeleteDocument.cs @@ -0,0 +1,102 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteDocument_ShouldIncludeSessionHeaders_WhenProvided() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + _appwriteClient.SetSession(TestConstants.Session); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task DeleteDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(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/Databases/DatabasesClientTests.GetDocument.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.GetDocument.cs new file mode 100644 index 00000000..29af6f14 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.GetDocument.cs @@ -0,0 +1,127 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetDocument_ShouldIncludeSessionHeaders_WhenProvided() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + _appwriteClient.SetSession(TestConstants.Session); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task GetDocument_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Select(["col1", "col2"]); + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + Queries = [query] + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetDocument(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/Databases/DatabasesClientTests.ListDocuments.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.ListDocuments.cs new file mode 100644 index 00000000..1107abe5 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.ListDocuments.cs @@ -0,0 +1,122 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListDocuments_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDocuments_ShouldIncludeSessionHeaders_WhenProvided() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders(true) + .Respond(TestConstants.AppJson, TestConstants.DocumentsListResponse); + + _appwriteClient.SetSession(TestConstants.Session); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task ListDocuments_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Limit(5); + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Queries = [query] + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.DocumentsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDocuments_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListDocuments_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(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/Databases/DatabasesClientTests.UpdateDocument.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.UpdateDocument.cs new file mode 100644 index 00000000..05e95f9a --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.UpdateDocument.cs @@ -0,0 +1,102 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateDocument_ShouldIncludeSessionHeaders_WhenProvided() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders(true) + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + _appwriteClient.SetSession(TestConstants.Session); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(request); + + // Assert + _mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task UpdateDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(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/Databases/DatabasesClientTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.cs new file mode 100644 index 00000000..c2b3d5d2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Databases/DatabasesClientTests.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Tests; +using Refit; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + private readonly MockHttpMessageHandler _mockHttp; + private readonly IAppwriteClient _appwriteClient; + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public DatabasesClientTests() + { + _mockHttp = new MockHttpMessageHandler(); + var services = new ServiceCollection(); + + services.AddAppwriteClientForServer(TestConstants.ProjectId, TestConstants.Endpoint, new RefitSettings + { + HttpMessageHandlerFactory = () => _mockHttp + }); + + var serviceProvider = services.BuildServiceProvider(); + + _appwriteClient = serviceProvider.GetRequiredService(); + + _jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + _jsonSerializerOptions.Converters.Add(new IgnoreSdkExcludedPropertiesConverterFactory()); + } +} 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 e10d1eef..14178769 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj +++ b/tests/PinguApps.Appwrite.Client.Tests/PinguApps.Appwrite.Client.Tests.csproj @@ -15,7 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/PinguApps.Appwrite.Client.Tests/test.ps1 b/tests/PinguApps.Appwrite.Client.Tests/test.ps1 index 1e7a9698..12a3f6b6 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/test.ps1 +++ b/tests/PinguApps.Appwrite.Client.Tests/test.ps1 @@ -9,7 +9,7 @@ if (-not $toolInstalled) { } # Generate the report -reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Client +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Client riskHotspotsAnalysisThresholds:metricThresholdForCyclomaticComplexity=30 # Open the generated report in the default browser Start-Process "coverage-report/index.html" diff --git a/tests/PinguApps.Appwrite.Server.Tests/Clients/AppwriteServerTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/AppwriteServerTests.cs index 326ee814..bb9f29e0 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/Clients/AppwriteServerTests.cs +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/AppwriteServerTests.cs @@ -11,11 +11,13 @@ public void Constructor_AssignsAccountServerCorrectly() var mockAccountClient = new Mock(); var mockUsersClient = new Mock(); var mockTeamsClient = new Mock(); + var mockDatabasesClient = new Mock(); // Act - var appwriteServer = new AppwriteClient(mockAccountClient.Object, mockUsersClient.Object, mockTeamsClient.Object); + var appwriteServer = new AppwriteClient(mockAccountClient.Object, mockUsersClient.Object, mockTeamsClient.Object, mockDatabasesClient.Object); // Assert Assert.Equal(mockAccountClient.Object, appwriteServer.Account); Assert.Equal(mockUsersClient.Object, appwriteServer.Users); Assert.Equal(mockTeamsClient.Object, appwriteServer.Teams); + Assert.Equal(mockDatabasesClient.Object, appwriteServer.Databases); } } diff --git a/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateBooleanAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateBooleanAttribute.cs new file mode 100644 index 00000000..01638421 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateBooleanAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateBooleanAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeBooleanResponse); + + // Act + var result = await _appwriteClient.Databases.CreateBooleanAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateBooleanAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateBooleanAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateBooleanAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateBooleanAttribute(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/Clients/Databases/DatabasesClientTests.CreateCollection.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateCollection.cs new file mode 100644 index 00000000..5fcbcf3b --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateCollection.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateCollection_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "My Collection" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.CollectionResponse); + + // Act + var result = await _appwriteClient.Databases.CreateCollection(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateCollection_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "My Collection" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateCollection(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateCollection_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "My Collection" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateCollection(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/Clients/Databases/DatabasesClientTests.CreateDatabase.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDatabase.cs new file mode 100644 index 00000000..0b53cba2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDatabase.cs @@ -0,0 +1,74 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateDatabase_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateDatabaseRequest + { + Name = "Pingu's Database" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.DatabaseResponse); + + // Act + var result = await _appwriteClient.Databases.CreateDatabase(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateDatabase_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateDatabaseRequest + { + Name = "Pingu's Database" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateDatabase(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateDatabase_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateDatabaseRequest + { + Name = "Pingu's Database" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateDatabase(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/Clients/Databases/DatabasesClientTests.CreateDatetimeAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDatetimeAttribute.cs new file mode 100644 index 00000000..95c3f667 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDatetimeAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateDatetimeAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeDatetimeResponse); + + // Act + var result = await _appwriteClient.Databases.CreateDatetimeAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateDatetimeAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateDatetimeAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateDatetimeAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateDatetimeAttribute(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/Clients/Databases/DatabasesClientTests.CreateDocument.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDocument.cs new file mode 100644 index 00000000..35cfa091 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateDocument.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .AddField("AttributeName", "MyValue") + .Build(); + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateDocument(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/Clients/Databases/DatabasesClientTests.CreateEmailAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateEmailAttribute.cs new file mode 100644 index 00000000..b7a8ec75 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateEmailAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateEmailAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "email" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeEmailResponse); + + // Act + var result = await _appwriteClient.Databases.CreateEmailAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateEmailAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "email" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateEmailAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateEmailAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "email" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateEmailAttribute(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/Clients/Databases/DatabasesClientTests.CreateEnumAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateEnumAttribute.cs new file mode 100644 index 00000000..d0e59d3e --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateEnumAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateEnumAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "enum", + Elements = ["one", "two", "three"] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeEnumResponse); + + // Act + var result = await _appwriteClient.Databases.CreateEnumAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateEnumAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "enum", + Elements = ["one", "two", "three"] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateEnumAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateEnumAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "enum", + Elements = ["one", "two", "three"] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateEnumAttribute(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/Clients/Databases/DatabasesClientTests.CreateFloatAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateFloatAttribute.cs new file mode 100644 index 00000000..e1a7a4d2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateFloatAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateFloatAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeFloatResponse); + + // Act + var result = await _appwriteClient.Databases.CreateFloatAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateFloatAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateFloatAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateFloatAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateFloatAttribute(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/Clients/Databases/DatabasesClientTests.CreateIndex.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIndex.cs new file mode 100644 index 00000000..91a582fd --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIndex.cs @@ -0,0 +1,91 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateIndex_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myIndex", + IndexType = IndexType.Unique, + Attributes = ["new_int"], + Orders = [SortDirection.Asc] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.IndexResponse); + + // Act + var result = await _appwriteClient.Databases.CreateIndex(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateIndex_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myIndex", + IndexType = IndexType.Unique, + Attributes = ["new_int"], + Orders = [SortDirection.Asc] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateIndex(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateIndex_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myIndex", + IndexType = IndexType.Unique, + Attributes = ["new_int"], + Orders = [SortDirection.Asc] + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateIndex(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/Clients/Databases/DatabasesClientTests.CreateIntegerAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIntegerAttribute.cs new file mode 100644 index 00000000..8aa904a6 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIntegerAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateIntegerAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeIntegerResponse); + + // Act + var result = await _appwriteClient.Databases.CreateIntegerAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateIntegerAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateIntegerAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateIntegerAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateIntegerAttribute(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/Clients/Databases/DatabasesClientTests.CreateIpAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIpAttribute.cs new file mode 100644 index 00000000..d2b21fb9 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateIpAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateIpAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeIpResponse); + + // Act + var result = await _appwriteClient.Databases.CreateIpAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateIpAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateIpAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateIpAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateIpAttribute(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/Clients/Databases/DatabasesClientTests.CreateRelationshipAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateRelationshipAttribute.cs new file mode 100644 index 00000000..9779c068 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateRelationshipAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateRelationshipAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + RelatedCollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeRelationshipResponse); + + // Act + var result = await _appwriteClient.Databases.CreateRelationshipAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateRelationshipAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + RelatedCollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateRelationshipAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateRelationshipAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + RelatedCollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateRelationshipAttribute(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/Clients/Databases/DatabasesClientTests.CreateStringAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateStringAttribute.cs new file mode 100644 index 00000000..b456a3cf --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateStringAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateStringAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + Size = 100 + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeStringResponse); + + // Act + var result = await _appwriteClient.Databases.CreateStringAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateStringAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + Size = 100 + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateStringAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateStringAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created", + Size = 100 + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateStringAttribute(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/Clients/Databases/DatabasesClientTests.CreateUrlAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateUrlAttribute.cs new file mode 100644 index 00000000..14d2aa97 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.CreateUrlAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task CreateUrlAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new CreateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeUrlResponse); + + // Act + var result = await _appwriteClient.Databases.CreateUrlAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task CreateUrlAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new CreateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.CreateUrlAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task CreateUrlAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new CreateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "created" + }; + + _mockHttp.Expect(HttpMethod.Post, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.CreateUrlAttribute(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/Clients/Databases/DatabasesClientTests.DeleteAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteAttribute.cs new file mode 100644 index 00000000..71a11f06 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteAttribute.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteAttribute(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/Clients/Databases/DatabasesClientTests.DeleteCollection.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteCollection.cs new file mode 100644 index 00000000..ae8da736 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteCollection.cs @@ -0,0 +1,75 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteCollection_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteCollection(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteCollection_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteCollection(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteCollection_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteCollection(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/Clients/Databases/DatabasesClientTests.DeleteDatabase.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteDatabase.cs new file mode 100644 index 00000000..16de8bf8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteDatabase.cs @@ -0,0 +1,72 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteDatabase_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteDatabase(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteDatabase_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteDatabase(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteDatabase_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteDatabase(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/Clients/Databases/DatabasesClientTests.DeleteDocument.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteDocument.cs new file mode 100644 index 00000000..916b3cf9 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteDocument.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteDocument(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/Clients/Databases/DatabasesClientTests.DeleteIndex.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteIndex.cs new file mode 100644 index 00000000..13a0fcba --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.DeleteIndex.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task DeleteIndex_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new DeleteIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.NoContent); + + // Act + var result = await _appwriteClient.Databases.DeleteIndex(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task DeleteIndex_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new DeleteIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.DeleteIndex(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task DeleteIndex_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new DeleteIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Delete, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.DeleteIndex(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/Clients/Databases/DatabasesClientTests.GetAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetAttribute.cs new file mode 100644 index 00000000..af2e7396 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetAttribute.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.AttributeResponse); + + // Act + var result = await _appwriteClient.Databases.GetAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetAttribute(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/Clients/Databases/DatabasesClientTests.GetCollection.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetCollection.cs new file mode 100644 index 00000000..f50e13d2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetCollection.cs @@ -0,0 +1,75 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetCollection_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.CollectionResponse); + + // Act + var result = await _appwriteClient.Databases.GetCollection(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetCollection_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetCollection(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetCollection_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetCollection(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/Clients/Databases/DatabasesClientTests.GetDatabase.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetDatabase.cs new file mode 100644 index 00000000..7e5a022f --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetDatabase.cs @@ -0,0 +1,72 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetDatabase_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DatabaseResponse); + + // Act + var result = await _appwriteClient.Databases.GetDatabase(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetDatabase_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetDatabase(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetDatabase_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetDatabase(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/Clients/Databases/DatabasesClientTests.GetDocument.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetDocument.cs new file mode 100644 index 00000000..4a030cf8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetDocument.cs @@ -0,0 +1,103 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetDocument_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Select(["col1", "col2"]); + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + Queries = [query] + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetDocument(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/Clients/Databases/DatabasesClientTests.GetIndex.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetIndex.cs new file mode 100644 index 00000000..383471ed --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.GetIndex.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task GetIndex_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new GetIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.IndexResponse); + + // Act + var result = await _appwriteClient.Databases.GetIndex(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task GetIndex_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new GetIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.GetIndex(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task GetIndex_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new GetIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "indexKey" + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes/{request.Key}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.GetIndex(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/Clients/Databases/DatabasesClientTests.ListAttributes.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListAttributes.cs new file mode 100644 index 00000000..fa60a459 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListAttributes.cs @@ -0,0 +1,99 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListAttributes_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListAttributesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.AttributesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListAttributes(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListAttributes_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Limit(5); + var request = new ListAttributesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Queries = new List { query } + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.AttributesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListAttributes(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListAttributes_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListAttributesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListAttributes(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListAttributes_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListAttributesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListAttributes(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/Clients/Databases/DatabasesClientTests.ListCollections.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListCollections.cs new file mode 100644 index 00000000..27410f0d --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListCollections.cs @@ -0,0 +1,97 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListCollections_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListCollectionsRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.CollectionsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListCollections(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListCollections_ShouldProvideQueryParams_WhenQueriesAndSearchProvided() + { + // Arrange + var query = Query.Limit(5); + var search = "SearchString"; + var request = new ListCollectionsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Queries = new List { query }, + Search = search + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}&search={search}") + .Respond(TestConstants.AppJson, TestConstants.CollectionsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListCollections(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListCollections_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListCollectionsRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListCollections(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListCollections_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListCollectionsRequest + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListCollections(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/Clients/Databases/DatabasesClientTests.ListDatabases.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListDatabases.cs new file mode 100644 index 00000000..67bcf65a --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListDatabases.cs @@ -0,0 +1,87 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListDatabases_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListDatabasesRequest(); + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DatabasesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDatabases(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDatabases_ShouldProvideQueryParams_WhenQueriesAndSearchProvided() + { + // Arrange + var query = Query.Limit(5); + var search = "SearchString"; + var request = new ListDatabasesRequest + { + Queries = new List { query }, + Search = search + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}&search={search}") + .Respond(TestConstants.AppJson, TestConstants.DatabasesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDatabases(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDatabases_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListDatabasesRequest(); + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListDatabases(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListDatabases_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListDatabasesRequest(); + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListDatabases(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/Clients/Databases/DatabasesClientTests.ListDocuments.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListDocuments.cs new file mode 100644 index 00000000..c7099d4f --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListDocuments.cs @@ -0,0 +1,99 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListDocuments_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDocuments_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Limit(5); + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Queries = [query] + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.DocumentsListResponse); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListDocuments_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListDocuments_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListDocuments(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/Clients/Databases/DatabasesClientTests.ListIndexes.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListIndexes.cs new file mode 100644 index 00000000..2fbef873 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.ListIndexes.cs @@ -0,0 +1,99 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task ListIndexes_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new ListIndexesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.IndexesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListIndexes(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListIndexes_ShouldProvideQueryParams_WhenQueriesProvided() + { + // Arrange + var query = Query.Limit(5); + var request = new ListIndexesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Queries = [query] + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(TestConstants.AppJson, TestConstants.IndexesListResponse); + + // Act + var result = await _appwriteClient.Databases.ListIndexes(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListIndexes_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new ListIndexesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.ListIndexes(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListIndexes_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new ListIndexesRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + _mockHttp.Expect(HttpMethod.Get, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/indexes") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.ListIndexes(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/Clients/Databases/DatabasesClientTests.UpdateBooleanAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateBooleanAttribute.cs new file mode 100644 index 00000000..b0ba2cba --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateBooleanAttribute.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateBooleanAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeBooleanResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateBooleanAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateBooleanAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateBooleanAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateBooleanAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateBooleanAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "myKey" + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/boolean/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateBooleanAttribute(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/Clients/Databases/DatabasesClientTests.UpdateCollection.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateCollection.cs new file mode 100644 index 00000000..c5061c86 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateCollection.cs @@ -0,0 +1,81 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateCollection_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.CollectionResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateCollection(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateCollection_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateCollection(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateCollection_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateCollection(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/Clients/Databases/DatabasesClientTests.UpdateDatabase.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDatabase.cs new file mode 100644 index 00000000..79a5b6d8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDatabase.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateDatabase_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.DatabaseResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateDatabase(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateDatabase_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateDatabase(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateDatabase_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "New Name" + }; + + _mockHttp.Expect(HttpMethod.Put, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateDatabase(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/Clients/Databases/DatabasesClientTests.UpdateDatetimeAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDatetimeAttribute.cs new file mode 100644 index 00000000..b8f4c021 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDatetimeAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateDatetimeAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeDatetimeResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateDatetimeAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateDatetimeAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateDatetimeAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateDatetimeAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateDatetimeAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/datetime/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateDatetimeAttribute(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/Clients/Databases/DatabasesClientTests.UpdateDocument.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDocument.cs new file mode 100644 index 00000000..7eaf2f65 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateDocument.cs @@ -0,0 +1,78 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateDocument_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(TestConstants.AppJson, TestConstants.DocumentResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateDocument_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateDocument_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("test", "test") + .Build(); + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/documents/{request.DocumentId}") + .ExpectedHeaders() + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateDocument(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/Clients/Databases/DatabasesClientTests.UpdateEmailAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateEmailAttribute.cs new file mode 100644 index 00000000..1029b6fc --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateEmailAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateEmailAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeEmailResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateEmailAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateEmailAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateEmailAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateEmailAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/email/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateEmailAttribute(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/Clients/Databases/DatabasesClientTests.UpdateEnumAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateEnumAttribute.cs new file mode 100644 index 00000000..50a7207a --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateEnumAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateEnumAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + Elements = ["element1", "element2", "element3"] + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeEnumResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateEnumAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateEnumAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + Elements = ["element1", "element2", "element3"] + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateEnumAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateEnumAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + Elements = ["element1", "element2", "element3"] + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/enum/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateEnumAttribute(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/Clients/Databases/DatabasesClientTests.UpdateFloatAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateFloatAttribute.cs new file mode 100644 index 00000000..16715f46 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateFloatAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateFloatAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeFloatResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateFloatAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateFloatAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateFloatAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateFloatAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateFloatAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/float/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateFloatAttribute(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/Clients/Databases/DatabasesClientTests.UpdateIntegerAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateIntegerAttribute.cs new file mode 100644 index 00000000..c8e3f8ef --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateIntegerAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateIntegerAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeIntegerResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateIntegerAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateIntegerAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateIntegerAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateIntegerAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateIntegerAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/integer/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateIntegerAttribute(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/Clients/Databases/DatabasesClientTests.UpdateIpAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateIpAttribute.cs new file mode 100644 index 00000000..36672906 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateIpAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateIpAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeIpResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateIpAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateIpAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateIpAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateIpAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/ip/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateIpAttribute(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/Clients/Databases/DatabasesClientTests.UpdateRelationshipAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateRelationshipAttribute.cs new file mode 100644 index 00000000..1f9becf0 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateRelationshipAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateRelationshipAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeRelationshipResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateRelationshipAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateRelationshipAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateRelationshipAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateRelationshipAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/{request.Key}/relationship") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateRelationshipAttribute(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/Clients/Databases/DatabasesClientTests.UpdateStringAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateStringAttribute.cs new file mode 100644 index 00000000..bedaeedc --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateStringAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateStringAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeStringResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateStringAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateStringAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateStringAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateStringAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/string/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateStringAttribute(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/Clients/Databases/DatabasesClientTests.UpdateUrlAttribute.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateUrlAttribute.cs new file mode 100644 index 00000000..a9738398 --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.UpdateUrlAttribute.cs @@ -0,0 +1,84 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + [Fact] + public async Task UpdateUrlAttribute_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + var request = new UpdateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(TestConstants.AppJson, TestConstants.AttributeUrlResponse); + + // Act + var result = await _appwriteClient.Databases.UpdateUrlAttribute(request); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task UpdateUrlAttribute_ShouldHandleException_WhenApiCallFails() + { + // Arrange + var request = new UpdateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Respond(HttpStatusCode.BadRequest, TestConstants.AppJson, TestConstants.AppwriteError); + + // Act + var result = await _appwriteClient.Databases.UpdateUrlAttribute(request); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task UpdateUrlAttribute_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + var request = new UpdateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "attributeKey", + // Populate other request properties as needed + }; + + _mockHttp.Expect(HttpMethod.Patch, $"{TestConstants.Endpoint}/databases/{request.DatabaseId}/collections/{request.CollectionId}/attributes/url/{request.Key}") + .ExpectedHeaders() + .WithJsonContent(request, _jsonSerializerOptions) + .Throw(new HttpRequestException("An error occurred")); + + // Act + var result = await _appwriteClient.Databases.UpdateUrlAttribute(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/Clients/Databases/DatabasesClientTests.cs b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.cs new file mode 100644 index 00000000..bbf4016e --- /dev/null +++ b/tests/PinguApps.Appwrite.Server.Tests/Clients/Databases/DatabasesClientTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.DependencyInjection; +using PinguApps.Appwrite.Server.Clients; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Tests; +using Refit; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Server.Tests.Clients.Databases; +public partial class DatabasesClientTests +{ + private readonly MockHttpMessageHandler _mockHttp; + private readonly IAppwriteClient _appwriteClient; + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public DatabasesClientTests() + { + _mockHttp = new MockHttpMessageHandler(); + var services = new ServiceCollection(); + + services.AddAppwriteServer(TestConstants.ProjectId, TestConstants.ApiKey, TestConstants.Endpoint, new RefitSettings + { + HttpMessageHandlerFactory = () => _mockHttp + }); + + var serviceProvider = services.BuildServiceProvider(); + + _appwriteClient = serviceProvider.GetRequiredService(); + + _jsonSerializerOptions = new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + _jsonSerializerOptions.Converters.Add(new IgnoreSdkExcludedPropertiesConverterFactory()); + } +} 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 33974eea..8e041aa8 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj +++ b/tests/PinguApps.Appwrite.Server.Tests/PinguApps.Appwrite.Server.Tests.csproj @@ -15,7 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/PinguApps.Appwrite.Server.Tests/test.ps1 b/tests/PinguApps.Appwrite.Server.Tests/test.ps1 index 12457080..a3db3d38 100644 --- a/tests/PinguApps.Appwrite.Server.Tests/test.ps1 +++ b/tests/PinguApps.Appwrite.Server.Tests/test.ps1 @@ -9,7 +9,7 @@ if (-not $toolInstalled) { } # Generate the report -reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Server +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Server riskHotspotsAnalysisThresholds:metricThresholdForCyclomaticComplexity=30 # Open the generated report in the default browser Start-Process "coverage-report/index.html" diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/AlwaysWriteNullableDateTimeConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AlwaysWriteNullableDateTimeConverterTests.cs new file mode 100644 index 00000000..0f28e78a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AlwaysWriteNullableDateTimeConverterTests.cs @@ -0,0 +1,94 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class AlwaysWriteNullableDateTimeConverterTests +{ + private readonly AlwaysWriteNullableDateTimeConverter _converter; + private readonly JsonSerializerOptions _options; + + public AlwaysWriteNullableDateTimeConverterTests() + { + _converter = new AlwaysWriteNullableDateTimeConverter(); + _options = new JsonSerializerOptions() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + _options.Converters.Add(_converter); + } + + [Fact] + public void Write_WhenValueIsNull_WritesNullValue() + { + // Arrange + DateTime? value = null; + var json = JsonSerializer.SerializeToUtf8Bytes(value, _options); + using var doc = JsonDocument.Parse(json); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + // Act + _converter.Write(writer, value, _options); + writer.Flush(); + + // Assert + stream.Position = 0; + using var result = JsonDocument.Parse(stream); + Assert.Equal(JsonValueKind.Null, result.RootElement.ValueKind); + } + + [Fact] + public void Write_WhenValueHasValue_CallsBaseConverter() + { + // Arrange + var testDate = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc); + DateTime? value = testDate; + + // Act + var json = JsonSerializer.Serialize(value, _options); + var deserializedValue = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(deserializedValue); + Assert.Equal(testDate, deserializedValue.Value); + } + + // Test class to verify serialization behavior in a property context + private class TestClass + { + [JsonConverter(typeof(AlwaysWriteNullableDateTimeConverter))] + public DateTime? Date { get; set; } + } + + [Fact] + public void Serialize_WhenPropertyIsNull_IncludesNullInOutput() + { + // Arrange + var testObject = new TestClass { Date = null }; + + // Act + var json = JsonSerializer.Serialize(testObject, _options); + + // Assert + Assert.Contains("\"Date\":null", json); + } + + [Fact] + public void Serialize_WhenPropertyHasValue_SerializesDateTime() + { + // Arrange + var testDate = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc); + var testObject = new TestClass { Date = testDate }; + + // Act + var json = JsonSerializer.Serialize(testObject, _options); + var deserialized = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(deserialized?.Date); + Assert.Equal(testDate, deserialized.Date.Value); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeJsonConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeJsonConverterTests.cs new file mode 100644 index 00000000..7f277a19 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeJsonConverterTests.cs @@ -0,0 +1,144 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class AttributeJsonConverterTests +{ + private readonly JsonSerializerOptions _options; + + public AttributeJsonConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new AttributeJsonConverter() } + }; + } + + [Fact] + public void Read_ShouldDeserializeBooleanAttribute() + { + var json = "{\"type\":\"boolean\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeIntegerAttribute() + { + var json = "{\"type\":\"integer\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeDoubleAttribute() + { + var json = "{\"type\":\"double\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeDatetimeAttribute() + { + var json = "{\"type\":\"datetime\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeStringAttribute() + { + var json = "{\"type\":\"string\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeEmailAttribute() + { + var json = "{\"type\":\"string\", \"format\":\"email\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeUrlAttribute() + { + var json = "{\"type\":\"string\", \"format\":\"url\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeIpAttribute() + { + var json = "{\"type\":\"string\", \"format\":\"ip\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeEnumAttribute() + { + var json = "{\"type\":\"string\", \"format\":\"enum\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldDeserializeRelationshipAttribute() + { + var json = "{\"type\":\"string\", \"relatedCollection\":\"someCollection\"}"; + var attribute = JsonSerializer.Deserialize(json, _options); + Assert.IsType(attribute); + } + + [Fact] + public void Read_ShouldThrowJsonExceptionForUnknownType() + { + var json = "{\"type\":\"unknown\"}"; + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_ShouldThrowJsonExceptionForUnknownFormat() + { + var json = "{\"type\":\"string\", \"format\":\"unknown\"}"; + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_ShouldThrowJsonExceptionForMissingType() + { + var json = "{}"; + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Write_ShouldSerializeAttribute() + { + var fixedDate = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var attribute = new AttributeBoolean("a", "boolean", DatabaseElementStatus.Available, null, false, false, fixedDate, fixedDate, null); + var converter = new AttributeJsonConverter(); + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + converter.Write(writer, attribute, _options); + writer.Flush(); + + var json = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.Contains("\"type\":\"boolean\"", json); + Assert.Contains("\"key\":\"a\"", json); + Assert.Contains("\"status\":\"available\"", json); + Assert.Contains("\"required\":false", json); + Assert.Contains("\"array\":false", json); + Assert.Contains($"\"$createdAt\":\"{fixedDate:yyyy-MM-ddTHH:mm:ss.fff}", json); + Assert.Contains($"\"$updatedAt\":\"{fixedDate:yyyy-MM-ddTHH:mm:ss.fff}", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeListJsonConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeListJsonConverterTests.cs new file mode 100644 index 00000000..3db420fa --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/AttributeListJsonConverterTests.cs @@ -0,0 +1,64 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class AttributeListJsonConverterTests +{ + private readonly JsonSerializerOptions _options; + + public AttributeListJsonConverterTests() + { + _options = new JsonSerializerOptions + { + Converters = { new AttributeListJsonConverter() } + }; + } + + [Fact] + public void Read_ShouldDeserializeAttributeList() + { + var json = "[{\"type\":\"boolean\"}, {\"type\":\"integer\"}]"; + var attributes = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(attributes); + Assert.Equal(2, attributes.Count); + Assert.IsType(attributes[0]); + Assert.IsType(attributes[1]); + } + + [Fact] + public void Read_ShouldThrowJsonExceptionForInvalidStartToken() + { + var json = "{\"type\":\"boolean\"}"; + var exception = Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + Assert.Equal("Expected start of array", exception.Message); + } + + [Fact] + public void Write_ShouldSerializeAttributeList() + { + var attributes = new List + { + new AttributeBoolean("a", "boolean", DatabaseElementStatus.Available, null, false, false, DateTime.UtcNow, DateTime.UtcNow, null), + new AttributeInteger("b", "integer", DatabaseElementStatus.Available, null, false, false, DateTime.UtcNow, DateTime.UtcNow, null, null, null) + }; + + var json = JsonSerializer.Serialize((IReadOnlyList)attributes, _options); + + Assert.Contains("\"type\":\"boolean\"", json); + Assert.Contains("\"type\":\"integer\"", json); + } + + [Fact] + public void Write_ShouldHandleEmptyList() + { + var attributes = new List(); + + var json = JsonSerializer.Serialize((IReadOnlyList)attributes, _options); + + Assert.Equal("[]", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentConverterTests.cs new file mode 100644 index 00000000..c8cbdc18 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentConverterTests.cs @@ -0,0 +1,432 @@ +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class DocumentConverterTests +{ + private readonly JsonSerializerOptions _options; + + public DocumentConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new DocumentConverter() } + }; + } + + [Fact] + public void Read_ValidJson_ReturnsDocument() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""customField"": ""customValue"" + }"; + + var document = JsonSerializer.Deserialize(json, _options); + + Assert.NotNull(document); + Assert.Equal("1", document.Id); + Assert.Equal("col1", document.CollectionId); + Assert.Equal("db1", document.DatabaseId); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00"), document.CreatedAt); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00"), document.UpdatedAt); + Assert.Single(document.Permissions); + Assert.Equal(PermissionType.Read, document.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, document.Permissions[0].RoleType); + Assert.Equal("customValue", document["customField"]); + } + + [Fact] + public void Read_InvalidJson_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""invalid-date"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_MissingRequiredFields_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Write_ValidDocument_WritesJson() + { + var document = new Document( + "1", + "col1", + "db1", + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + [Permission.Read().Any()], + new Dictionary { { "customField", "customValue" } } + ); + + var json = JsonSerializer.Serialize(document, _options); + + var expectedJson = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""customField"": ""customValue"" + }".ReplaceLineEndings("").Replace(" ", ""); + + Assert.Equal(JsonDocument.Parse(expectedJson).RootElement.ToString(), JsonDocument.Parse(json).RootElement.ToString()); + } + + [Fact] + public void Write_NullValue_WritesNull() + { + var document = new Document( + "1", + "col1", + "db1", + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + [Permission.Read().Any()], + new Dictionary { { "customField", null } } + ); + + var json = JsonSerializer.Serialize(document, _options); + + var expectedJson = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""customField"": null + }".ReplaceLineEndings("").Replace(" ", ""); + + Assert.Equal(JsonDocument.Parse(expectedJson).RootElement.ToString(), JsonDocument.Parse(json).RootElement.ToString()); + } + + [Fact] + public void ReadValue_UnsupportedTokenType_ThrowsJsonException() + { + var json = @" + { + ""unsupported"": {} + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void ReadArray_ValidJsonArray_ReturnsArray() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""arrayField"": [""value1"", ""value2""] + }"; + + var document = JsonSerializer.Deserialize(json, _options); + + Assert.NotNull(document); + var arrayField = document["arrayField"] as IReadOnlyCollection; + Assert.NotNull(arrayField); + Assert.Contains("value1", arrayField); + Assert.Contains("value2", arrayField); + } + + [Fact] + public void ReadObject_ValidJsonObject_ReturnsObject() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""objectField"": { ""key1"": ""value1"", ""key2"": ""value2"" } + }"; + + var document = JsonSerializer.Deserialize(json, _options); + + Assert.NotNull(document); + var objectField = document["objectField"] as Dictionary; + Assert.NotNull(objectField); + Assert.Equal("value1", objectField["key1"]); + Assert.Equal("value2", objectField["key2"]); + } + + [Fact] + public void Read_InvalidJsonTokenType_ThrowsJsonException() + { + var json = @" + [ + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""customField"": ""customValue"" + } + ]"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_MissingCollectionId_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""] + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_MissingDatabaseId_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""] + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_MissingPermissions_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize(json, _options)); + } + + [Fact] + public void Read_NullProperty_InsertedIntoData() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""customField"": null + }"; + + var document = JsonSerializer.Deserialize(json, _options); + + Assert.NotNull(document); + Assert.True(document.Data.ContainsKey("customField")); + Assert.Null(document["customField"]); + } + + [Fact] + public void Read_Comment_ThrowsJsonException() + { + var json = @"{}"; + + Assert.Throws(() => + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + while (reader.TokenType is not JsonTokenType.EndObject) + { + reader.Read(); + } + + var docReader = new DocumentConverter(); + + docReader.ReadValue(ref reader, _options); + }); + } + + [Fact] + public void Write_IntValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "intField", 123 } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"intField\":123", json); + } + + [Fact] + public void Write_LongValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "longField", 12345L } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"longField\":12345", json); + } + + [Fact] + public void Write_FloatValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "floatField", 1.23f } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"floatField\":1.23", json); + } + + [Fact] + public void Write_DoubleValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "doubleField", 1.23d } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"doubleField\":1.23", json); + } + + [Fact] + public void Write_DecimalValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "decimalField", 1.23m } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"decimalField\":1.23", json); + } + + [Fact] + public void Write_BoolValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "boolField", true } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"boolField\":true", json); + } + + [Fact] + public void Write_DateTimeValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "datetimeField", DateTime.Parse("2020-10-15T06:38:00.000+00:00") } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"datetimeField\":\"2020-10-15T06:38:00.000+00:00\"", json); + } + + [Fact] + public void Write_ListValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "listField", new List() { "val1","val2" } } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"listField\":[\"val1\",\"val2\"]", json); + } + + [Fact] + public void Write_DictValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "dictField", new Dictionary { + { "key", "val" } + } } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"dictField\":{\"key\":\"val\"}}", json); + } + + [Fact] + public void Write_ObjectValue_SerializesCorrectly() + { + var document = new Document("1", "col1", "db1", DateTime.UtcNow, DateTime.UtcNow, [], new Dictionary + { + { "objectField", new { } } + }); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"objectField\":{}}", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterFactoryTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterFactoryTests.cs new file mode 100644 index 00000000..2110449e --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterFactoryTests.cs @@ -0,0 +1,99 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class DocumentGenericConverterFactoryTests +{ + private readonly DocumentGenericConverterFactory _factory; + + public DocumentGenericConverterFactoryTests() + { + _factory = new DocumentGenericConverterFactory(); + } + + public class TestData + { + public string? Field1 { get; set; } + } + + [Fact] + public void CanConvert_DocumentGenericType_ReturnsTrue() + { + // Arrange + var typeToConvert = typeof(Document); + + // Act + var result = _factory.CanConvert(typeToConvert); + + // Assert + Assert.True(result); + } + + [Fact] + public void CanConvert_NonGenericType_ReturnsFalse() + { + // Arrange + var typeToConvert = typeof(TestData); + + // Act + var result = _factory.CanConvert(typeToConvert); + + // Assert + Assert.False(result); + } + + [Fact] + public void CanConvert_DifferentGenericType_ReturnsFalse() + { + // Arrange + var typeToConvert = typeof(List); + + // Act + var result = _factory.CanConvert(typeToConvert); + + // Assert + Assert.False(result); + } + + [Fact] + public void CreateConverter_DocumentGenericType_ReturnsConverter() + { + // Arrange + var typeToConvert = typeof(Document); + var options = new JsonSerializerOptions(); + + // Act + var converter = _factory.CreateConverter(typeToConvert, options); + + // Assert + Assert.NotNull(converter); + Assert.IsType>(converter); + } + + [Fact] + public void CreateConverter_NonGenericType_ThrowsException() + { + // Arrange + var typeToConvert = typeof(TestData); + var options = new JsonSerializerOptions(); + + // Act & Assert + Assert.Throws(() => _factory.CreateConverter(typeToConvert, options)); + } + + [Fact] + public void CreateConverter_DifferentGenericType_ReturnsConverterWithFirstGenericArgument() + { + // Arrange + var typeToConvert = typeof(List); + var options = new JsonSerializerOptions(); + + // Act + var converter = _factory.CreateConverter(typeToConvert, options); + + // Assert + Assert.NotNull(converter); + Assert.IsType>(converter); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterTests.cs new file mode 100644 index 00000000..129dd713 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentGenericConverterTests.cs @@ -0,0 +1,652 @@ +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class DocumentGenericConverterTests +{ + private readonly JsonSerializerOptions _options; + + public DocumentGenericConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new DocumentGenericConverter() } + }; + } + + public class TestData + { + public string? Field1 { get; set; } + public int Field2 { get; set; } + public bool Field3 { get; set; } + public DateTime? Field4 { get; set; } + public List? Field5 { get; set; } + public Dictionary? Field6 { get; set; } + public float? FloatField { get; set; } + public long? LongField { get; set; } + public double? DoubleField { get; set; } + } + + [Fact] + public void Read_ValidJson_ReturnsDocument() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"", + ""Field2"": 42, + ""Field3"": true, + ""Field4"": ""2020-10-15T06:38:00.000+00:00"", + ""Field5"": [""item1"", ""item2""], + ""Field6"": { ""key1"": ""value1"", ""key2"": 2 } + }"; + + var document = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(document); + Assert.Equal("1", document.Id); + Assert.Equal("col1", document.CollectionId); + Assert.Equal("db1", document.DatabaseId); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00"), document.CreatedAt); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00"), document.UpdatedAt); + Assert.Single(document.Permissions); + Assert.Equal(PermissionType.Read, document.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, document.Permissions[0].RoleType); + + Assert.NotNull(document.Data); + Assert.Equal("value1", document.Data.Field1); + Assert.Equal(42, document.Data.Field2); + Assert.True(document.Data.Field3); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00"), document.Data.Field4); + Assert.Equal(new List { "item1", "item2" }, document.Data.Field5); + + Assert.NotNull(document.Data.Field6); + var field6 = document.Data.Field6!; + Assert.Equal(2, field6.Count); + + // Extract values from JsonElement + Assert.True(field6.ContainsKey("key1")); + Assert.Equal("value1", field6["key1"]?.ToString()); + + Assert.True(field6.ContainsKey("key2")); + Assert.Equal(2, Convert.ToInt32(field6["key2"]?.ToString())); + } + + [Fact] + public void Read_InvalidJson_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""invalid-date"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Read_MissingRequiredFields_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_ValidDocument_WritesJson() + { + var testData = new TestData + { + Field1 = "value1", + Field2 = 42, + Field3 = true, + Field4 = DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + Field5 = ["item1", "item2"], + Field6 = new Dictionary { { "key1", "value1" }, { "key2", 2 } } + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + DateTime.Parse("2020-10-15T06:38:00.000+00:00"), + [Permission.Read().Any()], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + var expectedJson = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"", + ""Field2"": 42, + ""Field3"": true, + ""Field4"": ""2020-10-15T06:38:00.000+00:00"", + ""Field5"": [""item1"", ""item2""], + ""Field6"": { ""key1"": ""value1"", ""key2"": 2 }, + ""FloatField"": null, + ""LongField"": null, + ""DoubleField"": null + }".ReplaceLineEndings("").Replace(" ", ""); + + Assert.Equal(JsonDocument.Parse(expectedJson).RootElement.ToString(), JsonDocument.Parse(json).RootElement.ToString()); + } + + [Fact] + public void Write_NullData_WritesJsonWithNoDataProperties() + { + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [Permission.Read().Any()], + null! + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"$id\"", json); + Assert.DoesNotContain("\"Field1\"", json); + } + + [Fact] + public void Read_NullProperty_InsertedIntoData() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": null + }"; + + var document = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(document); + Assert.NotNull(document.Data); + Assert.Null(document.Data.Field1); + } + + [Fact] + public void Write_NullValue_SerializesCorrectly() + { + var testData = new TestData + { + Field1 = null + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [Permission.Read().Any()], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Field1\":null", json); + } + + //[Fact] + //public void ReadValue_UnsupportedTokenType_ThrowsJsonException() + //{ + // var json = @" + // { + // ""$id"": ""1"", + // ""$collectionId"": ""col1"", + // ""$databaseId"": ""db1"", + // ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + // ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + // ""$permissions"": [""read(\""any\"")""], + // ""unsupported"": /** comment */ + // }"; + + // Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + //} + + [Fact] + public void Write_CustomObject_SerializesUsingJsonSerializer() + { + var testData = new TestData + { + Field6 = new Dictionary + { + { "nestedObject", new { Prop1 = "value1", Prop2 = 2 } } + } + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [Permission.Read().Any()], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Prop1\":\"value1\"", json); + Assert.Contains("\"Prop2\":2", json); + } + + [Fact] + public void Read_InvalidJsonTokenType_ThrowsJsonException() + { + var json = @" + [ + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + } + ]"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_DateTimeValue_SerializesCorrectly() + { + var testData = new TestData + { + Field4 = DateTime.Parse("2020-10-15T06:38:00.000+00:00") + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Field4\":\"2020-10-15T06:38:00.000+00:00\"", json); + } + + [Fact] + public void Write_NullDataProperty_WritesNull() + { + var testData = new TestData + { + Field5 = null + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Field5\":null", json); + } + + [Fact] + public void Write_BooleanValue_SerializesCorrectly() + { + var testData = new TestData + { + Field3 = true + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Field3\":true", json); + } + + [Fact] + public void Write_NumberValues_SerializesCorrectly() + { + var testData = new TestData + { + Field2 = 123 + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"Field2\":123", json); + } + + [Fact] + public void Read_MissingId_ThrowsJsonException() + { + var json = @" + { + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Read_MissingCollectionId_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Read_MissingDatabaseId_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Read_MissingPermissions_ThrowsJsonException() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""Field1"": ""value1"" + }"; + + Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_NullDocumentData_SerializesCorrectly() + { + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + null! + ); + + var json = JsonSerializer.Serialize(document, _options); + + Assert.Contains("\"$id\"", json); + Assert.DoesNotContain("\"Field1\"", json); + } + + // Custom converter that returns null during deserialization + public class NullReturningConverter : JsonConverter where T : class + { + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Always return null + reader.Skip(); // Skip the current value to avoid infinite loops + return null; + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + // Write null + writer.WriteNullValue(); + } + } + + [Fact] + public void Read_DataDeserializationReturnsNull_DataSetToNewInstance() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""Field1"": ""value1"", + ""Field2"": 42 + }"; + + // Create new options with the NullReturningConverter for TestData + var optionsWithNullConverter = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = + { + new NullReturningConverter(), + new DocumentGenericConverter(), + new MultiFormatDateTimeConverter(), + new PermissionListConverter() + } + }; + + var document = JsonSerializer.Deserialize>(json, optionsWithNullConverter); + + Assert.NotNull(document); + Assert.NotNull(document.Data); + // Since data deserialization returned null, Data should be set to new TData() + // So Data's properties should have default values + Assert.Null(document.Data.Field1); + Assert.Equal(0, document.Data.Field2); + } + + [Fact] + public void ReadValue_UnsupportedTokenType_ThrowsJsonException() + { + var json = @"{ + ""Field1"": /* Comment */ ""value1"" + }"; + + var readerOptions = new JsonReaderOptions + { + CommentHandling = JsonCommentHandling.Allow + }; + + var bytes = Encoding.UTF8.GetBytes(json); + + var reader = new Utf8JsonReader(bytes, readerOptions); + + // Read the StartObject token + reader.Read(); // JsonTokenType.StartObject + + // Read the PropertyName token + reader.Read(); // JsonTokenType.PropertyName + + var propertyName = reader.GetString()!; + + // Read the Comment token + reader.Read(); // JsonTokenType.Comment + + // At this point, reader.TokenType is Comment, which is not handled in ReadValue + // Calling ReadValue should now hit the default case and throw JsonException + try + { + DocumentGenericConverter.ReadValue(ref reader, _options); + Assert.Fail("Did not throw JsonException"); + } + catch (JsonException) + { + } + } + + [Fact] + public void ReadValue_FloatNumber_ReturnsSingle() + { + var json = @" + { + ""$id"": ""1"", + ""$collectionId"": ""col1"", + ""$databaseId"": ""db1"", + ""$createdAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$updatedAt"": ""2020-10-15T06:38:00.000+00:00"", + ""$permissions"": [""read(\""any\"")""], + ""FloatField"": 1.23 + }"; + + var document = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(document); + Assert.NotNull(document.Data); + Assert.Equal(1.23f, document.Data.FloatField); + } + + [Fact] + public void WriteValue_UndefinedValueKind_CallsJsonSerializer() + { + var converter = new DocumentGenericConverter(); + + // Create a default-initialized JsonElement (ValueKind is Undefined) + JsonElement undefinedElement = default; + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + // Since JsonSerializer.Serialize will throw an exception when trying to serialize an undefined JsonElement, + // we can expect an InvalidOperationException + Assert.Throws(() => converter.WriteValue(writer, undefinedElement, _options)); + } + + [Fact] + public void WriteValue_LongNumber_WritesLongValue() + { + var testData = new TestData + { + LongField = (long)int.MaxValue + 1 // Value larger than int.MaxValue + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + // Verify that the LongField is serialized correctly + var jsonDoc = JsonDocument.Parse(json); + Assert.True(jsonDoc.RootElement.TryGetProperty("LongField", out var longFieldElement)); + Assert.Equal(JsonValueKind.Number, longFieldElement.ValueKind); + Assert.Equal((long)int.MaxValue + 1, longFieldElement.GetInt64()); + } + + [Fact] + public void WriteValue_DoubleNumber_WritesDoubleValue() + { + var testData = new TestData + { + DoubleField = 1.23e20 // A large double value + }; + + var document = new Document( + "1", + "col1", + "db1", + DateTime.UtcNow, + DateTime.UtcNow, + [], + testData + ); + + var json = JsonSerializer.Serialize(document, _options); + + // Verify that the DoubleField is serialized correctly + var jsonDoc = JsonDocument.Parse(json); + Assert.True(jsonDoc.RootElement.TryGetProperty("DoubleField", out var doubleFieldElement)); + Assert.Equal(JsonValueKind.Number, doubleFieldElement.ValueKind); + Assert.Equal(1.23e20, doubleFieldElement.GetDouble()); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentListConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentListConverterTests.cs new file mode 100644 index 00000000..b0203b51 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/DocumentListConverterTests.cs @@ -0,0 +1,62 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class DocumentListConverterTests +{ + private readonly JsonSerializerOptions _options; + + public DocumentListConverterTests() + { + _options = new JsonSerializerOptions + { + Converters = { new DocumentListConverter() } + }; + } + + [Fact] + public void Read_ShouldDeserializeDocumentList() + { + var json = "[{\"$id\":\"1\",\"$collectionId\":\"c1\",\"$databaseId\":\"d1\",\"$createdAt\":\"2020-10-15T06:38:00.000+00:00\",\"$updatedAt\":\"2020-10-15T06:38:00.000+00:00\",\"$permissions\":[]}, {\"$id\":\"2\",\"$collectionId\":\"c2\",\"$databaseId\":\"d2\",\"$createdAt\":\"2020-10-15T06:38:00.000+00:00\",\"$updatedAt\":\"2020-10-15T06:38:00.000+00:00\",\"$permissions\":[]}]"; + var documents = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(documents); + Assert.Equal(2, documents.Count); + Assert.Equal("1", documents[0].Id); + Assert.Equal("2", documents[1].Id); + } + + [Fact] + public void Read_ShouldThrowJsonExceptionForInvalidStartToken() + { + var json = "{\"id\":\"1\"}"; + var exception = Assert.Throws(() => JsonSerializer.Deserialize>(json, _options)); + Assert.Equal("Expected start of array", exception.Message); + } + + [Fact] + public void Write_ShouldSerializeDocumentList() + { + var documents = new List + { + new Document("5e5ea5c16897e", "5e5ea5c15117e", "5e5ea5c15117e", DateTime.UtcNow, DateTime.UtcNow, [], []), + new Document("5e5ea5c16897f", "5e5ea5c15117e", "5e5ea5c15117e", DateTime.UtcNow, DateTime.UtcNow, [], []) + }; + + var json = JsonSerializer.Serialize((IReadOnlyList)documents, _options); + + Assert.Contains("\"$id\":\"5e5ea5c16897e\"", json); + Assert.Contains("\"$id\":\"5e5ea5c16897f\"", json); + } + + [Fact] + public void Write_ShouldHandleEmptyList() + { + var documents = new List(); + + var json = JsonSerializer.Serialize((IReadOnlyList)documents, _options); + + Assert.Equal("[]", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/IgnoreSdkExcludedPropertiesConverterFactoryTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/IgnoreSdkExcludedPropertiesConverterFactoryTests.cs index cdc67218..4249f5d9 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Converters/IgnoreSdkExcludedPropertiesConverterFactoryTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/IgnoreSdkExcludedPropertiesConverterFactoryTests.cs @@ -234,6 +234,15 @@ public void Write_PropertyValueIsNullAndDefaultIgnoreConditionIsWhenWritingNull_ Assert.DoesNotContain("\"Name\"", json); } + [Fact] + public void Write_JsonIgnoreAttributeWithNeverCondition_PropertyIsNotIgnored() + { + var testClass = new TestClassWithJsonIgnoreNever { Val = null }; + var json = JsonSerializer.Serialize(testClass, _options); + + Assert.Contains("\"Val\":null", json); + } + private class TestClass { public string Name { get; set; } = string.Empty; @@ -307,4 +316,10 @@ private class TestClassWithNullProperty { public string? Name { get; set; } } + + private class TestClassWithJsonIgnoreNever + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public bool? Val { get; set; } + } } diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/MultiFormatDateTimeConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/MultiFormatDateTimeConverterTests.cs index 382eb00e..cef2db5e 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Converters/MultiFormatDateTimeConverterTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/MultiFormatDateTimeConverterTests.cs @@ -9,14 +9,17 @@ public class MultiFormatDateTimeConverterTests public MultiFormatDateTimeConverterTests() { - _options = new JsonSerializerOptions(); + _options = new JsonSerializerOptions() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; _options.Converters.Add(new MultiFormatDateTimeConverter()); } [Fact] public void Read_ValidDateStringWithTimeZone_ReturnsDateTime() { - var json = "\"2023-01-01T00:00:00.000Z\""; + var json = "\"2023-01-01T00:00:00.000+00:00\""; var result = JsonSerializer.Deserialize(json, _options); // Convert both to UTC to compare @@ -53,7 +56,7 @@ public void Write_ValidDateTime_WritesExpectedString() { var dateTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc); var json = JsonSerializer.Serialize(dateTime, _options); - Assert.Equal("\"2023-01-01T00:00:00.000Z\"", json); + Assert.Equal("\"2023-01-01T00:00:00.000+00:00\"", json); } public class MultiFormatDateTimeObject @@ -66,7 +69,7 @@ public class MultiFormatDateTimeObject [Fact] public void Read_ValidDateStringInObject_ReturnsDateTime() { - var json = "{\"x\": \"2023-01-01T00:00:00.000Z\"}"; + var json = "{\"x\": \"2023-01-01T00:00:00.000+00:00\"}"; var result = JsonSerializer.Deserialize(json, _options); Assert.NotNull(result); @@ -83,6 +86,6 @@ public void Write_ValidDateTimeInObject_WritesExpectedString() { var obj = new MultiFormatDateTimeObject { X = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var json = JsonSerializer.Serialize(obj, _options); - Assert.Equal("{\"x\":\"2023-01-01T00:00:00.000Z\"}", json); + Assert.Equal("{\"x\":\"2023-01-01T00:00:00.000+00:00\"}", json); } } diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/NullableDateTimeConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/NullableDateTimeConverterTests.cs index 2c0fb17a..f050759f 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Converters/NullableDateTimeConverterTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/NullableDateTimeConverterTests.cs @@ -10,14 +10,18 @@ public class NullableDateTimeConverterTests public NullableDateTimeConverterTests() { - _options = new JsonSerializerOptions(); + _options = new JsonSerializerOptions() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + _options.Converters.Add(new NullableDateTimeConverter()); } [Fact] public void Read_ValidDateString_ReturnsDateTime() { - var json = "\"2023-01-01T00:00:00\""; + var json = "\"2023-01-01T00:00:00.000+00:00\""; var result = JsonSerializer.Deserialize(json, _options); Assert.NotNull(result); Assert.Equal(new DateTime(2023, 1, 1), result.Value); @@ -72,9 +76,9 @@ public void Read_UnexpectedTokenType_ThrowsJsonException() [Fact] public void Write_NonNullDateTime_WritesExpectedString() { - var dateTime = new DateTime(2023, 1, 1); + var dateTime = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc); var json = JsonSerializer.Serialize(dateTime, _options); - Assert.Equal("\"2023-01-01T00:00:00.0000000\"", json); + Assert.Equal("\"2023-01-01T00:00:00.000+00:00\"", json); } [Fact] diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionJsonConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionJsonConverterTests.cs new file mode 100644 index 00000000..50eac396 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionJsonConverterTests.cs @@ -0,0 +1,314 @@ +using System.Reflection; +using System.Text.Encodings.Web; +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class PermissionJsonConverterTests +{ + private readonly JsonSerializerOptions _options; + + public PermissionJsonConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new PermissionJsonConverter() } + }; + } + + [Theory] + [InlineData("read(\\\"any\\\")", PermissionType.Read, RoleType.Any)] + [InlineData("write(\\\"any\\\")", PermissionType.Write, RoleType.Any)] + [InlineData("create(\\\"any\\\")", PermissionType.Create, RoleType.Any)] + [InlineData("update(\\\"any\\\")", PermissionType.Update, RoleType.Any)] + [InlineData("delete(\\\"any\\\")", PermissionType.Delete, RoleType.Any)] + public void Read_SimplePermissions_DeserializeCorrectly(string json, PermissionType expectedPermType, RoleType expectedRoleType) + { + // Act + var permission = JsonSerializer.Deserialize($"\"{json}\"", _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(expectedPermType, permission.PermissionType); + Assert.Equal(expectedRoleType, permission.RoleType); + } + + [Theory] + [InlineData("user:123", "123", null)] + [InlineData("user:123/verified", "123", RoleStatus.Verified)] + [InlineData("user:456/unverified", "456", RoleStatus.Unverified)] + public void Read_UserPermissions_DeserializeCorrectly(string roleStr, string expectedId, RoleStatus? expectedStatus) + { + // Arrange + var json = $"\"read(\\\"{roleStr}\\\")\""; + + // Act + var permission = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.User, permission.RoleType); + Assert.Equal(expectedId, permission.Id); + Assert.Equal(expectedStatus, permission.Status); + } + + [Fact] + public void Read_Guests_DeserializeCorrectly() + { + // Arrange + var json = $"\"read(\\\"guests\\\")\""; + + // Act + var permission = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Guests, permission.RoleType); + } + + [Theory] + [InlineData("users")] + [InlineData("users/verified")] + [InlineData("users/unverified")] + public void Read_UsersPermissions_DeserializeCorrectly(string roleStr) + { + // Arrange + var json = $"\"read(\\\"{roleStr}\\\")\""; + + // Act + var permission = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Users, permission.RoleType); + + if (roleStr.Contains('/')) + { + Assert.Equal( + Enum.Parse(roleStr.Split('/')[1], true), + permission.Status); + } + } + + [Theory] + [InlineData("team:123", "123", null)] + [InlineData("team:456/admin", "456", "admin")] + [InlineData("team:789/member", "789", "member")] + public void Read_TeamPermissions_DeserializeCorrectly(string roleStr, string expectedId, string? expectedRole) + { + // Arrange + var json = $"\"write(\\\"{roleStr}\\\")\""; + + // Act + var permission = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(PermissionType.Write, permission.PermissionType); + Assert.Equal(RoleType.Team, permission.RoleType); + Assert.Equal(expectedId, permission.Id); + Assert.Equal(expectedRole, permission.TeamRole); + } + + [Theory] + [InlineData("member:123")] + [InlineData("label:testLabel")] + public void Read_MemberAndLabelPermissions_DeserializeCorrectly(string roleStr) + { + // Arrange + var json = $"\"read(\\\"{roleStr}\\\")\""; + + // Act + var permission = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(permission); + Assert.Equal(PermissionType.Read, permission.PermissionType); + + if (roleStr.StartsWith("member:")) + { + Assert.Equal(RoleType.Member, permission.RoleType); + Assert.Equal(roleStr[7..], permission.Id); + } + else + { + Assert.Equal(RoleType.Label, permission.RoleType); + Assert.Equal(roleStr[6..], permission.Label); + } + } + + [Theory] + [InlineData(42)] + [InlineData(true)] + public void Read_NonStringToken_ThrowsJsonException(object? invalidValue) + { + // Arrange + var json = JsonSerializer.Serialize(invalidValue); + + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize(json, _options)); + } + + [Theory] + [InlineData("")] + [InlineData("invalid")] + [InlineData("read")] + [InlineData("read(any)")] + [InlineData("read\\\"any\\\")")] + [InlineData("read(\\\"any\\\"")] + [InlineData("unknown(\\\"any\\\")")] + public void Read_InvalidFormat_ThrowsJsonException(string invalidJson) + { + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize($"\"{invalidJson}\"", _options)); + } + + [Fact] + public void Write_Any_SerializesCorrectly() + { + // Arrange + var permission = Permission.Read().Any(); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal("\"read(\\\"any\\\")\"", json); + } + + [Theory] + [InlineData(null, "users")] + [InlineData(RoleStatus.Verified, "users/verified")] + [InlineData(RoleStatus.Unverified, "users/unverified")] + public void Write_Users_SerializesCorrectly(RoleStatus? status, string expectedRole) + { + // Arrange + var permission = status.HasValue + ? Permission.Read().Users(status.Value) + : Permission.Read().Users(); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal($"\"read(\\\"{expectedRole}\\\")\"", json); + } + + [Fact] + public void Write_Guests_SerializesCorrectly() + { + // Arrange + var permission = Permission.Read().Guests(); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal("\"read(\\\"guests\\\")\"", json); + } + + [Theory] + [InlineData(null, "user:123")] + [InlineData(RoleStatus.Verified, "user:123/verified")] + [InlineData(RoleStatus.Unverified, "user:123/unverified")] + public void Write_User_SerializesCorrectly(RoleStatus? status, string expectedRole) + { + // Arrange + var permission = status.HasValue + ? Permission.Write().User("123", status.Value) + : Permission.Write().User("123"); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal($"\"write(\\\"{expectedRole}\\\")\"", json); + } + + [Theory] + [InlineData(null, "team:123")] + [InlineData("admin", "team:123/admin")] + public void Write_Team_SerializesCorrectly(string? teamRole, string expectedRole) + { + // Arrange + var permission = teamRole != null + ? Permission.Create().Team("123", teamRole) + : Permission.Create().Team("123"); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal($"\"create(\\\"{expectedRole}\\\")\"", json); + } + + [Fact] + public void Write_Member_SerializesCorrectly() + { + // Arrange + var permission = Permission.Create().Member("123"); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal("\"create(\\\"member:123\\\")\"", json); + } + + [Fact] + public void Write_Label_SerializesCorrectly() + { + // Arrange + var permission = Permission.Write().Label("writer"); + + // Act + var json = JsonSerializer.Serialize(permission, _options); + + // Assert + Assert.Equal("\"write(\\\"label:writer\\\")\"", json); + } + + [Fact] + public void Write_ThrowsJsonException_ForUnknownRoleType() + { + // Arrange + var invalidRoleType = (RoleType)999; + + var permission = CreatePermissionWithCustomProperties(PermissionType.Create, invalidRoleType, null, null, null, null); + + // Act & Assert + var exception = Assert.Throws(() => + JsonSerializer.Serialize(permission, _options) + ); + + Assert.Equal("Unknown role type: 999", exception.Message); + } + + private Permission CreatePermissionWithCustomProperties(PermissionType permissionType, RoleType roleType, string? id, RoleStatus? status, string? teamRole, string? label) + { + // Use reflection to create an instance of Permission + var permissionT = typeof(Permission); + var constructor = permissionT.GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + null, + [typeof(PermissionType), typeof(RoleType), typeof(string), typeof(RoleStatus?), typeof(string), typeof(string)], + null + ); + + if (constructor == null) + { + throw new InvalidOperationException("Permission constructor not found."); + } + + var permission = (Permission)constructor.Invoke([permissionType, roleType, id, status, teamRole, label]); + return permission; + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionListConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionListConverterTests.cs new file mode 100644 index 00000000..f6127e30 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionListConverterTests.cs @@ -0,0 +1,160 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class PermissionListConverterTests +{ + private readonly JsonSerializerOptions _options; + + public PermissionListConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = + { + new PermissionListConverter(), + new PermissionJsonConverter() + } + }; + } + + [Fact] + public void Read_EmptyArray_ReturnsEmptyList() + { + // Arrange + var json = "[]"; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + Assert.IsType>(result); + } + + [Fact] + public void Read_SinglePermission_DeserializesCorrectly() + { + // Arrange + var json = "[\"read(\\\"any\\\")\"]"; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal(PermissionType.Read, result[0].PermissionType); + Assert.Equal(RoleType.Any, result[0].RoleType); + } + + [Fact] + public void Read_MultiplePermissions_DeserializesCorrectly() + { + // Arrange + var json = """ + [ + "read(\"any\")", + "write(\"user:123/verified\")", + "create(\"team:456/admin\")" + ] + """; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count); + + Assert.Equal(PermissionType.Read, result[0].PermissionType); + Assert.Equal(RoleType.Any, result[0].RoleType); + + Assert.Equal(PermissionType.Write, result[1].PermissionType); + Assert.Equal(RoleType.User, result[1].RoleType); + Assert.Equal("123", result[1].Id); + Assert.Equal(RoleStatus.Verified, result[1].Status); + + Assert.Equal(PermissionType.Create, result[2].PermissionType); + Assert.Equal(RoleType.Team, result[2].RoleType); + Assert.Equal("456", result[2].Id); + Assert.Equal("admin", result[2].TeamRole); + } + + [Theory] + [InlineData("")] + [InlineData("{}")] + [InlineData("\"not-an-array\"")] + [InlineData("[")] + [InlineData("]")] + public void Read_InvalidJson_ThrowsJsonException(string invalidJson) + { + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize>(invalidJson, _options)); + } + + [Fact] + public void Read_NullValueInArray_ThrowsJsonException() + { + // Arrange + var json = "[\"read(\\\"any\\\")\", null]"; + + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_EmptyList_SerializesCorrectly() + { + // Arrange + var permissions = new List(); + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + Assert.Equal("[]", json); + } + + [Fact] + public void Write_SinglePermission_SerializesCorrectly() + { + // Arrange + var permissions = new List + { + Permission.Read().Any() + }; + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + Assert.Equal("[\"read(\\\"any\\\")\"]", json); + } + + [Fact] + public void Write_MultiplePermissions_SerializesCorrectly() + { + // Arrange + var permissions = new List + { + Permission.Read().Any(), + Permission.Write().User("123", RoleStatus.Verified), + Permission.Create().Team("456", "admin") + }; + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + var expectedJson = "[\"read(\\\"any\\\")\",\"write(\\\"user:123/verified\\\")\",\"create(\\\"team:456/admin\\\")\"]"; + Assert.Equal(expectedJson, json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionReadOnlyListConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionReadOnlyListConverterTests.cs new file mode 100644 index 00000000..b967f417 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/PermissionReadOnlyListConverterTests.cs @@ -0,0 +1,182 @@ +using System.Text.Encodings.Web; +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class PermissionReadOnlyListConverterTests +{ + private readonly JsonSerializerOptions _options; + + public PermissionReadOnlyListConverterTests() + { + _options = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = + { + new PermissionReadOnlyListConverter(), + new PermissionJsonConverter() + } + }; + } + + [Fact] + public void Read_EmptyArray_ReturnsEmptyReadOnlyList() + { + // Arrange + var json = "[]"; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + Assert.IsAssignableFrom>(result); + } + + [Fact] + public void Read_SinglePermission_DeserializesCorrectly() + { + // Arrange + var json = "[\"read(\\\"any\\\")\"]"; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + Assert.Equal(PermissionType.Read, result[0].PermissionType); + Assert.Equal(RoleType.Any, result[0].RoleType); + Assert.IsAssignableFrom>(result); + } + + [Fact] + public void Read_MultiplePermissions_DeserializesCorrectly() + { + // Arrange + var json = """ + [ + "read(\"any\")", + "write(\"user:123/verified\")", + "create(\"team:456/admin\")" + ] + """; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count); + Assert.IsAssignableFrom>(result); + + Assert.Equal(PermissionType.Read, result[0].PermissionType); + Assert.Equal(RoleType.Any, result[0].RoleType); + + Assert.Equal(PermissionType.Write, result[1].PermissionType); + Assert.Equal(RoleType.User, result[1].RoleType); + Assert.Equal("123", result[1].Id); + Assert.Equal(RoleStatus.Verified, result[1].Status); + + Assert.Equal(PermissionType.Create, result[2].PermissionType); + Assert.Equal(RoleType.Team, result[2].RoleType); + Assert.Equal("456", result[2].Id); + Assert.Equal("admin", result[2].TeamRole); + } + + [Theory] + [InlineData("")] + [InlineData("{}")] + [InlineData("\"not-an-array\"")] + [InlineData("[")] + [InlineData("]")] + public void Read_InvalidJson_ThrowsJsonException(string invalidJson) + { + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize>(invalidJson, _options)); + } + + [Fact] + public void Read_NullValueInArray_ThrowsJsonException() + { + // Arrange + var json = "[\"read(\\\"any\\\")\", null]"; + + // Act & Assert + Assert.Throws(() => + JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_EmptyList_SerializesCorrectly() + { + // Arrange + IReadOnlyList permissions = new List().AsReadOnly(); + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + Assert.Equal("[]", json); + } + + [Fact] + public void Write_SinglePermission_SerializesCorrectly() + { + // Arrange + IReadOnlyList permissions = new List + { + Permission.Read().Any() + }.AsReadOnly(); + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + Assert.Equal("[\"read(\\\"any\\\")\"]", json); + } + + [Fact] + public void Write_MultiplePermissions_SerializesCorrectly() + { + // Arrange + IReadOnlyList permissions = new List + { + Permission.Read().Any(), + Permission.Write().User("123", RoleStatus.Verified), + Permission.Create().Team("456", "admin") + }.AsReadOnly(); + + // Act + var json = JsonSerializer.Serialize(permissions, _options); + + // Assert + var expectedJson = "[\"read(\\\"any\\\")\",\"write(\\\"user:123/verified\\\")\",\"create(\\\"team:456/admin\\\")\"]"; + Assert.Equal(expectedJson, json); + } + + [Fact] + public void Read_EnsuresReadOnlyBehavior() + { + // Arrange + var json = "[\"read(\\\"any\\\")\"]"; + + // Act + var result = JsonSerializer.Deserialize>(json, _options); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + Assert.IsAssignableFrom>(result); + Assert.Throws(() => + { + var list = result as System.Collections.IList; + list?.Add(Permission.Read().Any()); + }); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumConverterTests.cs new file mode 100644 index 00000000..afe848b2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumConverterTests.cs @@ -0,0 +1,104 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class UpperCaseEnumConverterTests +{ + public enum TestEnum + { + FirstValue, + SecondValue, + UPPERCASE_VALUE + } + + private readonly UpperCaseEnumConverter _converter; + private readonly JsonSerializerOptions _options; + + public UpperCaseEnumConverterTests() + { + _converter = new UpperCaseEnumConverter(); + _options = new JsonSerializerOptions(); + } + + [Fact] + public void CanConvert_WhenTypeIsEnum_ReturnsTrue() + { + // Arrange + var enumType = typeof(TestEnum); + + // Act + var result = _converter.CanConvert(enumType); + + // Assert + Assert.True(result); + } + + [Fact] + public void CanConvert_WhenTypeIsNotEnum_ReturnsFalse() + { + // Arrange + var nonEnumType = typeof(string); + + // Act + var result = _converter.CanConvert(nonEnumType); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("FirstValue", TestEnum.FirstValue)] + [InlineData("FIRSTVALUE", TestEnum.FirstValue)] + [InlineData("secondValue", TestEnum.SecondValue)] + [InlineData("UPPERCASE_VALUE", TestEnum.UPPERCASE_VALUE)] + public void Read_ValidEnumString_ReturnsEnumValue(string input, TestEnum expected) + { + // Arrange + var json = $"\"{input}\""; + var reader = new Utf8JsonReader(System.Text.Encoding.UTF8.GetBytes(json)); + reader.Read(); // Move to first token + + // Act + var result = _converter.Read(ref reader, typeof(TestEnum), _options); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void Read_InvalidEnumString_ThrowsArgumentException() + { + // Arrange + var json = "\"InvalidValue\""; + var jsonBytes = System.Text.Encoding.UTF8.GetBytes(json); + + // Act & Assert + void TestCode() + { + var reader = new Utf8JsonReader(jsonBytes); + reader.Read(); // Move to first token + _converter.Read(ref reader, typeof(TestEnum), _options); + } + + Assert.Throws(TestCode); + } + + [Theory] + [InlineData(TestEnum.FirstValue, "FIRSTVALUE")] + [InlineData(TestEnum.SecondValue, "SECONDVALUE")] + [InlineData(TestEnum.UPPERCASE_VALUE, "UPPERCASE_VALUE")] + public void Write_EnumValue_WritesUpperCaseString(TestEnum input, string expected) + { + // Arrange + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + // Act + _converter.Write(writer, input, _options); + writer.Flush(); + + // Assert + var json = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal($"\"{expected}\"", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumListConverterTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumListConverterTests.cs new file mode 100644 index 00000000..cbda3251 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Converters/UpperCaseEnumListConverterTests.cs @@ -0,0 +1,83 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Converters; + +namespace PinguApps.Appwrite.Shared.Tests.Converters; +public class UpperCaseEnumListConverterTests +{ + private enum TestEnum + { + FirstValue, + SecondValue, + UPPERCASE_VALUE + } + + private readonly JsonSerializerOptions _options; + + public UpperCaseEnumListConverterTests() + { + _options = new JsonSerializerOptions + { + Converters = { new UpperCaseEnumListConverter() } + }; + } + + [Fact] + public void Read_NotArray_ThrowsJsonException() + { + var json = "\"NotAnArray\""; + Assert.ThrowsAny(() => + JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Read_ValidArray_ReturnsEnumList() + { + var json = "[\"FirstValue\", \"SECONDVALUE\", \"uppercase_value\"]"; + var result = JsonSerializer.Deserialize>(json, _options); + + Assert.NotNull(result); + Assert.Collection(result, + item => Assert.Equal(TestEnum.FirstValue, item), + item => Assert.Equal(TestEnum.SecondValue, item), + item => Assert.Equal(TestEnum.UPPERCASE_VALUE, item)); + } + + [Fact] + public void Read_EmptyArray_ReturnsEmptyList() + { + var json = "[]"; + var result = JsonSerializer.Deserialize>(json, _options); + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void Read_InvalidEnumValue_ThrowsArgumentException() + { + var json = "[\"InvalidValue\"]"; + Assert.Throws(() => + JsonSerializer.Deserialize>(json, _options)); + } + + [Fact] + public void Write_ValidList_WritesUpperCaseArray() + { + var list = new List + { + TestEnum.FirstValue, + TestEnum.SecondValue, + TestEnum.UPPERCASE_VALUE + }; + + var json = JsonSerializer.Serialize(list, _options); + Assert.Equal("[\"FIRSTVALUE\",\"SECONDVALUE\",\"UPPERCASE_VALUE\"]", json); + } + + [Fact] + public void Write_EmptyList_WritesEmptyArray() + { + var list = new List(); + var json = JsonSerializer.Serialize(list, _options); + Assert.Equal("[]", json); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj b/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj index 562fe4bb..de47fce6 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj +++ b/tests/PinguApps.Appwrite.Shared.Tests/PinguApps.Appwrite.Shared.Tests.csproj @@ -15,7 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateAttributeBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateAttributeBaseRequestTests.cs new file mode 100644 index 00000000..b268a3b3 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateAttributeBaseRequestTests.cs @@ -0,0 +1,114 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class CreateAttributeBaseRequestTests : DatabaseCollectionIdBaseRequestTests + where TRequest : CreateAttributeBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidDatabaseCollectionIdRequest + { + get + { + var request = CreateValidCreateAttributeBaseRequest; + request.Key = IdUtils.GenerateUniqueId(); + return request; + } + } + + protected abstract TRequest CreateValidCreateAttributeBaseRequest { get; } + + [Fact] + public void CreateAttributeBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidCreateAttributeBaseRequest; + + // Assert + Assert.Equal(string.Empty, request.Key); + Assert.False(request.Required); + Assert.False(request.Array); + } + + [Fact] + public void CreateAttributeBase_Properties_CanBeSet() + { + // Arrange + var keyValue = "validKey"; + var request = CreateValidCreateAttributeBaseRequest; + + // Act + request.Key = keyValue; + request.Required = true; + request.Array = true; + + // Assert + Assert.Equal(keyValue, request.Key); + Assert.True(request.Required); + Assert.True(request.Array); + } + + [Fact] + public void CreateAttributeBase_IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = CreateValidCreateAttributeBaseRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void CreateAttributeBase_IsValid_WithInvalidData_ReturnsFalse(string? key) + { + // Arrange + var request = CreateValidCreateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = key!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void CreateAttributeBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidCreateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = ""; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void CreateAttributeBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidCreateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = ""; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateBooleanAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateBooleanAttributeRequestTests.cs new file mode 100644 index 00000000..a82e4912 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateBooleanAttributeRequestTests.cs @@ -0,0 +1,135 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateBooleanAttributeRequestTests : CreateAttributeBaseRequestTests +{ + protected override CreateBooleanAttributeRequest CreateValidCreateAttributeBaseRequest => new(); + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateBooleanAttributeRequest(); + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var defaultValue = true; + + var request = new CreateBooleanAttributeRequest(); + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = true, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = false, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateBooleanAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = true, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = false, + Required = true + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateBooleanAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateBooleanAttributeRequest + { + Default = true, + Required = true + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateBooleanAttributeRequest + { + Default = true, + Required = true + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateCollectionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateCollectionRequestTests.cs new file mode 100644 index 00000000..51add175 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateCollectionRequestTests.cs @@ -0,0 +1,197 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateCollectionRequestTests : DatabaseIdBaseRequestTests +{ + protected override CreateCollectionRequest CreateValidRequest => new() + { + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Pingu" + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateCollectionRequest(); + + // Assert + Assert.NotEmpty(request.CollectionId); + Assert.Equal(string.Empty, request.Name); + Assert.NotNull(request.Permissions); + Assert.False(request.DocumentSecurity); + Assert.False(request.Enabled); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var collectionId = IdUtils.GenerateUniqueId(); + var name = "My Collection"; + var permissions = new List { Permission.Read().Any() }; + var documentSecurity = true; + var enabled = true; + + var request = new CreateCollectionRequest(); + + // Act + request.CollectionId = collectionId; + request.Name = name; + request.Permissions = permissions; + request.DocumentSecurity = documentSecurity; + request.Enabled = enabled; + + // Assert + Assert.Equal(collectionId, request.CollectionId); + Assert.Equal(name, request.Name); + Assert.Equal(permissions, request.Permissions); + Assert.Equal(documentSecurity, request.DocumentSecurity); + Assert.Equal(enabled, request.Enabled); + } + + public static TheoryData ValidRequestsData = + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Valid Collection Name", + Permissions = [Permission.Read().Any()], + DocumentSecurity = true, + Enabled = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "validCollectionId123", + Name = "Another Valid Collection", + Permissions = [], + DocumentSecurity = false, + Enabled = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateCollectionRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = null!, + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "", + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "invalid chars!", + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = ".startsWithSymbol", + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = new string('a', 37), + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = new string('a', 129) + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Pingu", + Permissions = null! + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateCollectionRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "", + Name = "", + Permissions = null!, + DocumentSecurity = false, + Enabled = false + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "", + Name = "", + Permissions = null!, + DocumentSecurity = false, + Enabled = false + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatabaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatabaseRequestTests.cs new file mode 100644 index 00000000..13eb4202 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatabaseRequestTests.cs @@ -0,0 +1,153 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateDatabaseRequestTests +{ + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateDatabaseRequest(); + + // Assert + Assert.NotEmpty(request.DatabaseId); + Assert.Equal(string.Empty, request.Name); + Assert.False(request.Enabled); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var databaseId = IdUtils.GenerateUniqueId(); + var name = "My Database"; + var enabled = true; + + var request = new CreateDatabaseRequest(); + + // Act + request.DatabaseId = databaseId; + request.Name = name; + request.Enabled = enabled; + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + Assert.Equal(name, request.Name); + Assert.Equal(enabled, request.Enabled); + } + + public static TheoryData ValidRequestsData = + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "Valid Database Name", + Enabled = true + }, + new() + { + DatabaseId = "validDatabaseId123", + Name = "Another Valid Database", + Enabled = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateDatabaseRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = null!, + Name = "Pingu" + }, + new() + { + DatabaseId = "", + Name = "Pingu" + }, + new() + { + DatabaseId = "invalid chars!", + Name = "Pingu" + }, + new() + { + DatabaseId = ".startsWithSymbol", + Name = "Pingu" + }, + new() + { + DatabaseId = new string('a', 37), + Name = "Pingu" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = new string('a', 129) + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateDatabaseRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateDatabaseRequest + { + DatabaseId = "", + Name = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateDatabaseRequest + { + DatabaseId = "", + Name = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatetimeAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatetimeAttributeRequestTests.cs new file mode 100644 index 00000000..65e82615 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDatetimeAttributeRequestTests.cs @@ -0,0 +1,120 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateDatetimeAttributeRequestTests : CreateAttributeBaseRequestTests +{ + protected override CreateDatetimeAttributeRequest CreateValidCreateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateDatetimeAttributeRequest(); + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var defaultValue = DateTime.UtcNow; + + var request = new CreateDatetimeAttributeRequest(); + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + public static TheoryData ValidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = DateTime.UtcNow, + Required = false + } + }; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateDatetimeAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = DateTime.UtcNow, + Required = true + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateDatetimeAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateDatetimeAttributeRequest + { + Default = DateTime.UtcNow, + Required = true + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateDatetimeAttributeRequest + { + Default = DateTime.UtcNow, + Required = true + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestBuilderTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestBuilderTests.cs new file mode 100644 index 00000000..3701a9c2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestBuilderTests.cs @@ -0,0 +1,339 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; +using static PinguApps.Appwrite.Shared.Requests.Databases.ICreateDocumentRequestBuilder; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateDocumentRequestBuilderTests +{ + [Fact] + public void CreateBuilder_ReturnsNewBuilderInstance() + { + // Act + var builder = CreateDocumentRequest.CreateBuilder(); + + // Assert + Assert.NotNull(builder); + Assert.IsAssignableFrom(builder); + } + + [Fact] + public void WithDatabaseId_SetsDatabaseId_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var databaseId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithDatabaseId(databaseId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(databaseId, request.DatabaseId); + } + + [Fact] + public void WithCollectionId_SetsCollectionId_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var collectionId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithCollectionId(collectionId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(collectionId, request.CollectionId); + } + + [Fact] + public void WithDocumentId_SetsDocumentId_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var documentId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithDocumentId(documentId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(documentId, request.DocumentId); + } + + [Fact] + public void WithPermissions_SetsPermissions_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var permissions = new List { Permission.Read().Any() }; + + // Act + var result = builder.WithPermissions(permissions); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Same(permissions, request.Permissions); + } + + [Fact] + public void AddPermission_AddsPermissionToList_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var permission = Permission.Read().Any(); + + // Act + var result = builder.AddPermission(permission); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Contains(permission, request.Permissions); + Assert.Single(request.Permissions); + } + + [Fact] + public void AddPermission_CanAddMultiplePermissions_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + var permission1 = Permission.Read().Any(); + var permission2 = Permission.Write().Any(); + + // Act + builder.AddPermission(permission1) + .AddPermission(permission2); + var request = builder.Build(); + + // Assert + Assert.Equal(2, request.Permissions.Count); + Assert.Contains(permission1, request.Permissions); + Assert.Contains(permission2, request.Permissions); + } + + [Fact] + public void AddField_AddsFieldToData_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + const string fieldName = "testField"; + const string fieldValue = "testValue"; + + // Act + var result = builder.AddField(fieldName, fieldValue); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(fieldValue, request.Data[fieldName]); + } + + [Fact] + public void AddField_CanAddMultipleFields_ReturnsBuilder() + { + // Arrange + var builder = CreateDocumentRequest.CreateBuilder(); + + // Act + builder.AddField("string", "value") + .AddField("number", 42) + .AddField("boolean", true) + .AddField("null", null); + + var request = builder.Build(); + + // Assert + Assert.Equal(4, request.Data.Count); + Assert.Equal("value", request.Data["string"]); + Assert.Equal(42, request.Data["number"]); + Assert.Equal(true, request.Data["boolean"]); + Assert.Null(request.Data["null"]); + } + + [Fact] + public void Build_CreatesRequestWithAllSetValues() + { + // Arrange + var databaseId = IdUtils.GenerateUniqueId(); + var collectionId = IdUtils.GenerateUniqueId(); + var documentId = IdUtils.GenerateUniqueId(); + var permissions = new List { Permission.Read().Any() }; + const string fieldName = "testField"; + const string fieldValue = "testValue"; + + // Act + var request = CreateDocumentRequest.CreateBuilder() + .WithDatabaseId(databaseId) + .WithCollectionId(collectionId) + .WithDocumentId(documentId) + .WithPermissions(permissions) + .AddField(fieldName, fieldValue) + .Build(); + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + Assert.Equal(collectionId, request.CollectionId); + Assert.Equal(documentId, request.DocumentId); + Assert.Same(permissions, request.Permissions); + Assert.Equal(fieldValue, request.Data[fieldName]); + } + + [Fact] + public void Build_WithNoFieldsAdded_CreatesEmptyDataDictionary() + { + // Act + var request = CreateDocumentRequest.CreateBuilder().Build(); + + // Assert + Assert.NotNull(request.Data); + Assert.Empty(request.Data); + } + + public class TestClass + { + public string? StringProp { get; set; } + public int IntProp { get; set; } + [JsonIgnore] + public string? IgnoredProp { get; set; } + [JsonPropertyName("custom")] + public string? CustomNameProp { get; set; } + } + + [Fact] + public void WithData_NullData_ThrowsArgumentNullException() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + TestClass? nullData = null; + var ex = Assert.Throws(() => builder.WithData(nullData)); + Assert.Equal("data", ex.ParamName); + } + + [Fact] + public void WithData_NullOptions_UsesDefaultOptions() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { StringProp = "test" }; + builder.WithData(data, options: null); + // Assert AddField was called with "stringProp" and "test" + // You'll need to mock/verify this based on your implementation + } + + [Fact] + public void WithData_CustomOptions_AppliesOptions() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { StringProp = "test" }; + builder.WithData(data, options => options.IgnoreNullValues = false); + // Assert all properties were added, including nulls + } + + [Fact] + public void WithData_PropertyFilter_AppliesFilter() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { StringProp = "test", IntProp = 42 }; + builder.WithData(data, options => + options.PropertyFilter = prop => prop.PropertyType == typeof(string)); + // Assert only string properties were added + } + + [Fact] + public void WithData_JsonIgnoreAttribute_SkipsProperty() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { IgnoredProp = "ignored" }; + builder.WithData(data); + // Assert IgnoredProp was not added + } + + [Fact] + public void WithData_JsonPropertyNameAttribute_UsesCustomName() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { CustomNameProp = "test" }; + builder.WithData(data); + // Assert AddField was called with "custom" instead of "customNameProp" + } + + [Fact] + public void WithData_NullValue_IgnoredByDefault() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { StringProp = null }; + builder.WithData(data); + // Assert AddField was not called for StringProp + } + + [Fact] + public void WithData_NullValue_IncludedWhenIgnoreNullValuesFalse() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data = new TestClass { StringProp = null }; + builder.WithData(data, options => options.IgnoreNullValues = false); + // Assert AddField was called with null value + } + + [Fact] + public void WithData_PropertyCacheReuse_CachesProperties() + { + var builder = CreateDocumentRequest.CreateBuilder(); + + var data1 = new TestClass(); + var data2 = new TestClass(); + builder.WithData(data1); + builder.WithData(data2); + // Assert properties were cached (you'll need to expose cache or use reflection to verify) + } + + [Fact] + public void ShouldIncludeProperty_NoAttributeNoFilter_ReturnsTrue() + { + var options = new WithDataOptions(); + var prop = typeof(TestClass).GetProperty(nameof(TestClass.StringProp))!; + Assert.True(options.ShouldIncludeProperty(prop)); + } + + [Fact] + public void ShouldIncludeProperty_JsonIgnoreAttribute_ReturnsFalse() + { + var options = new WithDataOptions(); + var prop = typeof(TestClass).GetProperty(nameof(TestClass.IgnoredProp))!; + Assert.False(options.ShouldIncludeProperty(prop)); + } + + [Fact] + public void ShouldIncludeProperty_CustomFilterTrue_ReturnsTrue() + { + var options = new WithDataOptions + { + PropertyFilter = _ => true + }; + var prop = typeof(TestClass).GetProperty(nameof(TestClass.StringProp))!; + Assert.True(options.ShouldIncludeProperty(prop)); + } + + [Fact] + public void ShouldIncludeProperty_CustomFilterFalse_ReturnsFalse() + { + var options = new WithDataOptions + { + PropertyFilter = _ => false + }; + var prop = typeof(TestClass).GetProperty(nameof(TestClass.StringProp))!; + Assert.False(options.ShouldIncludeProperty(prop)); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestGenericTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestGenericTests.cs new file mode 100644 index 00000000..ef83e4a3 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateDocumentRequestGenericTests.cs @@ -0,0 +1,190 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; +using static PinguApps.Appwrite.Shared.Tests.Requests.Databases.CreateDocumentRequestGenericTests; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; + +public class CreateDocumentRequestGenericTests : DatabaseCollectionIdBaseRequestTests, CreateDocumentRequestValidator> +{ + // Test model for our generic type + public class CreateDocumentTestData + { + public string Name { get; set; } = string.Empty; + public int Age { get; set; } + } + + protected override CreateDocumentRequest CreateValidDatabaseCollectionIdRequest => new() + { + DocumentId = IdUtils.GenerateUniqueId(), + Data = new CreateDocumentTestData { Name = "Pingu", Age = 25 } + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateDocumentRequest(); + + // Assert + Assert.NotEmpty(request.DocumentId); + Assert.Null(request.Data); + Assert.NotNull(request.Permissions); + Assert.Empty(request.Permissions); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var documentId = IdUtils.GenerateUniqueId(); + var data = new CreateDocumentTestData { Name = "Pingu", Age = 30 }; + var permissions = new List { Permission.Read().Any() }; + + var request = new CreateDocumentRequest(); + + // Act + request.DocumentId = documentId; + request.Data = data; + request.Permissions = permissions; + + // Assert + Assert.Equal(documentId, request.DocumentId); + Assert.Equal(data, request.Data); + Assert.Equal(permissions, request.Permissions); + } + + public static TheoryData> ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + Data = new CreateDocumentTestData { Name = "Valid Name", Age = 25 }, + Permissions = [Permission.Read().Any()] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = "validDocumentId123", + Data = new CreateDocumentTestData { Name = "Another Valid Name", Age = 30 }, + Permissions = [] + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateDocumentRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData> InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = null!, + Data = new CreateDocumentTestData { Name = "Test", Age = 25 } + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = "", + Data = new CreateDocumentTestData { Name = "Test", Age = 25 } + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = "invalid chars!", + Data = new CreateDocumentTestData { Name = "Test", Age = 25 } + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = ".startsWithSymbol", + Data = new CreateDocumentTestData { Name = "Test", Age = 25 } + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = new string('a', 37), + Data = new CreateDocumentTestData { Name = "Test", Age = 25 } + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + Data = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = IdUtils.GenerateUniqueId(), + Data = new CreateDocumentTestData { Name = "Test", Age = 25 }, + Permissions = null! + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateDocumentRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = "", + Data = null!, + Permissions = null! + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateDocumentRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + DocumentId = "", + Data = null!, + Permissions = null! + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEmailAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEmailAttributeRequestTests.cs new file mode 100644 index 00000000..9d725ee8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEmailAttributeRequestTests.cs @@ -0,0 +1,64 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateEmailAttributeRequestTests : CreateStringAttributeBaseRequestTests +{ + protected override CreateEmailAttributeRequest CreateValidCreateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "pingu@example.com"; + + public static TheoryData ValidDefaultValues => + [ + "pingu@example.com", + "ugnip@mydomain.co.uk" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new CreateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "not an email address", + "something at something else dot com", + "@!.d" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new CreateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEnumAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEnumAttributeRequestTests.cs new file mode 100644 index 00000000..fc93636f --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateEnumAttributeRequestTests.cs @@ -0,0 +1,99 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateEnumAttributeRequestTests : CreateStringAttributeBaseRequestTests +{ + protected override CreateEnumAttributeRequest CreateValidCreateStringAttributeBaseRequest => new() + { + Elements = ["element1", "element2", "element3"] + }; + + protected override string ValidDefaultValue => "element2"; + + public static TheoryData ValidDefaultValues => + [ + "element1", + "element2", + "element3", + null + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string? value) + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = ["element1", "element2", "element3"], + Default = value + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "element4", + "", + "not an existing element at all" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsTrue(string value) + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = ["element1", "element2", "element3"], + Default = value + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + public static TheoryData> InvalidElementsValues => + [ + [], + [new string('a', 256)], + Enumerable.Range(0,101).Select(x => x.ToString()).ToList(), + [""] + ]; + + [Theory] + [MemberData(nameof(InvalidElementsValues))] + public static void IsValid_WithInvalidElements_ReturnsFalse(List elements) + { + // Arrange + var request = new CreateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = elements + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateFloatAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateFloatAttributeRequestTests.cs new file mode 100644 index 00000000..a790d3d1 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateFloatAttributeRequestTests.cs @@ -0,0 +1,175 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateFloatAttributeRequestTests : CreateAttributeBaseRequestTests +{ + protected override CreateFloatAttributeRequest CreateValidCreateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateFloatAttributeRequest(); + + // Assert + Assert.Null(request.Default); + Assert.Null(request.Min); + Assert.Null(request.Max); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var min = 1f; + var defaultValue = 5f; + var max = 10f; + + var request = new CreateFloatAttributeRequest(); + + // Act + request.Min = min; + request.Default = defaultValue; + request.Max = max; + + // Assert + Assert.Equal(min, request.Min); + Assert.Equal(defaultValue, request.Default); + Assert.Equal(max, request.Max); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = null, + Max = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 5, + Min = 0, + Max = 10, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = -10, + Min = null, + Max = null, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateFloatAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0f, + Min = null, + Max = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0f, + Min = 1f, + Max = null, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 1f, + Min = null, + Max = 0f, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = 1f, + Max = 0f, + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateFloatAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateFloatAttributeRequest + { + Default = 5f, + Required = true, + Min = 10f, + Max = 0f + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateFloatAttributeRequest + { + Default = 5f, + Required = true, + Min = 10f, + Max = 0f + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIPAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIPAttributeRequestTests.cs new file mode 100644 index 00000000..2d000a63 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIPAttributeRequestTests.cs @@ -0,0 +1,78 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateIPAttributeRequestTests : CreateStringAttributeBaseRequestTests +{ + protected override CreateIPAttributeRequest CreateValidCreateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "192.168.1.1"; + + public static TheoryData ValidDefaultValues => + [ + "192.0.2.1", + "192.168.1.1", + "2001:db8::1", + "2001:db8:85a3::8a2e:370:7334", + "::1", + "fe80::1" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new CreateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "256.256.256.256", + "192.168.1.1.1", + "192.168.1.", + ".192.168.1.1", + "192.168.1.1/24", + "192.168.1.x", + "192.168.1.*", + "abcd", + "", + "2001:db8::::1", + "2001:db8", + "2001:db8::/32", + "2001:dg8::1" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new CreateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIndexRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIndexRequestTests.cs new file mode 100644 index 00000000..5e29a672 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIndexRequestTests.cs @@ -0,0 +1,195 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateIndexRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override CreateIndexRequest CreateValidDatabaseCollectionIdRequest => new() + { + Key = "test_index", + IndexType = IndexType.Key, + Attributes = ["attr1"], + Orders = [SortDirection.Asc] + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateIndexRequest(); + + // Assert + Assert.Equal(string.Empty, request.Key); + Assert.Equal(IndexType.Key, request.IndexType); + Assert.Empty(request.Attributes); + Assert.Empty(request.Orders); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var key = "test_index"; + var indexType = IndexType.Unique; + var attributes = new List { "attr1", "attr2" }; + var orders = new List { SortDirection.Asc, SortDirection.Desc }; + + var request = new CreateIndexRequest(); + + // Act + request.Key = key; + request.IndexType = indexType; + request.Attributes = attributes; + request.Orders = orders; + + // Assert + Assert.Equal(key, request.Key); + Assert.Equal(indexType, request.IndexType); + Assert.Equal(attributes, request.Attributes); + Assert.Equal(orders, request.Orders); + } + + public static TheoryData ValidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "valid_index", + IndexType = IndexType.Key, + Attributes = ["attr1"], + Orders = [SortDirection.Asc] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "multi_index", + IndexType = IndexType.Unique, + Attributes = ["attr1", "attr2"], + Orders = [SortDirection.Asc, SortDirection.Desc] + } + }; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateIndexRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "", + IndexType = IndexType.Key, + Attributes = ["attr1"], + Orders = [SortDirection.Asc] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = null!, + IndexType = IndexType.Key, + Attributes = ["attr1"], + Orders = [SortDirection.Asc] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "valid_key", + IndexType = (IndexType) 9999, + Attributes = ["attr1"], + Orders = [SortDirection.Asc] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "valid_key", + IndexType = IndexType.Key, + Attributes = [new string('a', 33)], + Orders = [SortDirection.Asc] + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "valid_key", + IndexType = IndexType.Key, + Attributes = Enumerable.Range(0,101).Select(x => x.ToString()).ToList(), + Orders = Enumerable.Range(0,101).Select(x => SortDirection.Asc).ToList() + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "valid_key", + IndexType = IndexType.Key, + Attributes = ["attr1", "attr2"], + Orders = [SortDirection.Asc] + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateIndexRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "", + IndexType = IndexType.Key, + Attributes = [], + Orders = [] + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateIndexRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "", + IndexType = IndexType.Key, + Attributes = [], + Orders = [] + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIntegerAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIntegerAttributeRequestTests.cs new file mode 100644 index 00000000..3149cba0 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateIntegerAttributeRequestTests.cs @@ -0,0 +1,175 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateIntegerAttributeRequestTests : CreateAttributeBaseRequestTests +{ + protected override CreateIntegerAttributeRequest CreateValidCreateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateIntegerAttributeRequest(); + + // Assert + Assert.Null(request.Default); + Assert.Null(request.Min); + Assert.Null(request.Max); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var min = 1; + var defaultValue = 5; + var max = 10; + + var request = new CreateIntegerAttributeRequest(); + + // Act + request.Min = min; + request.Default = defaultValue; + request.Max = max; + + // Assert + Assert.Equal(min, request.Min); + Assert.Equal(defaultValue, request.Default); + Assert.Equal(max, request.Max); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = null, + Max = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 5, + Min = 0, + Max = 10, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = -10, + Min = null, + Max = null, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(CreateIntegerAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0, + Min = null, + Max = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0, + Min = 1, + Max = null, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 1, + Min = null, + Max = 0, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = 1, + Max = 0, + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateIntegerAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateIntegerAttributeRequest + { + Default = 5, + Required = true, + Min = 10, + Max = 0 + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateIntegerAttributeRequest + { + Default = 5, + Required = true, + Min = 10, + Max = 0 + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateRelationshipAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateRelationshipAttributeRequestTests.cs new file mode 100644 index 00000000..2b1a0267 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateRelationshipAttributeRequestTests.cs @@ -0,0 +1,194 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateRelationshipAttributeRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override CreateRelationshipAttributeRequest CreateValidDatabaseCollectionIdRequest => new() + { + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey" + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateRelationshipAttributeRequest(); + + // Assert + Assert.Equal(string.Empty, request.RelatedCollectionId); + Assert.Equal(RelationType.OneToOne, request.Type); + Assert.False(request.TwoWay); + Assert.Equal(string.Empty, request.Key); + Assert.NotEmpty(request.TwoWayKey); + Assert.Equal(OnDelete.Restrict, request.OnDelete); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var relatedCollectionId = IdUtils.GenerateUniqueId(); + var type = RelationType.ManyToMany; + var twoWay = true; + var key = "validKey"; + var twoWayKey = "validTwoWayKey"; + var onDelete = OnDelete.Cascade; + var request = new CreateRelationshipAttributeRequest(); + + // Act + request.RelatedCollectionId = relatedCollectionId; + request.Type = type; + request.TwoWay = twoWay; + request.Key = key; + request.TwoWayKey = twoWayKey; + request.OnDelete = onDelete; + + // Assert + Assert.Equal(relatedCollectionId, request.RelatedCollectionId); + Assert.Equal(type, request.Type); + Assert.Equal(twoWay, request.TwoWay); + Assert.Equal(key, request.Key); + Assert.Equal(twoWayKey, request.TwoWayKey); + Assert.Equal(onDelete, request.OnDelete); + } + + [Fact] + public void IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey" + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequests => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = null!, + Key = "validKey" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = "", + Key = "validKey" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Type = (RelationType) 999 + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + TwoWayKey = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + TwoWayKey = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + OnDelete = (OnDelete) 999 + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequests))] + public void IsValid_WithInvalidData_ReturnsFalse(CreateRelationshipAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "", + Type = (RelationType)999, + TwoWayKey = "", + OnDelete = (OnDelete)999 + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new CreateRelationshipAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + RelatedCollectionId = IdUtils.GenerateUniqueId(), + Key = "", + Type = (RelationType)999, + TwoWayKey = "", + OnDelete = (OnDelete)999 + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeBaseRequestTests.cs new file mode 100644 index 00000000..7d3b6ff8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeBaseRequestTests.cs @@ -0,0 +1,107 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class CreateStringAttributeBaseRequestTests : CreateAttributeBaseRequestTests + where TRequest : CreateStringAttributeBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidCreateAttributeBaseRequest => CreateValidCreateStringAttributeBaseRequest; + + protected abstract TRequest CreateValidCreateStringAttributeBaseRequest { get; } + + protected abstract string ValidDefaultValue { get; } + + [Fact] + public void CreateStringAttributeBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidCreateStringAttributeBaseRequest; + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void CreateStringAttributeBase_Properties_CanBeSet() + { + // Arrange + var defaultValue = ValidDefaultValue; + var request = CreateValidCreateStringAttributeBaseRequest; + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + [Fact] + public void CreateStringAttributeBase_IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = CreateValidCreateStringAttributeBaseRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + request.Default = null; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void CreateStringAttributeBase_IsValid_WithInvalidData_ReturnsFalse() + { + // Arrange + var request = CreateValidCreateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void CreateStringAttributeBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidCreateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void CreateStringAttributeBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidCreateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeRequestTests.cs new file mode 100644 index 00000000..52cfc5c6 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateStringAttributeRequestTests.cs @@ -0,0 +1,89 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateStringAttributeRequestTests : CreateStringAttributeBaseRequestTests +{ + protected override CreateStringAttributeRequest CreateValidCreateStringAttributeBaseRequest => new() + { + Size = 100 + }; + + protected override string ValidDefaultValue => "A valid string"; + + public static TheoryData ValidRequests => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = null, + Size = 1, + Encrypt = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = new string('1', 10), + Size = 10, + Encrypt = true + } + ]; + + [Theory] + [MemberData(nameof(ValidRequests))] + public void IsValid_WithValidRequest_ReturnsTrue(CreateStringAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequests => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = null, + Size = 0, + Encrypt = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = null, + Size = 1_073_741_825, + Encrypt = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = new string('a', 11), + Size = 10, + Encrypt = false + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequests))] + public void IsValid_WithInvalidRequest_ReturnsFalse(CreateStringAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateURLAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateURLAttributeRequestTests.cs new file mode 100644 index 00000000..9e02df61 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/CreateURLAttributeRequestTests.cs @@ -0,0 +1,88 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class CreateURLAttributeRequestTests : CreateStringAttributeBaseRequestTests +{ + protected override CreateURLAttributeRequest CreateValidCreateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "https://example.com"; + + public static TheoryData ValidDefaultValues => + [ + "https://example.com", + "https://www.example.com", + "http://example.com", + "https://example.com/path/to/resource.html", + "https://example.com/path/to/resource", + "https://example.com/path-with-hyphens", + "https://example.com/path_with_underscores", + "https://example.com?key=value", + "https://example.com/?key=value", + "https://example.com/path?key1=value1&key2=value2", + "https://example.com/path?key=value%20with%20encoded%20spaces", + "https://localhost:1234", + "http://127.0.0.1:8080", + "https://example.com:8080/path", + "https://user:pass@example.com", + "https://user:pass@example.com:8080/path?query=value", + "https://example.com/path?search=R%26D", + "https://example.com/path?currency=%24100", + "https://example.com/path(1)/resource", + "https://example.com/path?name=John+Doe", + "https://example.com#section", + "https://example.com/#section", + "https://example.com/path#section", + "https://example.com/path?query=value#section" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new CreateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "example.com", + "www.example.com", + "https:/example.com", + "https//example.com", + "https://exam ple.com", + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new CreateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionDocumentIdBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionDocumentIdBaseRequestTests.cs new file mode 100644 index 00000000..03d5f468 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionDocumentIdBaseRequestTests.cs @@ -0,0 +1,108 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class DatabaseCollectionDocumentIdBaseRequestTests : DatabaseCollectionIdBaseRequestTests + where TRequest : DatabaseCollectionDocumentIdBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidDatabaseCollectionIdRequest + { + get + { + var request = CreateValidDatabaseCollectionDocumentIdRequest; + request.DocumentId = IdUtils.GenerateUniqueId(); + return request; + } + } + + protected abstract TRequest CreateValidDatabaseCollectionDocumentIdRequest { get; } + + [Fact] + public void KeyBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidDatabaseCollectionDocumentIdRequest; + + // Assert + Assert.Equal(string.Empty, request.DocumentId); + } + + [Fact] + public void KeyBase_Properties_CanBeSet() + { + // Arrange + var value = "validId"; + var request = CreateValidDatabaseCollectionDocumentIdRequest; + + // Act + request.DocumentId = value; + + // Assert + Assert.Equal(value, request.DocumentId); + } + + [Fact] + public void KeyBase_IsValid_WithValidTeamId_ReturnsTrue() + { + // Arrange + var request = CreateValidDatabaseCollectionDocumentIdRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.DocumentId = "valid_Team-Id."; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void KeyBase_IsValid_WithInvalidData_ReturnsFalse(string? id) + { + // Arrange + var request = CreateValidDatabaseCollectionDocumentIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = id!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionDocumentIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = string.Empty; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionDocumentIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = string.Empty; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequestTests.cs new file mode 100644 index 00000000..10d13640 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdAttributeKeyBaseRequestTests.cs @@ -0,0 +1,108 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class DatabaseCollectionIdAttributeKeyBaseRequestTests : DatabaseCollectionIdBaseRequestTests + where TRequest : DatabaseCollectionIdAttributeKeyBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidDatabaseCollectionIdRequest + { + get + { + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + request.Key = IdUtils.GenerateUniqueId(); + return request; + } + } + + protected abstract TRequest CreateValidDatabaseCollectionIdAttributeKeyRequest { get; } + + [Fact] + public void KeyBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + + // Assert + Assert.Equal(string.Empty, request.Key); + } + + [Fact] + public void KeyBase_Properties_CanBeSet() + { + // Arrange + var value = "validId"; + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + + // Act + request.Key = value; + + // Assert + Assert.Equal(value, request.Key); + } + + [Fact] + public void KeyBase_IsValid_WithValidTeamId_ReturnsTrue() + { + // Arrange + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "ValidKey"; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void KeyBase_IsValid_WithInvalidData_ReturnsFalse(string? id) + { + // Arrange + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = id!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = string.Empty; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdAttributeKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = string.Empty; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdBaseRequestTests.cs new file mode 100644 index 00000000..417f909d --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdBaseRequestTests.cs @@ -0,0 +1,104 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class DatabaseCollectionIdBaseRequestTests : DatabaseIdBaseRequestTests + where TRequest : DatabaseCollectionIdBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidRequest + { + get + { + var request = CreateValidDatabaseCollectionIdRequest; + request.CollectionId = IdUtils.GenerateUniqueId(); + return request; + } + } + + protected abstract TRequest CreateValidDatabaseCollectionIdRequest { get; } + + [Fact] + public void CollectionIdBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidDatabaseCollectionIdRequest; + + // Assert + Assert.Equal(string.Empty, request.CollectionId); + } + + [Fact] + public void CollectionIdBase_Properties_CanBeSet() + { + // Arrange + var collectionIdValue = "validId"; + var request = CreateValidDatabaseCollectionIdRequest; + + // Act + request.CollectionId = collectionIdValue; + + // Assert + Assert.Equal(collectionIdValue, request.CollectionId); + } + + [Fact] + public void CollectionIdBase_IsValid_WithValidTeamId_ReturnsTrue() + { + // Arrange + var request = CreateValidDatabaseCollectionIdRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void CollectionIdBase_IsValid_WithInvalidData_ReturnsFalse(string? id) + { + // Arrange + var request = CreateValidDatabaseCollectionIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = id!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void CollectionIdBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = string.Empty; // Invalid Id + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void CollectionIdBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = string.Empty; // Invalid Id + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequestTests.cs new file mode 100644 index 00000000..4c756d65 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseCollectionIdIndexKeyBaseRequestTests.cs @@ -0,0 +1,108 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class DatabaseCollectionIdIndexKeyBaseRequestTests : DatabaseCollectionIdBaseRequestTests + where TRequest : DatabaseCollectionIdIndexKeyBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidDatabaseCollectionIdRequest + { + get + { + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + request.Key = IdUtils.GenerateUniqueId(); + return request; + } + } + + protected abstract TRequest CreateValidDatabaseCollectionIdIndexKeyRequest { get; } + + [Fact] + public void KeyBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + + // Assert + Assert.Equal(string.Empty, request.Key); + } + + [Fact] + public void KeyBase_Properties_CanBeSet() + { + // Arrange + var value = "validId"; + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + + // Act + request.Key = value; + + // Assert + Assert.Equal(value, request.Key); + } + + [Fact] + public void KeyBase_IsValid_WithValidTeamId_ReturnsTrue() + { + // Arrange + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void KeyBase_IsValid_WithInvalidData_ReturnsFalse(string? id) + { + // Arrange + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = id!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = string.Empty; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void KeyBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidDatabaseCollectionIdIndexKeyRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = string.Empty; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseIdBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseIdBaseRequestTests.cs new file mode 100644 index 00000000..faa2f371 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DatabaseIdBaseRequestTests.cs @@ -0,0 +1,89 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class DatabaseIdBaseRequestTests + where TRequest : DatabaseIdBaseRequest + where TValidator : AbstractValidator, new() +{ + protected abstract TRequest CreateValidRequest { get; } + + [Fact] + public void DatabaseIdBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidRequest; + + // Assert + Assert.Equal(string.Empty, request.DatabaseId); + } + + [Fact] + public void DatabaseIdBase_Properties_CanBeSet() + { + // Arrange + var teamIdValue = "validId"; + var request = CreateValidRequest; + + // Act + request.DatabaseId = teamIdValue; + + // Assert + Assert.Equal(teamIdValue, request.DatabaseId); + } + + [Fact] + public void DatabaseIdBase_IsValid_WithValidTeamId_ReturnsTrue() + { + // Arrange + var request = CreateValidRequest; + request.DatabaseId = "valid_Team-Id."; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void DatabaseIdBase_IsValid_WithInvalidData_ReturnsFalse(string? id) + { + // Arrange + var request = CreateValidRequest; + request.DatabaseId = id!; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void DatabaseIdBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidRequest; + request.DatabaseId = string.Empty; // Invalid Id + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void DatabaseIdBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidRequest; + request.DatabaseId = string.Empty; // Invalid Id + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteAttributeRequestTests.cs new file mode 100644 index 00000000..a6568d87 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteAttributeRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class DeleteAttributeRequestTests : DatabaseCollectionIdAttributeKeyBaseRequestTests +{ + protected override DeleteAttributeRequest CreateValidDatabaseCollectionIdAttributeKeyRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteCollectionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteCollectionRequestTests.cs new file mode 100644 index 00000000..4363950b --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteCollectionRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class DeleteCollectionRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override DeleteCollectionRequest CreateValidDatabaseCollectionIdRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDatabaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDatabaseRequestTests.cs new file mode 100644 index 00000000..00cce789 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDatabaseRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class DeleteDatabaseRequestTests : DatabaseIdBaseRequestTests +{ + protected override DeleteDatabaseRequest CreateValidRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDocumentRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDocumentRequestTests.cs new file mode 100644 index 00000000..919a8ace --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteDocumentRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class DeleteDocumentRequestTests : DatabaseCollectionDocumentIdBaseRequestTests +{ + protected override DeleteDocumentRequest CreateValidDatabaseCollectionDocumentIdRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteIndexRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteIndexRequestTests.cs new file mode 100644 index 00000000..a0a1f395 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/DeleteIndexRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class DeleteIndexRequestTests : DatabaseCollectionIdIndexKeyBaseRequestTests +{ + protected override DeleteIndexRequest CreateValidDatabaseCollectionIdIndexKeyRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetAttributeRequestTests.cs new file mode 100644 index 00000000..2c764618 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetAttributeRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class GetAttributeRequestTests : DatabaseCollectionIdAttributeKeyBaseRequestTests +{ + protected override GetAttributeRequest CreateValidDatabaseCollectionIdAttributeKeyRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetCollectionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetCollectionRequestTests.cs new file mode 100644 index 00000000..136723e2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetCollectionRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class GetCollectionRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override GetCollectionRequest CreateValidDatabaseCollectionIdRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDatabaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDatabaseRequestTests.cs new file mode 100644 index 00000000..e9cb681b --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDatabaseRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class GetDatabaseRequestTests : DatabaseIdBaseRequestTests +{ + protected override GetDatabaseRequest CreateValidRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDocumentRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDocumentRequestTests.cs new file mode 100644 index 00000000..9dcd6c89 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetDocumentRequestTests.cs @@ -0,0 +1,150 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class GetDocumentRequestTests : DatabaseCollectionDocumentIdBaseRequestTests +{ + protected override GetDocumentRequest CreateValidDatabaseCollectionDocumentIdRequest => new(); + + [Fact] + public void QueryBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new GetDocumentRequest(); + + // Assert + Assert.Null(request.Queries); + } + + [Fact] + public void QueryBase_Properties_CanBeSet() + { + // Arrange + var attributeName = "attributeName"; + var value = "value"; + List queries = [Query.Equal(attributeName, value)]; + + var request = new GetDocumentRequest(); + + // Act + request.Queries = queries; + + // Assert + Assert.Collection(request.Queries, query => + { + Assert.Equal(attributeName, query.Attribute); + Assert.NotNull(query.Values); + Assert.Collection(query.Values, v => + { + Assert.Equal(value, v); + }); + Assert.Equal("equal", query.Method); + }); + } + + [Fact] + public void QueryBase_IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + request.Queries = [Query.Equal("attributeName", "value")]; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithNullQueries_ReturnsTrue() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithInvalidData_QueryTooLarge_ReturnsFalse() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + request.Queries = [Query.Equal("attributeName", new string('a', 4097))]; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithInvalidData_TooManyQueries_ReturnsFalse() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void QueryBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void QueryBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new GetDocumentRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.DocumentId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetIndexRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetIndexRequestTests.cs new file mode 100644 index 00000000..0f5273e9 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/GetIndexRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class GetIndexRequestTests : DatabaseCollectionIdIndexKeyBaseRequestTests +{ + protected override GetIndexRequest CreateValidDatabaseCollectionIdIndexKeyRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListAttributesRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListAttributesRequestTests.cs new file mode 100644 index 00000000..6f02eaf7 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListAttributesRequestTests.cs @@ -0,0 +1,134 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class ListAttributesRequestTests : QueryBaseRequestTests +{ + protected override ListAttributesRequest CreateValidRequest => new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new ListAttributesRequest(); + + // Assert + Assert.Equal(string.Empty, request.DatabaseId); + Assert.Equal(string.Empty, request.CollectionId); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var databaseId = "testDatabaseId"; + var collectionId = "testCollectionId"; + + var request = new ListAttributesRequest(); + + // Act + request.DatabaseId = databaseId; + request.CollectionId = collectionId; + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + Assert.Equal(collectionId, request.CollectionId); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = "validDatabaseId123", + CollectionId = "validCollectionId123" + }, + new() + { + DatabaseId = "anotherValidDatabaseId", + CollectionId = "anotherValidCollectionId" + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(ListAttributesRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = null!, + CollectionId = IdUtils.GenerateUniqueId() + }, + new() + { + DatabaseId = "", + CollectionId = IdUtils.GenerateUniqueId() + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "" + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(ListAttributesRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new ListAttributesRequest + { + DatabaseId = "", + CollectionId = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new ListAttributesRequest + { + DatabaseId = "", + CollectionId = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListCollectionsRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListCollectionsRequestTests.cs new file mode 100644 index 00000000..0581ff2f --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListCollectionsRequestTests.cs @@ -0,0 +1,113 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class ListCollectionsRequestTests : QuerySearchBaseRequestTests +{ + protected override ListCollectionsRequest CreateValidRequest => new() + { + DatabaseId = IdUtils.GenerateUniqueId() + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new ListCollectionsRequest(); + + // Assert + Assert.Equal(string.Empty, request.DatabaseId); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var databaseId = "testDatabaseId"; + + var request = new ListCollectionsRequest(); + + // Act + request.DatabaseId = databaseId; + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + } + + public static TheoryData ValidRequestsData = new() + { + new() + { + DatabaseId = "validDatabaseId123" + }, + new() + { + DatabaseId = "anotherValidDatabaseId" + } + }; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(ListCollectionsRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = null! + }, + new() + { + DatabaseId = "" + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(ListCollectionsRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new ListCollectionsRequest + { + DatabaseId = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new ListCollectionsRequest + { + DatabaseId = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDatabasesRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDatabasesRequestTests.cs new file mode 100644 index 00000000..5e9f8751 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDatabasesRequestTests.cs @@ -0,0 +1,8 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class ListDatabasesRequestTests : QuerySearchBaseRequestTests +{ + protected override ListDatabasesRequest CreateValidRequest => new(); +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDocumentsRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDocumentsRequestTests.cs new file mode 100644 index 00000000..6f1264a2 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListDocumentsRequestTests.cs @@ -0,0 +1,134 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class ListDocumentsRequestTests : QueryBaseRequestTests +{ + protected override ListDocumentsRequest CreateValidRequest => new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId() + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new ListDocumentsRequest(); + + // Assert + Assert.Equal(string.Empty, request.DatabaseId); + Assert.Equal(string.Empty, request.CollectionId); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var databaseId = "testDatabaseId"; + var collectionId = "testCollectionId"; + + var request = new ListDocumentsRequest(); + + // Act + request.DatabaseId = databaseId; + request.CollectionId = collectionId; + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + Assert.Equal(collectionId, request.CollectionId); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = "validDatabaseId123", + CollectionId = "validCollectionId123" + }, + new() + { + DatabaseId = "anotherValidDatabaseId", + CollectionId = "anotherValidCollectionId" + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(ListDocumentsRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = null!, + CollectionId = IdUtils.GenerateUniqueId() + }, + new() + { + DatabaseId = "", + CollectionId = IdUtils.GenerateUniqueId() + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = "" + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(ListDocumentsRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = "", + CollectionId = "" + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new ListDocumentsRequest + { + DatabaseId = "", + CollectionId = "" + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListIndexesRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListIndexesRequestTests.cs new file mode 100644 index 00000000..760b15c8 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/ListIndexesRequestTests.cs @@ -0,0 +1,144 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class ListIndexesRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override ListIndexesRequest CreateValidDatabaseCollectionIdRequest => new(); + + + [Fact] + public void QueryBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new ListIndexesRequest(); + + // Assert + Assert.Null(request.Queries); + } + + [Fact] + public void QueryBase_Properties_CanBeSet() + { + // Arrange + var attributeName = "attributeName"; + var value = "value"; + List queries = [Query.Equal(attributeName, value)]; + + var request = new ListIndexesRequest(); + + // Act + request.Queries = queries; + + // Assert + Assert.Collection(request.Queries, query => + { + Assert.Equal(attributeName, query.Attribute); + Assert.NotNull(query.Values); + Assert.Collection(query.Values, v => + { + Assert.Equal(value, v); + }); + Assert.Equal("equal", query.Method); + }); + } + + [Fact] + public void QueryBase_IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Queries = [Query.Equal("attributeName", "value")]; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithNullQueries_ReturnsTrue() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithInvalidData_QueryTooLarge_ReturnsFalse() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Queries = [Query.Equal("attributeName", new string('a', 4097))]; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void QueryBase_IsValid_WithInvalidData_TooManyQueries_ReturnsFalse() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void QueryBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void QueryBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new ListIndexesRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Queries = Enumerable.Range(0, 101) + .Select(_ => Query.Equal("attributeName", "value")) + .ToList(); + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/NonGenericCreateDocumentRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/NonGenericCreateDocumentRequestTests.cs new file mode 100644 index 00000000..4732365d --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/NonGenericCreateDocumentRequestTests.cs @@ -0,0 +1,55 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class NonGenericCreateDocumentRequestTests : DatabaseCollectionIdBaseRequestTests>, CreateDocumentRequestValidator>> +{ + protected override CreateDocumentRequest> CreateValidDatabaseCollectionIdRequest => CreateDocumentRequest + .CreateBuilder() + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("name", "Pingu") + .Build(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new CreateDocumentRequest(); + + // Assert + Assert.NotEmpty(request.DocumentId); + Assert.Null(request.Data); + Assert.NotNull(request.Permissions); + Assert.Empty(request.Permissions); + } + + [Theory] + [InlineData("string", "value")] + [InlineData("number", 42)] + [InlineData("boolean", true)] + [InlineData("null", null)] + public void Data_CanStoreVariousTypes(string key, object? value) + { + // Arrange + var request = new CreateDocumentRequest(); + request.Data = []; + + // Act + request.Data[key] = value; + + // Assert + Assert.Equal(value, request.Data[key]); + } + + [Fact] + public void CreateBuilder_ReturnsNewBuilder() + { + // Act + var builder = CreateDocumentRequest.CreateBuilder(); + + // Assert + Assert.NotNull(builder); + Assert.IsAssignableFrom(builder); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateAttributeBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateAttributeBaseRequestTests.cs new file mode 100644 index 00000000..c13f3fb7 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateAttributeBaseRequestTests.cs @@ -0,0 +1,109 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class UpdateAttributeBaseRequestTests : DatabaseCollectionIdAttributeKeyBaseRequestTests + where TRequest : UpdateAttributeBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidDatabaseCollectionIdAttributeKeyRequest => CreateValidUpdateAttributeBaseRequest; + + protected abstract TRequest CreateValidUpdateAttributeBaseRequest { get; } + + [Fact] + public void UpdateAttributeBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidUpdateAttributeBaseRequest; + + // Assert + Assert.False(request.Required); + Assert.Null(request.NewKey); + } + + [Fact] + public void UpdateAttributeBase_Properties_CanBeSet() + { + // Arrange + var keyValue = "validKey"; + var request = CreateValidUpdateAttributeBaseRequest; + + // Act + request.Required = true; + request.NewKey = keyValue; + + // Assert + Assert.True(request.Required); + Assert.Equal(keyValue, request.NewKey); + } + + [Theory] + [InlineData(null, false)] + [InlineData("validKey", true)] + public void UpdateAttributeBase_IsValid_WithValidData_ReturnsTrue(string? key, bool required) + { + // Arrange + var request = CreateValidUpdateAttributeBaseRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + request.Required = required; + request.NewKey = key; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("")] + public void UpdateAttributeBase_IsValid_WithInvalidData_ReturnsFalse(string? key) + { + // Arrange + var request = CreateValidUpdateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = key; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void UpdateAttributeBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidUpdateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = ""; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void UpdateAttributeBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidUpdateAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = ""; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateBooleanAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateBooleanAttributeRequestTests.cs new file mode 100644 index 00000000..f6554e03 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateBooleanAttributeRequestTests.cs @@ -0,0 +1,135 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateBooleanAttributeRequestTests : UpdateAttributeBaseRequestTests +{ + protected override UpdateBooleanAttributeRequest CreateValidUpdateAttributeBaseRequest => new(); + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateBooleanAttributeRequest(); + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var defaultValue = true; + + var request = new UpdateBooleanAttributeRequest(); + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = true, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = false, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateBooleanAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = true, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = false, + Required = true + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateBooleanAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateBooleanAttributeRequest + { + Default = true, + Required = true + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateBooleanAttributeRequest + { + Default = true, + Required = true + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateCollectionRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateCollectionRequestTests.cs new file mode 100644 index 00000000..99863e54 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateCollectionRequestTests.cs @@ -0,0 +1,162 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateCollectionRequestTests : DatabaseCollectionIdBaseRequestTests +{ + protected override UpdateCollectionRequest CreateValidDatabaseCollectionIdRequest => new() + { + Name = "Pingu" + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateCollectionRequest(); + + // Assert + Assert.Equal(string.Empty, request.Name); + Assert.NotNull(request.Permissions); + Assert.False(request.DocumentSecurity); + Assert.False(request.Enabled); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var name = "Updated Collection"; + var permissions = new List { Permission.Read().Any() }; + var documentSecurity = true; + var enabled = true; + + var request = new UpdateCollectionRequest(); + + // Act + request.Name = name; + request.Permissions = permissions; + request.DocumentSecurity = documentSecurity; + request.Enabled = enabled; + + // Assert + Assert.Equal(name, request.Name); + Assert.Equal(permissions, request.Permissions); + Assert.Equal(documentSecurity, request.DocumentSecurity); + Assert.Equal(enabled, request.Enabled); + } + + public static TheoryData ValidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Valid Collection Name", + Permissions = [Permission.Read().Any()], + DocumentSecurity = true, + Enabled = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Another Valid Collection", + Permissions = [], + DocumentSecurity = false, + Enabled = false + } + }; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateCollectionRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = new string('a', 129) + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "Pingu", + Permissions = null! + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateCollectionRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "", + Permissions = null!, + DocumentSecurity = false, + Enabled = false + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateCollectionRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Name = "", + Permissions = null!, + DocumentSecurity = false, + Enabled = false + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatabaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatabaseRequestTests.cs new file mode 100644 index 00000000..78392f07 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatabaseRequestTests.cs @@ -0,0 +1,132 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateDatabaseRequestTests : DatabaseIdBaseRequestTests +{ + protected override UpdateDatabaseRequest CreateValidRequest => new() + { + Name = "Pingu" + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateDatabaseRequest(); + + // Assert + Assert.Equal(string.Empty, request.Name); + Assert.False(request.Enabled); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var name = "Updated Database"; + var enabled = true; + + var request = new UpdateDatabaseRequest(); + + // Act + request.Name = name; + request.Enabled = enabled; + + // Assert + Assert.Equal(name, request.Name); + Assert.Equal(enabled, request.Enabled); + } + + public static TheoryData ValidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "Valid Database Name", + Enabled = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "Another Valid Database", + Enabled = false + } + }; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateDatabaseRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData = new() + { + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = null! + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "" + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = new string('a', 129) + } + }; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateDatabaseRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "", + Enabled = false + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateDatabaseRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + Name = "", + Enabled = false + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatetimeAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatetimeAttributeRequestTests.cs new file mode 100644 index 00000000..58d4ea5d --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDatetimeAttributeRequestTests.cs @@ -0,0 +1,120 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateDatetimeAttributeRequestTests : UpdateAttributeBaseRequestTests +{ + protected override UpdateDatetimeAttributeRequest CreateValidUpdateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateDatetimeAttributeRequest(); + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var defaultValue = DateTime.UtcNow; + + var request = new UpdateDatetimeAttributeRequest(); + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = DateTime.UtcNow, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateDatetimeAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = DateTime.UtcNow, + Required = true + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateDatetimeAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateDatetimeAttributeRequest + { + Default = DateTime.UtcNow, + Required = true + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateDatetimeAttributeRequest + { + Default = DateTime.UtcNow, + Required = true + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestBuilderTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestBuilderTests.cs new file mode 100644 index 00000000..ed67a3dc --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestBuilderTests.cs @@ -0,0 +1,348 @@ +using System.Text.Json.Serialization; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateDocumentRequestBuilderTests +{ + [Fact] + public void CreateBuilder_ReturnsNewBuilderInstance() + { + // Act + var builder = UpdateDocumentRequest.CreateBuilder(); + + // Assert + Assert.NotNull(builder); + Assert.IsAssignableFrom(builder); + } + + [Fact] + public void WithDatabaseId_SetsDatabaseId_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var databaseId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithDatabaseId(databaseId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(databaseId, request.DatabaseId); + } + + [Fact] + public void WithCollectionId_SetsCollectionId_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var collectionId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithCollectionId(collectionId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(collectionId, request.CollectionId); + } + + [Fact] + public void WithDocumentId_SetsDocumentId_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var documentId = IdUtils.GenerateUniqueId(); + + // Act + var result = builder.WithDocumentId(documentId); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(documentId, request.DocumentId); + } + + [Fact] + public void WithPermissions_SetsPermissions_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var permissions = new List { Permission.Read().Any() }; + + // Act + var result = builder.WithPermissions(permissions); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Same(permissions, request.Permissions); + } + + [Fact] + public void AddPermission_AddsPermissionToList_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var permission = Permission.Read().Any(); + + // Act + var result = builder.AddPermission(permission); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Contains(permission, request.Permissions); + Assert.Single(request.Permissions); + } + + [Fact] + public void AddPermission_CanAddMultiplePermissions_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var permission1 = Permission.Read().Any(); + var permission2 = Permission.Write().Any(); + + // Act + builder.AddPermission(permission1) + .AddPermission(permission2); + var request = builder.Build(); + + // Assert + Assert.Equal(2, request.Permissions.Count); + Assert.Contains(permission1, request.Permissions); + Assert.Contains(permission2, request.Permissions); + } + + [Fact] + public void AddField_AddsFieldToData_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + const string fieldName = "testField"; + const string fieldValue = "testValue"; + + // Act + var result = builder.AddField(fieldName, fieldValue); + var request = result.Build(); + + // Assert + Assert.Same(builder, result); + Assert.Equal(fieldValue, request.Data[fieldName]); + } + + [Fact] + public void AddField_CanAddMultipleFields_ReturnsBuilder() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + + // Act + builder.AddField("string", "value") + .AddField("number", 42) + .AddField("boolean", true) + .AddField("null", null); + + var request = builder.Build(); + + // Assert + Assert.Equal(4, request.Data.Count); + Assert.Equal("value", request.Data["string"]); + Assert.Equal(42, request.Data["number"]); + Assert.Equal(true, request.Data["boolean"]); + Assert.Null(request.Data["null"]); + } + + [Fact] + public void Build_CreatesRequestWithAllSetValues() + { + // Arrange + var databaseId = IdUtils.GenerateUniqueId(); + var collectionId = IdUtils.GenerateUniqueId(); + var documentId = IdUtils.GenerateUniqueId(); + var permissions = new List { Permission.Read().Any() }; + const string fieldName = "testField"; + const string fieldValue = "testValue"; + + // Act + var request = UpdateDocumentRequest.CreateBuilder() + .WithDatabaseId(databaseId) + .WithCollectionId(collectionId) + .WithDocumentId(documentId) + .WithPermissions(permissions) + .AddField(fieldName, fieldValue) + .Build(); + + // Assert + Assert.Equal(databaseId, request.DatabaseId); + Assert.Equal(collectionId, request.CollectionId); + Assert.Equal(documentId, request.DocumentId); + Assert.Same(permissions, request.Permissions); + Assert.Equal(fieldValue, request.Data[fieldName]); + } + + [Fact] + public void Build_WithNoFieldsAdded_CreatesEmptyDataDictionary() + { + // Act + var request = UpdateDocumentRequest.CreateBuilder().Build(); + + // Assert + Assert.NotNull(request.Data); + Assert.Empty(request.Data); + } + + private class TestModel + { + public string? RegularProperty { get; set; } + + [JsonPropertyName("custom_name")] + public string? PropertyWithJsonName { get; set; } + + // Property that can't be read + public string WriteOnlyProperty + { + set { } + } + + public IEnumerable? CollectionProperty { get; set; } + } + + [Fact] + public void WithChanges_WhenBeforeIsNull_ThrowsArgumentNullException() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + TestModel? before = null; + var after = new TestModel(); + + // Act & Assert + var exception = Assert.Throws(() => builder.WithChanges(before!, after)); + Assert.Equal("before", exception.ParamName); + } + + [Fact] + public void WithChanges_WhenAfterIsNull_ThrowsArgumentNullException() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel(); + TestModel? after = null; + + // Act & Assert + var exception = Assert.Throws(() => builder.WithChanges(before, after!)); + Assert.Equal("after", exception.ParamName); + } + + [Fact] + public void WithChanges_WithWriteOnlyProperty_SkipsProperty() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel(); + var after = new TestModel(); + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.Empty(request.Data); + } + + [Fact] + public void WithChanges_WithJsonPropertyNameAttribute_UsesCustomName() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel { PropertyWithJsonName = "old" }; + var after = new TestModel { PropertyWithJsonName = "new" }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.True(request.Data.ContainsKey("custom_name")); + Assert.Equal("new", request.Data["custom_name"]); + } + + [Fact] + public void WithChanges_BothValuesNull_NoChange() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel { RegularProperty = null }; + var after = new TestModel { RegularProperty = null }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.Empty(request.Data); + } + + [Fact] + public void WithChanges_CollectionPropertyChanged_AddsToData() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel { CollectionProperty = [new object(), new object()] }; + var after = new TestModel { CollectionProperty = [new object()] }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.True(request.Data.ContainsKey("CollectionProperty")); + Assert.Equal(after.CollectionProperty, request.Data["CollectionProperty"]); + } + + [Fact] + public void WithChanges_CollectionPropertySameReference_NoChange() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var collection = new[] { new object() }; + var before = new TestModel { CollectionProperty = collection }; + var after = new TestModel { CollectionProperty = collection }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.Empty(request.Data); + } + + [Fact] + public void WithChanges_PropertyChangedFromNullToValue_AddsToData() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel { RegularProperty = null }; + var after = new TestModel { RegularProperty = "value" }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.True(request.Data.ContainsKey("RegularProperty")); + Assert.Equal("value", request.Data["RegularProperty"]); + } + + [Fact] + public void WithChanges_PropertyChangedFromValueToNull_AddsToData() + { + // Arrange + var builder = UpdateDocumentRequest.CreateBuilder(); + var before = new TestModel { RegularProperty = "value" }; + var after = new TestModel { RegularProperty = null }; + + // Act + var request = builder.WithChanges(before, after).Build(); + + // Assert + Assert.True(request.Data.ContainsKey("RegularProperty")); + Assert.Null(request.Data["RegularProperty"]); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestTests.cs new file mode 100644 index 00000000..fbd6a62f --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateDocumentRequestTests.cs @@ -0,0 +1,165 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateDocumentRequestTests : DatabaseCollectionDocumentIdBaseRequestTests +{ + protected override UpdateDocumentRequest CreateValidDatabaseCollectionDocumentIdRequest => new() + { + Data = new Dictionary + { + { "name", "Pingu" }, + { "age", 25 } + } + }; + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateDocumentRequest(); + + // Assert + Assert.NotNull(request.Data); + Assert.Empty(request.Data); + Assert.NotNull(request.Permissions); + Assert.Empty(request.Permissions); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var request = UpdateDocumentRequest + .CreateBuilder() + .AddField("name", "Pingu") + .AddField("age", 30) + .AddPermission(Permission.Read().Any()) + .Build(); + + // Assert + Assert.NotNull(request.Data); + var name = request.Data["name"]; + Assert.Equal("Pingu", name); + var age = request.Data["age"]; + Assert.Equal(30, age); + Assert.NotNull(request.Permissions); + Assert.Single(request.Permissions); + } + + public static TheoryData ValidRequestsData => + [ + UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("name", "Pingu") + .AddField("age", 25) + .AddPermission(Permission.Read().Any()) + .Build(), + UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .AddField("name", "Valid Name") + .AddField("age", 30) + .Build() + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateDocumentRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData + { + get + { + var data = new TheoryData(); + + var nullData = UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .Build(); + + nullData.Data = null!; + + data.Add(nullData); + + var nullPermissions = UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .Build(); + + nullPermissions.Permissions = null!; + + data.Add(nullPermissions); + + return data; + } + } + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateDocumentRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .Build(); + + request.Data = null!; + request.Permissions = null!; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = UpdateDocumentRequest + .CreateBuilder() + .WithDatabaseId(IdUtils.GenerateUniqueId()) + .WithCollectionId(IdUtils.GenerateUniqueId()) + .WithDocumentId(IdUtils.GenerateUniqueId()) + .Build(); + + request.Data = null!; + request.Permissions = null!; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEmailAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEmailAttributeRequestTests.cs new file mode 100644 index 00000000..8019ba61 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEmailAttributeRequestTests.cs @@ -0,0 +1,64 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateEmailAttributeRequestTests : UpdateStringAttributeBaseRequestTests +{ + protected override UpdateEmailAttributeRequest CreateValidUpdateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "pingu@example.com"; + + public static TheoryData ValidDefaultValues => + [ + "pingu@example.com", + "ugnip@mydomain.co.uk" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new UpdateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "not an email address", + "something at something else dot com", + "@!.d" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new UpdateEmailAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEnumAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEnumAttributeRequestTests.cs new file mode 100644 index 00000000..e595bffa --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateEnumAttributeRequestTests.cs @@ -0,0 +1,99 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateEnumAttributeRequestTests : UpdateStringAttributeBaseRequestTests +{ + protected override UpdateEnumAttributeRequest CreateValidUpdateStringAttributeBaseRequest => new() + { + Elements = ["element1", "element2", "element3"] + }; + + protected override string ValidDefaultValue => "element2"; + + public static TheoryData ValidDefaultValues => + [ + "element1", + "element2", + "element3", + null + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string? value) + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = ["element1", "element2", "element3"], + Default = value + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "element4", + "", + "not an existing element at all" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsTrue(string value) + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = ["element1", "element2", "element3"], + Default = value + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + public static TheoryData> InvalidElementsValues => + [ + [], + [new string('a', 256)], + Enumerable.Range(0,101).Select(x => x.ToString()).ToList(), + [""] + ]; + + [Theory] + [MemberData(nameof(InvalidElementsValues))] + public static void IsValid_WithInvalidElements_ReturnsFalse(List elements) + { + // Arrange + var request = new UpdateEnumAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Elements = elements + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateFloatAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateFloatAttributeRequestTests.cs new file mode 100644 index 00000000..0e401aa6 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateFloatAttributeRequestTests.cs @@ -0,0 +1,167 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateFloatAttributeRequestTests : UpdateAttributeBaseRequestTests +{ + protected override UpdateFloatAttributeRequest CreateValidUpdateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateFloatAttributeRequest(); + + // Assert + Assert.Null(request.Default); + Assert.Equal(double.MinValue, request.Min); + Assert.Equal(double.MaxValue, request.Max); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var min = 1f; + var defaultValue = 5f; + var max = 10f; + + var request = new UpdateFloatAttributeRequest(); + + // Act + request.Min = min; + request.Default = defaultValue; + request.Max = max; + + // Assert + Assert.Equal(min, request.Min); + Assert.Equal(defaultValue, request.Default); + Assert.Equal(max, request.Max); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 5, + Min = 0, + Max = 10, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = -10, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateFloatAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0f, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0f, + Min = 1f + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 1f, + Max = 0f, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = 1f, + Max = 0f, + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateFloatAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateFloatAttributeRequest + { + Default = 5f, + Required = true, + Min = 10f, + Max = 0f + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateFloatAttributeRequest + { + Default = 5f, + Required = true, + Min = 10f, + Max = 0f + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIPAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIPAttributeRequestTests.cs new file mode 100644 index 00000000..763f9f94 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIPAttributeRequestTests.cs @@ -0,0 +1,78 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateIPAttributeRequestTests : UpdateStringAttributeBaseRequestTests +{ + protected override UpdateIPAttributeRequest CreateValidUpdateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "192.168.1.1"; + + public static TheoryData ValidDefaultValues => + [ + "192.0.2.1", + "192.168.1.1", + "2001:db8::1", + "2001:db8:85a3::8a2e:370:7334", + "::1", + "fe80::1" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new UpdateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "256.256.256.256", + "192.168.1.1.1", + "192.168.1.", + ".192.168.1.1", + "192.168.1.1/24", + "192.168.1.x", + "192.168.1.*", + "abcd", + "", + "2001:db8::::1", + "2001:db8", + "2001:db8::/32", + "2001:dg8::1" + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new UpdateIPAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIntegerAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIntegerAttributeRequestTests.cs new file mode 100644 index 00000000..5c7a9dc7 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateIntegerAttributeRequestTests.cs @@ -0,0 +1,167 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateIntegerAttributeRequestTests : UpdateAttributeBaseRequestTests +{ + protected override UpdateIntegerAttributeRequest CreateValidUpdateAttributeBaseRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateIntegerAttributeRequest(); + + // Assert + Assert.Null(request.Default); + Assert.Equal(long.MinValue, request.Min); + Assert.Equal(long.MaxValue, request.Max); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var min = 1; + var defaultValue = 5; + var max = 10; + + var request = new UpdateIntegerAttributeRequest(); + + // Act + request.Min = min; + request.Default = defaultValue; + request.Max = max; + + // Assert + Assert.Equal(min, request.Min); + Assert.Equal(defaultValue, request.Default); + Assert.Equal(max, request.Max); + } + + public static TheoryData ValidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 5, + Min = 0, + Max = 10, + Required = false + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = -10, + Required = false + } + ]; + + [Theory] + [MemberData(nameof(ValidRequestsData))] + public void IsValid_WithValidData_ReturnsTrue(UpdateIntegerAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequestsData => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0, + Required = true + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 0, + Min = 1, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = 1, + Max = 0, + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = IdUtils.GenerateUniqueId(), + Default = null, + Min = 1, + Max = 0, + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequestsData))] + public void IsValid_WithInvalidData_ReturnsFalse(UpdateIntegerAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateIntegerAttributeRequest + { + Default = 5, + Required = true, + Min = 10, + Max = 0 + }; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateIntegerAttributeRequest + { + Default = 5, + Required = true, + Min = 10, + Max = 0 + }; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateRelationshipAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateRelationshipAttributeRequestTests.cs new file mode 100644 index 00000000..272f97d3 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateRelationshipAttributeRequestTests.cs @@ -0,0 +1,114 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateRelationshipAttributeRequestTests : DatabaseCollectionIdAttributeKeyBaseRequestTests +{ + protected override UpdateRelationshipAttributeRequest CreateValidDatabaseCollectionIdAttributeKeyRequest => new(); + + [Fact] + public void Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = new UpdateRelationshipAttributeRequest(); + + // Assert + Assert.Null(request.NewKey); + Assert.Null(request.OnDelete); + } + + [Fact] + public void Properties_CanBeSet() + { + // Arrange + var keyValue = "validKey"; + var onDeleteValue = OnDelete.SetNull; + var request = new UpdateRelationshipAttributeRequest(); + + // Act + request.NewKey = keyValue; + request.OnDelete = onDeleteValue; + + // Assert + Assert.Equal(keyValue, request.NewKey); + Assert.Equal(onDeleteValue, request.OnDelete); + } + + [Theory] + [InlineData(null, null)] + [InlineData("validKey", OnDelete.Restrict)] + [InlineData("validKey", OnDelete.Cascade)] + [InlineData("validKey", OnDelete.SetNull)] + public void IsValid_WithValidData_ReturnsTrue(string? key, OnDelete? onDelete) + { + // Arrange + var request = new UpdateRelationshipAttributeRequest(); + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + request.NewKey = key; + request.OnDelete = onDelete; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Theory] + [InlineData("", null)] + [InlineData(null, (OnDelete)999)] + public void IsValid_WithInvalidData_ReturnsFalse(string? key, OnDelete? onDelete) + { + // Arrange + var request = new UpdateRelationshipAttributeRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = key; + request.OnDelete = onDelete; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = new UpdateRelationshipAttributeRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = ""; + request.OnDelete = (OnDelete)999; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = new UpdateRelationshipAttributeRequest(); + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.NewKey = ""; + request.OnDelete = (OnDelete)999; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeBaseRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeBaseRequestTests.cs new file mode 100644 index 00000000..ee9e3781 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeBaseRequestTests.cs @@ -0,0 +1,107 @@ +using FluentValidation; +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public abstract class UpdateStringAttributeBaseRequestTests : UpdateAttributeBaseRequestTests + where TRequest : UpdateStringAttributeBaseRequest + where TValidator : AbstractValidator, new() +{ + protected sealed override TRequest CreateValidUpdateAttributeBaseRequest => CreateValidUpdateStringAttributeBaseRequest; + + protected abstract TRequest CreateValidUpdateStringAttributeBaseRequest { get; } + + protected abstract string ValidDefaultValue { get; } + + [Fact] + public void UpdateStringAttributeBase_Constructor_InitializesWithExpectedValues() + { + // Arrange & Act + var request = CreateValidUpdateStringAttributeBaseRequest; + + // Assert + Assert.Null(request.Default); + } + + [Fact] + public void UpdateStringAttributeBase_Properties_CanBeSet() + { + // Arrange + var defaultValue = ValidDefaultValue; + var request = CreateValidUpdateStringAttributeBaseRequest; + + // Act + request.Default = defaultValue; + + // Assert + Assert.Equal(defaultValue, request.Default); + } + + [Fact] + public void UpdateStringAttributeBase_IsValid_WithValidData_ReturnsTrue() + { + // Arrange + var request = CreateValidUpdateStringAttributeBaseRequest; + request.DatabaseId = "valid_Team-Id."; + request.CollectionId = "valid_Team-Id."; + request.Key = "validKey"; + request.Default = null; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + [Fact] + public void UpdateStringAttributeBase_IsValid_WithInvalidData_ReturnsFalse() + { + // Arrange + var request = CreateValidUpdateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } + + [Fact] + public void UpdateStringAttributeBase_Validate_WithThrowOnFailuresTrue_ThrowsValidationExceptionOnFailure() + { + // Arrange + var request = CreateValidUpdateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Assert + Assert.Throws(() => request.Validate(true)); + } + + [Fact] + public void UpdateStringAttributeBase_Validate_WithThrowOnFailuresFalse_ReturnsInvalidResultOnFailure() + { + // Arrange + var request = CreateValidUpdateStringAttributeBaseRequest; + request.DatabaseId = IdUtils.GenerateUniqueId(); + request.CollectionId = IdUtils.GenerateUniqueId(); + request.Key = "validKey"; + request.Required = true; + request.Default = ValidDefaultValue; + + // Act + var result = request.Validate(false); + + // Assert + Assert.False(result.IsValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeRequestTests.cs new file mode 100644 index 00000000..b6ef0dc4 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateStringAttributeRequestTests.cs @@ -0,0 +1,105 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateStringAttributeRequestTests : UpdateStringAttributeBaseRequestTests +{ + protected override UpdateStringAttributeRequest CreateValidUpdateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "Valid Value"; + + public static TheoryData ValidRequests => + [ + new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Size = 10, + Default = "Valid" + }, + new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Size = 1_073_741_824, + Default = "Valid Value" + }, + new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Size = 1_073_741_824, + Default = null + }, + new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Size = null, + Default = "Valid" + }, + new UpdateStringAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Size = null, + Default = null + } + ]; + + [Theory] + [MemberData(nameof(ValidRequests))] + public void IsValid_WithValidRequest_ReturnsTrue(UpdateStringAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidRequests => + [ + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = null, + Size = 0 + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = null, + Size = 1_073_741_825 + }, + new() + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = new string('a', 11), + Size = 10 + } + ]; + + [Theory] + [MemberData(nameof(InvalidRequests))] + public void IsValid_WithInvalidRequest_ReturnsFalse(UpdateStringAttributeRequest request) + { + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateURLAttributeRequestTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateURLAttributeRequestTests.cs new file mode 100644 index 00000000..29dfe090 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Requests/Databases/UpdateURLAttributeRequestTests.cs @@ -0,0 +1,88 @@ +using PinguApps.Appwrite.Shared.Requests.Databases; +using PinguApps.Appwrite.Shared.Requests.Databases.Validators; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Requests.Databases; +public class UpdateURLAttributeRequestTests : UpdateStringAttributeBaseRequestTests +{ + protected override UpdateURLAttributeRequest CreateValidUpdateStringAttributeBaseRequest => new(); + + protected override string ValidDefaultValue => "https://example.com"; + + public static TheoryData ValidDefaultValues => + [ + "https://example.com", + "https://www.example.com", + "http://example.com", + "https://example.com/path/to/resource.html", + "https://example.com/path/to/resource", + "https://example.com/path-with-hyphens", + "https://example.com/path_with_underscores", + "https://example.com?key=value", + "https://example.com/?key=value", + "https://example.com/path?key1=value1&key2=value2", + "https://example.com/path?key=value%20with%20encoded%20spaces", + "https://localhost:1234", + "http://127.0.0.1:8080", + "https://example.com:8080/path", + "https://user:pass@example.com", + "https://user:pass@example.com:8080/path?query=value", + "https://example.com/path?search=R%26D", + "https://example.com/path?currency=%24100", + "https://example.com/path(1)/resource", + "https://example.com/path?name=John+Doe", + "https://example.com#section", + "https://example.com/#section", + "https://example.com/path#section", + "https://example.com/path?query=value#section" + ]; + + [Theory] + [MemberData(nameof(ValidDefaultValues))] + public void IsValid_WithValidDefaults_ReturnsTrue(string defaultValue) + { + // Arrange + var request = new UpdateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.True(isValid); + } + + public static TheoryData InvalidDefaultValues => + [ + "example.com", + "www.example.com", + "https:/example.com", + "https//example.com", + "https://exam ple.com", + ]; + + [Theory] + [MemberData(nameof(InvalidDefaultValues))] + public void IsValid_WithInvalidDefaults_ReturnsFalse(string defaultValue) + { + // Arrange + var request = new UpdateURLAttributeRequest + { + DatabaseId = IdUtils.GenerateUniqueId(), + CollectionId = IdUtils.GenerateUniqueId(), + Key = "validKey", + Default = defaultValue + }; + + // Act + var isValid = request.IsValid(); + + // Assert + Assert.False(isValid); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeBooleanTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeBooleanTests.cs new file mode 100644 index 00000000..25d2da9d --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeBooleanTests.cs @@ -0,0 +1,92 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeBooleanTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeBoolean( + "a", + "boolean", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + true + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":true", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeBoolean( + "a", + "boolean", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + true + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeBoolean( + "a", + "boolean", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + true + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeBooleanResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("isEnabled", attribute.Key); + Assert.Equal("boolean", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.False(attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeDatetimeTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeDatetimeTests.cs new file mode 100644 index 00000000..7bf4f745 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeDatetimeTests.cs @@ -0,0 +1,116 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeDatetimeTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeDatetime( + "a", + "datetime", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "datetime", + DateTime.UtcNow + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"", json); + } + + [Fact] + public void Format_ShouldBeSerialized() + { + var attribute = new AttributeDatetime( + "a", + "datetime", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "datetime", + DateTime.UtcNow + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"format\":\"datetime\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeDatetime( + "a", + "datetime", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "datetime", + DateTime.UtcNow + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeDatetime( + "a", + "datetime", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "datetime", + DateTime.UtcNow + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeDatetimeResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("birthDay", attribute.Key); + Assert.Equal("datetime", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal("datetime", attribute.Format); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.Default?.ToUniversalTime()); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEmailTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEmailTests.cs new file mode 100644 index 00000000..4ce68010 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEmailTests.cs @@ -0,0 +1,116 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeEmailTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeEmail( + "a", + "email", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "email", + "default@example.com" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"default@example.com\"", json); + } + + [Fact] + public void Format_ShouldBeSerialized() + { + var attribute = new AttributeEmail( + "a", + "email", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "email", + "default@example.com" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"format\":\"email\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeEmail( + "a", + "email", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "email", + "default@example.com" + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeEmail( + "a", + "email", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "email", + "default@example.com" + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeEmailResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("userEmail", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal("email", attribute.Format); + Assert.Equal("default@example.com", attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEnumTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEnumTests.cs new file mode 100644 index 00000000..76226390 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeEnumTests.cs @@ -0,0 +1,142 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeEnumTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeEnum( + "a", + "string", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + ["element1", "element2"], + "enum", + "element1" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"element1\"", json); + } + + [Fact] + public void Elements_ShouldBeSerialized() + { + var attribute = new AttributeEnum( + "a", + "string", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + ["element1", "element2"], + "enum", + "element1" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"elements\":[\"element1\",\"element2\"]", json); + } + + [Fact] + public void Format_ShouldBeSerialized() + { + var attribute = new AttributeEnum( + "a", + "string", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + ["element1", "element2"], + "enum", + "element1" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"format\":\"enum\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeEnum( + "a", + "string", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + ["element1", "element2"], + "enum", + "element1" + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeEnum( + "a", + "string", + DatabaseElementStatus.Available, + null, + false, + false, + DateTime.UtcNow, + DateTime.UtcNow, + ["element1", "element2"], + "enum", + "element1" + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeEnumResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("status", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal(new List { "element" }, attribute.Elements); + Assert.Equal("enum", attribute.Format); + Assert.Equal("element", attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeFloatTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeFloatTests.cs new file mode 100644 index 00000000..d6c8d8d4 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeFloatTests.cs @@ -0,0 +1,142 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeFloatTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeFloat( + "percentageCompleted", + "double", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1.5f, + 10.5f, + 2.5f + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":2.5", json); + } + + [Fact] + public void Min_ShouldBeSerialized() + { + var attribute = new AttributeFloat( + "percentageCompleted", + "double", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1.5f, + 10.5f, + 2.5f + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"min\":1.5", json); + } + + [Fact] + public void Max_ShouldBeSerialized() + { + var attribute = new AttributeFloat( + "percentageCompleted", + "double", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1.5f, + 10.5f, + 2.5f + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"max\":10.5", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeFloat( + "percentageCompleted", + "double", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1.5f, + 10.5f, + 2.5f + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeFloat( + "percentageCompleted", + "double", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1.5f, + 10.5f, + 2.5f + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeFloatResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("percentageCompleted", attribute.Key); + Assert.Equal("double", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal(1.5f, attribute.Min); + Assert.Equal(10.5f, attribute.Max); + Assert.Equal(2.5f, attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIntegerTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIntegerTests.cs new file mode 100644 index 00000000..f8a9aa50 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIntegerTests.cs @@ -0,0 +1,142 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeIntegerTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeInteger( + "count", + "integer", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1, + 10, + 10 + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":10", json); + } + + [Fact] + public void Min_ShouldBeSerialized() + { + var attribute = new AttributeInteger( + "count", + "integer", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1, + 10, + 10 + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"min\":1", json); + } + + [Fact] + public void Max_ShouldBeSerialized() + { + var attribute = new AttributeInteger( + "count", + "integer", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1, + 10, + 10 + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"max\":10", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeInteger( + "count", + "integer", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1, + 10, + 10 + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeInteger( + "count", + "integer", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 1, + 10, + 10 + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeIntegerResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("count", attribute.Key); + Assert.Equal("integer", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal(1, attribute.Min); + Assert.Equal(10, attribute.Max); + Assert.Equal(10, attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIpTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIpTests.cs new file mode 100644 index 00000000..3e792dcb --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeIpTests.cs @@ -0,0 +1,116 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeIpTests +{ + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeIp( + "ipAddress", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "ip", + "192.0.2.0" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"192.0.2.0\"", json); + } + + [Fact] + public void Format_ShouldBeSerialized() + { + var attribute = new AttributeIp( + "ipAddress", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "ip", + "192.0.2.0" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"format\":\"ip\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeIp( + "ipAddress", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "ip", + "192.0.2.0" + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeIp( + "ipAddress", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "ip", + "192.0.2.0" + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeIpResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("ipAddress", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal("ip", attribute.Format); + Assert.Equal("192.0.2.0", attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeRelationshipTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeRelationshipTests.cs new file mode 100644 index 00000000..948a0a27 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeRelationshipTests.cs @@ -0,0 +1,232 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeRelationshipTests +{ + [Fact] + public void RelatedCollection_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"relatedCollection\":\"collection\"", json); + } + + [Fact] + public void RelationType_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"relationType\":\"oneToOne\"", json); + } + + [Fact] + public void TwoWay_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"twoWay\":false", json); + } + + [Fact] + public void TwoWayKey_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"twoWayKey\":\"string\"", json); + } + + [Fact] + public void OnDelete_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"onDelete\":\"restrict\"", json); + } + + [Fact] + public void Side_ShouldBeSerialized() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"side\":\"parent\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeRelationship( + "fullName", + "string", + DatabaseElementStatus.Available, + null, + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "collection", + RelationType.OneToOne, + false, + "string", + OnDelete.Restrict, + RelationshipSide.Parent + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeRelationshipResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("fullName", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal("collection", attribute.RelatedCollection); + Assert.Equal(RelationType.ManyToOne, attribute.RelationType); + Assert.False(attribute.TwoWay); + Assert.Equal("string", attribute.TwoWayKey); + Assert.Equal(OnDelete.Cascade, attribute.OnDelete); + Assert.Equal(RelationshipSide.Child, attribute.Side); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeStringTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeStringTests.cs new file mode 100644 index 00000000..70fd32bb --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeStringTests.cs @@ -0,0 +1,116 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeStringTests +{ + [Fact] + public void Size_ShouldBeSerialized() + { + var attribute = new AttributeString( + "fullName", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 128, + "default" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"size\":128", json); + } + + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeString( + "fullName", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 128, + "default" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"default\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeString( + "fullName", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 128, + "default" + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeString( + "fullName", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + 128, + "default" + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var json = TestConstants.AttributeStringResponse; + + var attribute = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("fullName", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal(128, attribute.Size); + Assert.Equal("default", attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeUrlTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeUrlTests.cs new file mode 100644 index 00000000..4bf1ec65 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributeUrlTests.cs @@ -0,0 +1,114 @@ +using System.Text.Json; +using Moq; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Responses.Interfaces; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributeUrlTests +{ + [Fact] + public void Format_ShouldBeSerialized() + { + var attribute = new AttributeUrl( + "githubUrl", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "url", + "http://example.com" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"format\":\"url\"", json); + } + + [Fact] + public void Default_ShouldBeSerialized() + { + var attribute = new AttributeUrl( + "githubUrl", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "url", + "http://example.com" + ); + + var json = JsonSerializer.Serialize(attribute); + Assert.Contains("\"default\":\"http://example.com\"", json); + } + + [Fact] + public void Accept_ShouldInvokeVisitor() + { + var attribute = new AttributeUrl( + "githubUrl", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "url", + "http://example.com" + ); + + var visitorMock = new Mock(); + attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + } + + [Fact] + public void AcceptT_ShouldInvokeVisitor() + { + var attribute = new AttributeUrl( + "githubUrl", + "string", + DatabaseElementStatus.Available, + "string", + true, + false, + DateTime.UtcNow, + DateTime.UtcNow, + "url", + "http://example.com" + ); + + var visitorMock = new Mock>(); + visitorMock.Setup(v => v.Visit(attribute)).Returns("Visited"); + + var result = attribute.Accept(visitorMock.Object); + + visitorMock.Verify(v => v.Visit(attribute), Times.Once); + Assert.Equal("Visited", result); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + var attribute = JsonSerializer.Deserialize(TestConstants.AttributeUrlResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(attribute); + Assert.Equal("githubUrl", attribute.Key); + Assert.Equal("string", attribute.Type); + Assert.Equal(DatabaseElementStatus.Available, attribute.Status); + Assert.Equal("string", attribute.Error); + Assert.True(attribute.Required); + Assert.False(attribute.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attribute.UpdatedAt.ToUniversalTime()); + Assert.Equal("url", attribute.Format); + Assert.Equal("http://example.com", attribute.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributesListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributesListTests.cs new file mode 100644 index 00000000..5375c07a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/AttributesListTests.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class AttributesListTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var attributes = new List + { + new AttributeBoolean("isEnabled", "boolean", DatabaseElementStatus.Available, "string", true, + false, DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), false) + }; + + // Act + var attributesList = new AttributesList(total, attributes); + + // Assert + Assert.Equal(total, attributesList.Total); + Assert.Equal(attributes, attributesList.Attributes); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var attributesList = JsonSerializer.Deserialize(TestConstants.AttributesListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(attributesList); + Assert.Equal(5, attributesList.Total); + Assert.Single(attributesList.Attributes); + + var attribute = attributesList.Attributes[0]; + Assert.IsType(attribute); + var attributeBoolean = (AttributeBoolean)attribute; + Assert.Equal("isEnabled", attributeBoolean.Key); + Assert.Equal("boolean", attributeBoolean.Type); + Assert.Equal(DatabaseElementStatus.Available, attributeBoolean.Status); + Assert.Equal("string", attributeBoolean.Error); + Assert.True(attributeBoolean.Required); + Assert.False(attributeBoolean.Array); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attributeBoolean.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), attributeBoolean.UpdatedAt.ToUniversalTime()); + Assert.False(attributeBoolean.Default); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionTests.cs new file mode 100644 index 00000000..2981ba39 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionTests.cs @@ -0,0 +1,69 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; +using Attribute = PinguApps.Appwrite.Shared.Responses.Attribute; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class CollectionTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var id = "5e5ea5c16897e"; + var createdAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var updatedAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var permissions = new List { Permission.Read().Any() }; + var databaseId = "5e5ea5c16897e"; + var name = "My Collection"; + var enabled = false; + var documentSecurity = true; + var attributes = new List + { + new AttributeBoolean("isEnabled", "boolean", DatabaseElementStatus.Available, "string", true, false, createdAt, updatedAt, false) + }; + var indexes = new List + { + new("index1", IndexType.Unique, DatabaseElementStatus.Available, "string", [], [], createdAt, updatedAt) + }; + + // Act + var collection = new Collection(id, createdAt, updatedAt, permissions, databaseId, name, enabled, documentSecurity, attributes, indexes); + + // Assert + Assert.Equal(id, collection.Id); + Assert.Equal(createdAt, collection.CreatedAt); + Assert.Equal(updatedAt, collection.UpdatedAt); + Assert.Equal(permissions, collection.Permissions); + Assert.Equal(databaseId, collection.DatabaseId); + Assert.Equal(name, collection.Name); + Assert.Equal(enabled, collection.Enabled); + Assert.Equal(documentSecurity, collection.DocumentSecurity); + Assert.Equal(attributes, collection.Attributes); + Assert.Equal(indexes, collection.Indexes); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var collection = JsonSerializer.Deserialize(TestConstants.CollectionResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(collection); + Assert.Equal("5e5ea5c16897e", collection.Id); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), collection.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), collection.UpdatedAt.ToUniversalTime()); + Assert.Single(collection.Permissions); + Assert.Equal(PermissionType.Read, collection.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, collection.Permissions[0].RoleType); + Assert.Equal("5e5ea5c16897e", collection.DatabaseId); + Assert.Equal("My Collection", collection.Name); + Assert.False(collection.Enabled); + Assert.True(collection.DocumentSecurity); + Assert.Single(collection.Attributes); + Assert.Single(collection.Indexes); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionsListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionsListTests.cs new file mode 100644 index 00000000..2dcfe681 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/CollectionsListTests.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class CollectionsListTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var collections = new List + { + new Collection( + "5e5ea5c16897e", + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), + [Permission.Read().Any()], + "5e5ea5c16897e", + "My Collection", + false, + true, + [ + new AttributeBoolean("isEnabled", "boolean", DatabaseElementStatus.Available, "string", true, false, DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), false) + ], + [ + new Index("index1", IndexType.Unique, DatabaseElementStatus.Available, "string", new List(), new List(), DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime()) + ] + ) + }; + + // Act + var collectionsList = new CollectionsList(total, collections); + + // Assert + Assert.Equal(total, collectionsList.Total); + Assert.Equal(collections, collectionsList.Collections); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var collectionsList = JsonSerializer.Deserialize(TestConstants.CollectionsListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(collectionsList); + Assert.Equal(5, collectionsList.Total); + Assert.Single(collectionsList.Collections); + var collection = collectionsList.Collections[0]; + Assert.Equal("5e5ea5c16897e", collection.Id); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), collection.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), collection.UpdatedAt.ToUniversalTime()); + Assert.Single(collection.Permissions); + Assert.Equal(PermissionType.Read, collection.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, collection.Permissions[0].RoleType); + Assert.Equal("5e5ea5c16897e", collection.DatabaseId); + Assert.Equal("My Collection", collection.Name); + Assert.False(collection.Enabled); + Assert.True(collection.DocumentSecurity); + Assert.Single(collection.Attributes); + Assert.Single(collection.Indexes); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabaseTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabaseTests.cs new file mode 100644 index 00000000..b8db1201 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabaseTests.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class DatabaseTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var id = "5e5ea5c16897e"; + var name = "My Database"; + var createdAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var updatedAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var enabled = false; + + // Act + var database = new Database(id, name, createdAt, updatedAt, enabled); + + // Assert + Assert.Equal(id, database.Id); + Assert.Equal(name, database.Name); + Assert.Equal(createdAt, database.CreatedAt); + Assert.Equal(updatedAt, database.UpdatedAt); + Assert.Equal(enabled, database.Enabled); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var database = JsonSerializer.Deserialize(TestConstants.DatabaseResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(database); + Assert.Equal("5e5ea5c16897e", database.Id); + Assert.Equal("My Database", database.Name); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), database.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), database.UpdatedAt.ToUniversalTime()); + Assert.False(database.Enabled); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabasesListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabasesListTests.cs new file mode 100644 index 00000000..837ecd1a --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DatabasesListTests.cs @@ -0,0 +1,43 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class DatabasesListTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var databases = new List + { + new("5e5ea5c16897e", "My Database", DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), false) + }; + + // Act + var databasesList = new DatabasesList(total, databases); + + // Assert + Assert.Equal(total, databasesList.Total); + Assert.Equal(databases, databasesList.Databases); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var databasesList = JsonSerializer.Deserialize(TestConstants.DatabasesListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(databasesList); + Assert.Equal(5, databasesList.Total); + Assert.Single(databasesList.Databases); + + var database = databasesList.Databases[0]; + Assert.Equal("5e5ea5c16897e", database.Id); + Assert.Equal("My Database", database.Name); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), database.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), database.UpdatedAt.ToUniversalTime()); + Assert.False(database.Enabled); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentTests.cs new file mode 100644 index 00000000..555f5bb7 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentTests.cs @@ -0,0 +1,183 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class DocumentTests +{ + [Fact] + public void Document_ShouldBeDeserialized_FromJson() + { + var document = JsonSerializer.Deserialize(TestConstants.DocumentResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(document); + Assert.Equal("5e5ea5c16897e", document.Id); + Assert.Equal("5e5ea5c15117e", document.CollectionId); + Assert.Equal("5e5ea5c15117e", document.DatabaseId); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), document.CreatedAt?.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), document.UpdatedAt?.ToUniversalTime()); + Assert.Single(document.Permissions); + Assert.Equal(PermissionType.Read, document.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, document.Permissions[0].RoleType); + Assert.Equal("a string prop", document.Data["str"]); + var dt = (DateTime?)document.Data["dt"]; + Assert.NotNull(dt); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), dt.Value.ToUniversalTime()); + Assert.Equal(5L, document.Data["num"]); + var num2 = document.Data["num2"]; + Assert.IsType(num2); + Assert.Equal(6.7, (float)num2, 0.0001); + Assert.Equal(true, document.Data["boo"]); + Assert.Equal(new List { "one", "two", "three" }, document.Data["lis"]); + } + + [Fact] + public void Indexer_ShouldReturnCorrectValue() + { + var data = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 } + }; + + var document = new Document( + "id", + "collectionId", + "databaseId", + DateTime.UtcNow, + DateTime.UtcNow, + [], + data + ); + + Assert.Equal("value1", document["key1"]); + Assert.Equal(123, document["key2"]); + Assert.Null(document["key3"]); + } + + [Fact] + public void GetValue_ShouldReturnCorrectValue() + { + var data = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 } + }; + + var document = new Document( + "id", + "collectionId", + "databaseId", + DateTime.UtcNow, + DateTime.UtcNow, + [], + data + ); + + Assert.Equal("value1", document.GetValue("key1")); + Assert.Equal(123, document.GetValue("key2")); + } + + [Fact] + public void GetValue_ShouldThrowException_WhenKeyNotFound() + { + var document = new Document( + "id", + "collectionId", + "databaseId", + DateTime.UtcNow, + DateTime.UtcNow, + [], + new Dictionary() + ); + + Assert.Throws(() => document.GetValue("key1")); + } + + [Fact] + public void GetValue_ShouldThrowException_WhenInvalidCast() + { + var data = new Dictionary + { + { "key1", "value1" } + }; + + var document = new Document( + "id", + "collectionId", + "databaseId", + DateTime.UtcNow, + DateTime.UtcNow, + [], + data + ); + + Assert.Throws(() => document.GetValue("key1")); + } + + [Fact] + public void TryGetValue_ReturnsTrue_WhenKeyExistsAndTypeMatches() + { + // Arrange + var data = new Dictionary + { + { "key1", "value1" }, + { "key2", 123 } + }; + var document = new Document("id", "collectionId", "databaseId", DateTime.UtcNow, DateTime.UtcNow, [], data); + + // Act + var result = document.TryGetValue("key1", out string? value); + + // Assert + Assert.True(result); + Assert.Equal("value1", value); + } + + [Fact] + public void TryGetValue_ReturnsFalse_WhenKeyDoesNotExist() + { + // Arrange + var data = new Dictionary(); + var document = new Document("id", "collectionId", "databaseId", DateTime.UtcNow, DateTime.UtcNow, [], data); + + // Act + var result = document.TryGetValue("nonexistentKey", out string? value); + + // Assert + Assert.False(result); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_ReturnsFalse_WhenTypeDoesNotMatch() + { + // Arrange + var data = new Dictionary + { + { "key1", "value1" } + }; + var document = new Document("id", "collectionId", "databaseId", DateTime.UtcNow, DateTime.UtcNow, [], data); + + // Act + var result = document.TryGetValue("key1", out int? value); + + // Assert + Assert.False(result); + Assert.Null(value); + } + + [Fact] + public void Document_ShouldContainUnmatchedProperties() + { + var document = JsonSerializer.Deserialize(TestConstants.DocumentResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + Assert.NotNull(document); + Assert.True(document.Data.ContainsKey("str")); + Assert.True(document.Data.ContainsKey("dt")); + Assert.True(document.Data.ContainsKey("num")); + Assert.True(document.Data.ContainsKey("num2")); + Assert.True(document.Data.ContainsKey("boo")); + Assert.True(document.Data.ContainsKey("lis")); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentsListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentsListTests.cs new file mode 100644 index 00000000..a9e5e297 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/DocumentsListTests.cs @@ -0,0 +1,51 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class DocumentsListTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var documents = new List + { + new("5e5ea5c16897e", "5e5ea5c15117e", "5e5ea5c15117e", + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), [Permission.Read().Any()], + []) + }; + + // Act + var documentsList = new DocumentsList(total, documents); + + // Assert + Assert.Equal(total, documentsList.Total); + Assert.Equal(documents, documentsList.Documents); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var documentsList = JsonSerializer.Deserialize(TestConstants.DocumentsListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(documentsList); + Assert.Equal(5, documentsList.Total); + Assert.Single(documentsList.Documents); + + var document = documentsList.Documents[0]; + Assert.Equal("5e5ea5c16897e", document.Id); + Assert.Equal("5e5ea5c15117e", document.CollectionId); + Assert.Equal("5e5ea5c15117e", document.DatabaseId); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), document.CreatedAt?.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), document.UpdatedAt?.ToUniversalTime()); + Assert.Single(document.Permissions); + Assert.Equal(PermissionType.Read, document.Permissions[0].PermissionType); + Assert.Equal(RoleType.Any, document.Permissions[0].RoleType); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexTests.cs new file mode 100644 index 00000000..3a6f8662 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexTests.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class IndexTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var key = "index1"; + var type = IndexType.Unique; + var status = DatabaseElementStatus.Available; + var error = "string"; + var attributes = Array.Empty(); + var orders = Array.Empty(); + var createdAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var updatedAt = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + + // Act + var index = new Index(key, type, status, error, attributes, orders, createdAt, updatedAt); + + // Assert + Assert.Equal(key, index.Key); + Assert.Equal(type, index.Type); + Assert.Equal(status, index.Status); + Assert.Equal(error, index.Error); + Assert.Equal(attributes, index.Attributes); + Assert.Equal(orders, index.Orders); + Assert.Equal(createdAt, index.CreatedAt); + Assert.Equal(updatedAt, index.UpdatedAt); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var index = JsonSerializer.Deserialize(TestConstants.IndexResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(index); + Assert.Equal("index1", index.Key); + Assert.Equal(IndexType.Unique, index.Type); + Assert.Equal(DatabaseElementStatus.Available, index.Status); + Assert.Equal("string", index.Error); + Assert.Empty(index.Attributes); + Assert.Empty(index.Orders); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), index.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), index.UpdatedAt.ToUniversalTime()); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexesListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexesListTests.cs new file mode 100644 index 00000000..0ff6b44e --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/IndexesListTests.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Responses; +using Index = PinguApps.Appwrite.Shared.Responses.Index; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class IndexesListTests +{ + [Fact] + public void Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var indexes = new List + { + new Index( + "index1", + IndexType.Unique, + DatabaseElementStatus.Available, + "string", + new List(), + new List(), + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), + DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime() + ) + }; + + // Act + var indexesList = new IndexesList(total, indexes); + + // Assert + Assert.Equal(total, indexesList.Total); + Assert.Equal(indexes, indexesList.Indexes); + } + + [Fact] + public void CanBeDeserialized_FromJson() + { + // Act + var indexesList = JsonSerializer.Deserialize(TestConstants.IndexesListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(indexesList); + Assert.Equal(5, indexesList.Total); + Assert.Single(indexesList.Indexes); + var index = indexesList.Indexes[0]; + Assert.Equal("index1", index.Key); + Assert.Equal(IndexType.Unique, index.Type); + Assert.Equal(DatabaseElementStatus.Available, index.Status); + Assert.Equal("string", index.Error); + Assert.Empty(index.Attributes); + Assert.Empty(index.Orders); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), index.CreatedAt.ToUniversalTime()); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), index.UpdatedAt.ToUniversalTime()); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/TestConstants.cs b/tests/PinguApps.Appwrite.Shared.Tests/TestConstants.cs index 2ffef9d0..6a663f5b 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/TestConstants.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/TestConstants.cs @@ -406,4 +406,377 @@ public static class TestConstants ] } """; + + public const string DatabaseResponse = """ + { + "$id": "5e5ea5c16897e", + "name": "My Database", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "enabled": false + } + """; + + public const string DatabasesListResponse = """ + { + "total": 5, + "databases": [ + { + "$id": "5e5ea5c16897e", + "name": "My Database", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "enabled": false + } + ] + } + """; + + public const string AttributesListResponse = """ + { + "total": 5, + "attributes": [ + { + "key": "isEnabled", + "type": "boolean", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "default": false + } + ] + } + """; + + public const string AttributeBooleanResponse = """ + { + "key": "isEnabled", + "type": "boolean", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "default": false + } + """; + + public const string AttributeDatetimeResponse = """ + { + "key": "birthDay", + "type": "datetime", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "format": "datetime", + "default": "2020-10-15T06:38:00.000+00:00" + } + """; + + public const string AttributeEmailResponse = """ + { + "key": "userEmail", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "format": "email", + "default": "default@example.com" + } + """; + + public const string AttributeEnumResponse = """ + { + "key": "status", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "elements": [ + "element" + ], + "format": "enum", + "default": "element" + } + """; + + public const string AttributeFloatResponse = """ + { + "key": "percentageCompleted", + "type": "double", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "min": 1.5, + "max": 10.5, + "default": 2.5 + } + """; + + public const string AttributeIntegerResponse = """ + { + "key": "count", + "type": "integer", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "min": 1, + "max": 10, + "default": 10 + } + """; + + public const string AttributeIpResponse = """ + { + "key": "ipAddress", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "format": "ip", + "default": "192.0.2.0" + } + """; + + public const string AttributeRelationshipResponse = """ + { + "key": "fullName", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "relatedCollection": "collection", + "relationType": "manyToOne", + "twoWay": false, + "twoWayKey": "string", + "onDelete": "cascade", + "side": "child" + } + """; + + public const string AttributeStringResponse = """ + { + "key": "fullName", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "size": 128, + "default": "default" + } + """; + + public const string AttributeUrlResponse = """ + { + "key": "githubUrl", + "type": "string", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "format": "url", + "default": "http://example.com" + } + """; + + public const string AttributeResponse = """ + { + "key": "isEnabled", + "type": "boolean", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "default": false + } + """; + + public const string IndexResponse = """ + { + "key": "index1", + "type": "unique", + "status": "available", + "error": "string", + "attributes": [], + "orders": [], + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00" + } + """; + + public const string CollectionResponse = """ + { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "$permissions": [ + "read(\"any\")" + ], + "databaseId": "5e5ea5c16897e", + "name": "My Collection", + "enabled": false, + "documentSecurity": true, + "attributes": [ + { + "key": "isEnabled", + "type": "boolean", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "default": false + } + ], + "indexes": [ + { + "key": "index1", + "type": "unique", + "status": "available", + "error": "string", + "attributes": [], + "orders": [], + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00" + } + ] + } + """; + + public const string CollectionsListResponse = """ + { + "total": 5, + "collections": [ + { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "$permissions": [ + "read(\"any\")" + ], + "databaseId": "5e5ea5c16897e", + "name": "My Collection", + "enabled": false, + "documentSecurity": true, + "attributes": [ + { + "key": "isEnabled", + "type": "boolean", + "status": "available", + "error": "string", + "required": true, + "array": false, + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "default": false + } + ], + "indexes": [ + { + "key": "index1", + "type": "unique", + "status": "available", + "error": "string", + "attributes": [], + "orders": [], + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00" + } + ] + } + ] + } + """; + + public const string IndexesListResponse = """ + { + "total": 5, + "indexes": [ + { + "key": "index1", + "type": "unique", + "status": "available", + "error": "string", + "attributes": [], + "orders": [], + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00" + } + ] + } + """; + + public const string DocumentResponse = """ + { + "$id": "5e5ea5c16897e", + "$collectionId": "5e5ea5c15117e", + "$databaseId": "5e5ea5c15117e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "$permissions": [ + "read(\"any\")" + ], + "str": "a string prop", + "dt": "2020-10-15T06:38:00.000+00:00", + "num": 5, + "num2": 6.7, + "boo": true, + "lis": [ + "one", + "two", + "three" + ] + } + """; + + public const string DocumentsListResponse = """ + { + "total": 5, + "documents": [ + { + "$id": "5e5ea5c16897e", + "$collectionId": "5e5ea5c15117e", + "$databaseId": "5e5ea5c15117e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "$permissions": [ + "read(\"any\")" + ] + } + ] + } + """; } diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Utils/PermissionTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Utils/PermissionTests.cs new file mode 100644 index 00000000..e879f2ff --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Utils/PermissionTests.cs @@ -0,0 +1,200 @@ +using PinguApps.Appwrite.Shared.Enums; +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Utils; +public class PermissionTests +{ + [Fact] + public void Read_Any_CreatesCorrectPermission() + { + // Act + var permission = Permission.Read().Any(); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Any, permission.RoleType); + Assert.Null(permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("user123")] + [InlineData("user456")] + public void Read_User_CreatesCorrectPermission(string userId) + { + // Act + var permission = Permission.Read().User(userId); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.User, permission.RoleType); + Assert.Equal(userId, permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("user123", RoleStatus.Verified)] + [InlineData("user456", RoleStatus.Unverified)] + public void Read_UserWithStatus_CreatesCorrectPermission(string userId, RoleStatus status) + { + // Act + var permission = Permission.Read().User(userId, status); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.User, permission.RoleType); + Assert.Equal(userId, permission.Id); + Assert.Equal(status, permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Fact] + public void Read_Users_CreatesCorrectPermission() + { + // Act + var permission = Permission.Read().Users(); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Users, permission.RoleType); + Assert.Null(permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData(RoleStatus.Verified)] + [InlineData(RoleStatus.Unverified)] + public void Read_UsersWithStatus_CreatesCorrectPermission(RoleStatus status) + { + // Act + var permission = Permission.Read().Users(status); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Users, permission.RoleType); + Assert.Null(permission.Id); + Assert.Equal(status, permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Fact] + public void Read_Guests_CreatesCorrectPermission() + { + // Act + var permission = Permission.Read().Guests(); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Guests, permission.RoleType); + Assert.Null(permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("team123")] + [InlineData("team456")] + public void Read_Team_CreatesCorrectPermission(string teamId) + { + // Act + var permission = Permission.Read().Team(teamId); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Team, permission.RoleType); + Assert.Equal(teamId, permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("team123", "admin")] + [InlineData("team456", "member")] + public void Read_TeamWithRole_CreatesCorrectPermission(string teamId, string teamRole) + { + // Act + var permission = Permission.Read().Team(teamId, teamRole); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Team, permission.RoleType); + Assert.Equal(teamId, permission.Id); + Assert.Null(permission.Status); + Assert.Equal(teamRole, permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("member123")] + [InlineData("member456")] + public void Read_Member_CreatesCorrectPermission(string memberId) + { + // Act + var permission = Permission.Read().Member(memberId); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Member, permission.RoleType); + Assert.Equal(memberId, permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Null(permission.Label); + } + + [Theory] + [InlineData("label1")] + [InlineData("label2")] + public void Read_Label_CreatesCorrectPermission(string labelName) + { + // Act + var permission = Permission.Read().Label(labelName); + + // Assert + Assert.Equal(PermissionType.Read, permission.PermissionType); + Assert.Equal(RoleType.Label, permission.RoleType); + Assert.Null(permission.Id); + Assert.Null(permission.Status); + Assert.Null(permission.TeamRole); + Assert.Equal(labelName, permission.Label); + } + + // Test all permission types with a sample role + [Theory] + [InlineData(PermissionType.Read)] + [InlineData(PermissionType.Write)] + [InlineData(PermissionType.Create)] + [InlineData(PermissionType.Update)] + [InlineData(PermissionType.Delete)] + public void AllPermissionTypes_CreateCorrectPermissions(PermissionType permissionType) + { + // Arrange + var builder = permissionType switch + { + PermissionType.Read => Permission.Read(), + PermissionType.Write => Permission.Write(), + PermissionType.Create => Permission.Create(), + PermissionType.Update => Permission.Update(), + PermissionType.Delete => Permission.Delete(), + _ => throw new ArgumentException("Invalid permission type") + }; + + // Act + var permission = builder.User("123", RoleStatus.Verified); + + // Assert + Assert.Equal(permissionType, permission.PermissionType); + Assert.Equal(RoleType.User, permission.RoleType); + Assert.Equal("123", permission.Id); + Assert.Equal(RoleStatus.Verified, permission.Status); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 b/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 index 57b4af39..72876c6e 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 +++ b/tests/PinguApps.Appwrite.Shared.Tests/test.ps1 @@ -9,7 +9,7 @@ if (-not $toolInstalled) { } # Generate the report -reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Shared +reportgenerator -reports:coverage.opencover.xml -targetdir:coverage-report -assemblyfilters:+PinguApps.Appwrite.Shared riskHotspotsAnalysisThresholds:metricThresholdForCyclomaticComplexity=30 # Open the generated report in the default browser Start-Process "coverage-report/index.html"