Add tests and fixes to .well-known/openid-configuration and create realm
This commit is contained in:
parent
e07d6e3ea5
commit
d440979451
17 changed files with 642 additions and 45 deletions
364
IdentityShroud.TestUtils/Asserts/JsonObjectAssert.cs
Normal file
364
IdentityShroud.TestUtils/Asserts/JsonObjectAssert.cs
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using Xunit;
|
||||
|
||||
namespace IdentityShroud.TestUtils.Asserts;
|
||||
|
||||
public static class JsonObjectAssert
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a path string that may contain array indices (e.g., "items[0].name") into individual segments.
|
||||
/// </summary>
|
||||
/// <param name="path">The path string with optional array indices</param>
|
||||
/// <returns>Array of path segments where array indices are separate segments</returns>
|
||||
public static string[] ParsePath(string path)
|
||||
{
|
||||
var segments = new List<string>();
|
||||
var parts = path.Split('.');
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
// Check if the part contains array indexing like "items[0]"
|
||||
var match = Regex.Match(part, @"^(.+?)\[(\d+)\]$");
|
||||
if (match.Success)
|
||||
{
|
||||
// Add the property name
|
||||
segments.Add(match.Groups[1].Value);
|
||||
// Add the array index
|
||||
segments.Add(match.Groups[2].Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
segments.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
return segments.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to a JsonNode at the specified path and returns it.
|
||||
/// Throws XunitException if the path doesn't exist or is invalid.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The root JsonObject to navigate from</param>
|
||||
/// <param name="pathArray">The path segments to navigate</param>
|
||||
/// <returns>The JsonNode at the specified path (can be null if the value is null)</returns>
|
||||
public static JsonNode? NavigateToPath(JsonObject jsonObject, string[] pathArray)
|
||||
{
|
||||
if (pathArray.Length == 0)
|
||||
throw new ArgumentException("Path cannot be empty");
|
||||
|
||||
JsonNode? current = jsonObject;
|
||||
string currentPath = "";
|
||||
|
||||
foreach (var segment in pathArray)
|
||||
{
|
||||
currentPath = string.IsNullOrEmpty(currentPath) ? segment : $"{currentPath}.{segment}";
|
||||
|
||||
if (current == null)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{currentPath}' does not exist - parent node is null");
|
||||
|
||||
if (current is JsonObject obj)
|
||||
{
|
||||
if (!obj.ContainsKey(segment))
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{currentPath}' does not exist - property '{segment}' not found");
|
||||
|
||||
current = obj[segment];
|
||||
}
|
||||
else if (current is JsonArray arr && int.TryParse(segment, out int index))
|
||||
{
|
||||
if (index < 0 || index >= arr.Count)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{currentPath}' does not exist - array index {index} out of bounds (array length: {arr.Count})");
|
||||
|
||||
current = arr[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{currentPath}' is invalid - cannot navigate through non-object/non-array node at '{segment}'");
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a JsonObject contains the expected value at the specified path.
|
||||
/// Validates that the path exists, field types match, and values are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected type of the value</typeparam>
|
||||
/// <param name="expected">The expected value</param>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the field as an enumerable of property names</param>
|
||||
public static void Equal<T>(T expected, JsonObject jsonObject, IEnumerable<string> path)
|
||||
{
|
||||
var pathArray = path.ToArray();
|
||||
var current = NavigateToPath(jsonObject, pathArray);
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
if (expected != null)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Expected value '{expected}' at path '{string.Join(".", pathArray)}', but found null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Type and value validation
|
||||
try
|
||||
{
|
||||
T? actualValue = current.GetValue<T>();
|
||||
Assert.Equal(expected, actualValue);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Type mismatch at path '{string.Join(".", pathArray)}': cannot convert JsonNode to {typeof(T).Name}. {ex.Message}");
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Format error at path '{string.Join(".", pathArray)}': cannot convert value to {typeof(T).Name}. {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a JsonObject contains the expected value at the specified path.
|
||||
/// Validates that the path exists, field types match, and values are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected type of the value</typeparam>
|
||||
/// <param name="expected">The expected value</param>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the field as dot-separated string with optional array indices (e.g., "user.addresses[0].city")</param>
|
||||
public static void Equal<T>(T expected, JsonObject jsonObject, string path)
|
||||
{
|
||||
Equal(expected, jsonObject, ParsePath(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a path exists in the JsonObject without validating the value.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to check for existence</param>
|
||||
public static void PathExists(JsonObject jsonObject, IEnumerable<string> path)
|
||||
{
|
||||
var pathArray = path.ToArray();
|
||||
NavigateToPath(jsonObject, pathArray);
|
||||
// If NavigateToPath doesn't throw, the path exists
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a path exists in the JsonObject without validating the value.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to check for existence as dot-separated string with optional array indices</param>
|
||||
public static void PathExists(JsonObject jsonObject, string path)
|
||||
{
|
||||
PathExists(jsonObject, ParsePath(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a JsonArray at the specified path has the expected count.
|
||||
/// Validates that the path exists, is a JsonArray, and has the expected number of elements.
|
||||
/// </summary>
|
||||
/// <param name="expectedCount">The expected number of elements in the array</param>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array as an enumerable of property names</param>
|
||||
public static void Count(int expectedCount, JsonObject jsonObject, IEnumerable<string> path)
|
||||
{
|
||||
var pathArray = path.ToArray();
|
||||
var current = NavigateToPath(jsonObject, pathArray);
|
||||
var pathString = string.Join(".", pathArray);
|
||||
|
||||
if (current == null)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{pathString}' contains null - cannot verify count on null value");
|
||||
|
||||
if (current is not JsonArray array)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{pathString}' does not contain a JsonArray - found {current.GetType().Name} instead");
|
||||
|
||||
if (array.Count != expectedCount)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Expected array at path '{pathString}' to have {expectedCount} element(s), but found {array.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a JsonArray at the specified path has the expected count.
|
||||
/// Validates that the path exists, is a JsonArray, and has the expected number of elements.
|
||||
/// </summary>
|
||||
/// <param name="expectedCount">The expected number of elements in the array</param>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array as dot-separated string with optional array indices (e.g., "user.addresses")</param>
|
||||
public static void Count(int expectedCount, JsonObject jsonObject, string path)
|
||||
{
|
||||
Count(expectedCount, jsonObject, ParsePath(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a JsonArray at the specified path for performing custom assertions on its elements.
|
||||
/// Validates that the path exists and is a JsonArray.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to navigate</param>
|
||||
/// <param name="path">The path to the array as an enumerable of property names</param>
|
||||
/// <returns>The JsonArray at the specified path</returns>
|
||||
public static JsonArray GetArray(JsonObject jsonObject, IEnumerable<string> path)
|
||||
{
|
||||
var pathArray = path.ToArray();
|
||||
var current = NavigateToPath(jsonObject, pathArray);
|
||||
var pathString = string.Join(".", pathArray);
|
||||
|
||||
if (current == null)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{pathString}' contains null - expected a JsonArray");
|
||||
|
||||
if (current is not JsonArray array)
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Path '{pathString}' does not contain a JsonArray - found {current.GetType().Name} instead");
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a JsonArray at the specified path for performing custom assertions on its elements.
|
||||
/// Validates that the path exists and is a JsonArray.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to navigate</param>
|
||||
/// <param name="path">The path to the array as dot-separated string with optional array indices (e.g., "user.addresses")</param>
|
||||
/// <returns>The JsonArray at the specified path</returns>
|
||||
public static JsonArray GetArray(JsonObject jsonObject, string path)
|
||||
{
|
||||
return GetArray(jsonObject, ParsePath(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that all elements in a JsonArray at the specified path satisfy the given predicate.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array</param>
|
||||
/// <param name="predicate">The predicate to test each element against</param>
|
||||
public static void All(JsonObject jsonObject, IEnumerable<string> path, Func<JsonNode?, bool> predicate)
|
||||
{
|
||||
var array = GetArray(jsonObject, path);
|
||||
var pathString = string.Join(".", path);
|
||||
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
if (!predicate(array[i]))
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Predicate failed for element at index {i} in array at path '{pathString}'");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that all elements in a JsonArray at the specified path satisfy the given predicate.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array as dot-separated string</param>
|
||||
/// <param name="predicate">The predicate to test each element against</param>
|
||||
public static void All(JsonObject jsonObject, string path, Func<JsonNode?, bool> predicate)
|
||||
{
|
||||
All(jsonObject, ParsePath(path), predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that at least one element in a JsonArray at the specified path satisfies the given predicate.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array</param>
|
||||
/// <param name="predicate">The predicate to test each element against</param>
|
||||
public static void Any(JsonObject jsonObject, IEnumerable<string> path, Func<JsonNode?, bool> predicate)
|
||||
{
|
||||
var array = GetArray(jsonObject, path);
|
||||
var pathString = string.Join(".", path);
|
||||
|
||||
foreach (var element in array)
|
||||
{
|
||||
if (predicate(element))
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"No element in array at path '{pathString}' satisfies the predicate");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that at least one element in a JsonArray at the specified path satisfies the given predicate.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array as dot-separated string</param>
|
||||
/// <param name="predicate">The predicate to test each element against</param>
|
||||
public static void Any(JsonObject jsonObject, string path, Func<JsonNode?, bool> predicate)
|
||||
{
|
||||
Any(jsonObject, ParsePath(path), predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an action on each element in a JsonArray at the specified path.
|
||||
/// Useful for running custom assertions on each element.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array</param>
|
||||
/// <param name="assertAction">The action to perform on each element</param>
|
||||
public static void ForEach(JsonObject jsonObject, IEnumerable<string> path, Action<JsonNode?, int> assertAction)
|
||||
{
|
||||
var array = GetArray(jsonObject, path);
|
||||
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
assertAction(array[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an action on each element in a JsonArray at the specified path.
|
||||
/// Useful for running custom assertions on each element.
|
||||
/// </summary>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="path">The path to the array as dot-separated string</param>
|
||||
/// <param name="assertAction">The action to perform on each element (element, index)</param>
|
||||
public static void ForEach(JsonObject jsonObject, string path, Action<JsonNode?, int> assertAction)
|
||||
{
|
||||
ForEach(jsonObject, ParsePath(path), assertAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that a JsonArray at the specified path contains an element with a specific value at a property path.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected type of the value</typeparam>
|
||||
/// <param name="jsonObject">The JsonObject to validate</param>
|
||||
/// <param name="arrayPath">The path to the array</param>
|
||||
/// <param name="propertyPath">The property path within each array element to check</param>
|
||||
/// <param name="expectedValue">The expected value</param>
|
||||
public static void Contains<T>(JsonObject jsonObject, string arrayPath, string propertyPath, T expectedValue)
|
||||
{
|
||||
var array = GetArray(jsonObject, arrayPath);
|
||||
var propertySegments = ParsePath(propertyPath);
|
||||
|
||||
foreach (var element in array)
|
||||
{
|
||||
if (element is JsonObject elementObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
var current = NavigateToPath(elementObj, propertySegments);
|
||||
if (current != null)
|
||||
{
|
||||
var actualValue = current.GetValue<T>();
|
||||
if (EqualityComparer<T>.Default.Equals(actualValue, expectedValue))
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Continue checking other elements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Xunit.Sdk.XunitException(
|
||||
$"Array at path '{arrayPath}' does not contain an element with {propertyPath} = {expectedValue}");
|
||||
}
|
||||
}
|
||||
66
IdentityShroud.TestUtils/Asserts/ResultAssert.cs
Normal file
66
IdentityShroud.TestUtils/Asserts/ResultAssert.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using FluentResults;
|
||||
using Xunit;
|
||||
|
||||
namespace IdentityShroud.Core.Tests;
|
||||
|
||||
public static class ResultAssert
|
||||
{
|
||||
public static void Success(Result result)
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
var errors = string.Join("\n", result.Errors.Select(e => "\t" + e.Message));
|
||||
Assert.True(result.IsSuccess, $"ResultAssert.Success: failed, got errors:\n{errors}");
|
||||
}
|
||||
}
|
||||
|
||||
public static T Success<T>(Result<T> result)
|
||||
{
|
||||
Success(result.ToResult());
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public static void Failed(Result result, Predicate<IError>? filter = null)
|
||||
{
|
||||
if (!result.IsFailed)
|
||||
{
|
||||
Assert.Fail("ResultAssert.Failed: failed, unexpected success result");
|
||||
}
|
||||
|
||||
if (filter is not null)
|
||||
Assert.Contains(result.Errors, filter);
|
||||
}
|
||||
|
||||
public static void Failed<T>(Result<T> result, Predicate<IError>? filter = null)
|
||||
{
|
||||
Failed(result.ToResult(), filter);
|
||||
}
|
||||
|
||||
public static void FailedWith<TError>(Result result) where TError : IError
|
||||
{
|
||||
if (!result.IsFailed)
|
||||
{
|
||||
Assert.Fail("ResultAssert.Failed: failed, unexpected success result");
|
||||
}
|
||||
|
||||
if (!result.Errors.Any(e => e is TError))
|
||||
{
|
||||
string typeName = typeof(TError).Name;
|
||||
Assert.Fail($"ResultAssert.Failed: failed, no error of the type {typeName} found");
|
||||
}
|
||||
}
|
||||
|
||||
public static void FailedWith<T, TError>(Result<T> result) where TError : IError
|
||||
{
|
||||
if (!result.IsFailed)
|
||||
{
|
||||
Assert.Fail("ResultAssert.Failed: failed, unexpected success result");
|
||||
}
|
||||
|
||||
if (!result.Errors.Any(e => e is TError))
|
||||
{
|
||||
string typeName = typeof(TError).Name;
|
||||
Assert.Fail($"ResultAssert.Failed: failed, no error of the type {typeName} found");
|
||||
}
|
||||
}
|
||||
}
|
||||
15
IdentityShroud.TestUtils/IdentityShroud.TestUtils.csproj
Normal file
15
IdentityShroud.TestUtils/IdentityShroud.TestUtils.csproj
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentResults" Version="4.0.0" />
|
||||
<PackageReference Include="xunit.v3.assert" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue