From d99e65253d0a056fecebc66598a1e456fabf637f Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Sat, 2 Dec 2023 16:59:27 +0100 Subject: [PATCH] tests: add tests for policyhub bl Refs: CPLP-3362 --- DEPENDENCIES | 1 + src/PolicyHub.sln | 7 ++ .../PolicyHub.DbAccess/HubRepositories.cs | 16 --- .../PolicyHub.DbAccess/IHubRepositories.cs | 4 - .../PolicyHub.Entities/PolicyHubContext.cs | 21 +--- src/database/PolicyHub.Migrations/Program.cs | 2 +- .../Seeder/BatchInsertSeeder.cs | 10 +- .../Seeder/BatchUpdateSeeder.cs | 9 +- .../KeycloakClaimsTransformation.cs | 2 +- .../BusinessLogic/PolicyHubBusinessLogic.cs | 8 +- .../Extensions/CorsExtensions.cs | 67 ------------- .../HealthCheck/HealthCheckExtensions.cs | 81 ---------------- .../HealthCheck/HealthCheckSettings.cs | 30 ------ .../JwtBearerConfigurationHealthCheck.cs | 66 ------------- .../Setup/TestDbFixture.cs | 4 +- .../PolicyHub.Entities.Tests.csproj | 52 ++++++++++ .../PolicyKindExtensionsTests.cs | 51 ++++++++++ .../PolicyHub.Entities.Tests/Usings.cs | 23 +++++ .../KeycloakClaimsTransformationTests.cs | 92 ++++++++++++++++++ .../PolicyHubBusinessLogicTests.cs | 97 ++++++++++++++++++- .../Controllers/PolicyHubControllerTests.cs | 59 +++++++++-- .../JsonGenerationExtensionsTests.cs | 52 ++++++++++ .../Setup/IntegrationTestFactory.cs | 8 +- 23 files changed, 450 insertions(+), 312 deletions(-) delete mode 100644 src/hub/PolicyHub.Service/Extensions/CorsExtensions.cs delete mode 100644 src/hub/PolicyHub.Service/HealthCheck/HealthCheckExtensions.cs delete mode 100644 src/hub/PolicyHub.Service/HealthCheck/HealthCheckSettings.cs delete mode 100644 src/hub/PolicyHub.Service/HealthCheck/JwtBearerConfigurationHealthCheck.cs create mode 100644 tests/database/PolicyHub.Entities.Tests/PolicyHub.Entities.Tests.csproj create mode 100644 tests/database/PolicyHub.Entities.Tests/PolicyKindExtensionsTests.cs create mode 100644 tests/database/PolicyHub.Entities.Tests/Usings.cs create mode 100644 tests/hub/PolicyHub.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs create mode 100644 tests/hub/PolicyHub.Service.Tests/Extensions/JsonGenerationExtensionsTests.cs diff --git a/DEPENDENCIES b/DEPENDENCIES index 192b4d7..aa9dbca 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -9,6 +9,7 @@ nuget/nuget/-/FluentAssertions/6.11.0, Apache-2.0 AND MIT, approved, #10061 nuget/nuget/-/Flurl.Signed/3.0.6, MIT, approved, #3501 nuget/nuget/-/Humanizer.Core/2.14.1, MIT, approved, #10060 nuget/nuget/-/Mono.TextTemplating/2.2.1, MIT, approved, clearlydefined +nuget/nuget/-/Newtonsoft.Json/13.0.1, MIT AND BSD-3-Clause, approved, #3266 nuget/nuget/-/Newtonsoft.Json/13.0.3, MIT AND BSD-3-Clause, approved, #3266 nuget/nuget/-/Npgsql.EntityFrameworkCore.PostgreSQL/7.0.11, PostgreSQL AND MIT AND Apache-2.0, approved, #10081 nuget/nuget/-/Npgsql/7.0.6, PostgreSQL, approved, #10062 diff --git a/src/PolicyHub.sln b/src/PolicyHub.sln index 4788d2c..6c0fe51 100644 --- a/src/PolicyHub.sln +++ b/src/PolicyHub.sln @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolicyHub.DbAccess.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolicyHub.Service.Tests", "..\tests\hub\PolicyHub.Service.Tests\PolicyHub.Service.Tests.csproj", "{A337EA35-B303-4F1B-9A83-5205E2CBA69D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolicyHub.Entities.Tests", "..\tests\database\PolicyHub.Entities.Tests\PolicyHub.Entities.Tests.csproj", "{1F4F15B3-C3D6-4030-9822-62D72B6DA1C9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +50,10 @@ Global {A337EA35-B303-4F1B-9A83-5205E2CBA69D}.Debug|Any CPU.Build.0 = Debug|Any CPU {A337EA35-B303-4F1B-9A83-5205E2CBA69D}.Release|Any CPU.ActiveCfg = Release|Any CPU {A337EA35-B303-4F1B-9A83-5205E2CBA69D}.Release|Any CPU.Build.0 = Release|Any CPU + {1F4F15B3-C3D6-4030-9822-62D72B6DA1C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F4F15B3-C3D6-4030-9822-62D72B6DA1C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F4F15B3-C3D6-4030-9822-62D72B6DA1C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F4F15B3-C3D6-4030-9822-62D72B6DA1C9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {92B16AED-3229-4C46-8498-11D33E5C7F69} = {D30D713C-43E8-488F-BFBB-FD610E77ACA3} @@ -56,5 +62,6 @@ Global {F5BED39D-6419-49AD-A5E0-FAB88723285C} = {6EB9F0CF-3A57-42AA-BF74-AADCF8815F90} {F1551715-BECA-45B2-A71B-CAB60F2D35FE} = {9C9DA872-893E-48BA-9DD5-025D477F6EC3} {A337EA35-B303-4F1B-9A83-5205E2CBA69D} = {9C9DA872-893E-48BA-9DD5-025D477F6EC3} + {1F4F15B3-C3D6-4030-9822-62D72B6DA1C9} = {9C9DA872-893E-48BA-9DD5-025D477F6EC3} EndGlobalSection EndGlobal diff --git a/src/database/PolicyHub.DbAccess/HubRepositories.cs b/src/database/PolicyHub.DbAccess/HubRepositories.cs index 647eae5..5c74d96 100644 --- a/src/database/PolicyHub.DbAccess/HubRepositories.cs +++ b/src/database/PolicyHub.DbAccess/HubRepositories.cs @@ -17,10 +17,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Microsoft.EntityFrameworkCore; using Org.Eclipse.TractusX.PolicyHub.DbAccess.Repositories; using Org.Eclipse.TractusX.PolicyHub.Entities; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using System.Collections.Immutable; namespace Org.Eclipse.TractusX.PolicyHub.DbAccess; @@ -49,18 +47,4 @@ public RepositoryType GetInstance() return (RepositoryType)(repository ?? throw new ArgumentException($"unexpected type {typeof(RepositoryType).Name}", nameof(RepositoryType))); } - - public Task SaveAsync() - { - try - { - return _dbContext.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException e) - { - throw new ConflictException("while processing a concurrent update was saved to the database (reason could also be data to be deleted is no longer existing)", e); - } - } - - public void Clear() => _dbContext.ChangeTracker.Clear(); } diff --git a/src/database/PolicyHub.DbAccess/IHubRepositories.cs b/src/database/PolicyHub.DbAccess/IHubRepositories.cs index d09728a..360c798 100644 --- a/src/database/PolicyHub.DbAccess/IHubRepositories.cs +++ b/src/database/PolicyHub.DbAccess/IHubRepositories.cs @@ -22,8 +22,4 @@ namespace Org.Eclipse.TractusX.PolicyHub.DbAccess; public interface IHubRepositories { public T GetInstance(); - - public Task SaveAsync(); - - void Clear(); } diff --git a/src/database/PolicyHub.Entities/PolicyHubContext.cs b/src/database/PolicyHub.Entities/PolicyHubContext.cs index 7e1f291..2da36e1 100644 --- a/src/database/PolicyHub.Entities/PolicyHubContext.cs +++ b/src/database/PolicyHub.Entities/PolicyHubContext.cs @@ -25,6 +25,10 @@ namespace Org.Eclipse.TractusX.PolicyHub.Entities; public class PolicyHubContext : DbContext { + public PolicyHubContext() + { + } + public PolicyHubContext(DbContextOptions options) : base(options) { @@ -94,23 +98,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) x.HasKey(e => new { e.PolicyId, e.UseCaseId }); }); - entity.HasMany(p => p.UseCases) - .WithMany(pt => pt.Policies) - .UsingEntity(p => p - .HasOne(x => x.UseCase) - .WithMany() - .HasForeignKey(x => x.UseCaseId) - .OnDelete(DeleteBehavior.ClientSetNull), - p => p - .HasOne(x => x.Policy) - .WithMany() - .HasForeignKey(x => x.PolicyId) - .OnDelete(DeleteBehavior.ClientSetNull), - x => - { - x.HasKey(e => new { e.PolicyId, e.UseCaseId }); - }); - entity.HasOne(p => p.AttributeKey) .WithMany(pt => pt.Policies) .HasForeignKey(p => p.AttributeKeyId) diff --git a/src/database/PolicyHub.Migrations/Program.cs b/src/database/PolicyHub.Migrations/Program.cs index 200b672..a90acc9 100644 --- a/src/database/PolicyHub.Migrations/Program.cs +++ b/src/database/PolicyHub.Migrations/Program.cs @@ -38,7 +38,7 @@ .AddDbContext(o => o.UseNpgsql(hostContext.Configuration.GetConnectionString("PolicyHubDb"), x => x.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name) - .MigrationsHistoryTable("__efmigrations_history_hub"))) + .MigrationsHistoryTable("__efmigrations_history_hub", "public"))) .AddDatabaseInitializer(hostContext.Configuration.GetSection("Seeding")); }) .AddLogging() diff --git a/src/database/PolicyHub.Migrations/Seeder/BatchInsertSeeder.cs b/src/database/PolicyHub.Migrations/Seeder/BatchInsertSeeder.cs index 4d8b968..ba580a2 100644 --- a/src/database/PolicyHub.Migrations/Seeder/BatchInsertSeeder.cs +++ b/src/database/PolicyHub.Migrations/Seeder/BatchInsertSeeder.cs @@ -70,21 +70,21 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) private async Task SeedTable(string fileName, Func keySelector, CancellationToken cancellationToken) where T : class { - _logger.LogInformation("Start seeding {Filename}", fileName); + _logger.LogDebug("Start seeding {Filename}", fileName); var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); - _logger.LogInformation("Found {ElementCount} data", data.Count); + _logger.LogDebug("Found {ElementCount} data", data.Count); if (data.Any()) { var typeName = typeof(T).Name; - _logger.LogInformation("Started to Seed {TableName}", typeName); + _logger.LogDebug("Started to Seed {TableName}", typeName); data = data.GroupJoin(_context.Set(), keySelector, keySelector, (d, dbEntry) => new { d, dbEntry }) .SelectMany(t => t.dbEntry.DefaultIfEmpty(), (t, x) => new { t, x }) .Where(t => t.x == null) .Select(t => t.t.d).ToList(); - _logger.LogInformation("Seeding {DataCount} {TableName}", data.Count, typeName); + _logger.LogDebug("Seeding {DataCount} {TableName}", data.Count, typeName); await _context.Set().AddRangeAsync(data, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Seeded {TableName}", typeName); + _logger.LogDebug("Seeded {TableName}", typeName); } } } diff --git a/src/database/PolicyHub.Migrations/Seeder/BatchUpdateSeeder.cs b/src/database/PolicyHub.Migrations/Seeder/BatchUpdateSeeder.cs index d3b4ab7..ff172d4 100644 --- a/src/database/PolicyHub.Migrations/Seeder/BatchUpdateSeeder.cs +++ b/src/database/PolicyHub.Migrations/Seeder/BatchUpdateSeeder.cs @@ -65,10 +65,10 @@ await SeedTable( private async Task SeedTable(string fileName, Func keySelector, Func<(T dataEntity, T dbEntity), bool> whereClause, Action updateEntries, CancellationToken cancellationToken) where T : class { - _logger.LogInformation("Start seeding {Filename}", fileName); + _logger.LogDebug("Start seeding {Filename}", fileName); var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); - _logger.LogInformation("Found {ElementCount} data", data.Count); + _logger.LogDebug("Found {ElementCount} data", data.Count); if (data.Any()) { var typeName = typeof(T).Name; @@ -78,12 +78,13 @@ private async Task SeedTable(string fileName, Func keySelector, Fu .ToList(); if (entriesForUpdate.Any()) { - _logger.LogInformation("Started to Update {EntryCount} entries of {TableName}", entriesForUpdate.Count, typeName); + _logger.LogDebug("Started to Update {EntryCount} entries of {TableName}", entriesForUpdate.Count, typeName); foreach (var entry in entriesForUpdate) { updateEntries.Invoke(entry.DbEntry, entry.DataEntry); } - _logger.LogInformation("Updated {TableName}", typeName); + + _logger.LogDebug("Updated {TableName}", typeName); } } } diff --git a/src/hub/PolicyHub.Service/Authentication/KeycloakClaimsTransformation.cs b/src/hub/PolicyHub.Service/Authentication/KeycloakClaimsTransformation.cs index 8e20755..c81c94a 100644 --- a/src/hub/PolicyHub.Service/Authentication/KeycloakClaimsTransformation.cs +++ b/src/hub/PolicyHub.Service/Authentication/KeycloakClaimsTransformation.cs @@ -28,7 +28,7 @@ namespace Org.Eclipse.TractusX.PolicyHub.Service.Authentication; public class KeycloakClaimsTransformation : IClaimsTransformation { private readonly JwtBearerOptions _options; - private const string ResourceAccess = "resource_access"; + public const string ResourceAccess = "resource_access"; public KeycloakClaimsTransformation(IOptions options) { diff --git a/src/hub/PolicyHub.Service/BusinessLogic/PolicyHubBusinessLogic.cs b/src/hub/PolicyHub.Service/BusinessLogic/PolicyHubBusinessLogic.cs index e07e3e1..c888798 100644 --- a/src/hub/PolicyHub.Service/BusinessLogic/PolicyHubBusinessLogic.cs +++ b/src/hub/PolicyHub.Service/BusinessLogic/PolicyHubBusinessLogic.cs @@ -86,7 +86,7 @@ private static object GetRegexValue((AttributeKeyId? Key, IEnumerable Va throw new UnexpectedConditionException("There should only be one regex pattern defined"); } - if (!Regex.IsMatch(value, attributes.Values.Single())) + if (!Regex.IsMatch(value, attributes.Values.Single(), RegexOptions.Compiled, TimeSpan.FromSeconds(1))) { throw new ControllerArgumentException($"The provided value {value} does not match the regex pattern {attributes.Values.Single()}", nameof(value)); } @@ -118,13 +118,13 @@ public async Task GetPolicyContentAsync(PolicyContentRequest req var multipleDefinedKey = keyCounts.Where(x => x.Value != 1); if (multipleDefinedKey.Any()) { - throw new ControllerArgumentException($"Keys {multipleDefinedKey.Select(x => x.Key)} have been defined multiple times"); + throw new ControllerArgumentException($"Keys {string.Join(",", multipleDefinedKey.Select(x => x.Key).Distinct())} have been defined multiple times"); } var policies = await _hubRepositories.GetInstance().GetPolicyForOperandContent(requestData.PolicyType, requestData.Constraints.Select(x => x.Key)).ToListAsync().ConfigureAwait(false); if (policies.Count != requestData.Constraints.Count()) { - throw new NotFoundException($"Policy for type {requestData.PolicyType} and technicalKeys {requestData.Constraints.Select(x => x.Key).Except(policies.Select(x => x.TechnicalKey))} does not exists"); + throw new NotFoundException($"Policy for type {requestData.PolicyType} and technicalKeys {string.Join(",", requestData.Constraints.Select(x => x.Key).Except(policies.Select(x => x.TechnicalKey)))} does not exists"); } var constraints = new List(); @@ -172,5 +172,5 @@ public async Task GetPolicyContentAsync(PolicyContentRequest req return new PolicyResponse(content, additionalAttributes); } - private static object[] GetContext() => new object[] { "https://www.w3.org/ns/odrl.jsonld", new { cx = "https://w3id.org/catenax/v0.0.1/ns/" } }; + private static IEnumerable GetContext() => new object[] { "https://www.w3.org/ns/odrl.jsonld", new { cx = "https://w3id.org/catenax/v0.0.1/ns/" } }; } diff --git a/src/hub/PolicyHub.Service/Extensions/CorsExtensions.cs b/src/hub/PolicyHub.Service/Extensions/CorsExtensions.cs deleted file mode 100644 index 1122357..0000000 --- a/src/hub/PolicyHub.Service/Extensions/CorsExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://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. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using Microsoft.AspNetCore.Cors.Infrastructure; - -namespace Org.Eclipse.TractusX.PolicyHub.Service.Extensions; - -/// -/// Configuration for CORS -/// -public class CorsConfiguration -{ - /// The Cors configuration - public CorsModel Cors { get; init; } = null!; -} - -/// -/// Model for the Cors Configuration -/// -public class CorsModel -{ - /// The allowed origins - public string[] AllowedOrigins { get; init; } = null!; -} - -/// -/// Provides Extension methods for cors -/// -public static class CorsExtensions -{ - public const string AllowSpecificOrigins = "_catenaXAllowSpecificOrigins"; - - /// - /// Setup for the cors configuration - /// - /// Cors options - /// configuration to access the allowed domains - public static void SetupCors(this CorsOptions corsOption, IConfigurationRoot configuration) - { - var corsConfig = configuration.Get(); - if (corsConfig?.Cors?.AllowedOrigins?.Any() ?? false) - { - corsOption.AddPolicy(AllowSpecificOrigins, policy => - { - policy.WithOrigins(corsConfig.Cors.AllowedOrigins) - .AllowAnyHeader() - .AllowAnyMethod(); - }); - } - } -} diff --git a/src/hub/PolicyHub.Service/HealthCheck/HealthCheckExtensions.cs b/src/hub/PolicyHub.Service/HealthCheck/HealthCheckExtensions.cs deleted file mode 100644 index e2682b5..0000000 --- a/src/hub/PolicyHub.Service/HealthCheck/HealthCheckExtensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -// /******************************************************************************** -// * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation -// * -// * See the NOTICE file(s) distributed with this work for additional -// * information regarding copyright ownership. -// * -// * This program and the accompanying materials are made available under the -// * terms of the Apache License, Version 2.0 which is available at -// * https://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. -// * -// * SPDX-License-Identifier: Apache-2.0 -// ********************************************************************************/ -// -// using Microsoft.Extensions.Diagnostics.HealthChecks; -// using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -// using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; -// using System.Net.Mime; -// using System.Text.Json; -// using System.Text.Json.Serialization; -// -// namespace Org.Eclipse.TractusX.PolicyHub.Service.HealthCheck; -// -// public static class HealthCheckExtensions -// { -// private static readonly JsonSerializerOptions SerializerOptions = new() -// { -// WriteIndented = false, -// PropertyNamingPolicy = JsonNamingPolicy.CamelCase, -// DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull -// }; -// -// private static Task WriteResponse( -// HttpContext context, -// HealthReport report) -// { -// context.Response.ContentType = MediaTypeNames.Application.Json; -// return context.Response.WriteAsync(JsonSerializer.Serialize( -// new -// { -// Status = report.Status.ToString(), -// Duration = report.TotalDuration, -// Info = report.Entries -// .Select(e => new -// { -// Key = e.Key, -// Description = e.Value.Description, -// Duration = e.Value.Duration, -// Status = Enum.GetName(e.Value.Status), -// Error = e.Value.Exception?.Message, -// Data = e.Value.Data -// }) -// }, -// SerializerOptions)); -// } -// -// public static void MapDefaultHealthChecks(this WebApplication app, IEnumerable? settings) -// { -// if (settings == null) -// return; -// -// if (settings.DuplicatesBy(x => x.Path).Any()) -// { -// throw new ConfigurationException($"HealthChecks mapping {string.Join(", ", settings.Select(x => x.Path))} contains ambiguous pathes"); -// } -// -// foreach (var configured in settings) -// { -// app.MapHealthChecks(configured.Path, new() -// { -// Predicate = registration => configured.Tags != null && configured.Tags.Intersect(registration.Tags).Any(), -// ResponseWriter = WriteResponse -// }); -// } -// } -// } diff --git a/src/hub/PolicyHub.Service/HealthCheck/HealthCheckSettings.cs b/src/hub/PolicyHub.Service/HealthCheck/HealthCheckSettings.cs deleted file mode 100644 index 3e65fb5..0000000 --- a/src/hub/PolicyHub.Service/HealthCheck/HealthCheckSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -// /******************************************************************************** -// * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation -// * -// * See the NOTICE file(s) distributed with this work for additional -// * information regarding copyright ownership. -// * -// * This program and the accompanying materials are made available under the -// * terms of the Apache License, Version 2.0 which is available at -// * https://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. -// * -// * SPDX-License-Identifier: Apache-2.0 -// ********************************************************************************/ -// -// using System.ComponentModel.DataAnnotations; -// -// namespace Org.Eclipse.TractusX.PolicyHub.Service.HealthCheck; -// -// public class HealthCheckSettings -// { -// [Required(AllowEmptyStrings = false)] -// public string Path { get; set; } = null!; -// -// public IEnumerable? Tags { get; set; } -// } diff --git a/src/hub/PolicyHub.Service/HealthCheck/JwtBearerConfigurationHealthCheck.cs b/src/hub/PolicyHub.Service/HealthCheck/JwtBearerConfigurationHealthCheck.cs deleted file mode 100644 index 13c13e4..0000000 --- a/src/hub/PolicyHub.Service/HealthCheck/JwtBearerConfigurationHealthCheck.cs +++ /dev/null @@ -1,66 +0,0 @@ -// /******************************************************************************** -// * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation -// * -// * See the NOTICE file(s) distributed with this work for additional -// * information regarding copyright ownership. -// * -// * This program and the accompanying materials are made available under the -// * terms of the Apache License, Version 2.0 which is available at -// * https://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. -// * -// * SPDX-License-Identifier: Apache-2.0 -// ********************************************************************************/ -// -// using Microsoft.AspNetCore.Authentication.JwtBearer; -// using Microsoft.Extensions.Diagnostics.HealthChecks; -// using Microsoft.Extensions.Options; -// using Microsoft.IdentityModel.Protocols; -// using Microsoft.IdentityModel.Protocols.OpenIdConnect; -// -// namespace Org.Eclipse.TractusX.PolicyHub.Service.HealthCheck; -// -// public class JwtBearerConfigurationHealthCheck : IHealthCheck -// { -// private readonly IConfigurationManager _configurationManager; -// -// public JwtBearerConfigurationHealthCheck(IOptions jwtOptions) -// { -// var options = jwtOptions.Value; -// if (!options.RequireHttpsMetadata) -// { -// options.BackchannelHttpHandler = new HttpClientHandler -// { -// ServerCertificateCustomValidationCallback = (_, _, _, _) => true -// }; -// } -// -// _configurationManager = options.BackchannelHttpHandler == null -// ? new ConfigurationManager( -// options.MetadataAddress, -// new OpenIdConnectConfigurationRetriever()) -// : new ConfigurationManager( -// options.MetadataAddress, -// new OpenIdConnectConfigurationRetriever(), -// new HttpClient(options.BackchannelHttpHandler)); -// } -// -// public async Task CheckHealthAsync( -// HealthCheckContext context, CancellationToken cancellationToken = default) -// { -// try -// { -// await _configurationManager.GetConfigurationAsync(cancellationToken).ConfigureAwait(false); -// return HealthCheckResult.Healthy(); -// } -// catch (Exception e) -// { -// return HealthCheckResult.Unhealthy(exception: e); -// } -// } -// } diff --git a/tests/database/PolicyHub.DbAccess.Tests/Setup/TestDbFixture.cs b/tests/database/PolicyHub.DbAccess.Tests/Setup/TestDbFixture.cs index 0d9bdb0..250dab3 100644 --- a/tests/database/PolicyHub.DbAccess.Tests/Setup/TestDbFixture.cs +++ b/tests/database/PolicyHub.DbAccess.Tests/Setup/TestDbFixture.cs @@ -53,7 +53,7 @@ public async Task GetPolicyHubDbContext() optionsBuilder.UseNpgsql( _container.GetConnectionString(), x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) - .MigrationsHistoryTable("__efmigrations_history_hub") + .MigrationsHistoryTable("__efmigrations_history_hub", "public") ); var context = new PolicyHubContext(optionsBuilder.Options); await context.Database.EnsureCreatedAsync().ConfigureAwait(false); @@ -74,7 +74,7 @@ await _container.StartAsync() optionsBuilder.UseNpgsql( _container.GetConnectionString(), x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) - .MigrationsHistoryTable("__efmigrations_history_hub") + .MigrationsHistoryTable("__efmigrations_history_hub", "public") ); var context = new PolicyHubContext(optionsBuilder.Options); await context.Database.MigrateAsync(); diff --git a/tests/database/PolicyHub.Entities.Tests/PolicyHub.Entities.Tests.csproj b/tests/database/PolicyHub.Entities.Tests/PolicyHub.Entities.Tests.csproj new file mode 100644 index 0000000..be39110 --- /dev/null +++ b/tests/database/PolicyHub.Entities.Tests/PolicyHub.Entities.Tests.csproj @@ -0,0 +1,52 @@ + + + + + Org.Eclipse.TractusX.PolicyHub.Entities.Tests + Org.Eclipse.TractusX.PolicyHub.Entities.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/tests/database/PolicyHub.Entities.Tests/PolicyKindExtensionsTests.cs b/tests/database/PolicyHub.Entities.Tests/PolicyKindExtensionsTests.cs new file mode 100644 index 0000000..5439a3e --- /dev/null +++ b/tests/database/PolicyHub.Entities.Tests/PolicyKindExtensionsTests.cs @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.PolicyHub.Entities.Enums; +using Org.Eclipse.TractusX.PolicyHub.Entities.Extensions; + +namespace Org.Eclipse.TractusX.PolicyHub.Entities.Tests; + +public class PolicyKindExtensionsTests +{ + [Theory] + [InlineData(PolicyKindId.BusinessPartnerNumber, true)] + [InlineData(PolicyKindId.Membership, true)] + [InlineData(PolicyKindId.Framework, true)] + [InlineData(PolicyKindId.Purpose, false)] + [InlineData(PolicyKindId.Dismantler, true)] + public void IsTechnicalEnforced_WithValidData_ReturnsExpected(PolicyKindId policyKindId, bool expectedResult) + { + // Act + var result = policyKindId.IsTechnicalEnforced(); + + // Assert + result.Should().Be(expectedResult); + } + + [Fact] + public void IsTechnicalEnforced_WithInvalidData_ThrowsArgumentOutOfRangeException() + { + // Act + var ex = Assert.Throws(() => ((PolicyKindId)0).IsTechnicalEnforced()); + + // Assert + ex.Message.Should().Be("PolicyKindId 0 is not supported (Parameter 'policyKindId')\nActual value was 0."); + } +} diff --git a/tests/database/PolicyHub.Entities.Tests/Usings.cs b/tests/database/PolicyHub.Entities.Tests/Usings.cs new file mode 100644 index 0000000..5f8f40c --- /dev/null +++ b/tests/database/PolicyHub.Entities.Tests/Usings.cs @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +global using AutoFixture; +global using AutoFixture.AutoFakeItEasy; +global using FluentAssertions; +global using Xunit; diff --git a/tests/hub/PolicyHub.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs b/tests/hub/PolicyHub.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs new file mode 100644 index 0000000..9c5bb63 --- /dev/null +++ b/tests/hub/PolicyHub.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Org.Eclipse.TractusX.PolicyHub.Service.Authentication; +using System.Security.Claims; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.PolicyHub.Service.Tests.Authentication; + +public class KeycloakClaimsTransformationTests +{ + private readonly KeycloakClaimsTransformation _sut; + + public KeycloakClaimsTransformationTests() + { + var options = Options.Create(new JwtBearerOptions + { + TokenValidationParameters = new TokenValidationParameters + { + ValidAudience = "validAudience" + } + }); + _sut = new KeycloakClaimsTransformation(options); + } + + [Fact] + public async Task TransformAsync_WithoutRoles_ReturnsExpected() + { + // Arrange + var identity = new ClaimsIdentity(Enumerable.Repeat(new Claim(ClaimTypes.Email, "test@mail.com"), 1)); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().ContainSingle() + .And.Satisfy(x => x.Type == ClaimTypes.Email && x.Value == "test@mail.com"); + } + + [Fact] + public async Task TransformAsync_WithRoles_ReturnsExpected() + { + // Arrange + var json = JsonSerializer.Serialize(new { validAudience = new { roles = Enumerable.Repeat("testRole", 1) } }); + var identity = new ClaimsIdentity(Enumerable.Repeat(new Claim(KeycloakClaimsTransformation.ResourceAccess, json, "JSON"), 1)); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().HaveCount(2).And.Satisfy( + x => x.Type == KeycloakClaimsTransformation.ResourceAccess && x.Value == json, + x => x.Type == ClaimTypes.Role && x.Value == "testRole"); + } + + [Fact] + public async Task TransformAsync_WithIntRole_ReturnsExpected() + { + // Arrange + var json = JsonSerializer.Serialize(new { validAudience = new { roles = Enumerable.Repeat(1, 1) } }); + var identity = new ClaimsIdentity(Enumerable.Repeat(new Claim(KeycloakClaimsTransformation.ResourceAccess, json, "JSON"), 1)); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().ContainSingle() + .And.Satisfy(x => x.Type == KeycloakClaimsTransformation.ResourceAccess && x.Value == json); + } +} diff --git a/tests/hub/PolicyHub.Service.Tests/BusinessLogic/PolicyHubBusinessLogicTests.cs b/tests/hub/PolicyHub.Service.Tests/BusinessLogic/PolicyHubBusinessLogicTests.cs index 6877145..8d03945 100644 --- a/tests/hub/PolicyHub.Service.Tests/BusinessLogic/PolicyHubBusinessLogicTests.cs +++ b/tests/hub/PolicyHub.Service.Tests/BusinessLogic/PolicyHubBusinessLogicTests.cs @@ -22,6 +22,7 @@ using Org.Eclipse.TractusX.PolicyHub.DbAccess.Repositories; using Org.Eclipse.TractusX.PolicyHub.Entities.Enums; using Org.Eclipse.TractusX.PolicyHub.Service.BusinessLogic; +using Org.Eclipse.TractusX.PolicyHub.Service.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; namespace Org.Eclipse.TractusX.PolicyHub.Service.Tests.BusinessLogic; @@ -108,10 +109,10 @@ public async Task GetPolicyTypes_WithUseCaseFilter_ReturnsExpected() #endregion - #region GetPolicyContentAsync + #region GetPolicyContentWithFiltersAsync [Fact] - public async Task GetPolicyContentAsync_WithNotExistingInDatabase_ThrowsNotFoundException() + public async Task GetPolicyContentWithFiltersAsync_WithNotExistingInDatabase_ThrowsNotFoundException() { // Arrange const PolicyTypeId policyTypeId = PolicyTypeId.Access; @@ -126,6 +127,98 @@ public async Task GetPolicyContentAsync_WithNotExistingInDatabase_ThrowsNotFound ex.Message.Should().Be($"Policy for type {policyTypeId} and technicalKey membership does not exists"); } + [Fact] + public async Task GetPolicyContentWithFiltersAsync_AttributeAndRightOperandNull_ThrowsUnexpectedConditionException() + { + // Arrange + const PolicyTypeId policyTypeId = PolicyTypeId.Access; + A.CallTo(() => _policyRepository.GetPolicyContentAsync(null, policyTypeId, "membership")) + .Returns(new ValueTuple), string?>(true, "test", (null, Enumerable.Empty()), null!)); + async Task Act() => await _sut.GetPolicyContentWithFiltersAsync(null, policyTypeId, "membership", OperatorId.Equals, null); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("There must be one configured rightOperand value"); + } + + [Fact] + public async Task GetPolicyContentWithFiltersAsync_WithDynamicValue_ThrowsUnexpectedConditionException() + { + // Arrange + const PolicyTypeId policyTypeId = PolicyTypeId.Access; + A.CallTo(() => _policyRepository.GetPolicyContentAsync(null, policyTypeId, "membership")) + .Returns(new ValueTuple), string?>(true, "test", (AttributeKeyId.DynamicValue, Enumerable.Empty()), "test:{0}")); + + // Act + var result = await _sut.GetPolicyContentWithFiltersAsync(null, policyTypeId, "membership", OperatorId.Equals, "abc"); + + // Assert + result.Content.Permission.Constraint.RightOperandValue.Should().Be("abc"); + } + + [Fact] + public async Task GetPolicyContentWithFiltersAsync_WithMultipleRegexValues_ThrowsUnexpectedConditionException() + { + // Arrange + const PolicyTypeId policyTypeId = PolicyTypeId.Access; + A.CallTo(() => _policyRepository.GetPolicyContentAsync(null, policyTypeId, "membership")) + .Returns(new ValueTuple), string?>(true, "test", (AttributeKeyId.Regex, new[] { "test1", "test2" }), null)); + async Task Act() => await _sut.GetPolicyContentWithFiltersAsync(null, policyTypeId, "membership", OperatorId.Equals, "test"); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be("There should only be one regex pattern defined"); + } + + #endregion + + #region GetPolicyContentAsync + + [Fact] + public async Task GetPolicyContentAsync_WithUnmatchingPoliciesAndConstraints_ThrowsNotFoundException() + { + // Arrange + var data = new PolicyContentRequest(PolicyTypeId.Access, ConstraintOperandId.Or, + new[] + { + new Constraints("test", OperatorId.In, null), + new Constraints("abc", OperatorId.Equals, null) + }); + A.CallTo(() => _policyRepository.GetPolicyForOperandContent(data.PolicyType, A>._)) + .Returns(Enumerable.Repeat(new ValueTuple>, string?>("test", "active", default, null), 1).ToAsyncEnumerable()); + async Task Act() => await _sut.GetPolicyContentAsync(data); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be($"Policy for type {data.PolicyType} and technicalKeys abc does not exists"); + } + + [Fact] + public async Task GetPolicyContentAsync_WithAttributeAndRightOperandNull_ThrowsUnexpectedConditionException() + { + // Arrange + var data = new PolicyContentRequest(PolicyTypeId.Access, ConstraintOperandId.Or, + new[] + { + new Constraints("test", OperatorId.In, null), + }); + A.CallTo(() => _policyRepository.GetPolicyForOperandContent(data.PolicyType, A>._)) + .Returns(Enumerable.Repeat(new ValueTuple>, string?>("test", "active", (null, Enumerable.Empty()), null), 1).ToAsyncEnumerable()); + async Task Act() => await _sut.GetPolicyContentAsync(data); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be("There must be one configured rightOperand value"); + } + #endregion #region Setup diff --git a/tests/hub/PolicyHub.Service.Tests/Controllers/PolicyHubControllerTests.cs b/tests/hub/PolicyHub.Service.Tests/Controllers/PolicyHubControllerTests.cs index 5f08075..614f4e7 100644 --- a/tests/hub/PolicyHub.Service.Tests/Controllers/PolicyHubControllerTests.cs +++ b/tests/hub/PolicyHub.Service.Tests/Controllers/PolicyHubControllerTests.cs @@ -2,6 +2,7 @@ using Org.Eclipse.TractusX.PolicyHub.Entities.Enums; using Org.Eclipse.TractusX.PolicyHub.Service.Models; using Org.Eclipse.TractusX.PolicyHub.Service.Tests.Setup; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using System.Net; using System.Net.Http.Json; using System.Text.Json; @@ -103,17 +104,31 @@ public async Task GetPolicyTypes_WithUseCaseFilter_ReturnsExpected() #region Policy Content [Fact] - public async Task GetPolicyContent_UsageMembershipEquals_ReturnsExpected() + public async Task GetPolicyContent_WithRegexWithIncorrectValue_ReturnsExpected() { // Act - var response = await _client.GetAsync($"{BaseUrl}/policy-content?type={PolicyTypeId.Usage}&credential=Membership&operatorId={OperatorId.Equals}").ConfigureAwait(false); + var response = await _client.GetAsync($"{BaseUrl}/policy-content?type={PolicyTypeId.Access}&credential=BusinessPartnerNumber&operatorId={OperatorId.Equals}&value=notmatching").ConfigureAwait(false); // Assert response.Should().NotBeNull(); - response.StatusCode.Should().Be(HttpStatusCode.OK); - (await response.Content.ReadAsStringAsync().ConfigureAwait(false)) - .Should() - .Be("{\"content\":{\"@context\":[\"https://www.w3.org/ns/odrl.jsonld\",{\"cx\":\"https://w3id.org/catenax/v0.0.1/ns/\"}],\"@type\":\"Offer\",\"@id\":\"....\",\"permission\":{\"action\":\"use\",\"constraint\":{\"leftOperand\":\"Membership\",\"operator\":\"eq\",\"rightOperand\":\"active\"}}}}"); + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var error = await response.Content.ReadFromJsonAsync(Options).ConfigureAwait(false); + error!.Errors.Should().ContainSingle().And.Satisfy( + x => x.Value.Single() == @"The provided value notmatching does not match the regex pattern ^BPNL[\w|\d]{12}$ (Parameter 'value')"); + } + + [Fact] + public async Task GetPolicyContent_WithRegexWithoutValue_ReturnsExpected() + { + // Act + var response = await _client.GetAsync($"{BaseUrl}/policy-content?type={PolicyTypeId.Access}&credential=BusinessPartnerNumber&operatorId={OperatorId.Equals}").ConfigureAwait(false); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var error = await response.Content.ReadFromJsonAsync(Options).ConfigureAwait(false); + error!.Errors.Should().ContainSingle().And.Satisfy( + x => x.Value.Single() == "you must provide a value for the regex (Parameter 'value')"); } [Fact] @@ -174,10 +189,10 @@ public async Task GetPolicyContent_TraceabilityUsagePurposeEquals_ReturnsExpecte #endregion - #region Policy Content with Constraints + #region Policy Content with Filters [Fact] - public async Task GetPolicyContent_TwoEqualsConstraintsAndOperand_ReturnsExpected() + public async Task GetPolicyContentWithFiltersAsync_TwoEqualsConstraintsAndOperand_ReturnsExpected() { // Arrange var data = new PolicyContentRequest( @@ -201,7 +216,7 @@ public async Task GetPolicyContent_TwoEqualsConstraintsAndOperand_ReturnsExpecte } [Fact] - public async Task GetPolicyContent_MultipleConstraintsEqualsAndOperand_ReturnsExpected() + public async Task GetPolicyContentWithFiltersAsync_MultipleConstraintsEqualsAndOperand_ReturnsExpected() { // Arrange var data = new PolicyContentRequest( @@ -226,7 +241,7 @@ public async Task GetPolicyContent_MultipleConstraintsEqualsAndOperand_ReturnsEx } [Fact] - public async Task GetPolicyContent_MultipleConstraintsEqualsOrOperand_ReturnsExpected() + public async Task GetPolicyContentWithFiltersAsync_MultipleConstraintsEqualsOrOperand_ReturnsExpected() { // Arrange var data = new PolicyContentRequest( @@ -249,6 +264,30 @@ public async Task GetPolicyContent_MultipleConstraintsEqualsOrOperand_ReturnsExp .Be("{\"content\":{\"@context\":[\"https://www.w3.org/ns/odrl.jsonld\",{\"cx\":\"https://w3id.org/catenax/v0.0.1/ns/\"}],\"@type\":\"Offer\",\"@id\":\"....\",\"permission\":{\"action\":\"use\",\"constraint\":{\"odrl:or\":[{\"leftOperand\":\"FrameworkAgreement.traceability\",\"operator\":\"eq\",\"rightOperand\":\"@FrameworkAgreement.traceability-Version\"},{\"leftOperand\":\"Dismantler.activityType\",\"operator\":\"in\",\"rightOperand\":[\"Audi\",\"BMW\",\"VW\"]}]}}},\"attributes\":[{\"key\":\"@FrameworkAgreement.traceability-Version\",\"possibleValues\":[\"active:1.0\",\"active:1.1\",\"active:1.2\"]}]}"); } + [Fact] + public async Task GetPolicyContentWithFiltersAsync_WithSameConstraintKeys_ReturnsError() + { + // Arrange + var data = new PolicyContentRequest( + PolicyTypeId.Usage, + ConstraintOperandId.Or, + new[] + { + new Constraints("FrameworkAgreement.traceability", OperatorId.Equals, null), + new Constraints("FrameworkAgreement.traceability", OperatorId.Equals, null), + }); + + // Act + var response = await _client.PostAsJsonAsync($"{BaseUrl}/policy-content", data, Options).ConfigureAwait(false); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var error = await response.Content.ReadFromJsonAsync(Options).ConfigureAwait(false); + error!.Errors.Should().ContainSingle().And.Satisfy( + x => x.Value.Single() == "Keys FrameworkAgreement.traceability have been defined multiple times"); + } + #endregion #region Swagger diff --git a/tests/hub/PolicyHub.Service.Tests/Extensions/JsonGenerationExtensionsTests.cs b/tests/hub/PolicyHub.Service.Tests/Extensions/JsonGenerationExtensionsTests.cs new file mode 100644 index 0000000..9230f34 --- /dev/null +++ b/tests/hub/PolicyHub.Service.Tests/Extensions/JsonGenerationExtensionsTests.cs @@ -0,0 +1,52 @@ +using Org.Eclipse.TractusX.PolicyHub.Entities.Enums; +using Org.Eclipse.TractusX.PolicyHub.Service.Extensions; + +namespace Org.Eclipse.TractusX.PolicyHub.Service.Tests.Extensions; + +public class JsonGenerationExtensionsTests +{ + [Theory] + [InlineData(PolicyTypeId.Access, "access")] + [InlineData(PolicyTypeId.Usage, "use")] + [InlineData(PolicyTypeId.Purpose, "use")] + public void TypeToJsonString_WithValidData_ReturnsExpected(PolicyTypeId policyTypeId, string result) + { + // Act + var jsonString = policyTypeId.TypeToJsonString(); + + // Assert + jsonString.Should().Be(result); + } + + [Fact] + public void TypeToJsonString_WithInvalidData_ThrowsArgumentOutOfRangeException() + { + // Act + var ex = Assert.Throws(() => ((PolicyTypeId)0).TypeToJsonString()); + + // Assert + ex.Message.Should().Be("0 is not a valid value (Parameter 'type')\nActual value was 0."); + } + + [Theory] + [InlineData(OperatorId.Equals, "eq")] + [InlineData(OperatorId.In, "in")] + public void OperatorToJsonString_WithValidData_ReturnsExpected(OperatorId operatorId, string result) + { + // Act + var jsonString = operatorId.OperatorToJsonString(); + + // Assert + jsonString.Should().Be(result); + } + + [Fact] + public void OperatorToJsonString_WithInvalidData_ThrowsArgumentOutOfRangeException() + { + // Act + var ex = Assert.Throws(() => ((OperatorId)0).OperatorToJsonString()); + + // Assert + ex.Message.Should().Be("0 is not a valid value (Parameter 'type')\nActual value was 0."); + } +} diff --git a/tests/hub/PolicyHub.Service.Tests/Setup/IntegrationTestFactory.cs b/tests/hub/PolicyHub.Service.Tests/Setup/IntegrationTestFactory.cs index bb70853..a53a817 100644 --- a/tests/hub/PolicyHub.Service.Tests/Setup/IntegrationTestFactory.cs +++ b/tests/hub/PolicyHub.Service.Tests/Setup/IntegrationTestFactory.cs @@ -72,7 +72,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) { options.UseNpgsql(Container.GetConnectionString(), x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) - .MigrationsHistoryTable("__efmigrations_history_hub")); + .MigrationsHistoryTable("__efmigrations_history_hub", "public")); }); services.AddSingleton(); }); @@ -89,7 +89,7 @@ protected override IHost CreateHost(IHostBuilder builder) optionsBuilder.UseNpgsql( Container.GetConnectionString(), x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) - .MigrationsHistoryTable("__efmigrations_history_hub") + .MigrationsHistoryTable("__efmigrations_history_hub", "public") ); var context = new PolicyHubContext(optionsBuilder.Options); context.Database.Migrate(); @@ -103,6 +103,10 @@ protected override IHost CreateHost(IHostBuilder builder) LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(), seederOptions); insertSeeder.ExecuteAsync(CancellationToken.None).GetAwaiter().GetResult(); + var updateSeeder = new BatchUpdateSeeder(context, + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(), + seederOptions); + updateSeeder.ExecuteAsync(CancellationToken.None).GetAwaiter().GetResult(); return host; }