diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 186d1d7..4926913 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,10 +49,9 @@ jobs: path: artifacts - name: Create release - env: - GitHub_Access_Token: ${{ secrets.GITHUB_TOKEN }} - GitHub_Repository: ${{ github.repository }} - run: ./build.sh --target PublishToGitHub --skip PublishArtifacts - - - + run: > + ./build.sh + --target PublishToGitHub + --skip PublishArtifacts + --github-access-token ${{ secrets.GITHUB_TOKEN }} + --github-repository ${{ github.repository }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd3363f..f4d5601 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,8 +29,10 @@ jobs: path: ./artifacts token: ${{ secrets.GITHUB_TOKEN }} - - name: Push assets to NuGet - env: - NuGet_Source: ${{ secrets.NUGET_SOURCE }} - NuGet_ApiKey: ${{ secrets.NUGET_APIKEY }} - run: ./build.sh --target PublishToNuGetFeed --skip PublishArtifacts \ No newline at end of file + - name: Push assets to NuGet + run: > + ./build.sh + --target PublishToNuGetFeed + --skip PublishArtifacts + --nuget-api-key ${{ secrets.NUGET_APIKEY }} + --nuget-source ${{ secrets.NUGET_SOURCE }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfcfd56..667a9e1 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# VerifyTests files +*.received.* diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 0d5d962..b1137cd 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -20,7 +20,8 @@ }, "GitHubAccessToken": { "type": "string", - "description": "GitHub access token used for creating a new or updating an existing release" + "description": "GitHub access token used for creating a new or updating an existing release", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" }, "GitHubRepository": { "type": "string", @@ -57,7 +58,8 @@ }, "NuGetApiKey": { "type": "string", - "description": "NuGet API key used to pushing the NuGet package" + "description": "NuGet API key used to pushing the NuGet package", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" }, "NuGetSource": { "type": "string", diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..787c3b8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/build/bin/Debug/net6.0/Build.dll", + "args": [], + "cwd": "${workspaceFolder}/build", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..eb9110d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/build/Build.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/build/Build.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/build/Build.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index 197d570..bcb730b 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -7,6 +7,7 @@ class Build : NukeBuild readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; [Parameter("GitHub access token used for creating a new or updating an existing release.")] + [Secret] readonly string GitHubAccessToken; [Parameter("GitHub repository owner and name used for creating a new or updating an existing release. For example: 'stevenkuhn/openiddict-litedb'.")] @@ -16,6 +17,7 @@ class Build : NukeBuild readonly string NuGetSource = "https://api.nuget.org/v3/index.json"; [Parameter("NuGet API key used to pushing the NuGet package.")] + [Secret] readonly string NuGetApiKey; [Solution] readonly Solution Solution; diff --git a/build/Build.v3.ncrunchproject b/build/Build.v3.ncrunchproject new file mode 100644 index 0000000..319cd52 --- /dev/null +++ b/build/Build.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBApplication.cs b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBApplication.cs index d61a228..cc2c797 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBApplication.cs +++ b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBApplication.cs @@ -57,8 +57,8 @@ public class OpenIddictLiteDBApplication /// Gets or sets the localized display names associated with the current application. /// [BsonField("display_names")] - public virtual IReadOnlyDictionary? DisplayNames { get; set; } - = ImmutableDictionary.Create(); + public virtual ImmutableDictionary? DisplayNames { get; set; } + = ImmutableDictionary.Create(); /// /// Gets or sets the unique identifier associated with the current application. @@ -70,35 +70,34 @@ public class OpenIddictLiteDBApplication /// Gets or sets the permissions associated with the current application. /// [BsonField("permissions")] - public virtual IReadOnlyList? Permissions { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? Permissions { get; set; } = ImmutableArray.Create(); /// /// Gets or sets the logout callback URLs associated with the current application. /// [BsonField("post_logout_redirect_uris")] - public virtual IReadOnlyList? PostLogoutRedirectUris { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? PostLogoutRedirectUris { get; set; } = ImmutableArray.Create(); /// /// Gets or sets the additional properties associated with the current application. /// [BsonField("properties")] - public virtual IReadOnlyDictionary? Properties { get; set; } + public virtual ImmutableDictionary? Properties { get; set; } /// /// Gets or sets the callback URLs associated with the current application. /// [BsonField("redirect_uris")] - public virtual IReadOnlyList? RedirectUris { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? RedirectUris { get; set; } = ImmutableArray.Create(); /// /// Gets or sets the requirements associated with the current application. /// [BsonField("requirements")] - public virtual IReadOnlyList? Requirements { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? Requirements { get; set; } = ImmutableArray.Create(); /// - /// Gets or sets the application type - /// associated with the current application. + /// Gets or sets the application type associated with the current application. /// [BsonField("type")] public virtual string? Type { get; set; } diff --git a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBAuthorization.cs b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBAuthorization.cs index 82c13d6..00bced4 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBAuthorization.cs +++ b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBAuthorization.cs @@ -37,7 +37,7 @@ public class OpenIddictLiteDBAuthorization /// Gets or sets the UTC creation date of the current authorization. /// [BsonField("creation_date")] - public virtual DateTime? CreationDate { get; set; } + public virtual DateTimeOffset? CreationDate { get; set; } /// /// Gets or sets the unique identifier associated with the current authorization. @@ -49,13 +49,13 @@ public class OpenIddictLiteDBAuthorization /// Gets or sets the additional properties associated with the current authorization. /// [BsonField("properties")] - public virtual IReadOnlyDictionary? Properties { get; set; } + public virtual ImmutableDictionary? Properties { get; set; } /// /// Gets or sets the scopes associated with the current authorization. /// [BsonField("scopes")] - public virtual IReadOnlyList? Scopes { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? Scopes { get; set; } = ImmutableArray.Create(); /// /// Gets or sets the status of the current authorization. diff --git a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBScope.cs b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBScope.cs index 8f76a80..dad8b94 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBScope.cs +++ b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBScope.cs @@ -37,8 +37,8 @@ public class OpenIddictLiteDBScope /// Gets or sets the localized public descriptions associated with the current scope. /// [BsonField("descriptions")] - public virtual IReadOnlyDictionary? Descriptions { get; set; } - = ImmutableDictionary.Create(); + public virtual ImmutableDictionary? Descriptions { get; set; } + = ImmutableDictionary.Create(); /// /// Gets or sets the display name associated with the current scope. @@ -50,8 +50,8 @@ public class OpenIddictLiteDBScope /// Gets or sets the localized display names associated with the current scope. /// [BsonField("display_names")] - public virtual IReadOnlyDictionary? DisplayNames { get; set; } - = ImmutableDictionary.Create(); + public virtual ImmutableDictionary? DisplayNames { get; set; } + = ImmutableDictionary.Create(); /// /// Gets or sets the unique identifier associated with the current scope. @@ -69,11 +69,11 @@ public class OpenIddictLiteDBScope /// Gets or sets the additional properties associated with the current scope. /// [BsonField("properties")] - public virtual IReadOnlyDictionary? Properties { get; set; } + public virtual ImmutableDictionary? Properties { get; set; } /// /// Gets or sets the resources associated with the current scope. /// [BsonField("resources")] - public virtual IReadOnlyList? Resources { get; set; } = ImmutableList.Create(); + public virtual ImmutableArray? Resources { get; set; } = ImmutableArray.Create(); } diff --git a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBToken.cs b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBToken.cs index 859b9e3..a118030 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBToken.cs +++ b/src/Sknet.OpenIddict.LiteDB.Models/OpenIddictLiteDBToken.cs @@ -43,13 +43,13 @@ public class OpenIddictLiteDBToken /// Gets or sets the UTC creation date of the current token. /// [BsonField("creation_date")] - public virtual DateTime? CreationDate { get; set; } + public virtual DateTimeOffset? CreationDate { get; set; } /// /// Gets or sets the UTC expiration date of the current token. /// [BsonField("expiration_date")] - public virtual DateTime? ExpirationDate { get; set; } + public virtual DateTimeOffset? ExpirationDate { get; set; } /// /// Gets or sets the unique identifier associated with the current token. @@ -69,13 +69,13 @@ public class OpenIddictLiteDBToken /// Gets or sets the additional properties associated with the current token. /// [BsonField("properties")] - public virtual IReadOnlyDictionary? Properties { get; set; } + public virtual ImmutableDictionary? Properties { get; set; } /// /// Gets or sets the UTC redemption date of the current token. /// [BsonField("redemption_date")] - public virtual DateTime? RedemptionDate { get; set; } + public virtual DateTimeOffset? RedemptionDate { get; set; } /// /// Gets or sets the reference identifier associated diff --git a/src/Sknet.OpenIddict.LiteDB.Models/Sknet.OpenIddict.LiteDB.Models.csproj b/src/Sknet.OpenIddict.LiteDB.Models/Sknet.OpenIddict.LiteDB.Models.csproj index 8c0f9ab..4d1a1dd 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/Sknet.OpenIddict.LiteDB.Models.csproj +++ b/src/Sknet.OpenIddict.LiteDB.Models/Sknet.OpenIddict.LiteDB.Models.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/Sknet.OpenIddict.LiteDB.Models/Usings.cs b/src/Sknet.OpenIddict.LiteDB.Models/Usings.cs index 954fe3d..5b02c7d 100644 --- a/src/Sknet.OpenIddict.LiteDB.Models/Usings.cs +++ b/src/Sknet.OpenIddict.LiteDB.Models/Usings.cs @@ -1,4 +1,5 @@ global using LiteDB; global using System.Collections.Immutable; global using System.Diagnostics; -global using System.Text.Json; \ No newline at end of file +global using System.Globalization; +global using System.Text.Json; diff --git a/src/Sknet.OpenIddict.LiteDB/IOpenIddictLiteDBContext.cs b/src/Sknet.OpenIddict.LiteDB/IOpenIddictLiteDBContext.cs index 68a872f..7fa816c 100644 --- a/src/Sknet.OpenIddict.LiteDB/IOpenIddictLiteDBContext.cs +++ b/src/Sknet.OpenIddict.LiteDB/IOpenIddictLiteDBContext.cs @@ -24,7 +24,7 @@ public interface IOpenIddictLiteDBContext /// Gets the . /// /// - /// A that can be used to monitor the + /// A that can be used to monitor the /// asynchronous operation, whose result returns the LiteDB database. /// ValueTask GetDatabaseAsync(CancellationToken cancellationToken); diff --git a/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBExtensions.cs b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBExtensions.cs index 05b6f5f..fb0d6d2 100644 --- a/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBExtensions.cs +++ b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBExtensions.cs @@ -15,7 +15,6 @@ */ namespace Microsoft.Extensions.DependencyInjection; - /// /// Exposes extensions allowing to register the OpenIddict LiteDB services. /// diff --git a/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBOptions.cs b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBOptions.cs index 1c2dbb3..99501a4 100644 --- a/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBOptions.cs +++ b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDBOptions.cs @@ -21,14 +21,14 @@ namespace Sknet.OpenIddict.LiteDB; public class OpenIddictLiteDBOptions { /// - /// Gets or sets the name of the applications collection (by default, openiddict.applications). + /// Gets or sets the name of the applications collection (by default, openiddict_applications). /// - public string ApplicationsCollectionName { get; set; } = "openiddict.applications"; + public string ApplicationsCollectionName { get; set; } = "openiddict_applications"; /// - /// Gets or sets the name of the authorizations collection (by default, openiddict.authorizations). + /// Gets or sets the name of the authorizations collection (by default, openiddict_authorizations). /// - public string AuthorizationsCollectionName { get; set; } = "openiddict.authorizations"; + public string AuthorizationsCollectionName { get; set; } = "openiddict_authorizations"; /// /// Gets or sets the used by the OpenIddict stores. @@ -37,12 +37,12 @@ public class OpenIddictLiteDBOptions public ILiteDatabase? Database { get; set; } /// - /// Gets or sets the name of the scopes collection (by default, openiddict.scopes). + /// Gets or sets the name of the scopes collection (by default, openiddict_scopes). /// - public string ScopesCollectionName { get; set; } = "openiddict.scopes"; + public string ScopesCollectionName { get; set; } = "openiddict_scopes"; /// - /// Gets or sets the name of the tokens collection (by default, openiddict.tokens). + /// Gets or sets the name of the tokens collection (by default, openiddict_tokens). /// - public string TokensCollectionName { get; set; } = "openiddict.tokens"; + public string TokensCollectionName { get; set; } = "openiddict_tokens"; } \ No newline at end of file diff --git a/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDatabase.cs b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDatabase.cs new file mode 100644 index 0000000..ec0eb81 --- /dev/null +++ b/src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDatabase.cs @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using LiteDB.Engine; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Sknet.OpenIddict.LiteDB; + +/// +public class OpenIddictLiteDatabase : LiteDatabase +{ + /// + public OpenIddictLiteDatabase(string connectionString, BsonMapper? mapper = null) + : base(connectionString, mapper) + { + ConfigureMapper(); + } + + /// + public OpenIddictLiteDatabase(ConnectionString connectionString, BsonMapper? mapper = null) + : base(connectionString, mapper) + { + ConfigureMapper(); + } + + /// + public OpenIddictLiteDatabase(Stream stream, BsonMapper? mapper = null, Stream? logStream = null) + : base(stream, mapper, logStream) + { + ConfigureMapper(); + } + + /// + public OpenIddictLiteDatabase(ILiteEngine engine, BsonMapper? mapper = null, bool disposeOnClose = true) + : base(engine, mapper, disposeOnClose) + { + ConfigureMapper(); + } + + private void ConfigureMapper() + { + if (Mapper == null) + { + throw new NotImplementedException(); + } + + Mapper.RegisterType> + ( + serialize: dictionary => + { + if (dictionary == null) { return null; } + + var document = new BsonDocument(); + foreach (var pair in dictionary) + { + document.Add(pair.Key.Name, pair.Value); + } + return document; + }, + deserialize: bson => bson.AsDocument.ToImmutableDictionary( + pair => new CultureInfo(pair.Key), + pair => pair.Value.AsString) + ); + Mapper.RegisterType> + ( + serialize: items => items != null + ? new BsonArray(items.Select(x => new BsonValue(x))) + : null, + deserialize: bson => bson.AsArray.Select(x => x.AsString).ToImmutableArray() + ); + Mapper.RegisterType> + ( + serialize: dictionary => dictionary != null + ? JsonSerializer.Serialize(dictionary) + : null, + deserialize: bson => JsonSerializer.Deserialize>(bson.AsString) + ?? ImmutableDictionary.Empty + ); + } +} diff --git a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBApplicationStore.cs b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBApplicationStore.cs index 836bdae..fdaf287 100644 --- a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBApplicationStore.cs +++ b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBApplicationStore.cs @@ -133,7 +133,7 @@ public virtual async ValueTask DeleteAsync(TApplication application, Cancellatio var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ApplicationsCollectionName); - return collection.FindById(identifier); + return collection.FindById(new ObjectId(identifier)); } /// @@ -273,12 +273,10 @@ public virtual ValueTask> GetDisplayNam if (application.DisplayNames is not { Count: > 0 }) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(application.DisplayNames.ToImmutableDictionary( - pair => CultureInfo.GetCultureInfo(pair.Key), - pair => pair.Value)); + return new(application.DisplayNames); } /// @@ -301,12 +299,12 @@ public virtual ValueTask> GetPermissionsAsync( throw new ArgumentNullException(nameof(application)); } - if (application.Permissions is not { Count: > 0 }) + if (application.Permissions is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(application.Permissions.ToImmutableArray()); + return new(application.Permissions.Value); } /// @@ -318,12 +316,12 @@ public virtual ValueTask> GetPostLogoutRedirectUrisAsync( throw new ArgumentNullException(nameof(application)); } - if (application.PostLogoutRedirectUris is not { Count: > 0 }) + if (application.PostLogoutRedirectUris is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(application.PostLogoutRedirectUris.ToImmutableArray()); + return new(application.PostLogoutRedirectUris.Value); } /// @@ -336,10 +334,10 @@ public virtual ValueTask> GetProperties if (application.Properties is null || application.Properties.Count == 0) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(application.Properties.ToImmutableDictionary()); + return new(application.Properties); } /// @@ -351,12 +349,12 @@ public virtual ValueTask> GetRedirectUrisAsync( throw new ArgumentNullException(nameof(application)); } - if (application.RedirectUris is not { Count: > 0 }) + if (application.RedirectUris is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(application.RedirectUris.ToImmutableArray()); + return new(application.RedirectUris.Value); } /// @@ -367,12 +365,12 @@ public virtual ValueTask> GetRequirementsAsync(TApplicati throw new ArgumentNullException(nameof(application)); } - if (application.Requirements is not { Count: > 0 }) + if (application.Requirements is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(application.Requirements.ToImmutableArray()); + return new(application.Requirements.Value); } /// @@ -515,14 +513,10 @@ public virtual ValueTask SetDisplayNamesAsync(TApplication application, if (names is not { Count: > 0 }) { application.DisplayNames = null; - return default; } - application.DisplayNames = names.ToImmutableDictionary( - pair => pair.Key.Name, - pair => pair.Value); - + application.DisplayNames = names; return default; } @@ -537,12 +531,10 @@ public virtual ValueTask SetPermissionsAsync(TApplication application, Immutable if (permissions.IsDefaultOrEmpty) { application.Permissions = null; - return default; } - application.Permissions = permissions.ToImmutableList(); - + application.Permissions = permissions; return default; } @@ -558,12 +550,10 @@ public virtual ValueTask SetPostLogoutRedirectUrisAsync(TApplication application if (addresses.IsDefaultOrEmpty) { application.PostLogoutRedirectUris = null; - return default; } - application.PostLogoutRedirectUris = addresses.ToImmutableList(); - + application.PostLogoutRedirectUris = addresses; return default; } @@ -579,12 +569,10 @@ public virtual ValueTask SetPropertiesAsync(TApplication application, if (properties is not { Count: > 0 }) { application.Properties = null; - return default; } application.Properties = properties; - return default; } @@ -600,12 +588,10 @@ public virtual ValueTask SetRedirectUrisAsync(TApplication application, if (addresses.IsDefaultOrEmpty) { application.RedirectUris = null; - return default; } - application.RedirectUris = addresses.ToImmutableList(); - + application.RedirectUris = addresses; return default; } @@ -621,12 +607,10 @@ public virtual ValueTask SetRequirementsAsync(TApplication application, if (requirements.IsDefaultOrEmpty) { application.Requirements = null; - return default; } - application.Requirements = requirements.ToImmutableList(); - + application.Requirements = requirements; return default; } diff --git a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBAuthorizationStore.cs b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBAuthorizationStore.cs index 3a29104..7270a3f 100644 --- a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBAuthorizationStore.cs +++ b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBAuthorizationStore.cs @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +using static OpenIddict.Abstractions.OpenIddictConstants; + namespace Sknet.OpenIddict.LiteDB; /// @@ -140,8 +142,8 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can /// public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, CancellationToken cancellationToken) + string subject, string client, string status, + CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { @@ -181,8 +183,8 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can /// public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, CancellationToken cancellationToken) + string subject, string client, string status, string type, + CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { @@ -228,8 +230,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can /// public virtual IAsyncEnumerable FindAsync( - string subject, string client, - string status, string type, + string subject, string client, string status, string type, ImmutableArray scopes, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) @@ -246,7 +247,7 @@ public virtual IAsyncEnumerable FindAsync( { throw new ArgumentException("The status cannot be null or empty.", nameof(status)); } - + if (string.IsNullOrEmpty(type)) { throw new ArgumentException("The type cannot be null or empty.", nameof(type)); @@ -258,15 +259,16 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can { var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - + var authorizations = collection.Query() .Where(entity => entity.Subject == subject && entity.ApplicationId == new ObjectId(client) && entity.Status == status && - entity.Type == type && - Enumerable.All(scopes, scope => entity.Scopes != null && entity.Scopes.Contains(scope))) - .ToEnumerable().ToAsyncEnumerable(); + entity.Type == type) + .ToEnumerable() + .Where(entity => scopes.All(scope => entity.Scopes!.Contains(scope))) + .ToAsyncEnumerable(); await foreach (var authorization in authorizations) { @@ -313,7 +315,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - return collection.FindById(identifier); + return collection.FindById(new ObjectId(identifier)); } /// @@ -390,7 +392,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Can return new(result: null); } - return new(DateTime.SpecifyKind(authorization.CreationDate.Value, DateTimeKind.Utc)); + return new(authorization.CreationDate); } /// @@ -414,10 +416,10 @@ public virtual ValueTask> GetProperties if (authorization.Properties is null || authorization.Properties.Count == 0) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(authorization.Properties.ToImmutableDictionary()); + return new(authorization.Properties); } /// @@ -428,12 +430,12 @@ public virtual ValueTask> GetScopesAsync(TAuthorization a throw new ArgumentNullException(nameof(authorization)); } - if (authorization.Scopes is not { Count: > 0 }) + if (authorization.Scopes is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(authorization.Scopes.ToImmutableArray()); + return new(authorization.Scopes.Value); } /// @@ -526,57 +528,46 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellati } } } - + /// - public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + public async virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) { - throw new NotImplementedException(); - - //var database = await Context.GetDatabaseAsync(cancellationToken); - //var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - - // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDb - // To work around this limitation, the authorization identifiers are stored in an intermediate - // list and delete requests are sent to remove the documents corresponding to these identifiers. - - //var identifiers = - // await (from authorization in collection.AsQueryable() - // join token in database.GetCollection(Options.CurrentValue.TokensCollectionName).AsQueryable() - // on authorization.Id equals token.AuthorizationId into tokens - // where authorization.CreationDate < threshold.UtcDateTime - // where authorization.Status != Statuses.Valid || - // (authorization.Type == AuthorizationTypes.AdHoc && !tokens.Any()) - // select authorization.Id).ToListAsync(cancellationToken); - - // Note: to avoid generating delete requests with very large filters, a buffer is used here and the - // maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. - //foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) - //{ - // await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id), cancellationToken); - //} + var database = await Context.GetDatabaseAsync(cancellationToken); + var authorizationCollection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); - //static IEnumerable> Buffer(IEnumerable source, int count) - //{ - // List? buffer = null; + var query = from auth in authorizationCollection.Query() + // only prune authorizations created before threshold + where auth.CreationDate < threshold.UtcDateTime + // prune tokens that are not valid + where auth.Status != Statuses.Valid + select auth.Id; + var authorizations = query.ToList(); - // foreach (var element in source) - // { - // buffer ??= new List(capacity: 1); - // buffer.Add(element); + // prune adhoc authorizations that have no tokens + query = from auth in authorizationCollection.Query() + where auth.CreationDate < threshold.UtcDateTime + where auth.Status == Statuses.Valid + where auth.Type == AuthorizationTypes.AdHoc + select auth.Id; + var adhocAuthorizations = new HashSet(query.ToEnumerable()); - // if (buffer.Count == count) - // { - // yield return buffer; + var tokenCollection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + var adhocAuthorizationsWithTokens = new HashSet(tokenCollection.Query() + .GroupBy("authorization_id") + .Where(x => adhocAuthorizations.Contains(x["authorization_id"])) + .Select("{authorization_id:@key}") + .ToEnumerable() + .Select(x => x["authorization_id"].AsObjectId)); - // buffer = null; - // } - // } + adhocAuthorizations.SymmetricExceptWith(adhocAuthorizationsWithTokens); + authorizations.AddRange(adhocAuthorizations); - // if (buffer is not null) - // { - // yield return buffer; - // } - //} + database.BeginTrans(); + foreach (var authorization in authorizations) + { + authorizationCollection.Delete(authorization); + } + database.Commit(); } /// @@ -592,7 +583,6 @@ public virtual ValueTask SetApplicationIdAsync(TAuthorization authorization, { authorization.ApplicationId = new ObjectId(identifier); } - else { authorization.ApplicationId = ObjectId.Empty; @@ -611,7 +601,6 @@ public virtual ValueTask SetCreationDateAsync(TAuthorization authorization, } authorization.CreationDate = date?.UtcDateTime; - return default; } @@ -627,12 +616,10 @@ public virtual ValueTask SetPropertiesAsync(TAuthorization authorization, if (properties is not { Count: > 0 }) { authorization.Properties = null; - return default; } authorization.Properties = properties; - return default; } @@ -648,12 +635,10 @@ public virtual ValueTask SetScopesAsync(TAuthorization authorization, if (scopes.IsDefaultOrEmpty) { authorization.Scopes = null; - return default; } - authorization.Scopes = scopes.ToImmutableList(); - + authorization.Scopes = scopes; return default; } @@ -666,7 +651,6 @@ public virtual ValueTask SetStatusAsync(TAuthorization authorization, string? st } authorization.Status = status; - return default; } @@ -679,7 +663,6 @@ public virtual ValueTask SetSubjectAsync(TAuthorization authorization, string? s } authorization.Subject = subject; - return default; } @@ -692,7 +675,6 @@ public virtual ValueTask SetTypeAsync(TAuthorization authorization, string? type } authorization.Type = type; - return default; } diff --git a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBScopeStore.cs b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBScopeStore.cs index b154f67..29a9b3a 100644 --- a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBScopeStore.cs +++ b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBScopeStore.cs @@ -111,7 +111,7 @@ public virtual async ValueTask DeleteAsync(TScope scope, CancellationToken cance var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.ScopesCollectionName); - return collection.FindById(identifier); + return collection.FindById(new ObjectId(identifier)); } /// @@ -221,12 +221,10 @@ public virtual ValueTask> GetDescriptio if (scope.Descriptions is not { Count: > 0 }) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(scope.Descriptions.ToImmutableDictionary( - pair => CultureInfo.GetCultureInfo(pair.Key), - pair => pair.Value)); + return new(scope.Descriptions); } /// @@ -250,12 +248,10 @@ public virtual ValueTask> GetDisplayNam if (scope.DisplayNames is not { Count: > 0 }) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(scope.DisplayNames.ToImmutableDictionary( - pair => CultureInfo.GetCultureInfo(pair.Key), - pair => pair.Value)); + return new(scope.DisplayNames); } /// @@ -290,10 +286,10 @@ public virtual ValueTask> GetProperties if (scope.Properties is null || scope.Properties.Count == 0) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(scope.Properties.ToImmutableDictionary()); + return new(scope.Properties); } /// @@ -304,12 +300,12 @@ public virtual ValueTask> GetResourcesAsync(TScope scope, throw new ArgumentNullException(nameof(scope)); } - if (scope.Resources is not { Count: > 0 }) + if (scope.Resources is not { Length: > 0 }) { - return new(ImmutableArray.Create()); + return new(ImmutableArray.Empty); } - return new(scope.Resources.ToImmutableArray()); + return new(scope.Resources.Value); } /// @@ -319,7 +315,6 @@ public virtual ValueTask InstantiateAsync(CancellationToken cancellation { return new(Activator.CreateInstance()); } - catch (MemberAccessException exception) { return new(Task.FromException( @@ -379,7 +374,6 @@ public virtual ValueTask SetDescriptionAsync(TScope scope, string? description, } scope.Description = description; - return default; } @@ -395,14 +389,10 @@ public virtual ValueTask SetDescriptionsAsync(TScope scope, if (descriptions is not { Count: > 0 }) { scope.Descriptions = null; - return default; } - scope.Descriptions = descriptions.ToImmutableDictionary( - pair => pair.Key.Name, - pair => pair.Value); - + scope.Descriptions = descriptions; return default; } @@ -418,14 +408,10 @@ public virtual ValueTask SetDisplayNamesAsync(TScope scope, if (names is not { Count: > 0 }) { scope.DisplayNames = null; - return default; } - scope.DisplayNames = names.ToImmutableDictionary( - pair => pair.Key.Name, - pair => pair.Value); - + scope.DisplayNames = names; return default; } @@ -438,7 +424,6 @@ public virtual ValueTask SetDisplayNameAsync(TScope scope, string? name, Cancell } scope.DisplayName = name; - return default; } @@ -451,7 +436,6 @@ public virtual ValueTask SetNameAsync(TScope scope, string? name, CancellationTo } scope.Name = name; - return default; } @@ -467,12 +451,10 @@ public virtual ValueTask SetPropertiesAsync(TScope scope, if (properties is not { Count: > 0 }) { scope.Properties = null; - return default; } scope.Properties = properties; - return default; } @@ -487,12 +469,10 @@ public virtual ValueTask SetResourcesAsync(TScope scope, ImmutableArray if (resources.IsDefaultOrEmpty) { scope.Resources = null; - return default; } - scope.Resources = resources.ToImmutableList(); - + scope.Resources = resources; return default; } diff --git a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBTokenStore.cs b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBTokenStore.cs index b9a1d40..78dd796 100644 --- a/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBTokenStore.cs +++ b/src/Sknet.OpenIddict.LiteDB/Stores/OpenIddictLiteDBTokenStore.cs @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +using static OpenIddict.Abstractions.OpenIddictConstants; + namespace Sknet.OpenIddict.LiteDB; /// @@ -285,7 +287,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellatio var database = await Context.GetDatabaseAsync(cancellationToken); var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - return collection.FindById(identifier); + return collection.FindById(new ObjectId(identifier)); } /// @@ -393,7 +395,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellatio return new(result: null); } - return new(DateTime.SpecifyKind(token.CreationDate.Value, DateTimeKind.Utc)); + return new(token.CreationDate); } /// @@ -409,7 +411,7 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellatio return new(result: null); } - return new(DateTime.SpecifyKind(token.ExpirationDate.Value, DateTimeKind.Utc)); + return new(token.ExpirationDate); } /// @@ -444,10 +446,10 @@ public virtual ValueTask> GetProperties if (token.Properties is null || token.Properties.Count == 0) { - return new(ImmutableDictionary.Create()); + return new(ImmutableDictionary.Empty); } - return new(token.Properties.ToImmutableDictionary()); + return new(token.Properties); } /// @@ -463,7 +465,7 @@ public virtual ValueTask> GetProperties return new(result: null); } - return new(DateTime.SpecifyKind(token.RedemptionDate.Value, DateTimeKind.Utc)); + return new(token.RedemptionDate); } /// @@ -569,56 +571,38 @@ async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] Cancellati } /// - public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) + public async virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) { - throw new NotImplementedException(); - - //var database = await Context.GetDatabaseAsync(cancellationToken); - //var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); - - //// Note: directly deleting the resulting set of an aggregate query is not supported by MongoDb. - //// To work around this limitation, the token identifiers are stored in an intermediate list - //// and delete requests are sent to remove the documents corresponding to these identifiers. - - //var identifiers = - // await (from token in collection.AsQueryable() - // join authorization in database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName).AsQueryable() - // on token.AuthorizationId equals authorization.Id into authorizations - // where token.CreationDate < threshold.UtcDateTime - // where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) || - // token.ExpirationDate < DateTime.UtcNow || - // authorizations.Any(authorization => authorization.Status != Statuses.Valid) - // select token.Id).ToListAsync(cancellationToken); - - //// Note: to avoid generating delete requests with very large filters, a buffer is used here and the - //// maximum number of elements that can be removed by a single call to PruneAsync() is deliberately limited. - //foreach (var buffer in Buffer(identifiers.Take(1_000_000), 1_000)) - //{ - // await collection.DeleteManyAsync(token => buffer.Contains(token.Id), cancellationToken); - //} - - //static IEnumerable> Buffer(IEnumerable source, int count) - //{ - // List? buffer = null; - - // foreach (var element in source) - // { - // buffer ??= new List(capacity: 1); - // buffer.Add(element); - - // if (buffer.Count == count) - // { - // yield return buffer; - - // buffer = null; - // } - // } + var database = await Context.GetDatabaseAsync(cancellationToken); - // if (buffer is not null) - // { - // yield return buffer; - // } - //} + // Get invalid authorizations from which tokens can be pruned + var invalidAuthorizations = database + .GetCollection(Options.CurrentValue.AuthorizationsCollectionName) + .Query() + .Where("$.Status != @0", Statuses.Valid) + .Select(x => x["_id"].AsObjectId) + .ToList(); + + var tokenCollection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + var query = from token in tokenCollection.Query() + // only prune tokens created before threshold + where token.CreationDate < threshold.UtcDateTime + // prune tokens that either + // 1. not inactive and not valid, + // 2. past expiration date, OR + // 3. belong to an invalid authorization + where (token.Status != Statuses.Inactive && token.Status != Statuses.Valid) + || token.ExpirationDate < DateTimeOffset.UtcNow + || invalidAuthorizations.Contains(token.AuthorizationId) + select token.Id; + var tokens = query.ToList(); + + database.BeginTrans(); + foreach (var token in tokens) + { + tokenCollection.Delete(token); + } + database.Commit(); } /// @@ -633,7 +617,6 @@ public virtual ValueTask SetApplicationIdAsync(TToken token, string? identifier, { token.ApplicationId = new ObjectId(identifier); } - else { token.ApplicationId = ObjectId.Empty; @@ -654,7 +637,6 @@ public virtual ValueTask SetAuthorizationIdAsync(TToken token, string? identifie { token.AuthorizationId = new ObjectId(identifier); } - else { token.AuthorizationId = ObjectId.Empty; @@ -672,7 +654,6 @@ public virtual ValueTask SetCreationDateAsync(TToken token, DateTimeOffset? date } token.CreationDate = date?.UtcDateTime; - return default; } @@ -685,7 +666,6 @@ public virtual ValueTask SetExpirationDateAsync(TToken token, DateTimeOffset? da } token.ExpirationDate = date?.UtcDateTime; - return default; } @@ -698,7 +678,6 @@ public virtual ValueTask SetPayloadAsync(TToken token, string? payload, Cancella } token.Payload = payload; - return default; } @@ -714,12 +693,10 @@ public virtual ValueTask SetPropertiesAsync(TToken token, if (properties is not { Count: > 0 }) { token.Properties = null; - return default; } token.Properties = properties; - return default; } @@ -732,7 +709,6 @@ public virtual ValueTask SetRedemptionDateAsync(TToken token, DateTimeOffset? da } token.RedemptionDate = date?.UtcDateTime; - return default; } @@ -745,7 +721,6 @@ public virtual ValueTask SetReferenceIdAsync(TToken token, string? identifier, C } token.ReferenceId = identifier; - return default; } @@ -758,7 +733,6 @@ public virtual ValueTask SetStatusAsync(TToken token, string? status, Cancellati } token.Status = status; - return default; } @@ -771,7 +745,6 @@ public virtual ValueTask SetSubjectAsync(TToken token, string? subject, Cancella } token.Subject = subject; - return default; } @@ -784,7 +757,6 @@ public virtual ValueTask SetTypeAsync(TToken token, string? type, CancellationTo } token.Type = type; - return default; } diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationFaker.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationFaker.cs new file mode 100644 index 0000000..4495957 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationFaker.cs @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBApplicationFaker : Faker +{ + public OpenIddictLiteDBApplicationFaker() + { + UseSeed(1); + + RuleFor(x => x.Id, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ClientId, f => f.Random.AlphaNumeric(32)) + .RuleFor(x => x.ClientSecret, f => f.Random.AlphaNumeric(32)) + .RuleFor(x => x.ConcurrencyToken, f => f.Random.Guid().ToString()) + .RuleFor(x => x.ConsentType, f => f.PickRandom(new[] { ConsentTypes.Explicit, ConsentTypes.External, ConsentTypes.Implicit, ConsentTypes.Systematic })) + .RuleFor(x => x.DisplayName, f => f.Commerce.ProductName()) + .RuleFor(x => x.DisplayNames, f => f.Random.ListItems<(CultureInfo Culture, string DisplayName)>(new() + { + (CultureInfo.GetCultureInfo("en"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("fr"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("de"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("es"), f.Commerce.ProductName()) + }) + .OrderBy(x => x.Culture.Name) + .ToImmutableDictionary(x => x.Culture, x => x.DisplayName)) + .RuleFor(x => x.Permissions, f => f.Random.ListItems(new() { "permission1", "permission2", "permission3" }) + .ToImmutableArray()) + .RuleFor(x => x.PostLogoutRedirectUris, f => f.Random.ListItems(new() + { + f.Internet.UrlWithPath(), + f.Internet.UrlWithPath(), + f.Internet.UrlWithPath() + }) + .ToImmutableArray()) + .RuleFor(x => x.Properties, f => f.Random.ListItems<(string PropertyName, JsonElement JsonValue)>(new() + { + ("property1", JsonDocument.Parse("true").RootElement), + ("property2", JsonDocument.Parse("false").RootElement), + ("property3", JsonDocument.Parse("null").RootElement), + ("property4", JsonDocument.Parse("1").RootElement), + ("property5", JsonDocument.Parse("1.1").RootElement), + ("property6", JsonDocument.Parse(@"""""").RootElement), + ("property7", JsonDocument.Parse(@"""value""").RootElement), + ("property8", JsonDocument.Parse(@"[""value1"", ""value2""]").RootElement), + ("property9", JsonDocument.Parse(@"{""key"": ""value""}").RootElement) + }) + .OrderBy(x => x.PropertyName) + .ToImmutableDictionary(x => x.PropertyName, x => x.JsonValue)) + .RuleFor(x => x.RedirectUris, f => f.Random.ListItems(new() + { + f.Internet.UrlWithPath(), + f.Internet.UrlWithPath(), + f.Internet.UrlWithPath() + }) + .ToImmutableArray()) + .RuleFor(x => x.Requirements, f => ImmutableArray.Create()) + .RuleFor(x => x.Type, f => f.PickRandom(new[] { ClientTypes.Confidential, ClientTypes.Public })); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationStoreBuilder.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationStoreBuilder.cs new file mode 100644 index 0000000..7a1c76e --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationStoreBuilder.cs @@ -0,0 +1,126 @@ +/* +* Copyright (c) 2022 Steven Kuhn and contributors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBApplicationStoreBuilder +{ + private readonly List _applications = new(); + private readonly List _authorizations = new(); + private readonly List _scopes = new(); + private readonly List _tokens = new(); + private ILiteDatabase? _database; + + public OpenIddictLiteDBApplicationStoreBuilder WithApplications(params OpenIddictLiteDBApplication[] applications) + { + _applications.AddRange(applications); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithApplications(IEnumerable applications) + { + _applications.AddRange(applications); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithAuthorizations(params OpenIddictLiteDBAuthorization[] authorizations) + { + _authorizations.AddRange(authorizations); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithAuthorizations(IEnumerable authorizations) + { + _authorizations.AddRange(authorizations); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithDatabase(ILiteDatabase database) + { + _database = database; + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithScopes(params OpenIddictLiteDBScope[] scopes) + { + _scopes.AddRange(scopes); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithScopes(IEnumerable scopes) + { + _scopes.AddRange(scopes); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithTokens(params OpenIddictLiteDBToken[] tokens) + { + _tokens.AddRange(tokens); + return this; + } + + public OpenIddictLiteDBApplicationStoreBuilder WithTokens(IEnumerable tokens) + { + _tokens.AddRange(tokens); + return this; + } + + + public OpenIddictLiteDBApplicationStore Build() + { + var database = _database ?? new OpenIddictLiteDatabase(":memory:"); + var options = new OpenIddictLiteDBOptions(); + + if (_applications.Count > 0) + { + database + .GetCollection(options.ApplicationsCollectionName) + .InsertBulk(_applications); + } + + if (_authorizations.Count > 0) + { + database + .GetCollection(options.AuthorizationsCollectionName) + .InsertBulk(_authorizations); + } + + if (_scopes.Count > 0) + { + database + .GetCollection(options.ScopesCollectionName) + .InsertBulk(_scopes); + } + + if (_tokens.Count > 0) + { + database + .GetCollection(options.TokensCollectionName) + .InsertBulk(_tokens); + } + + var contextMock = new Mock(); + contextMock + .Setup(x => x.GetDatabaseAsync(It.IsAny())) + .ReturnsAsync(database); + + var optionsMock = new Mock>(); + optionsMock + .SetupGet(x => x.CurrentValue) + .Returns(options); + + return new OpenIddictLiteDBApplicationStore(contextMock.Object, optionsMock.Object); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationFaker.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationFaker.cs new file mode 100644 index 0000000..1ff23fd --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationFaker.cs @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBAuthorizationFaker : Faker +{ + public OpenIddictLiteDBAuthorizationFaker() + { + UseSeed(1); + + RuleFor(x => x.Id, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ApplicationId, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ConcurrencyToken, f => f.Random.Guid().ToString()) + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset()) + .RuleFor(x => x.Properties, f => f.Random.ListItems<(string PropertyName, JsonElement JsonValue)>(new() + { + ("property1", JsonDocument.Parse("true").RootElement), + ("property2", JsonDocument.Parse("false").RootElement), + ("property3", JsonDocument.Parse("null").RootElement), + ("property4", JsonDocument.Parse("1").RootElement), + ("property5", JsonDocument.Parse("1.1").RootElement), + ("property6", JsonDocument.Parse(@"""""").RootElement), + ("property7", JsonDocument.Parse(@"""value""").RootElement), + ("property8", JsonDocument.Parse(@"[""value1"", ""value2""]").RootElement), + ("property9", JsonDocument.Parse(@"{""key"": ""value""}").RootElement) + }) + .OrderBy(x => x.PropertyName) + .ToImmutableDictionary(x => x.PropertyName, x => x.JsonValue)) + .RuleFor(x => x.Scopes, f => f.Random.ListItems(new() { "scope1", "scope2", "scope3" }) + .ToImmutableArray()) + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Redeemed, + Statuses.Rejected, + Statuses.Revoked, + Statuses.Valid + })) + .RuleFor(x => x.Subject, f => f.Person.UserName) + .RuleFor(x => x.Type, f => f.PickRandom(new[] { AuthorizationTypes.AdHoc, AuthorizationTypes.Permanent })); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationStoreBuilder.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationStoreBuilder.cs new file mode 100644 index 0000000..c0604a0 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationStoreBuilder.cs @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBAuthorizationStoreBuilder +{ + private readonly List _authorizations = new(); + private readonly List _tokens = new(); + private ILiteDatabase? _database; + + public OpenIddictLiteDBAuthorizationStoreBuilder WithAuthorizations(params OpenIddictLiteDBAuthorization[] authorizations) + { + _authorizations.AddRange(authorizations); + return this; + } + + public OpenIddictLiteDBAuthorizationStoreBuilder WithAuthorizations(IEnumerable authorizations) + { + _authorizations.AddRange(authorizations); + return this; + } + + public OpenIddictLiteDBAuthorizationStoreBuilder WithDatabase(ILiteDatabase database) + { + _database = database; + return this; + } + + public OpenIddictLiteDBAuthorizationStoreBuilder WithTokens(params OpenIddictLiteDBToken[] tokens) + { + _tokens.AddRange(tokens); + return this; + } + + public OpenIddictLiteDBAuthorizationStoreBuilder WithTokens(IEnumerable tokens) + { + _tokens.AddRange(tokens); + return this; + } + + public OpenIddictLiteDBAuthorizationStore Build() + { + var database = _database ?? new OpenIddictLiteDatabase(":memory:"); + var options = new OpenIddictLiteDBOptions(); + + if (_authorizations.Count > 0) + { + database + .GetCollection(options.AuthorizationsCollectionName) + .InsertBulk(_authorizations); + } + + if (_tokens.Count > 0) + { + database + .GetCollection(options.TokensCollectionName) + .InsertBulk(_tokens); + } + + var contextMock = new Mock(); + contextMock + .Setup(x => x.GetDatabaseAsync(It.IsAny())) + .ReturnsAsync(database); + + var optionsMock = new Mock>(); + optionsMock + .SetupGet(x => x.CurrentValue) + .Returns(options); + + return new OpenIddictLiteDBAuthorizationStore(contextMock.Object, optionsMock.Object); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeFaker.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeFaker.cs new file mode 100644 index 0000000..9c1abdb --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeFaker.cs @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBScopeFaker : Faker +{ + public OpenIddictLiteDBScopeFaker() + { + UseSeed(1); + + RuleFor(x => x.Id, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ConcurrencyToken, f => f.Random.Guid().ToString()) + .RuleFor(x => x.Description, f => f.Lorem.Sentence()) + .RuleFor(x => x.Descriptions, f => f.Random.ListItems<(CultureInfo Culture, string Description)>(new() + { + (CultureInfo.GetCultureInfo("en"), f.Lorem.Sentence()), + (CultureInfo.GetCultureInfo("fr"), f.Lorem.Sentence()), + (CultureInfo.GetCultureInfo("de"), f.Lorem.Sentence()), + (CultureInfo.GetCultureInfo("es"), f.Lorem.Sentence()) + }) + .OrderBy(x => x.Culture.Name) + .ToImmutableDictionary(x => x.Culture, x => x.Description)) + .RuleFor(x => x.DisplayName, f => f.Commerce.ProductName()) + .RuleFor(x => x.DisplayNames, f => f.Random.ListItems<(CultureInfo Culture, string DisplayName)>(new() + { + (CultureInfo.GetCultureInfo("en"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("fr"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("de"), f.Commerce.ProductName()), + (CultureInfo.GetCultureInfo("es"), f.Commerce.ProductName()) + }) + .OrderBy(x => x.Culture.Name) + .ToImmutableDictionary(x => x.Culture, x => x.DisplayName)) + .RuleFor(x => x.Name, f => f.Commerce.ProductName()) + .RuleFor(x => x.Properties, f => f.Random.ListItems<(string PropertyName, JsonElement JsonValue)>(new() + { + ("property1", JsonDocument.Parse("true").RootElement), + ("property2", JsonDocument.Parse("false").RootElement), + ("property3", JsonDocument.Parse("null").RootElement), + ("property4", JsonDocument.Parse("1").RootElement), + ("property5", JsonDocument.Parse("1.1").RootElement), + ("property6", JsonDocument.Parse(@"""""").RootElement), + ("property7", JsonDocument.Parse(@"""value""").RootElement), + ("property8", JsonDocument.Parse(@"[""value1"", ""value2""]").RootElement), + ("property9", JsonDocument.Parse(@"{""key"": ""value""}").RootElement) + }) + .OrderBy(x => x.PropertyName) + .ToImmutableDictionary(x => x.PropertyName, x => x.JsonValue)) + .RuleFor(x => x.Resources, f => f.Random.ListItems(new() + { + "resource1", + "resource2", + "resource3", + "resource4", + "resource5" + }) + .ToImmutableArray()); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeStoreBuilder.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeStoreBuilder.cs new file mode 100644 index 0000000..671851e --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeStoreBuilder.cs @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBScopeStoreBuilder +{ + private ILiteDatabase? _database; + private readonly List _scopes = new(); + + public OpenIddictLiteDBScopeStoreBuilder WithDatabase(ILiteDatabase database) + { + _database = database; + return this; + } + + public OpenIddictLiteDBScopeStoreBuilder WithScopes(params OpenIddictLiteDBScope[] scopes) + { + _scopes.AddRange(scopes); + return this; + } + + public OpenIddictLiteDBScopeStoreBuilder WithScopes(IEnumerable scopes) + { + _scopes.AddRange(scopes); + return this; + } + + public OpenIddictLiteDBScopeStore Build() + { + var database = _database ?? new OpenIddictLiteDatabase(":memory:"); + var options = new OpenIddictLiteDBOptions(); + + if (_scopes.Count > 0) + { + database + .GetCollection(options.ScopesCollectionName) + .InsertBulk(_scopes); + } + + var contextMock = new Mock(); + contextMock + .Setup(x => x.GetDatabaseAsync(It.IsAny())) + .ReturnsAsync(database); + + var optionsMock = new Mock>(); + optionsMock + .SetupGet(x => x.CurrentValue) + .Returns(options); + + return new OpenIddictLiteDBScopeStore(contextMock.Object, optionsMock.Object); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenFaker.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenFaker.cs new file mode 100644 index 0000000..10dfcc4 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenFaker.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBTokenFaker : Faker +{ + public OpenIddictLiteDBTokenFaker() + { + UseSeed(1); + + RuleFor(x => x.Id, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ApplicationId, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.AuthorizationId, f => new ObjectId(f.Random.Hexadecimal(24, prefix: ""))) + .RuleFor(x => x.ConcurrencyToken, f => f.Random.Guid().ToString()) + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset()) + .RuleFor(x => x.ExpirationDate, f => f.Date.FutureOffset()) + .RuleFor(x => x.Properties, f => f.Random.ListItems<(string PropertyName, JsonElement JsonValue)>(new() + { + ("property1", JsonDocument.Parse("true").RootElement), + ("property2", JsonDocument.Parse("false").RootElement), + ("property3", JsonDocument.Parse("null").RootElement), + ("property4", JsonDocument.Parse("1").RootElement), + ("property5", JsonDocument.Parse("1.1").RootElement), + ("property6", JsonDocument.Parse(@"""""").RootElement), + ("property7", JsonDocument.Parse(@"""value""").RootElement), + ("property8", JsonDocument.Parse(@"[""value1"", ""value2""]").RootElement), + ("property9", JsonDocument.Parse(@"{""key"": ""value""}").RootElement) + }) + .OrderBy(x => x.PropertyName) + .ToImmutableDictionary(x => x.PropertyName, x => x.JsonValue)) + .RuleFor(x => x.ReferenceId, f => f.Random.Guid().ToString()) + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Redeemed, + Statuses.Rejected, + Statuses.Revoked, + Statuses.Valid + })) + .RuleFor(x => x.Subject, f => f.Person.UserName) + .RuleFor(x => x.Type, f => f.PickRandom(new[] { TokenTypes.Bearer })); + } +} \ No newline at end of file diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenStoreBuilder.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenStoreBuilder.cs new file mode 100644 index 0000000..3a5f085 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenStoreBuilder.cs @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Builders; + +public class OpenIddictLiteDBTokenStoreBuilder +{ + private ILiteDatabase? _database; + private readonly List _authorizations = new(); + private readonly List _tokens = new(); + + public OpenIddictLiteDBTokenStoreBuilder WithAuthorizations(IEnumerable authorizations) + { + _authorizations.AddRange(authorizations); + return this; + } + + public OpenIddictLiteDBTokenStoreBuilder WithDatabase(ILiteDatabase database) + { + _database = database; + return this; + } + + public OpenIddictLiteDBTokenStoreBuilder WithTokens(params OpenIddictLiteDBToken[] tokens) + { + _tokens.AddRange(tokens); + return this; + } + + public OpenIddictLiteDBTokenStoreBuilder WithTokens(IEnumerable tokens) + { + _tokens.AddRange(tokens); + return this; + } + + public OpenIddictLiteDBTokenStore Build() + { + var database = _database ?? new OpenIddictLiteDatabase(":memory:"); + var options = new OpenIddictLiteDBOptions(); + + if (_authorizations.Count > 0) + { + database + .GetCollection(options.AuthorizationsCollectionName) + .InsertBulk(_authorizations); + } + + if (_tokens.Count > 0) + { + database + .GetCollection(options.TokensCollectionName) + .InsertBulk(_tokens); + } + + var contextMock = new Mock(); + contextMock + .Setup(x => x.GetDatabaseAsync(It.IsAny())) + .ReturnsAsync(database); + + var optionsMock = new Mock>(); + optionsMock + .SetupGet(x => x.CurrentValue) + .Returns(options); + + return new OpenIddictLiteDBTokenStore(contextMock.Object, optionsMock.Object); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBApplicationStoreResolverTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBApplicationStoreResolverTests.cs index 10517f9..a4de1f2 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBApplicationStoreResolverTests.cs +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBApplicationStoreResolverTests.cs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace Sknet.OpenIddict.LiteDB.Tests; +namespace Sknet.OpenIddict.LiteDB.Tests.Resolvers; public class OpenIddictLiteDBApplicationStoreResolverTests { diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBAuthorizationStoreResolverTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBAuthorizationStoreResolverTests.cs index d182976..626062f 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBAuthorizationStoreResolverTests.cs +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBAuthorizationStoreResolverTests.cs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace Sknet.OpenIddict.LiteDB.Tests; +namespace Sknet.OpenIddict.LiteDB.Tests.Resolvers; public class OpenIddictLiteDBAuthorizationStoreResolverTests { diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBScopeStoreResolverTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBScopeStoreResolverTests.cs index 892afd9..dcd1b63 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBScopeStoreResolverTests.cs +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBScopeStoreResolverTests.cs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace Sknet.OpenIddict.LiteDB.Tests; +namespace Sknet.OpenIddict.LiteDB.Tests.Resolvers; public class OpenIddictLiteDBScopeStoreResolverTests { diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBTokenStoreResolverTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBTokenStoreResolverTests.cs index c420e1e..0782634 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBTokenStoreResolverTests.cs +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Resolvers/OpenIddictLiteDBTokenStoreResolverTests.cs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace Sknet.OpenIddict.LiteDB.Tests; +namespace Sknet.OpenIddict.LiteDB.Tests.Resolvers; public class OpenIddictLiteDBTokenStoreResolverTests { diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Sknet.OpenIddict.LiteDB.Tests.csproj b/test/Sknet.OpenIddict.LiteDB.Tests/Sknet.OpenIddict.LiteDB.Tests.csproj index a0a75a7..ad0dda9 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Sknet.OpenIddict.LiteDB.Tests.csproj +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Sknet.OpenIddict.LiteDB.Tests.csproj @@ -10,9 +10,12 @@ - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -27,5 +30,5 @@ - + diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Stores/ILiteDatabaseExtensions.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/ILiteDatabaseExtensions.cs new file mode 100644 index 0000000..225aa1e --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/ILiteDatabaseExtensions.cs @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests; + +static class ILiteDatabaseExtensions +{ + public static ILiteCollection Applications(this ILiteDatabase database) + { + var options = new OpenIddictLiteDBOptions(); + + return database.GetCollection(options.ApplicationsCollectionName); + } + + public static ILiteCollection Authorizations(this ILiteDatabase database) + { + var options = new OpenIddictLiteDBOptions(); + + return database.GetCollection(options.AuthorizationsCollectionName); + } + + public static ILiteCollection Scopes(this ILiteDatabase database) + { + var options = new OpenIddictLiteDBOptions(); + + return database.GetCollection(options.ScopesCollectionName); + } + + public static ILiteCollection Tokens(this ILiteDatabase database) + { + var options = new OpenIddictLiteDBOptions(); + + return database.GetCollection(options.TokensCollectionName); + } +} \ No newline at end of file diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBApplicationStoreTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBApplicationStoreTests.cs new file mode 100644 index 0000000..e6fc6e8 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBApplicationStoreTests.cs @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Stores; + +public class OpenIddictLiteDBApplicationStoreTests +{ + [Fact] + public async Task CountAsync_WithZeroItems_ReturnsZero() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(0, result); + + } + + [Fact] + public async Task CountAsync_WithThreeItems_ReturnsThree() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(3, result); + } + + [Fact] + public async Task CountAync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.CountAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CountAsync_WithQuery_ReturnsApplicationCount() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Act + var result = await store.CountAsync( + query => query.Where(a => a.ClientId == applications[1].ClientId), + default); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public async Task CreateAsync_WithNullApplication_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "application", + () => store.CreateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CreateAsync_AddsApplicationToDatabase() + { + // Arrange + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithDatabase(database) + .Build(); + var application = new OpenIddictLiteDBApplicationFaker().Generate(); + + // Act + await store.CreateAsync(application, default); + + // Assert + var result = database.Applications().FindById(application.Id); + Assert.NotNull(result); + } + + [Fact] + public async Task DeleteAsync_WithNullApplication_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "application", + () => store.DeleteAsync(null!, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesApplicationFromDatabase() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .WithDatabase(database) + .Build(); + + // Act + await store.DeleteAsync(applications[0], default); + + // Assert + var result = database.Applications().FindById(applications[0].Id); + Assert.Null(result); + } + + [Fact] + public async Task DeleteAsync_WithConcurrencyTokenChange_ThrowsException() + { + // Arrange + var application = new OpenIddictLiteDBApplicationFaker().Generate(); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(application) + .Build(); + + application.ConcurrencyToken = Guid.NewGuid().ToString(); + + // Act/Assert + await Assert.ThrowsAsync( + () => store.DeleteAsync(application, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesAuthorizationsAndTokensFromDatabase() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(2); + var authorizations = new OpenIddictLiteDBAuthorizationFaker() + .RuleFor(x => x.ApplicationId, f => applications[f.IndexFaker % 2].Id) + .Generate(2); + var tokens = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.ApplicationId, f => authorizations[f.IndexFaker % 2].ApplicationId) + .RuleFor(x => x.AuthorizationId, f => authorizations[f.IndexFaker % 2].Id) + .Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .WithAuthorizations(authorizations) + .WithTokens(tokens) + .WithDatabase(database) + .Build(); + + // Assert + var applicationId = applications[0].Id; + var authorizationsResult = database.Authorizations().Find(x => x.ApplicationId == applicationId); + var tokensResult = database.Tokens().Find(x => x.ApplicationId == applicationId); + Assert.Multiple( + () => Assert.NotEmpty(authorizationsResult), + () => Assert.NotEmpty(tokensResult)); + + // Act + await store.DeleteAsync(applications[0], default); + + // Assert + authorizationsResult = database.Authorizations().Find(x => x.ApplicationId == applicationId); + tokensResult = database.Tokens().Find(x => x.ApplicationId == applicationId); + Assert.Multiple( + () => Assert.Empty(authorizationsResult), + () => Assert.Empty(tokensResult)); + } + + [Fact] + public async Task FindByClientIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByClientIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByClientIdAsync_WithIdentifier_ReturnsApplication() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Act + var result = await store.FindByClientIdAsync(applications[1].ClientId!, default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(applications[1].Id, result!.Id)); + } + + [Fact] + public async Task FindByIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByIdAsync_WithIdentifier_ReturnsApplication() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Act + var result = await store.FindByIdAsync(applications[1].Id.ToString(), default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(applications[1].Id, result!.Id)); + } + + [Fact] + public async Task FindByPostLogoutRedirectUriAsync_WithNullAddress_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "address", + () => store.FindByPostLogoutRedirectUriAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByPostLogoutRedirectUriAsync_WithAddress_ReturnsApplication() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Assert + Assert.NotNull(applications[2].PostLogoutRedirectUris); + Assert.NotEmpty(applications[2].PostLogoutRedirectUris!); + + // Act + var result = await store.FindByPostLogoutRedirectUriAsync(applications[2].PostLogoutRedirectUris!.Value[0], default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(applications[2].Id, result[0].Id)); + } + + [Fact] + public async Task FindByRedirectUriAsync_WithNullAddress_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "address", + () => store.FindByRedirectUriAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByRedirectUriAsync_WithAddress_ReturnsApplication() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Assert + Assert.NotNull(applications[2].RedirectUris); + Assert.NotEmpty(applications[2].RedirectUris!); + + // Act + var result = await store.FindByRedirectUriAsync(applications[2].RedirectUris!.Value[0], default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(applications[2].Id, result[0].Id)); + } + + [Fact] + public async Task GetAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.GetAsync(query!, null, default).AsTask()); + } + + [Fact] + public async Task GetAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + var query = (IQueryable query, ObjectId state) => + query.Where(x => x.Id == state).Select(x => x.DisplayName); + + // Act + var result = await store.GetAsync(query, applications[1].Id, default); + + // Assert + Assert.Equal(applications[1].DisplayName, result); + } + + [Fact] + public async Task InstantiateAsync_ReturnsApplication() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act + var result = await store.InstantiateAsync(default); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task ListAsync_WithCountAndOffset_ReturnsApplications() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + // Act + var result = await store.ListAsync(1, 0, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Single(result)); + } + + [Fact] + public async Task ListAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.ListAsync(query!, null, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task ListAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .Build(); + + var query = (IQueryable query, object state) => + query.Where(x => x.Id == applications[0].Id || x.Id == applications[1].Id).Select(x => x.DisplayName); + + // Act + var result = await store.ListAsync(query!, null, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(2, result.Distinct().Count()), + () => Assert.True(applications.Select(x => x.DisplayName).Intersect(result).Count() == 2)); + } + + [Fact] + public async Task PruneAsync_WithAuthorizationsCreatedBeforeThreshold_ShouldRemoveAuthorizations() + { + // Arrange + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + + var authorizationFaker = new OpenIddictLiteDBAuthorizationFaker(); + + var authorizationsToPrune = authorizationFaker + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)) + .Generate(3); + var authorizationsToNotPrune = authorizationFaker + .RuleFor(x => x.CreationDate, f => f.Date.FutureOffset(1, threshold)) + .Generate(3); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizationsToPrune) + .WithAuthorizations(authorizationsToNotPrune) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Authorizations().FindAll(); + result.Should().BeEquivalentTo(authorizationsToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task PruneAsync_WithAuthorizationsThatAreNotValid_ShouldRemoveAuthorizations() + { + // Arrange + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + + var authorizationFaker = new OpenIddictLiteDBAuthorizationFaker() + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)); + + var authorizationsToPrune = authorizationFaker + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Redeemed, + Statuses.Rejected, + Statuses.Revoked, + })) + .Generate(3); + var authorizationsToNotPrune = authorizationFaker + .RuleFor(x => x.Status, f => Statuses.Valid) + .Generate(3); + + var tokenFaker = new OpenIddictLiteDBTokenFaker(); + var tokensForAuthorizationToPrune = tokenFaker + .RuleFor(x => x.AuthorizationId, f => authorizationsToPrune[f.IndexFaker % 3].Id) + .Generate(9); + var tokensForAuthorizationToNotPrune = tokenFaker + .RuleFor(x => x.AuthorizationId, f => authorizationsToNotPrune[f.IndexFaker % 3].Id) + .Generate(9); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizationsToPrune) + .WithAuthorizations(authorizationsToNotPrune) + .WithTokens(tokensForAuthorizationToPrune) + .WithTokens(tokensForAuthorizationToNotPrune) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Authorizations().FindAll(); + result.Should().BeEquivalentTo(authorizationsToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task PruneAsync_WithAdhocAuthorizationsWithZeroTokens_ShouldRemoveAuthorizations() + { + // Arrange + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + var authorizationFaker = new OpenIddictLiteDBAuthorizationFaker() + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)) + .RuleFor(x => x.Status, f => Statuses.Valid) + .RuleFor(x => x.Type, f => AuthorizationTypes.AdHoc); + + var authorizationsToPrune = authorizationFaker.Generate(3); + var authorizationsToNotPrune = authorizationFaker.Generate(3); + + var tokens = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.AuthorizationId, f => authorizationsToNotPrune[f.IndexFaker % 3].Id) + .Generate(9); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizationsToPrune) + .WithAuthorizations(authorizationsToNotPrune) + .WithTokens(tokens) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Authorizations().FindAll(); + result.Should().BeEquivalentTo(authorizationsToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task UpdateAsync_WithNullApplication_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "application", + () => store.UpdateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task UpdateAsync_WithApplication_UpdatesAppropriateApplication() + { + // Arrange + var applications = new OpenIddictLiteDBApplicationFaker().Generate(3); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBApplicationStoreBuilder() + .WithApplications(applications) + .WithDatabase(database) + .Build(); + + applications[1].DisplayName = "My Fabrikam"; + + // Act + await store.UpdateAsync(applications[1], default); + + // Assert + var result = database.Applications().FindById(applications[1].Id); + Assert.Equal("My Fabrikam", result.DisplayName); + } +} \ No newline at end of file diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBAuthorizationStoreTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBAuthorizationStoreTests.cs new file mode 100644 index 0000000..2c7b165 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBAuthorizationStoreTests.cs @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Stores; + +public class OpenIddictLiteDBAuthorizationStoreTests +{ + [Fact] + public async Task CountAsync_WithZeroItems_ReturnsZero() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(0, result); + + } + + [Fact] + public async Task CountAsync_WithThreeItems_ReturnsThree() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(3, result); + } + + [Fact] + public async Task CountAync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.CountAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CountAsync_WithQuery_ReturnsAuthorizationCount() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.CountAsync( + query => query.Where(a => a.ApplicationId == authorizations[1].ApplicationId), + default); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public async Task CreateAsync_WithNullAuthorization_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "authorization", + () => store.CreateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CreateAsync_AddsAuthorizationToDatabase() + { + // Arrange + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithDatabase(database) + .Build(); + var authorization = new OpenIddictLiteDBAuthorizationFaker().Generate(); + + // Act + await store.CreateAsync(authorization, default); + + // Assert + var result = database.Authorizations().FindById(authorization.Id); + Assert.NotNull(result); + } + + [Fact] + public async Task DeleteAsync_WithNullAuthorizaton_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBApplicationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "application", + () => store.DeleteAsync(null!, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesAuthorizatonFromDatabase() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .WithDatabase(database) + .Build(); + + // Act + await store.DeleteAsync(authorizations[0], default); + + // Assert + var result = database.Authorizations().FindById(authorizations[0].Id); + Assert.Null(result); + } + + [Fact] + public async Task DeleteAsync_WithConcurrencyTokenChange_ThrowsException() + { + // Arrange + var authorization = new OpenIddictLiteDBAuthorizationFaker().Generate(); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorization) + .Build(); + + authorization.ConcurrencyToken = Guid.NewGuid().ToString(); + + // Act/Assert + await Assert.ThrowsAsync( + () => store.DeleteAsync(authorization, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesTokensFromDatabase() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(2); + var tokens = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.ApplicationId, f => authorizations[f.IndexFaker % 2].ApplicationId) + .RuleFor(x => x.AuthorizationId, f => authorizations[f.IndexFaker % 2].Id) + .Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .WithTokens(tokens) + .WithDatabase(database) + .Build(); + + // Assert + var authorizationId = authorizations[0].Id; + var tokensResult = database.Tokens().Find(x => x.AuthorizationId == authorizationId); + Assert.NotEmpty(tokensResult); + + // Act + await store.DeleteAsync(authorizations[0], default); + + // Assert + tokensResult = database.Tokens().Find(x => x.AuthorizationId == authorizationId); + Assert.Empty(tokensResult); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClient_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClient_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClient_ShouldReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + + // Act + var result = await store.FindAsync(authorizations[1].Subject!, authorizations[1].ApplicationId.ToString(), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClientAndStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", "status", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClientAndStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, "status", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndNullStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "status", + () => store.FindAsync("subject", "client", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClientAndStatus_ShouldReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + Assert.NotNull(authorizations[1].Status); + + // Act + var result = await store.FindAsync( + authorizations[1].Subject!, + authorizations[1].ApplicationId.ToString(), + authorizations[1].Status!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClientAndStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", "status", "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClientAndStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, "status", "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndNullStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "status", + () => store.FindAsync("subject", "client", null!, "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndNullType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "type", + () => store.FindAsync("subject", "client", "status", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClientAndStatusAndType_ShouldReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + Assert.NotNull(authorizations[1].Status); + Assert.NotNull(authorizations[1].Type); + + // Act + var result = await store.FindAsync( + authorizations[1].Subject!, + authorizations[1].ApplicationId.ToString(), + authorizations[1].Status!, + authorizations[1].Type!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClientAndStatusAndTypeAndEmptyScopes_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", "status", "type", ImmutableArray.Empty, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClientAndStatusAndTypeAndEmptyScopes_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, "status", "type", ImmutableArray.Empty, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndNullStatusAndTypeAndEmptyScopes_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "status", + () => store.FindAsync("subject", "client", null!, "type", ImmutableArray.Empty, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndNullTypeAndEmptyScopes_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "type", + () => store.FindAsync("subject", "client", "status", null!, ImmutableArray.Empty, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndTypeAndEmptyScopes_ShouldReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + Assert.NotNull(authorizations[1].Status); + Assert.NotNull(authorizations[1].Type); + Assert.NotEmpty(authorizations[1].Scopes!); + + // Act + var result = await store.FindAsync( + authorizations[1].Subject!, + authorizations[1].ApplicationId.ToString(), + authorizations[1].Status!, + authorizations[1].Type!, + ImmutableArray.Empty, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndTypeAndScope_ShouldReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + Assert.NotNull(authorizations[1].Status); + Assert.NotNull(authorizations[1].Type); + Assert.NotEmpty(authorizations[1].Scopes!); + + // Act + var result = await store.FindAsync( + authorizations[1].Subject!, + authorizations[1].ApplicationId.ToString(), + authorizations[1].Status!, + authorizations[1].Type!, + ImmutableArray.Create(authorizations[1].Scopes!.Value[0]), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndTypeAndInvalidScope_ShouldNotReturnAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Assert + Assert.NotNull(authorizations[1].Subject); + Assert.NotEqual(ObjectId.Empty, authorizations[1].ApplicationId); + Assert.NotNull(authorizations[1].Status); + Assert.NotNull(authorizations[1].Type); + Assert.NotEmpty(authorizations[1].Scopes!); + + // Act + var result = await store.FindAsync( + authorizations[1].Subject!, + authorizations[1].ApplicationId.ToString(), + authorizations[1].Status!, + authorizations[1].Type!, + ImmutableArray.Create("invalid-scope"), default).ToListAsync(); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task FindByApplicationIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByApplicationIdAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByApplicationIdAsync_WithIdentifier_ReturnsAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.FindByApplicationIdAsync(authorizations[1].ApplicationId.ToString(), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindByIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByIdAsync_WithIdentifier_ReturnsAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.FindByIdAsync(authorizations[1].Id.ToString(), default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(authorizations[1].Id, result!.Id)); + } + + [Fact] + public async Task FindBySubjectAsync_WithNullSubject_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindBySubjectAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindBySubjectAsync_WithSubject_ReturnsAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.FindBySubjectAsync(authorizations[1].Subject!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(authorizations[1].Id, result[0].Id)); + } + + [Fact] + public async Task GetAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.GetAsync(query!, null, default).AsTask()); + } + + [Fact] + public async Task GetAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + var query = (IQueryable query, ObjectId state) => + query.Where(x => x.Id == state).Select(x => x.Subject); + + // Act + var result = await store.GetAsync(query, authorizations[1].Id, default); + + // Assert + Assert.Equal(authorizations[1].Subject, result); + } + + [Fact] + public async Task InstantiateAsync_ReturnsAuthorization() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act + var result = await store.InstantiateAsync(default); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task ListAsync_WithCountAndOffset_ReturnsAuthorizations() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + // Act + var result = await store.ListAsync(1, 0, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Single(result)); + } + + [Fact] + public async Task ListAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.ListAsync(query!, null, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task ListAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .Build(); + + var query = (IQueryable query, object state) => + query.Where(x => x.Id == authorizations[0].Id || x.Id == authorizations[1].Id).Select(x => x.Subject); + + // Act + var result = await store.ListAsync(query!, null, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(2, result.Distinct().Count()), + () => Assert.True(authorizations.Select(x => x.Subject).Intersect(result).Count() == 2)); + } + + [Fact] + public async Task UpdateAsync_WithNullAuthorization_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBAuthorizationStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "authorization", + () => store.UpdateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task UpdateAsync_WithAuthorization_UpdatesAppropriateAuthorization() + { + // Arrange + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(3); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBAuthorizationStoreBuilder() + .WithAuthorizations(authorizations) + .WithDatabase(database) + .Build(); + + authorizations[1].Subject = "My Fabrikam"; + + // Act + await store.UpdateAsync(authorizations[1], default); + + // Assert + var result = database.Authorizations().FindById(authorizations[1].Id); + Assert.Equal("My Fabrikam", result.Subject); + } +} \ No newline at end of file diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBScopeStoreTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBScopeStoreTests.cs new file mode 100644 index 0000000..88fdd57 --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBScopeStoreTests.cs @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Stores; + +public class OpenIddictLiteDBScopeStoreTests +{ + [Fact] + public async Task CountAsync_WithZeroItems_ReturnsZero() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public async Task CountAsync_WithThreeItems_ReturnsThree() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(3, result); + } + + [Fact] + public async Task CountAync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.CountAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CountAsync_WithQuery_ReturnsScopeCount() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Act + var result = await store.CountAsync( + query => query.Where(s => s.Id == scopes[1].Id), + default); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public async Task CreateAsync_WithNullScope_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "scope", + () => store.CreateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CreateAsync_AddsScopeToDatabase() + { + // Arrange + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithDatabase(database) + .Build(); + var scope = new OpenIddictLiteDBScopeFaker().Generate(); + + // Act + await store.CreateAsync(scope, default); + + // Assert + var result = database.Scopes().FindById(scope.Id); + Assert.NotNull(result); + } + + [Fact] + public async Task DeleteAsync_WithNullScope_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "scope", + () => store.DeleteAsync(null!, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesScopeFromDatabase() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .WithDatabase(database) + .Build(); + + // Act + await store.DeleteAsync(scopes[0], default); + + // Assert + var result = database.Scopes().FindById(scopes[0].Id); + Assert.Null(result); + } + + [Fact] + public async Task DeleteAsync_WithConcurrencyTokenChange_ThrowsException() + { + // Arrange + var scope = new OpenIddictLiteDBScopeFaker().Generate(); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scope) + .Build(); + + scope.ConcurrencyToken = Guid.NewGuid().ToString(); + + // Act/Assert + await Assert.ThrowsAsync( + () => store.DeleteAsync(scope, default).AsTask()); + } + + [Fact] + public async Task FindByIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByIdAsync_WithIdentifier_ReturnsScope() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Act + var result = await store.FindByIdAsync(scopes[1].Id.ToString(), default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(scopes[1].Id, result!.Id)); + } + + [Fact] + public async Task FindByNameAsync_WithNullName_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "name", + () => store.FindByNameAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByNameAsync_WithName_ReturnsScope() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Act + var result = await store.FindByNameAsync(scopes[1].Name!, default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(scopes[1].Id, result!.Id)); + } + + [Fact] + public async Task FindByNameAsync_WithNullNames_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + var names = new[] { "" }.ToImmutableArray(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "names", + () => store.FindByNamesAsync(names, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByNameAsync_WithNames_ReturnsScope() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + var names = new[] { scopes[0].Name!, scopes[1].Name! }.ToImmutableArray(); + + // Act + var result = await store.FindByNamesAsync(names, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(2, result.Distinct().Count()), + () => Assert.Contains(scopes, s => s.Id == result[0].Id), + () => Assert.Contains(scopes, s => s.Id == result[1].Id)); + } + + [Fact] + public async Task FindByResourceAsync_WithResource_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "resource", + () => store.FindByResourceAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByResourceAsync_WithResource_ReturnsScope() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Assert + Assert.NotNull(scopes[1].Resources); + Assert.NotEmpty(scopes[1].Resources!); + + // Act + var result = await store.FindByResourceAsync(scopes[1].Resources!.Value[0], default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(scopes[1].Id, result[1].Id)); + } + + + [Fact] + public async Task GetAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.GetAsync(query!, null, default).AsTask()); + } + + [Fact] + public async Task GetAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + var query = (IQueryable query, ObjectId state) => + query.Where(x => x.Id == state).Select(x => x.DisplayName); + + // Act + var result = await store.GetAsync(query, scopes[1].Id, default); + + // Assert + Assert.Equal(scopes[1].DisplayName, result); + } + + [Fact] + public async Task InstantiateAsync_ReturnsScope() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act + var result = await store.InstantiateAsync(default); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task ListAsync_WithCountAndOffset_ReturnsScopes() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + // Act + var result = await store.ListAsync(1, 0, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Single(result)); + } + + [Fact] + public async Task ListAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.ListAsync(query!, null, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task ListAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .Build(); + + var query = (IQueryable query, object state) => + query.Where(x => x.Id == scopes[0].Id || x.Id == scopes[1].Id).Select(x => x.DisplayName); + + // Act + var result = await store.ListAsync(query!, null, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(2, result.Distinct().Count()), + () => Assert.True(scopes.Select(x => x.DisplayName).Intersect(result).Count() == 2)); + } + + [Fact] + public async Task UpdateAsync_WithNullScope_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBScopeStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "scope", + () => store.UpdateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task UpdateAsync_WithScope_UpdatesAppropriateScope() + { + // Arrange + var scopes = new OpenIddictLiteDBScopeFaker().Generate(3); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBScopeStoreBuilder() + .WithScopes(scopes) + .WithDatabase(database) + .Build(); + + scopes[1].DisplayName = "My Fabrikam"; + + // Act + await store.UpdateAsync(scopes[1], default); + + // Assert + var result = database.Scopes().FindById(scopes[1].Id); + Assert.Equal("My Fabrikam", result.DisplayName); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBTokenStoreTests.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBTokenStoreTests.cs new file mode 100644 index 0000000..0d027ce --- /dev/null +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBTokenStoreTests.cs @@ -0,0 +1,773 @@ +/* + * Copyright (c) 2022 Steven Kuhn and contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Sknet.OpenIddict.LiteDB.Tests.Stores; + +public class OpenIddictLiteDBTokenStoreTests +{ + [Fact] + public async Task CountAsync_WithZeroItems_ReturnsZero() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public async Task CountAsync_WithThreeItems_ReturnsThree() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.CountAsync(default); + + // Assert + Assert.Equal(3, result); + } + + [Fact] + public async Task CountAync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.CountAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CountAsync_WithQuery_ReturnsTokenCount() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.CountAsync( + query => query.Where(a => a.Id == tokens[1].Id), + default); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public async Task CreateAsync_WithNullToken_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "token", + () => store.CreateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task CreateAsync_AddsTokenToDatabase() + { + // Arrange + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithDatabase(database) + .Build(); + var token = new OpenIddictLiteDBTokenFaker().Generate(); + + // Act + await store.CreateAsync(token, default); + + // Assert + var result = database.Tokens().FindById(token.Id); + Assert.NotNull(result); + } + + [Fact] + public async Task DeleteAsync_WithNullToken_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "token", + () => store.DeleteAsync(null!, default).AsTask()); + } + + [Fact] + public async Task DeleteAsync_RemovesTokenFromDatabase() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(2); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .WithDatabase(database) + .Build(); + + // Act + await store.DeleteAsync(tokens[0], default); + + // Assert + var result = database.Tokens().FindById(tokens[0].Id); + Assert.Null(result); + } + + [Fact] + public async Task DeleteAsync_WithConcurrencyTokenChange_ThrowsException() + { + // Arrange + var token = new OpenIddictLiteDBTokenFaker().Generate(); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(token) + .Build(); + + token.ConcurrencyToken = Guid.NewGuid().ToString(); + + // Act/Assert + await Assert.ThrowsAsync( + () => store.DeleteAsync(token, default).AsTask()); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClient_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClient_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClient_ShouldReturnToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Assert + Assert.NotNull(tokens[1].Subject); + Assert.NotEqual(ObjectId.Empty, tokens[1].ApplicationId); + + // Act + var result = await store.FindAsync(tokens[1].Subject!, tokens[1].ApplicationId.ToString(), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClientAndStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", "status", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClientAndStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, "status", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndNullStatus_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "status", + () => store.FindAsync("subject", "client", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClientAndStatus_ShouldReturnToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Assert + Assert.NotNull(tokens[1].Subject); + Assert.NotEqual(ObjectId.Empty, tokens[1].ApplicationId); + Assert.NotNull(tokens[1].Status); + + // Act + var result = await store.FindAsync( + tokens[1].Subject!, + tokens[1].ApplicationId.ToString(), + tokens[1].Status!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindAsyc_WithNullSubjectAndClientAndStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindAsync(null!, "client", "status", "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndNullClientAndStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "client", + () => store.FindAsync("subject", null!, "status", "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndNullStatusAndType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "status", + () => store.FindAsync("subject", "client", null!, "type", default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsyc_WithSubjectAndClientAndStatusAndNullType_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "type", + () => store.FindAsync("subject", "client", "status", null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindAsync_WithSubjectAndClientAndStatusAndType_ShouldReturnToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Assert + Assert.NotNull(tokens[1].Subject); + Assert.NotEqual(ObjectId.Empty, tokens[1].ApplicationId); + Assert.NotNull(tokens[1].Status); + Assert.NotNull(tokens[1].Type); + + // Act + var result = await store.FindAsync( + tokens[1].Subject!, + tokens[1].ApplicationId.ToString(), + tokens[1].Status!, + tokens[1].Type!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.Single(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindByApplicationIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByApplicationIdAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByApplicationIdAsync_WithIdentifier_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.FindByApplicationIdAsync(tokens[1].ApplicationId.ToString(), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindByAuthorizationIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByAuthorizationIdAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindByAuthorizationIdAsync_WithIdentifier_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.FindByAuthorizationIdAsync(tokens[1].AuthorizationId.ToString(), default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task FindByIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByIdAsync_WithIdentifier_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.FindByIdAsync(tokens[1].Id.ToString(), default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(tokens[1].Id, result!.Id)); + } + + [Fact] + public async Task FindByReferenceIdAsync_WithNullIdentifier_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "identifier", + () => store.FindByReferenceIdAsync(null!, default).AsTask()); + } + + [Fact] + public async Task FindByReferenceIdAsync_WithIdentifier_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.FindByReferenceIdAsync(tokens[1].ReferenceId!.ToString(), default); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(tokens[1].Id, result!.Id)); + } + + [Fact] + public async Task FindBySubjectAsync_WithNullSubject_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "subject", + () => store.FindBySubjectAsync(null!, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task FindBySubjectAsync_WithSubject_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.FindBySubjectAsync(tokens[1].Subject!, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(tokens[1].Id, result[0].Id)); + } + + [Fact] + public async Task GetAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.GetAsync(query!, null, default).AsTask()); + } + + [Fact] + public async Task GetAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + var query = (IQueryable query, ObjectId state) => + query.Where(x => x.Id == state).Select(x => x.Subject); + + // Act + var result = await store.GetAsync(query, tokens[1].Id, default); + + // Assert + Assert.Equal(tokens[1].Subject, result); + } + + [Fact] + public async Task InstantiateAsync_ReturnsToken() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act + var result = await store.InstantiateAsync(default); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task ListAsync_WithCountAndOffset_ReturnsToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + // Act + var result = await store.ListAsync(1, 0, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Single(result)); + } + + [Fact] + public async Task ListAsync_WithNullQuery_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + Func, object, IQueryable>? query = null; + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "query", + () => store.ListAsync(query!, null, default).ToListAsync().AsTask()); + } + + [Fact] + public async Task ListAsync_WithQuery_ReturnsAppropriateResult() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .Build(); + + var query = (IQueryable query, object state) => + query.Where(x => x.Id == tokens[0].Id || x.Id == tokens[1].Id).Select(x => x.Subject); + + // Act + var result = await store.ListAsync(query!, null, default).ToListAsync(); + + // Assert + Assert.Multiple( + () => Assert.NotNull(result), + () => Assert.Equal(2, result.Distinct().Count()), + () => Assert.True(tokens.Select(x => x.Subject).Intersect(result).Count() == 2)); + } + + [Fact] + public async Task PruneAsync_WithTokensCreatedBeforeThreshold_ShouldRemoveTokens() + { + // Arrange + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + + var tokenFaker = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Redeemed, + Statuses.Rejected, + Statuses.Revoked, + })); + + var tokensToPrune = tokenFaker + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)) + .Generate(3); + var tokensToNotPrune = tokenFaker + .RuleFor(x => x.CreationDate, f => f.Date.FutureOffset(1, threshold)) + .Generate(3); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokensToPrune) + .WithTokens(tokensToNotPrune) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Tokens().FindAll(); + result.Should().BeEquivalentTo(tokensToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task PruneAsync_WithTokensThatAreNotInactiveAndNotValid_ShouldRemoveTokens() + { + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + + var tokenFaker = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)); + + var tokensToPrune = tokenFaker + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Redeemed, + Statuses.Rejected, + Statuses.Revoked, + })) + .Generate(3); + var tokensToNotPrune = tokenFaker + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Valid + })) + .Generate(3); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokensToPrune) + .WithTokens(tokensToNotPrune) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Tokens().FindAll(); + result.Should().BeEquivalentTo(tokensToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task PruneAsync_WithTokensThatAreExpired_ShouldRemoveTokens() + { + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + + var tokenFaker = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)) + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Valid + })); + + var tokensToPrune = tokenFaker + .RuleFor(x => x.ExpirationDate, f => f.Date.PastOffset(1, DateTimeOffset.UtcNow)) + .Generate(3); + var tokensToNotPrune = tokenFaker + .RuleFor(x => x.ExpirationDate, f => f.Date.FutureOffset(1, DateTimeOffset.UtcNow)) + .Generate(3); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokensToPrune) + .WithTokens(tokensToNotPrune) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Tokens().FindAll(); + result.Should().BeEquivalentTo(tokensToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task PruneAsync_WithTokensThatHaveInvalidAuthorizations_ShouldRemoveTokens() + { + var authorizations = new OpenIddictLiteDBAuthorizationFaker().Generate(2); + authorizations[0].Status = Statuses.Valid; + authorizations[1].Status = Statuses.Inactive; + + var threshold = DateTimeOffset.UtcNow.AddDays(-1); + var tokenFaker = new OpenIddictLiteDBTokenFaker() + .RuleFor(x => x.CreationDate, f => f.Date.PastOffset(1, threshold)) + .RuleFor(x => x.Status, f => f.PickRandom(new[] + { + Statuses.Inactive, + Statuses.Valid + })) + .RuleFor(x => x.ExpirationDate, f => f.Date.FutureOffset(1, DateTimeOffset.UtcNow)); + + var tokensToPrune = tokenFaker + .RuleFor(x => x.AuthorizationId, f => authorizations[1].Id) + .Generate(3); + var tokensToNotPrune = tokenFaker + .RuleFor(x => x.AuthorizationId, f => authorizations[0].Id) + .Generate(3); + + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokensToPrune) + .WithTokens(tokensToNotPrune) + .WithAuthorizations(authorizations) + .WithDatabase(database) + .Build(); + + // Act + await store.PruneAsync(threshold, default); + + // Assert + var result = database.Tokens().FindAll(); + result.Should().BeEquivalentTo(tokensToNotPrune, o => o.Including(x => x.Id)); + } + + [Fact] + public async Task UpdateAsync_WithNullToken_ThrowsException() + { + // Arrange + var store = new OpenIddictLiteDBTokenStoreBuilder().Build(); + + // Act/Assert + var exception = await Assert.ThrowsAsync( + "token", + () => store.UpdateAsync(null!, default).AsTask()); + } + + [Fact] + public async Task UpdateAsync_WithToken_UpdatesAppropriateToken() + { + // Arrange + var tokens = new OpenIddictLiteDBTokenFaker().Generate(3); + var database = new OpenIddictLiteDatabase(":memory:"); + var store = new OpenIddictLiteDBTokenStoreBuilder() + .WithTokens(tokens) + .WithDatabase(database) + .Build(); + + tokens[1].Subject = "My Fabrikam"; + + // Act + await store.UpdateAsync(tokens[1], default); + + // Assert + var result = database.Tokens().FindById(tokens[1].Id); + Assert.Equal("My Fabrikam", result.Subject); + } +} diff --git a/test/Sknet.OpenIddict.LiteDB.Tests/Usings.cs b/test/Sknet.OpenIddict.LiteDB.Tests/Usings.cs index 86ffacd..d67ed62 100644 --- a/test/Sknet.OpenIddict.LiteDB.Tests/Usings.cs +++ b/test/Sknet.OpenIddict.LiteDB.Tests/Usings.cs @@ -1,3 +1,5 @@ +global using Bogus; +global using FluentAssertions; global using LiteDB; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Options; @@ -5,4 +7,10 @@ global using OpenIddict.Abstractions; global using OpenIddict.Core; global using Sknet.OpenIddict.LiteDB.Models; +global using Sknet.OpenIddict.LiteDB.Tests.Builders; +global using System.Collections.Generic; +global using System.Collections.Immutable; +global using System.Globalization; +global using System.Text.Json; global using Xunit; +global using static OpenIddict.Abstractions.OpenIddictConstants;