From 782d7c8d3dc9d7edf2a8b8b15a0fdb38411ec9ce Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Mon, 5 Aug 2024 01:48:21 +0200 Subject: [PATCH 01/12] Added a note to my future self about adding in query parameters --- TEMP.md | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 TEMP.md diff --git a/TEMP.md b/TEMP.md new file mode 100644 index 00000000..827566b3 --- /dev/null +++ b/TEMP.md @@ -0,0 +1,208 @@ +The following is some sample to show exactly how the query params work to my current understanding. + +```csharp +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System; +using Newtonsoft.Json; +#nullable enable + +var parameters = new List +{ + Query.Equal("Test", "val").ToString(), + Query.Limit(5).ToString() +}; + +var apiParameters = new Dictionary() +{ + { "queries", parameters } +}; + +Console.WriteLine(apiParameters.ToQueryString()); + +public static class Ext +{ + public static string ToQueryString(this Dictionary parameters) + { + var query = new List(); + + foreach (var kvp in parameters) + { + switch (kvp.Value) + { + case null: + continue; + case IList list: + foreach (var item in list) + { + query.Add($"{kvp.Key}[]={item}"); + } + break; + default: + query.Add($"{kvp.Key}={kvp.Value.ToString()}"); + break; + } + } + + return Uri.EscapeUriString(string.Join("&", query)); + } +} + + +public class Query +{ + public string method; + public string? attribute; + public List? values; + + + public Query(string method, string? attribute, object? values) + { + this.method = method; + this.attribute = attribute; + + if (values is IList valuesList) + { + this.values = new List(); + foreach (var value in valuesList) + { + this.values.Add(value); // Automatically boxes if value is a value type + } + } + else if (values != null) + { + this.values = new List { values }; + } + } + + override public string ToString() + { + return JsonConvert.SerializeObject(this); + } + + public static string Equal(string attribute, object value) + { + return new Query("equal", attribute, value).ToString(); + } + + public static string NotEqual(string attribute, object value) + { + return new Query("notEqual", attribute, value).ToString(); + } + + public static string LessThan(string attribute, object value) + { + return new Query("lessThan", attribute, value).ToString(); + } + + public static string LessThanEqual(string attribute, object value) + { + return new Query("lessThanEqual", attribute, value).ToString(); + } + + public static string GreaterThan(string attribute, object value) + { + return new Query("greaterThan", attribute, value).ToString(); + } + + public static string GreaterThanEqual(string attribute, object value) + { + return new Query("greaterThanEqual", attribute, value).ToString(); + } + + public static string Search(string attribute, string value) + { + return new Query("search", attribute, value).ToString(); + } + + public static string IsNull(string attribute) + { + return new Query("isNull", attribute, null).ToString(); + } + + public static string IsNotNull(string attribute) + { + return new Query("isNotNull", attribute, null).ToString(); + } + + public static string StartsWith(string attribute, string value) + { + return new Query("startsWith", attribute, value).ToString(); + } + + public static string EndsWith(string attribute, string value) + { + return new Query("endsWith", attribute, value).ToString(); + } + + public static string Between(string attribute, string start, string end) + { + return new Query("between", attribute, new List { start, end }).ToString(); + } + + public static string Between(string attribute, int start, int end) + { + return new Query("between", attribute, new List { start, end }).ToString(); + } + + public static string Between(string attribute, double start, double end) + { + return new Query("between", attribute, new List { start, end }).ToString(); + } + + public static string Select(List attributes) + { + return new Query("select", null, attributes).ToString(); + } + + public static string CursorAfter(string documentId) + { + return new Query("cursorAfter", null, documentId).ToString(); + } + + public static string CursorBefore(string documentId) { + return new Query("cursorBefore", null, documentId).ToString(); + } + + public static string OrderAsc(string attribute) { + return new Query("orderAsc", attribute, null).ToString(); + } + + public static string OrderDesc(string attribute) { + return new Query("orderDesc", attribute, null).ToString(); + } + + public static string Limit(int limit) { + return new Query("limit", null, limit).ToString(); + } + + public static string Offset(int offset) { + return new Query("offset", null, offset).ToString(); + } + + public static string Contains(string attribute, object value) { + return new Query("contains", attribute, value).ToString(); + } + + public static string Or(List queries) { + return new Query("or", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); + } + + public static string And(List queries) { + return new Query("and", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); + } +} +``` + +This creates the following query string: + +```url +queries[]=%7B%22method%22:%22equal%22,%22attribute%22:%22Test%22,%22values%22:[%22val%22]%7D&queries[]=%7B%22method%22:%22limit%22,%22attribute%22:null,%22values%22:[5]%7D +``` + +Which, when URL Decoded, gives: + +```json +queries[]={"method":"equal","attribute":"Test","values":["val"]}&queries[]={"method":"limit","attribute":null,"values":[5]} +``` From 11f171cc4dd1092f6c1466764e7aaf5b166fe5bb Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 14:37:21 +0100 Subject: [PATCH 02/12] Added my own implementation for Query with limit and offset --- src/PinguApps.Appwrite.Playground/App.cs | 28 ++++++++------ src/PinguApps.Appwrite.Shared/Utils/Query.cs | 39 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/Utils/Query.cs diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index 2a244ebf..b1217f11 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using PinguApps.Appwrite.Client; using PinguApps.Appwrite.Server.Servers; -using PinguApps.Appwrite.Shared.Requests; +using PinguApps.Appwrite.Shared.Utils; namespace PinguApps.Appwrite.Playground; internal class App @@ -19,18 +19,24 @@ public App(IAppwriteClient client, IAppwriteServer server, IConfiguration config public async Task Run(string[] args) { - _client.SetSession(_session); + var limit = Query.Limit(5); + var offset = Query.Offset(5); - var request = new CreateEmailVerificationRequest - { - Url = "https://localhost:5001/abc123" - }; + Console.WriteLine(limit.GetQueryString()); + Console.WriteLine(offset.GetQueryString()); - var response = await _client.Account.CreateJwt(); + //_client.SetSession(_session); - Console.WriteLine(response.Result.Match( - account => account.ToString(), - appwriteError => appwriteError.Message, - internalERror => internalERror.Message)); + //var request = new CreateEmailVerificationRequest + //{ + // Url = "https://localhost:5001/abc123" + //}; + + //var response = await _client.Account.CreateJwt(); + + //Console.WriteLine(response.Result.Match( + // account => account.ToString(), + // appwriteError => appwriteError.Message, + // internalERror => internalERror.Message)); } } diff --git a/src/PinguApps.Appwrite.Shared/Utils/Query.cs b/src/PinguApps.Appwrite.Shared/Utils/Query.cs new file mode 100644 index 00000000..721ba7a1 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Utils/Query.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Utils; +public class Query +{ + [JsonPropertyName("method")] + public string Method { get; private set; } + + [JsonPropertyName("attribute")] + public string? Attribute { get; private set; } + + [JsonPropertyName("values")] + public List? Values { get; private set; } + + private Query(string method, string? attribute, object? values) + { + Method = method; + Attribute = attribute; + + if (values is IEnumerable valuesList) + { + Values = [valuesList]; + } + else if (values is not null) + { + Values = [values]; + } + } + + public string GetQueryString() => Uri.EscapeUriString(JsonSerializer.Serialize(this)); + + public static Query Limit(int limit) => new("limit", null, limit); + + public static Query Offset(int offset) => new("offset", null, offset); +} From 1be7b959915e4fd16fdb56dd40d62b0a317bfdd6 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 15:23:26 +0100 Subject: [PATCH 03/12] Added implementation for List Logs --- .../Clients/AccountClient.cs | 19 +++++++ .../Clients/IAccountClient.cs | 9 ++++ .../Internals/IAccountApi.cs | 4 ++ src/PinguApps.Appwrite.Playground/App.cs | 23 +++----- .../Responses/LogModel.cs | 52 +++++++++++++++++++ .../Responses/LogsList.cs | 14 +++++ 6 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 src/PinguApps.Appwrite.Shared/Responses/LogModel.cs create mode 100644 src/PinguApps.Appwrite.Shared/Responses/LogsList.cs diff --git a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs index 8b18bacc..2463a682 100644 --- a/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/AccountClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using PinguApps.Appwrite.Client.Clients; @@ -8,6 +9,7 @@ using PinguApps.Appwrite.Shared; using PinguApps.Appwrite.Shared.Requests; using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; namespace PinguApps.Appwrite.Client; @@ -278,4 +280,21 @@ public async Task> CreateJwt() return e.GetExceptionResponse(); } } + + /// + public async Task> ListLogs(List? queries = null) + { + try + { + var queryStrings = queries?.Select(x => x.GetQueryString()) ?? []; + + var result = await _accountApi.ListLogs(Session, queryStrings); + + return result.GetApiResponse(); + } + catch (Exception e) + { + return e.GetExceptionResponse(); + } + } } diff --git a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs index 1f62609b..7245f3b2 100644 --- a/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs +++ b/src/PinguApps.Appwrite.Client/Clients/IAccountClient.cs @@ -3,6 +3,7 @@ using PinguApps.Appwrite.Shared; using PinguApps.Appwrite.Shared.Requests; using PinguApps.Appwrite.Shared.Responses; +using PinguApps.Appwrite.Shared.Utils; namespace PinguApps.Appwrite.Client; @@ -132,4 +133,12 @@ public interface IAccountClient /// /// The JWT Task> CreateJwt(); + + /// + /// Get the list of latest security activity logs for the currently logged in user. Each log returns user IP address, location and date and time of log. + /// Appwrite Docs + /// + /// Array of query strings generated using the Query class provided by the SDK. Learn more about queries. Only supported methods are limit and offset + /// The Logs List + Task> ListLogs(List? queries = null); } diff --git a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs index 4a6f33ee..d382268e 100644 --- a/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs +++ b/src/PinguApps.Appwrite.Client/Internals/IAccountApi.cs @@ -52,4 +52,8 @@ internal interface IAccountApi : IBaseApi [Post("/account/jwt")] Task> CreateJwt([Header("x-appwrite-session")] string? session); + + [Get("/account/logs")] + [QueryUriFormat(System.UriFormat.Unescaped)] + Task> ListLogs([Header("x-appwrite-session")] string? session, [Query(CollectionFormat.Multi), AliasAs("queries[]")] IEnumerable queries); } diff --git a/src/PinguApps.Appwrite.Playground/App.cs b/src/PinguApps.Appwrite.Playground/App.cs index b1217f11..391ca95c 100644 --- a/src/PinguApps.Appwrite.Playground/App.cs +++ b/src/PinguApps.Appwrite.Playground/App.cs @@ -19,24 +19,13 @@ public App(IAppwriteClient client, IAppwriteServer server, IConfiguration config public async Task Run(string[] args) { - var limit = Query.Limit(5); - var offset = Query.Offset(5); + _client.SetSession(_session); - Console.WriteLine(limit.GetQueryString()); - Console.WriteLine(offset.GetQueryString()); + var response = await _client.Account.ListLogs([Query.Limit(2)]); - //_client.SetSession(_session); - - //var request = new CreateEmailVerificationRequest - //{ - // Url = "https://localhost:5001/abc123" - //}; - - //var response = await _client.Account.CreateJwt(); - - //Console.WriteLine(response.Result.Match( - // account => account.ToString(), - // appwriteError => appwriteError.Message, - // internalERror => internalERror.Message)); + Console.WriteLine(response.Result.Match( + account => account.ToString(), + appwriteError => appwriteError.Message, + internalERror => internalERror.Message)); } } diff --git a/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs b/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs new file mode 100644 index 00000000..1de05284 --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/LogModel.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// Ann Appwrite Log object +/// +/// Event name +/// User ID +/// User Email +/// User Name +/// API mode when event triggered +/// IP session in use when the session was created +/// Log creation date in ISO 8601 format +/// Operating system code name. View list of Available Options +/// Operating system name +/// Operating system version +/// Client type +/// Client code name. View list of Available Options +/// Client name +/// Client version +/// Client engine name +/// Client engine version +/// Device name +/// Device brand name +/// Device model name +/// Country two-character ISO 3166-1 alpha code +/// Country name +public record LogModel( + [property: JsonPropertyName("event")] string Event, + [property: JsonPropertyName("userId")] string UserId, + [property: JsonPropertyName("userEmail")] string UserEmail, + [property: JsonPropertyName("userName")] string UserName, + [property: JsonPropertyName("mode")] string Mode, + [property: JsonPropertyName("ip")] string Ip, + [property: JsonPropertyName("time")] DateTime Time, + [property: JsonPropertyName("osCode")] string OsCode, + [property: JsonPropertyName("osName")] string OsName, + [property: JsonPropertyName("osVersion")] string OsVersion, + [property: JsonPropertyName("clientType")] string ClientType, + [property: JsonPropertyName("clientCode")] string ClientCode, + [property: JsonPropertyName("clientName")] string ClientName, + [property: JsonPropertyName("clientVersion")] string ClientVersion, + [property: JsonPropertyName("clientEngine")] string ClientEngine, + [property: JsonPropertyName("clientEngineVersion")] string ClientEngineVersion, + [property: JsonPropertyName("deviceName")] string DeviceName, + [property: JsonPropertyName("deviceBrand")] string DeviceBrand, + [property: JsonPropertyName("deviceModel")] string DeviceModel, + [property: JsonPropertyName("countryCode")] string CountryCode, + [property: JsonPropertyName("countryName")] string CountryName +); diff --git a/src/PinguApps.Appwrite.Shared/Responses/LogsList.cs b/src/PinguApps.Appwrite.Shared/Responses/LogsList.cs new file mode 100644 index 00000000..e46e4d6a --- /dev/null +++ b/src/PinguApps.Appwrite.Shared/Responses/LogsList.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PinguApps.Appwrite.Shared.Responses; + +/// +/// An Appwrite Logs List object +/// +/// Total number of logs documents that matched your query. +/// List of logs. Can be one of: +public record LogsList( + [property: JsonPropertyName("total")] int Total, + [property: JsonPropertyName("logs")] List Logs +); From c9ec8d664f2bc11a347cce65fc5f3055dac7b650 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 15:26:17 +0100 Subject: [PATCH 04/12] Removed temp instructions on query as a not to myself as no longer needed --- TEMP.md | 208 -------------------------------------------------------- 1 file changed, 208 deletions(-) delete mode 100644 TEMP.md diff --git a/TEMP.md b/TEMP.md deleted file mode 100644 index 827566b3..00000000 --- a/TEMP.md +++ /dev/null @@ -1,208 +0,0 @@ -The following is some sample to show exactly how the query params work to my current understanding. - -```csharp -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System; -using Newtonsoft.Json; -#nullable enable - -var parameters = new List -{ - Query.Equal("Test", "val").ToString(), - Query.Limit(5).ToString() -}; - -var apiParameters = new Dictionary() -{ - { "queries", parameters } -}; - -Console.WriteLine(apiParameters.ToQueryString()); - -public static class Ext -{ - public static string ToQueryString(this Dictionary parameters) - { - var query = new List(); - - foreach (var kvp in parameters) - { - switch (kvp.Value) - { - case null: - continue; - case IList list: - foreach (var item in list) - { - query.Add($"{kvp.Key}[]={item}"); - } - break; - default: - query.Add($"{kvp.Key}={kvp.Value.ToString()}"); - break; - } - } - - return Uri.EscapeUriString(string.Join("&", query)); - } -} - - -public class Query -{ - public string method; - public string? attribute; - public List? values; - - - public Query(string method, string? attribute, object? values) - { - this.method = method; - this.attribute = attribute; - - if (values is IList valuesList) - { - this.values = new List(); - foreach (var value in valuesList) - { - this.values.Add(value); // Automatically boxes if value is a value type - } - } - else if (values != null) - { - this.values = new List { values }; - } - } - - override public string ToString() - { - return JsonConvert.SerializeObject(this); - } - - public static string Equal(string attribute, object value) - { - return new Query("equal", attribute, value).ToString(); - } - - public static string NotEqual(string attribute, object value) - { - return new Query("notEqual", attribute, value).ToString(); - } - - public static string LessThan(string attribute, object value) - { - return new Query("lessThan", attribute, value).ToString(); - } - - public static string LessThanEqual(string attribute, object value) - { - return new Query("lessThanEqual", attribute, value).ToString(); - } - - public static string GreaterThan(string attribute, object value) - { - return new Query("greaterThan", attribute, value).ToString(); - } - - public static string GreaterThanEqual(string attribute, object value) - { - return new Query("greaterThanEqual", attribute, value).ToString(); - } - - public static string Search(string attribute, string value) - { - return new Query("search", attribute, value).ToString(); - } - - public static string IsNull(string attribute) - { - return new Query("isNull", attribute, null).ToString(); - } - - public static string IsNotNull(string attribute) - { - return new Query("isNotNull", attribute, null).ToString(); - } - - public static string StartsWith(string attribute, string value) - { - return new Query("startsWith", attribute, value).ToString(); - } - - public static string EndsWith(string attribute, string value) - { - return new Query("endsWith", attribute, value).ToString(); - } - - public static string Between(string attribute, string start, string end) - { - return new Query("between", attribute, new List { start, end }).ToString(); - } - - public static string Between(string attribute, int start, int end) - { - return new Query("between", attribute, new List { start, end }).ToString(); - } - - public static string Between(string attribute, double start, double end) - { - return new Query("between", attribute, new List { start, end }).ToString(); - } - - public static string Select(List attributes) - { - return new Query("select", null, attributes).ToString(); - } - - public static string CursorAfter(string documentId) - { - return new Query("cursorAfter", null, documentId).ToString(); - } - - public static string CursorBefore(string documentId) { - return new Query("cursorBefore", null, documentId).ToString(); - } - - public static string OrderAsc(string attribute) { - return new Query("orderAsc", attribute, null).ToString(); - } - - public static string OrderDesc(string attribute) { - return new Query("orderDesc", attribute, null).ToString(); - } - - public static string Limit(int limit) { - return new Query("limit", null, limit).ToString(); - } - - public static string Offset(int offset) { - return new Query("offset", null, offset).ToString(); - } - - public static string Contains(string attribute, object value) { - return new Query("contains", attribute, value).ToString(); - } - - public static string Or(List queries) { - return new Query("or", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); - } - - public static string And(List queries) { - return new Query("and", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); - } -} -``` - -This creates the following query string: - -```url -queries[]=%7B%22method%22:%22equal%22,%22attribute%22:%22Test%22,%22values%22:[%22val%22]%7D&queries[]=%7B%22method%22:%22limit%22,%22attribute%22:null,%22values%22:[5]%7D -``` - -Which, when URL Decoded, gives: - -```json -queries[]={"method":"equal","attribute":"Test","values":["val"]}&queries[]={"method":"limit","attribute":null,"values":[5]} -``` From 62aaf061c3b900d0eadbed136a50ff779ef76904 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 15:39:21 +0100 Subject: [PATCH 05/12] Added all other possible queries --- src/PinguApps.Appwrite.Shared/Utils/Query.cs | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/PinguApps.Appwrite.Shared/Utils/Query.cs b/src/PinguApps.Appwrite.Shared/Utils/Query.cs index 721ba7a1..045747e8 100644 --- a/src/PinguApps.Appwrite.Shared/Utils/Query.cs +++ b/src/PinguApps.Appwrite.Shared/Utils/Query.cs @@ -33,7 +33,51 @@ private Query(string method, string? attribute, object? values) public string GetQueryString() => Uri.EscapeUriString(JsonSerializer.Serialize(this)); + public static Query Equal(string attribute, object value) => new("equal", attribute, value); + + public static Query NotEqual(string attribute, object value) => new("notEqual", attribute, value); + + public static Query LessThan(string attribute, object value) => new("lessThan", attribute, value); + + public static Query LessThanEqual(string attribute, object value) => new("lessThanEqual", attribute, value); + + public static Query GreaterThan(string attribute, object value) => new("greaterThan", attribute, value); + + public static Query GreaterThanEqual(string attribute, object value) => new("greaterThanEqual", attribute, value); + + public static Query Search(string attribute, object value) => new("search", attribute, value); + + public static Query IsNull(string attribute) => new("isNull", attribute, null); + + public static Query IsNotNull(string attribute) => new("isNotNull", attribute, null); + + public static Query StartsWith(string attribute, object value) => new("startsWith", attribute, value); + + public static Query EndsWith(string attribute, object value) => new("endsWith", attribute, value); + + public static Query Between(string attribute, string start, string end) => new("between", attribute, new List { start, end }); + + public static Query Between(string attribute, int start, int end) => new("between", attribute, new List { start, end }); + + public static Query Between(string attribute, double start, double end) => new("between", attribute, new List { start, end }); + + public static Query Select(List attributes) => new("select", null, attributes); + + public static Query CursorAfter(string documentId) => new("cursorAfter", null, documentId); + + public static Query CursorBefore(string documentId) => new("cursorBefore", null, documentId); + + public static Query OrderAsc(string attribute) => new("orderAsc", attribute, null); + + public static Query OrderDesc(string attribute) => new("orderDesc", attribute, null); + public static Query Limit(int limit) => new("limit", null, limit); public static Query Offset(int offset) => new("offset", null, offset); + + public static Query Contains(string attribute, object value) => new("contains", attribute, value); + + public static Query Or(List queries) => new("or", null, queries); + + public static Query And(List queries) => new("and", null, queries); } From d349f9ea85de81cd3eeed844c1a4dd4613567f9d Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 16:45:00 +0100 Subject: [PATCH 06/12] added client tests --- .../Account/AccountClientTests.ListLogs.cs | 82 +++++++++++++++++++ .../Constants.cs | 31 +++++++ 2 files changed, 113 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.ListLogs.cs diff --git a/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.ListLogs.cs b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.ListLogs.cs new file mode 100644 index 00000000..041d269c --- /dev/null +++ b/tests/PinguApps.Appwrite.Client.Tests/Clients/Account/AccountClientTests.ListLogs.cs @@ -0,0 +1,82 @@ +using System.Net; +using PinguApps.Appwrite.Shared.Tests; +using PinguApps.Appwrite.Shared.Utils; +using RichardSzalay.MockHttp; + +namespace PinguApps.Appwrite.Client.Tests.Clients.Account; +public partial class AccountClientTests +{ + [Fact] + public async Task ListLogs_ShouldReturnSuccess_WhenApiCallSucceeds() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/logs") + .ExpectedHeaders(true) + .Respond(Constants.AppJson, Constants.LogsListResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.ListLogs(); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListLogs_ShouldProvideQueries_WhenQueriesProvided() + { + // Arrange + var query = Query.Limit(5); + + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/logs") + .ExpectedHeaders(true) + .WithQueryString($"queries[]={query.GetQueryString()}") + .Respond(Constants.AppJson, Constants.LogsListResponse); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.ListLogs([query]); + + // Assert + Assert.True(result.Success); + } + + [Fact] + public async Task ListLogs_ShouldHandleException_WhenApiCallFails() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/logs") + .ExpectedHeaders(true) + .Respond(HttpStatusCode.BadRequest, Constants.AppJson, Constants.AppwriteError); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.ListLogs(); + + // Assert + Assert.True(result.IsError); + Assert.True(result.IsAppwriteError); + } + + [Fact] + public async Task ListLogs_ShouldReturnErrorResponse_WhenExceptionOccurs() + { + // Arrange + _mockHttp.Expect(HttpMethod.Get, $"{Constants.Endpoint}/account/logs") + .ExpectedHeaders(true) + .Throw(new HttpRequestException("An error occurred")); + + _appwriteClient.SetSession(Constants.Session); + + // Act + var result = await _appwriteClient.Account.ListLogs(); + + // Assert + Assert.False(result.Success); + Assert.True(result.IsInternalError); + Assert.Equal("An error occurred", result.Result.AsT2.Message); + } +} diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs index d99a2027..1f9f31b7 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Constants.cs @@ -132,4 +132,35 @@ public static class Constants "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" } """; + + public const string LogsListResponse = """ + { + "total": 5, + "logs": [ + { + "event": "account.sessions.create", + "userId": "610fc2f985ee0", + "userEmail": "john@appwrite.io", + "userName": "John Doe", + "mode": "admin", + "ip": "127.0.0.1", + "time": "2020-10-15T06:38:00.000+00:00", + "osCode": "Mac", + "osName": "Mac", + "osVersion": "Mac", + "clientType": "browser", + "clientCode": "CM", + "clientName": "Chrome Mobile iOS", + "clientVersion": "84.0", + "clientEngine": "WebKit", + "clientEngineVersion": "605.1.15", + "deviceName": "smartphone", + "deviceBrand": "Google", + "deviceModel": "Nexus 5", + "countryCode": "US", + "countryName": "United States" + } + ] + } + """; } From fa82393d8a95c837da9e6981322961eba3f6fd19 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 16:45:14 +0100 Subject: [PATCH 07/12] added test to get to 100% branch coverage --- .../Utils/ResponseUtilsTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs index a7663bc4..b8455be7 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs @@ -65,6 +65,16 @@ public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); } + [Fact] + public async Task GetApiResponse_FailureButNullError_ThrowsException() + { + var mockApiResponse = new Mock>(); + mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); + mockApiResponse.SetupGet(x => x.Error).Returns((ApiException)null!); + + Assert.Throws(() => mockApiResponse.Object.GetApiResponse()); + } + [Fact] public void GetExceptionResponse_ReturnsInternalError() { From 3e11d5a009fe85291d0534b6e4151751b1a87b99 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 16:45:56 +0100 Subject: [PATCH 08/12] changed from async task to void --- .../PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs index b8455be7..31167695 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Utils/ResponseUtilsTests.cs @@ -66,7 +66,7 @@ public async Task GetApiResponse_FailureButNullErrorContent_ThrowsException() } [Fact] - public async Task GetApiResponse_FailureButNullError_ThrowsException() + public void GetApiResponse_FailureButNullError_ThrowsException() { var mockApiResponse = new Mock>(); mockApiResponse.SetupGet(r => r.IsSuccessStatusCode).Returns(false); From 9f28ff139ff2af918a8b28b461950e5175cdfef1 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 17:07:46 +0100 Subject: [PATCH 09/12] Added tests for queries, and fixed an issue with them --- src/PinguApps.Appwrite.Shared/Utils/Query.cs | 9 +- .../Utils/QueryTests.cs | 354 ++++++++++++++++++ 2 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Utils/QueryTests.cs diff --git a/src/PinguApps.Appwrite.Shared/Utils/Query.cs b/src/PinguApps.Appwrite.Shared/Utils/Query.cs index 045747e8..c6efd700 100644 --- a/src/PinguApps.Appwrite.Shared/Utils/Query.cs +++ b/src/PinguApps.Appwrite.Shared/Utils/Query.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -21,9 +22,13 @@ private Query(string method, string? attribute, object? values) Method = method; Attribute = attribute; - if (values is IEnumerable valuesList) + if (values is IEnumerable objects) { - Values = [valuesList]; + Values = objects.ToList(); + } + else if (values is ICollection valuesList) + { + Values = [.. valuesList]; } else if (values is not null) { diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Utils/QueryTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Utils/QueryTests.cs new file mode 100644 index 00000000..84bf2830 --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Utils/QueryTests.cs @@ -0,0 +1,354 @@ +using PinguApps.Appwrite.Shared.Utils; + +namespace PinguApps.Appwrite.Shared.Tests.Utils; +public class QueryTests +{ + [Fact] + public void Equal_Returns_CorrectQuery() + { + // Act + var query = Query.Equal("name", "John"); + + // Assert + Assert.Equal("equal", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("John", query.Values[0]); + } + + [Fact] + public void NotEqual_Returns_CorrectQuery() + { + // Act + var query = Query.NotEqual("name", "John"); + + // Assert + Assert.Equal("notEqual", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("John", query.Values[0]); + } + + [Fact] + public void LessThan_Returns_CorrectQuery() + { + // Act + var query = Query.LessThan("age", 30); + + // Assert + Assert.Equal("lessThan", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(30, query.Values[0]); + } + + [Fact] + public void LessThanEqual_Returns_CorrectQuery() + { + // Act + var query = Query.LessThanEqual("age", 30); + + // Assert + Assert.Equal("lessThanEqual", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(30, query.Values[0]); + } + + [Fact] + public void GreaterThan_Returns_CorrectQuery() + { + // Act + var query = Query.GreaterThan("age", 30); + + // Assert + Assert.Equal("greaterThan", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(30, query.Values[0]); + } + + [Fact] + public void GreaterThanEqual_Returns_CorrectQuery() + { + // Act + var query = Query.GreaterThanEqual("age", 30); + + // Assert + Assert.Equal("greaterThanEqual", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(30, query.Values[0]); + } + + [Fact] + public void Search_Returns_CorrectQuery() + { + // Act + var query = Query.Search("name", "John"); + + // Assert + Assert.Equal("search", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("John", query.Values[0]); + } + + [Fact] + public void IsNull_Returns_CorrectQuery() + { + // Act + var query = Query.IsNull("name"); + + // Assert + Assert.Equal("isNull", query.Method); + Assert.Equal("name", query.Attribute); + Assert.Null(query.Values); + } + + [Fact] + public void IsNotNull_Returns_CorrectQuery() + { + // Act + var query = Query.IsNotNull("name"); + + // Assert + Assert.Equal("isNotNull", query.Method); + Assert.Equal("name", query.Attribute); + Assert.Null(query.Values); + } + + [Fact] + public void StartsWith_Returns_CorrectQuery() + { + // Act + var query = Query.StartsWith("name", "Jo"); + + // Assert + Assert.Equal("startsWith", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("Jo", query.Values[0]); + } + + [Fact] + public void EndsWith_Returns_CorrectQuery() + { + // Act + var query = Query.EndsWith("name", "hn"); + + // Assert + Assert.Equal("endsWith", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("hn", query.Values[0]); + } + + [Fact] + public void Between_Returns_CorrectQuery_ForString() + { + // Act + var query = Query.Between("age", "20", "30"); + + // Assert + Assert.Equal("between", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal("20", query.Values[0]); + Assert.Equal("30", query.Values[1]); + } + + [Fact] + public void Between_Returns_CorrectQuery_ForInt() + { + // Act + var query = Query.Between("age", 20, 30); + + // Assert + Assert.Equal("between", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal(20, query.Values[0]); + Assert.Equal(30, query.Values[1]); + } + + [Fact] + public void Between_Returns_CorrectQuery_ForDouble() + { + // Act + var query = Query.Between("age", 20.5, 30.5); + + // Assert + Assert.Equal("between", query.Method); + Assert.Equal("age", query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal(20.5, query.Values[0]); + Assert.Equal(30.5, query.Values[1]); + } + + [Fact] + public void Select_Returns_CorrectQuery() + { + // Act + var query = Query.Select(new List { "name", "age" }); + + // Assert + Assert.Equal("select", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + Assert.Equal("name", query.Values[0]); + Assert.Equal("age", query.Values[1]); + } + + [Fact] + public void CursorAfter_Returns_CorrectQuery() + { + // Act + var query = Query.CursorAfter("docId"); + + // Assert + Assert.Equal("cursorAfter", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("docId", query.Values[0]); + } + + [Fact] + public void CursorBefore_Returns_CorrectQuery() + { + // Act + var query = Query.CursorBefore("docId"); + + // Assert + Assert.Equal("cursorBefore", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("docId", query.Values[0]); + } + + [Fact] + public void OrderAsc_Returns_CorrectQuery() + { + // Act + var query = Query.OrderAsc("name"); + + // Assert + Assert.Equal("orderAsc", query.Method); + Assert.Equal("name", query.Attribute); + Assert.Null(query.Values); + } + + [Fact] + public void OrderDesc_Returns_CorrectQuery() + { + // Act + var query = Query.OrderDesc("name"); + + // Assert + Assert.Equal("orderDesc", query.Method); + Assert.Equal("name", query.Attribute); + Assert.Null(query.Values); + } + + [Fact] + public void Limit_Returns_CorrectQuery() + { + // Act + var query = Query.Limit(10); + + // Assert + Assert.Equal("limit", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(10, query.Values[0]); + } + + [Fact] + public void Offset_Returns_CorrectQuery() + { + // Act + var query = Query.Offset(5); + + // Assert + Assert.Equal("offset", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal(5, query.Values[0]); + } + + [Fact] + public void Contains_Returns_CorrectQuery() + { + // Act + var query = Query.Contains("name", "John"); + + // Assert + Assert.Equal("contains", query.Method); + Assert.Equal("name", query.Attribute); + Assert.NotNull(query.Values); + Assert.Single(query.Values); + Assert.Equal("John", query.Values[0]); + } + + [Fact] + public void Or_Returns_CorrectQuery() + { + // Arrange + var queries = new List { Query.Equal("name", "John"), Query.Equal("age", 30) }; + + // Act + var query = Query.Or(queries); + + // Assert + Assert.Equal("or", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void And_Returns_CorrectQuery() + { + // Arrange + var queries = new List { Query.Equal("name", "John"), Query.Equal("age", 30) }; + + // Act + var query = Query.And(queries); + + // Assert + Assert.Equal("and", query.Method); + Assert.Null(query.Attribute); + Assert.NotNull(query.Values); + Assert.Equal(2, query.Values.Count); + } + + [Fact] + public void GetQueryString_Returns_ValidJson() + { + // Act + var query = Query.Equal("name", "John"); + var queryString = query.GetQueryString(); + + // Assert + Assert.Contains("%22method%22:%22equal%22", queryString); + Assert.Contains("%22attribute%22:%22name%22", queryString); + Assert.Contains("%22values%22:[%22John%22]", queryString); + } +} From 7c2157ef6daeb54c81c5c06da826d5c29d6ecbe3 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 17:25:05 +0100 Subject: [PATCH 10/12] Add tests for logs list response --- .../Responses/LogsListTests.cs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs new file mode 100644 index 00000000..063dbcbd --- /dev/null +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs @@ -0,0 +1,101 @@ +using System.Text.Json; +using PinguApps.Appwrite.Shared.Responses; + +namespace PinguApps.Appwrite.Shared.Tests.Responses; +public class LogsListTests +{ + + [Fact] + public void LogsList_Constructor_AssignsPropertiesCorrectly() + { + // Arrange + var total = 5; + var logEvent = "account.sessions.create"; + var userId = "610fc2f985ee0"; + var userEmail = "john@appwrite.io"; + var userName = "John Doe"; + var mode = "admin"; + var ip = "127.0.0.1"; + var time = DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(); + var osCode = "Mac"; + var osName = "Mac"; + var osVersion = "Mac"; + var clientType = "browser"; + var clientCode = "CM"; + var clientName = "Chrome Mobile iOS"; + var clientVersion = "84.0"; + var clientEngine = "WebKit"; + var clientEngineVersion = "605.1.15"; + var deviceName = "smartphone"; + var deviceBrand = "Google"; + var deviceModel = "Nexus 5"; + var countryCode = "US"; + var countryName = "United States"; + + // Act + var logModel = new LogModel(logEvent, userId, userEmail, userName, mode, ip, time, osCode, osName, osVersion, clientType, clientCode, + clientName, clientVersion, clientEngine, clientEngineVersion, deviceName, deviceBrand, deviceModel, countryCode, countryName); + var logsList = new LogsList(total, [logModel]); + + // Assert + Assert.Equal(total, logsList.Total); + Assert.Single(logsList.Logs); + Assert.Equal(logModel, logsList.Logs[0]); + Assert.Equal(logEvent, logModel.Event); + Assert.Equal(userId, logModel.UserId); + Assert.Equal(userEmail, logModel.UserEmail); + Assert.Equal(userName, logModel.UserName); + Assert.Equal(mode, logModel.Mode); + Assert.Equal(ip, logModel.Ip); + Assert.Equal(time, logModel.Time); + Assert.Equal(osCode, logModel.OsCode); + Assert.Equal(osName, logModel.OsName); + Assert.Equal(osVersion, logModel.OsVersion); + Assert.Equal(clientType, logModel.ClientType); + Assert.Equal(clientCode, logModel.ClientCode); + Assert.Equal(clientName, logModel.ClientName); + Assert.Equal(clientVersion, logModel.ClientVersion); + Assert.Equal(clientEngine, logModel.ClientEngine); + Assert.Equal(clientEngineVersion, logModel.ClientEngineVersion); + Assert.Equal(deviceName, logModel.DeviceName); + Assert.Equal(deviceBrand, logModel.DeviceBrand); + Assert.Equal(deviceModel, logModel.DeviceModel); + Assert.Equal(countryCode, logModel.CountryCode); + Assert.Equal(countryName, logModel.CountryName); + } + + [Fact] + public void LogsList_CanBeDeserialized_FromJson() + { + // Act + var logsList = JsonSerializer.Deserialize(Constants.LogsListResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Assert + Assert.NotNull(logsList); + Assert.Equal(5, logsList.Total); + Assert.Single(logsList.Logs); + + var logModel = logsList.Logs[0]; + Assert.Equal("account.sessions.create", logModel.Event); + Assert.Equal("610fc2f985ee0", logModel.UserId); + Assert.Equal("john@appwrite.io", logModel.UserEmail); + Assert.Equal("John Doe", logModel.UserName); + Assert.Equal("admin", logModel.Mode); + Assert.Equal("127.0.0.1", logModel.Ip); + Assert.Equal(DateTime.Parse("2020-10-15T06:38:00.000+00:00").ToUniversalTime(), logModel.Time.ToUniversalTime()); + Assert.Equal("Mac", logModel.OsCode); + Assert.Equal("Mac", logModel.OsName); + Assert.Equal("Mac", logModel.OsVersion); + Assert.Equal("browser", logModel.ClientType); + Assert.Equal("CM", logModel.ClientCode); + Assert.Equal("Chrome Mobile iOS", logModel.ClientName); + Assert.Equal("84.0", logModel.ClientVersion); + Assert.Equal("WebKit", logModel.ClientEngine); + Assert.Equal("605.1.15", logModel.ClientEngineVersion); + Assert.Equal("smartphone", logModel.DeviceName); + Assert.Equal("Google", logModel.DeviceBrand); + Assert.Equal("Nexus 5", logModel.DeviceModel); + Assert.Equal("US", logModel.CountryCode); + Assert.Equal("United States", logModel.CountryName); + } +} From 627b052c4fe230746cd95d519df7f1fdaea33e52 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 17:27:24 +0100 Subject: [PATCH 11/12] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79ae1bde..96b04bed 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,11 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( ## ⌛ Progress ### Server & Client -![17 / 288](https://progress-bar.dev/17/?scale=288&suffix=%20/%20288&width=500) +![18 / 288](https://progress-bar.dev/18/?scale=288&suffix=%20/%20288&width=500) ### Server Only ![2 / 195](https://progress-bar.dev/2/?scale=195&suffix=%20/%20195&width=300) ### Client Only -![15 / 93](https://progress-bar.dev/15/?scale=93&suffix=%20/%2093&width=300) +![16 / 93](https://progress-bar.dev/16/?scale=93&suffix=%20/%2093&width=300) ### 🔑 Key | Icon | Definition | @@ -153,7 +153,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | ❌ | There is currently no intention to implement the endpoint for the given SDK type (client or server) | ### Account -![17 / 52](https://progress-bar.dev/17/?scale=52&suffix=%20/%2052&width=120) +![18 / 52](https://progress-bar.dev/18/?scale=52&suffix=%20/%2052&width=120) | Endpoint | Client | Server | |:-:|:-:|:-:| @@ -163,7 +163,7 @@ string emailAddressOrErrorMessage = userResponse.Result.Match( | [List Identities](https://appwrite.io/docs/references/1.5.x/client-rest/account#listIdentities) | ⬛ | ❌ | | [Delete Identity](https://appwrite.io/docs/references/1.5.x/client-rest/account#deleteIdentity) | ⬛ | ❌ | | [Create JWT](https://appwrite.io/docs/references/1.5.x/client-rest/account#createJWT) | ✅ | ❌ | -| [List Logs](https://appwrite.io/docs/references/1.5.x/client-rest/account#listLogs) | ⬛ | ❌ | +| [List Logs](https://appwrite.io/docs/references/1.5.x/client-rest/account#listLogs) | ✅ | ❌ | | [Update MFA](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMFA) | ⬛ | ❌ | | [Add Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#createMfaAuthenticator) | ⬛ | ❌ | | [Verify Authenticator](https://appwrite.io/docs/references/1.5.x/client-rest/account#updateMfaAuthenticator) | ⬛ | ❌ | From 0f599b4b5c38aa1eaf19ae9b66ae5165f7466300 Mon Sep 17 00:00:00 2001 From: Matthew Parker Date: Tue, 6 Aug 2024 17:41:52 +0100 Subject: [PATCH 12/12] Update tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com> --- tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs b/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs index 063dbcbd..4c317136 100644 --- a/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs +++ b/tests/PinguApps.Appwrite.Shared.Tests/Responses/LogsListTests.cs @@ -4,7 +4,6 @@ namespace PinguApps.Appwrite.Shared.Tests.Responses; public class LogsListTests { - [Fact] public void LogsList_Constructor_AssignsPropertiesCorrectly() {