From dccc3cf3a51b74681354801b250e943a2a093663 Mon Sep 17 00:00:00 2001 From: Steven Kuhn <109609+stevenkuhn@users.noreply.github.com> Date: Sun, 23 Oct 2022 20:37:47 -0500 Subject: [PATCH] Add robust testing and prune functionality (#1) This adds testing for the applications, authorization, scope, and token stores. It also fixes methods that were discovered to be broken during testing, and adds prune functionality for authorizations and tokens. --- .github/workflows/build.yml | 13 +- .github/workflows/release.yml | 12 +- .gitignore | 3 + .nuke/build.schema.json | 6 +- .vscode/launch.json | 26 + .vscode/tasks.json | 41 + build/Build.cs | 2 + build/Build.v3.ncrunchproject | 5 + .../OpenIddictLiteDBApplication.cs | 17 +- .../OpenIddictLiteDBAuthorization.cs | 6 +- .../OpenIddictLiteDBScope.cs | 12 +- .../OpenIddictLiteDBToken.cs | 8 +- .../Sknet.OpenIddict.LiteDB.Models.csproj | 2 +- src/Sknet.OpenIddict.LiteDB.Models/Usings.cs | 3 +- .../IOpenIddictLiteDBContext.cs | 2 +- .../OpenIddictLiteDBExtensions.cs | 1 - .../OpenIddictLiteDBOptions.cs | 16 +- .../OpenIddictLiteDatabase.cs | 92 +++ .../OpenIddictLiteDBApplicationStore.cs | 60 +- .../OpenIddictLiteDBAuthorizationStore.cs | 126 ++- .../Stores/OpenIddictLiteDBScopeStore.cs | 46 +- .../Stores/OpenIddictLiteDBTokenStore.cs | 104 +-- .../OpenIddictLiteDBApplicationFaker.cs | 72 ++ ...OpenIddictLiteDBApplicationStoreBuilder.cs | 126 +++ .../OpenIddictLiteDBAuthorizationFaker.cs | 55 ++ ...enIddictLiteDBAuthorizationStoreBuilder.cs | 85 ++ .../Builders/OpenIddictLiteDBScopeFaker.cs | 71 ++ .../OpenIddictLiteDBScopeStoreBuilder.cs | 65 ++ .../Builders/OpenIddictLiteDBTokenFaker.cs | 56 ++ .../OpenIddictLiteDBTokenStoreBuilder.cs | 79 ++ ...dictLiteDBApplicationStoreResolverTests.cs | 2 +- ...ctLiteDBAuthorizationStoreResolverTests.cs | 2 +- ...OpenIddictLiteDBScopeStoreResolverTests.cs | 2 +- ...OpenIddictLiteDBTokenStoreResolverTests.cs | 2 +- .../Sknet.OpenIddict.LiteDB.Tests.csproj | 9 +- .../Stores/ILiteDatabaseExtensions.cs | 47 ++ .../OpenIddictLiteDBApplicationStoreTests.cs | 567 +++++++++++++ ...OpenIddictLiteDBAuthorizationStoreTests.cs | 729 +++++++++++++++++ .../Stores/OpenIddictLiteDBScopeStoreTests.cs | 420 ++++++++++ .../Stores/OpenIddictLiteDBTokenStoreTests.cs | 773 ++++++++++++++++++ test/Sknet.OpenIddict.LiteDB.Tests/Usings.cs | 8 + 41 files changed, 3509 insertions(+), 264 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 build/Build.v3.ncrunchproject create mode 100644 src/Sknet.OpenIddict.LiteDB/OpenIddictLiteDatabase.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationFaker.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBApplicationStoreBuilder.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationFaker.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBAuthorizationStoreBuilder.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeFaker.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBScopeStoreBuilder.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenFaker.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Builders/OpenIddictLiteDBTokenStoreBuilder.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Stores/ILiteDatabaseExtensions.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBApplicationStoreTests.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBAuthorizationStoreTests.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBScopeStoreTests.cs create mode 100644 test/Sknet.OpenIddict.LiteDB.Tests/Stores/OpenIddictLiteDBTokenStoreTests.cs 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;