From b90365a2ce4da9cd4e8e9f997bb17e144d70d350 Mon Sep 17 00:00:00 2001 From: Marek Lesko Date: Thu, 11 Jun 2026 12:53:46 +0200 Subject: [PATCH] Add unit test project for configuration parsing --- .../Configuration/ConfigurationTests.cs | 380 ++++++++++++++++++ SimpleIdp.Tests/README.md | 3 + SimpleIdp.Tests/SimpleIdp.Tests.csproj | 17 + SimpleIdp.Tests/Tests.cs | 0 SimpleIdp.sln | 27 ++ SimpleIdp.slnx | 2 + 6 files changed, 429 insertions(+) create mode 100644 SimpleIdp.Tests/Configuration/ConfigurationTests.cs create mode 100644 SimpleIdp.Tests/README.md create mode 100644 SimpleIdp.Tests/SimpleIdp.Tests.csproj create mode 100644 SimpleIdp.Tests/Tests.cs create mode 100644 SimpleIdp.slnx diff --git a/SimpleIdp.Tests/Configuration/ConfigurationTests.cs b/SimpleIdp.Tests/Configuration/ConfigurationTests.cs new file mode 100644 index 0000000..e7d929f --- /dev/null +++ b/SimpleIdp.Tests/Configuration/ConfigurationTests.cs @@ -0,0 +1,380 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SimpleIdp.Tests.Configuration; + +public class ConfigurationTests +{ + private const string TestConfigPath = "test-config.json"; + + [TearDown] + public void Cleanup() + { + if (File.Exists(TestConfigPath)) + File.Delete(TestConfigPath); + } + + [Test] + public void Deserialize_ValidConfig_ReturnsConfig() + { + var config = new IdpConfig( + Api: new ApiConfig("api", "api-audience"), + Scope: new ScopeConfig("scope", "description", new List()), + Client: new ClientConfig("client-id", "client-name", new List { "http://redirect" }), + Users: new List() + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result); + Assert.That(result.Api.Name, Is.EqualTo(config.Api.Name)); + Assert.That(result.Scope.Name, Is.EqualTo(config.Scope.Name)); + Assert.That(result.Client.ClientId, Is.EqualTo(config.Client.ClientId)); + } + + [Test] + public void Deserialize_WithClaimMappers_ParsesClaimMappersCorrectly() + { + var claimMappers = new List + { + new ClaimMapperConfig("claim1", "source1", "string"), + new ClaimMapperConfig("claim2", "source2", "int", true) + }; + + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", claimMappers), + Client: new ClientConfig("client", "name", new List()), + Users: new List() + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result.Scope.ClaimMappers); + Assert.That(result.Scope.ClaimMappers.Count, Is.EqualTo(2)); + Assert.That(result.Scope.ClaimMappers[0].TargetClaimPath, Is.EqualTo("claim1")); + Assert.That(result.Scope.ClaimMappers[0].SourceUserAttribute, Is.EqualTo("source1")); + Assert.That(result.Scope.ClaimMappers[0].TokenClaimJsonType, Is.EqualTo("string")); + Assert.That(result.Scope.ClaimMappers[1].TargetClaimPath, Is.EqualTo("claim2")); + Assert.IsTrue(result.Scope.ClaimMappers[1].IsMultiValued); + } + + [Test] + public void Deserialize_WithUsers_ParsesUsersCorrectly() + { + var users = new List + { + new UserConfig( + "user1", + "pass1", + "User One", + "user1@test.com", + "User", + true, + new List { "admin" }, + new Dictionary { { "role", "admin" } }, + new List() + ) + }; + + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", new List()), + Client: new ClientConfig("client", "name", new List()), + Users: users + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result.Users); + Assert.That(result.Users.Count, Is.EqualTo(1)); + Assert.That(result.Users[0].Login, Is.EqualTo("user1")); + Assert.That(result.Users[0].Password, Is.EqualTo("pass1")); + Assert.That(result.Users[0].Name, Is.EqualTo("User One")); + Assert.That(result.Users[0].Email, Is.EqualTo("user1@test.com")); + Assert.IsTrue(result.Users[0].EmailVerified); + Assert.That(result.Users[0].Roles.Count, Is.EqualTo(1)); + Assert.That(result.Users[0].Roles[0], Is.EqualTo("admin")); + } + + [Test] + public void Deserialize_WithConsents_ParsesConsentsCorrectly() + { + var consents = new List + { + new ConsentConfig("realm1", "client1", "scope1") + }; + + var users = new List + { + new UserConfig( + "user1", + "pass1", + "User One", + "user1@test.com", + "User", + true, + new List(), + new Dictionary(), + consents + ) + }; + + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", new List()), + Client: new ClientConfig("client", "name", new List()), + Users: users + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result.Users[0].Consents); + Assert.That(result.Users[0].Consents.Count, Is.EqualTo(1)); + Assert.That(result.Users[0].Consents[0].Realm, Is.EqualTo("realm1")); + Assert.That(result.Users[0].Consents[0].ClientId, Is.EqualTo("client1")); + Assert.That(result.Users[0].Consents[0].Scope, Is.EqualTo("scope1")); + } + + [Test] + public void Deserialize_CaseInsensitive_ParsesCorrectly() + { + var json = """ + { + "api": { + "name": "test-api", + "audience": "test-audience" + }, + "scope": { + "name": "test-scope", + "description": "test-desc", + "claimmappers": [] + }, + "client": { + "clientid": "test-client", + "name": "test client", + "redirecturis": ["http://test.com"], + "ispublic": false + }, + "users": [] + } + """; + + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result); + Assert.That(result.Api.Name, Is.EqualTo("test-api")); + Assert.That(result.Client.ClientId, Is.EqualTo("test-client")); + Assert.IsFalse(result.Client.IsPublic); + } + + [Test] + public void Deserialize_MissingApiConfig_ReturnsNullApi() + { + var json = """ + { + "scope": { "name": "scope", "description": "desc", "claimmappers": [] }, + "client": { "clientid": "client", "name": "name", "redirecturis": [] }, + "users": [] + } + """; + + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNull(result.Api); + } + + [Test] + public void Deserialize_MissingScopeConfig_ReturnsNullScope() + { + var json = """ + { + "api": { "name": "api", "audience": "audience" }, + "client": { "clientid": "client", "name": "name", "redirecturis": [] }, + "users": [] + } + """; + + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNull(result.Scope); + } + + [Test] + public void Deserialize_MissingClientConfig_ReturnsNullClient() + { + var json = """ + { + "api": { "name": "api", "audience": "audience" }, + "scope": { "name": "scope", "description": "desc", "claimmappers": [] }, + "users": [] + } + """; + + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNull(result.Client); + } + + [Test] + public void Deserialize_EmptyUsersList_ParsesSuccessfully() + { + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", new List()), + Client: new ClientConfig("client", "name", new List()), + Users: new List() + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.IsNotNull(result.Users); + Assert.That(result.Users, Is.Empty); + } + + [Test] + public void Deserialize_MultipleUsers_ParsesAllUsers() + { + var users = new List + { + new UserConfig("user1", "pass1", "User 1", "user1@test.com", "User", true, new List(), new Dictionary(), new List()), + new UserConfig("user2", "pass2", "User 2", "user2@test.com", "User", false, new List(), new Dictionary(), new List()), + new UserConfig("user3", "pass3", "User 3", "user3@test.com", "User", true, new List(), new Dictionary(), new List()) + }; + + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", new List()), + Client: new ClientConfig("client", "name", new List()), + Users: users + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.That(result.Users.Count, Is.EqualTo(3)); + } + + [Test] + public void Deserialize_ClientWithMultipleRedirectUris_ParsesAllUris() + { + var redirectUris = new List + { + "http://redirect1.com", + "http://redirect2.com", + "https://redirect3.com" + }; + + var config = new IdpConfig( + Api: new ApiConfig("api", "audience"), + Scope: new ScopeConfig("scope", "desc", new List()), + Client: new ClientConfig("client", "name", redirectUris), + Users: new List() + ); + + var json = SerializeConfig(config); + File.WriteAllText(TestConfigPath, json); + + var result = LoadConfigFromFile(TestConfigPath); + + Assert.That(result.Client.RedirectUris.Count, Is.EqualTo(3)); + Assert.That(result.Client.RedirectUris, Contains.Item("http://redirect1.com")); + Assert.That(result.Client.RedirectUris, Contains.Item("http://redirect2.com")); + Assert.That(result.Client.RedirectUris, Contains.Item("https://redirect3.com")); + } + + private string SerializeConfig(IdpConfig config) + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + return JsonSerializer.Serialize(config, options); + } + + private IdpConfig LoadConfigFromFile(string path) + { + var json = File.ReadAllText(path); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + return JsonSerializer.Deserialize(json, options) + ?? throw new InvalidOperationException("Failed to deserialize config"); + } +} + +public record IdpConfig( + ApiConfig Api, + ScopeConfig Scope, + ClientConfig Client, + List Users); + +public record ApiConfig( + string Name, + string Audience); + +public record ScopeConfig( + string Name, + string Description, + List ClaimMappers); + +public record ClaimMapperConfig( + string TargetClaimPath, + string SourceUserAttribute, + string TokenClaimJsonType, + bool IsMultiValued = false); + +public record ClientConfig( + string ClientId, + string Name, + List RedirectUris, + bool IsPublic = true); + +public record UserConfig( + string Login, + string Password, + string Name, + string Email, + string Firstname, + bool EmailVerified, + List Roles, + Dictionary Claims, + List Consents); + +public record ConsentConfig( + string Realm, + string ClientId, + string Scope); diff --git a/SimpleIdp.Tests/README.md b/SimpleIdp.Tests/README.md new file mode 100644 index 0000000..d076d32 --- /dev/null +++ b/SimpleIdp.Tests/README.md @@ -0,0 +1,3 @@ +# SimpleIdp.Tests + +Unit tests for SimpleIdP diff --git a/SimpleIdp.Tests/SimpleIdp.Tests.csproj b/SimpleIdp.Tests/SimpleIdp.Tests.csproj new file mode 100644 index 0000000..e974c5f --- /dev/null +++ b/SimpleIdp.Tests/SimpleIdp.Tests.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + false + true + + + + + + + + + + + diff --git a/SimpleIdp.Tests/Tests.cs b/SimpleIdp.Tests/Tests.cs new file mode 100644 index 0000000..e69de29 diff --git a/SimpleIdp.sln b/SimpleIdp.sln index d211bcb..594fdbc 100644 --- a/SimpleIdp.sln +++ b/SimpleIdp.sln @@ -1,19 +1,46 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleIdp", "SimpleIdp.csproj", "{C962C62B-F4DA-ED13-F4C4-819534040A9C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleIdp.Tests", "SimpleIdp.Tests\SimpleIdp.Tests.csproj", "{336AC621-0811-4B37-9958-8C6EA6422DDA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|x64.ActiveCfg = Debug|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|x64.Build.0 = Debug|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|x86.ActiveCfg = Debug|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Debug|x86.Build.0 = Debug|Any CPU {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|Any CPU.Build.0 = Release|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|x64.ActiveCfg = Release|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|x64.Build.0 = Release|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|x86.ActiveCfg = Release|Any CPU + {C962C62B-F4DA-ED13-F4C4-819534040A9C}.Release|x86.Build.0 = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|x64.ActiveCfg = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|x64.Build.0 = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|x86.ActiveCfg = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Debug|x86.Build.0 = Debug|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|Any CPU.Build.0 = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|x64.ActiveCfg = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|x64.Build.0 = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|x86.ActiveCfg = Release|Any CPU + {336AC621-0811-4B37-9958-8C6EA6422DDA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SimpleIdp.slnx b/SimpleIdp.slnx new file mode 100644 index 0000000..ba788ff --- /dev/null +++ b/SimpleIdp.slnx @@ -0,0 +1,2 @@ + + -- 2.52.0