File: Passkeys\PasskeyScenarioTest.cs
Web Access
Project: src\src\Identity\test\Identity.Test\Microsoft.AspNetCore.Identity.Test.csproj (Microsoft.AspNetCore.Identity.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Text.Json;
using System.Text.Json.Nodes;
 
namespace Microsoft.AspNetCore.Identity.Test;
 
// Represents a test for a passkey scenario (attestation or assertion)
internal abstract class PasskeyScenarioTest<TResult>
{
    private bool _hasStarted;
 
    public Task<TResult> RunAsync()
    {
        if (_hasStarted)
        {
            throw new InvalidOperationException("The test can only be run once.");
        }
 
        _hasStarted = true;
        return RunCoreAsync();
    }
 
    protected abstract Task<TResult> RunCoreAsync();
 
    // While some test configuration can be set directly on scenario classes (AttestationTest and AssertionTest),
    // individual tests may need to modify values computed during execution (e.g., JSON payloads, hashes).
    // This helper enables trivial customization of test scenarios by allowing injection of custom logic to
    // transform runtime values.
    public class ComputedValue<TValue>
    {
        private bool _isComputed;
        private TValue? _computedValue;
        private Func<TValue, TValue?>? _transformFunc;
 
        public TValue GetValue()
        {
            if (!_isComputed)
            {
                throw new InvalidOperationException("Cannot get the value because it has not yet been computed.");
            }
 
            return _computedValue!;
        }
 
        public virtual TValue Compute(TValue initialValue)
        {
            if (_isComputed)
            {
                throw new InvalidOperationException("Cannot compute a value multiple times.");
            }
 
            if (_transformFunc is not null)
            {
                initialValue = _transformFunc(initialValue) ?? initialValue;
            }
 
            _isComputed = true;
            _computedValue = initialValue;
            return _computedValue;
        }
 
        public virtual void Transform(Func<TValue, TValue?> transform)
        {
            if (_transformFunc is not null)
            {
                throw new InvalidOperationException("Cannot transform a value multiple times.");
            }
 
            _transformFunc = transform;
        }
    }
 
    public sealed class ComputedJsonObject : ComputedValue<string>
    {
        private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
        {
            WriteIndented = true,
        };
 
        private JsonElement? _jsonElementValue;
 
        public JsonElement GetValueAsJsonElement()
        {
            if (_jsonElementValue is null)
            {
                var rawValue = GetValue() ?? throw new InvalidOperationException("Cannot get the value as a JSON element because it is null.");
                try
                {
                    _jsonElementValue = JsonSerializer.Deserialize<JsonElement>(rawValue, _jsonSerializerOptions);
                }
                catch (JsonException ex)
                {
                    throw new InvalidOperationException("Cannot get the value as a JSON element because it is not valid JSON.", ex);
                }
            }
 
            return _jsonElementValue.Value;
        }
 
        public void TransformAsJsonObject(Action<JsonObject> transform)
        {
            Transform(value =>
            {
                try
                {
                    var jsonObject = JsonNode.Parse(value)?.AsObject()
                        ?? throw new InvalidOperationException("Could not transform the JSON value because it was unexpectedly null.");
                    transform(jsonObject);
                    return jsonObject.ToJsonString(_jsonSerializerOptions);
                }
                catch (JsonException ex)
                {
                    throw new InvalidOperationException("Could not transform the value because it was not valid JSON.", ex);
                }
            });
        }
    }
}