File: PersistentValueProviderComponentSubscriptionTests.cs
Web Access
Project: src\src\Components\Components\test\Microsoft.AspNetCore.Components.Tests.csproj (Microsoft.AspNetCore.Components.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Runtime.ExceptionServices;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Infrastructure;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
 
namespace Microsoft.AspNetCore.Components;
 
public class PersistentValueProviderComponentSubscriptionTests
{
    [Fact]
    public void Constructor_CreatesSubscription_AndRegistersCallbacks()
    {
        // Arrange
        var state = new PersistentComponentState(new Dictionary<string, byte[]>(), [], []);
        state.InitializeExistingState(new Dictionary<string, byte[]>(), RestoreContext.InitialValue);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "test-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        // Act
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Assert - Constructor should complete without throwing
        Assert.NotNull(subscription);
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_ReturnsNull_WhenNotInitialized()
    {
        // Arrange
        var state = new PersistentComponentState(new Dictionary<string, byte[]>(), [], []);
        state.InitializeExistingState(new Dictionary<string, byte[]>(), RestoreContext.InitialValue);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "test-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Null(result);
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_RestoresFromPersistentState_OnFirstCall()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "initial-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate the state with serialized data
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(TestComponent.State));
        initialState[key] = JsonSerializer.SerializeToUtf8Bytes("persisted-value", JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal("persisted-value", result);
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_ReturnsCurrentPropertyValue_AfterInitialization()
    {
        // Arrange
        var state = new PersistentComponentState(new Dictionary<string, byte[]>(), [], []);
        state.InitializeExistingState(new Dictionary<string, byte[]>(), RestoreContext.InitialValue);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "current-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Initialize by calling once
        subscription.GetOrComputeLastValue();
 
        // Change the component's property value
        component.State = "updated-value";
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal("updated-value", result);
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_CanRestoreValueTypes()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new ValueTypeTestComponent { IntValue = 42 };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate the state with serialized data
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(ValueTypeTestComponent.IntValue));
        initialState[key] = JsonSerializer.SerializeToUtf8Bytes(123, JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(ValueTypeTestComponent.IntValue), typeof(int));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal(123, result);
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_CanRestoreNullableValueTypes()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new ValueTypeTestComponent { NullableIntValue = 42 };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate the state with serialized data
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(ValueTypeTestComponent.NullableIntValue));
        initialState[key] = JsonSerializer.SerializeToUtf8Bytes((int?)456, JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(ValueTypeTestComponent.NullableIntValue), typeof(int?));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal(456, result);
        subscription.Dispose();
    }
 
    [Fact]
    public void Dispose_DisposesSubscriptions()
    {
        // Arrange
        var state = new PersistentComponentState(new Dictionary<string, byte[]>(), [], []);
        state.InitializeExistingState(new Dictionary<string, byte[]>(), RestoreContext.InitialValue);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "test-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act & Assert - Should not throw
        subscription.Dispose();
    }
 
    [Fact]
    public void GetOrComputeLastValue_ReturnsNull_WhenSkipInitialValueAndInitialContext()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new TestComponent { State = "initial-value" };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate the state with serialized data that should be skipped
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(TestComponent.State));
        initialState[key] = JsonSerializer.SerializeToUtf8Bytes("persisted-value", JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.InitialValue);
 
        var cascadingParameterInfo = CreateCascadingParameterInfoWithBehavior(
            nameof(TestComponent.State),
            typeof(string),
            RestoreBehavior.SkipInitialValue);
 
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Null(result);
        subscription.Dispose();
    }
 
    [Fact]
    public async Task GetOrComputeLastValue_FollowsCorrectValueTransitionSequence()
    {
        // Arrange
        var appState = new Dictionary<string, byte[]>();
        var manager = new ComponentStatePersistenceManager(NullLogger<ComponentStatePersistenceManager>.Instance);
        var state = manager.State;
        var serviceProvider = PersistentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(new ServiceCollection())
            .AddSingleton(manager)
            .AddSingleton(manager.State)
            .AddFakeLogging()
            .BuildServiceProvider();
        var renderer = new TestRenderer(serviceProvider);
        var provider = (PersistentStateValueProvider)renderer.ServiceProviderCascadingValueSuppliers.Single();
        var component = new TestComponent { State = "initial-property-value" };
        var componentId = renderer.AssignRootComponentId(component);
        var componentState = renderer.GetComponentState(component);
 
        // Pre-populate the state with serialized data
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(TestComponent.State));
        appState[key] = JsonSerializer.SerializeToUtf8Bytes("first-restored-value", JsonSerializerOptions.Web);
        await manager.RestoreStateAsync(new TestStore(appState), RestoreContext.InitialValue);
 
        await renderer.Dispatcher.InvokeAsync(() => renderer.RenderRootComponentAsync(componentId, ParameterView.Empty));
        var cascadingParameterInfo = CreateCascadingParameterInfo(nameof(TestComponent.State), typeof(string));
 
        // Act & Assert - First call: Returns restored value from state
        Assert.Equal("first-restored-value", component.State);
 
        // Change the component's property value
        component.State = "updated-property-value";
 
        // Second call: Returns the component's property value
        var result2 = provider.GetCurrentValue(componentState, cascadingParameterInfo);
        Assert.Equal("updated-property-value", result2);
 
        appState.Clear();
        var newState = new Dictionary<string, byte[]>
        {
            [key] = JsonSerializer.SerializeToUtf8Bytes("second-restored-value", JsonSerializerOptions.Web)
        };
        // Simulate invoking the callback with a value update.
        await renderer.Dispatcher.InvokeAsync(() => manager.RestoreStateAsync(new TestStore(newState), RestoreContext.ValueUpdate));
        Assert.Equal("second-restored-value", component.State);
 
        component.State = "another-updated-value";
        // Other calls: Returns the updated value from state
        Assert.Equal("another-updated-value", provider.GetCurrentValue(componentState, cascadingParameterInfo));
        component.State = "final-updated-value";
        Assert.Equal("final-updated-value", provider.GetCurrentValue(componentState, cascadingParameterInfo));
        Assert.Equal("final-updated-value", provider.GetCurrentValue(componentState, cascadingParameterInfo));
    }
 
    [Fact]
    public void GetOrComputeLastValue_UsesCustomSerializer_ForRestoration()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new CustomSerializerTestComponent { CustomValue = new CustomData { Value = "initial" } };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate the state with custom serialized data
        var key = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(CustomSerializerTestComponent.CustomValue));
        var customSerializer = new TestCustomDataSerializer();
        var testData = new CustomData { Value = "restored-custom" };
        var writer = new ArrayBufferWriter<byte>();
        customSerializer.Persist(testData, writer);
        initialState[key] = writer.WrittenSpan.ToArray();
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var cascadingParameterInfo = CreateCascadingParameterInfo(
            nameof(CustomSerializerTestComponent.CustomValue),
            typeof(CustomData));
 
        var serviceProvider = new ServiceCollection()
            .AddSingleton<PersistentComponentStateSerializer<CustomData>, TestCustomDataSerializer>()
            .BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        var subscription = new PersistentValueProviderComponentSubscription(
            state, componentState, cascadingParameterInfo, serviceProvider, logger);
 
        // Act
        var result = subscription.GetOrComputeLastValue();
 
        // Assert
        var customDataResult = Assert.IsType<CustomData>(result);
        Assert.Equal("restored-custom", customDataResult.Value);
        subscription.Dispose();
    }
 
    [Fact]
    public void CanPersistAndRestore_MultipleProperties_OnSameComponent()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new MultiplePropertiesComponent
        {
            StringValue = "initial-string",
            IntValue = 42,
            BoolValue = true
        };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate state for all properties
        var stringKey = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(MultiplePropertiesComponent.StringValue));
        var intKey = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(MultiplePropertiesComponent.IntValue));
        var boolKey = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(MultiplePropertiesComponent.BoolValue));
 
        initialState[stringKey] = JsonSerializer.SerializeToUtf8Bytes("restored-string", JsonSerializerOptions.Web);
        initialState[intKey] = JsonSerializer.SerializeToUtf8Bytes(123, JsonSerializerOptions.Web);
        initialState[boolKey] = JsonSerializer.SerializeToUtf8Bytes(false, JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        // Create subscriptions for each property
        var stringSubscription = new PersistentValueProviderComponentSubscription(
            state, componentState,
            CreateCascadingParameterInfo(nameof(MultiplePropertiesComponent.StringValue), typeof(string)),
            serviceProvider, logger);
 
        var intSubscription = new PersistentValueProviderComponentSubscription(
            state, componentState,
            CreateCascadingParameterInfo(nameof(MultiplePropertiesComponent.IntValue), typeof(int)),
            serviceProvider, logger);
 
        var boolSubscription = new PersistentValueProviderComponentSubscription(
            state, componentState,
            CreateCascadingParameterInfo(nameof(MultiplePropertiesComponent.BoolValue), typeof(bool)),
            serviceProvider, logger);
 
        // Act
        var stringResult = stringSubscription.GetOrComputeLastValue();
        var intResult = intSubscription.GetOrComputeLastValue();
        var boolResult = boolSubscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal("restored-string", stringResult);
        Assert.Equal(123, intResult);
        Assert.Equal(false, boolResult);
 
        // Cleanup
        stringSubscription.Dispose();
        intSubscription.Dispose();
        boolSubscription.Dispose();
    }
 
    [Fact]
    public void CanPersistAndRestore_DifferentPropertyTypes_OnSameComponent()
    {
        // Arrange
        var initialState = new Dictionary<string, byte[]>();
        var state = new PersistentComponentState(initialState, [], []);
        var renderer = new TestRenderer();
        var component = new ValueTypeTestComponent
        {
            IntValue = 42,
            NullableIntValue = 100
        };
        var componentState = CreateComponentState(renderer, component, null, null);
 
        // Pre-populate state for different property types
        var intKey = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(ValueTypeTestComponent.IntValue));
        var nullableIntKey = PersistentStateValueProviderKeyResolver.ComputeKey(componentState, nameof(ValueTypeTestComponent.NullableIntValue));
 
        initialState[intKey] = JsonSerializer.SerializeToUtf8Bytes(999, JsonSerializerOptions.Web);
        initialState[nullableIntKey] = JsonSerializer.SerializeToUtf8Bytes((int?)777, JsonSerializerOptions.Web);
        state.InitializeExistingState(initialState, RestoreContext.LastSnapshot);
 
        var serviceProvider = new ServiceCollection().BuildServiceProvider();
        var logger = NullLogger.Instance;
 
        // Create subscriptions for different property types
        var intSubscription = new PersistentValueProviderComponentSubscription(
            state, componentState,
            CreateCascadingParameterInfo(nameof(ValueTypeTestComponent.IntValue), typeof(int)),
            serviceProvider, logger);
 
        var nullableIntSubscription = new PersistentValueProviderComponentSubscription(
            state, componentState,
            CreateCascadingParameterInfo(nameof(ValueTypeTestComponent.NullableIntValue), typeof(int?)),
            serviceProvider, logger);
 
        // Act
        var intResult = intSubscription.GetOrComputeLastValue();
        var nullableIntResult = nullableIntSubscription.GetOrComputeLastValue();
 
        // Assert
        Assert.Equal(999, intResult);
        Assert.Equal(777, nullableIntResult);
 
        // Cleanup
        intSubscription.Dispose();
        nullableIntSubscription.Dispose();
    }
 
    private static CascadingParameterInfo CreateCascadingParameterInfo(string propertyName, Type propertyType)
    {
        return new CascadingParameterInfo(
            new PersistentStateAttribute(),
            propertyName,
            propertyType);
    }
 
    private static CascadingParameterInfo CreateCascadingParameterInfoWithBehavior(
        string propertyName,
        Type propertyType,
        RestoreBehavior restoreBehavior,
        bool allowUpdates = false)
    {
        return new CascadingParameterInfo(
            new PersistentStateAttribute
            {
                RestoreBehavior = restoreBehavior,
                AllowUpdates = allowUpdates
            },
            propertyName,
            propertyType);
    }
 
    private static ComponentState CreateComponentState(
        TestRenderer renderer,
        IComponent component,
        IComponent parentComponent,
        object key)
    {
        var parentComponentState = parentComponent != null
            ? new ComponentState(renderer, 1, parentComponent, null)
            : null;
        var componentState = new ComponentState(renderer, 2, component, parentComponentState);
 
        if (parentComponentState != null && parentComponentState.CurrentRenderTree != null && key != null)
        {
            var currentRenderTree = parentComponentState.CurrentRenderTree;
 
            // Open component based on the actual component type
            if (component is TestComponent)
            {
                currentRenderTree.OpenComponent<TestComponent>(0);
            }
            else if (component is ValueTypeTestComponent)
            {
                currentRenderTree.OpenComponent<ValueTypeTestComponent>(0);
            }
            else
            {
                currentRenderTree.OpenComponent<IComponent>(0);
            }
 
            var frames = currentRenderTree.GetFrames();
            frames.Array[frames.Count - 1].ComponentStateField = componentState;
            currentRenderTree.SetKey(key);
            currentRenderTree.CloseComponent();
        }
 
        return componentState;
    }
 
    private class TestRenderer(IServiceProvider serviceProvider) : Renderer(serviceProvider, NullLoggerFactory.Instance)
    {
        public TestRenderer() : this(new ServiceCollection().BuildServiceProvider()) { }
 
        public override Dispatcher Dispatcher => new TestDispatcher();
 
        protected override void HandleException(Exception exception) => ExceptionDispatchInfo.Capture(exception);
        protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) => throw new NotImplementedException();
    }
 
    private class TestDispatcher : Dispatcher
    {
        public override bool CheckAccess() => true;
        public override Task InvokeAsync(Action workItem)
        {
            workItem();
            return Task.CompletedTask;
        }
 
        public override Task InvokeAsync(Func<Task> workItem)
        {
            return workItem();
        }
        public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
        {
            return Task.FromResult(workItem());
        }
        public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
        {
            return workItem();
        }
    }
 
    private class TestComponent : IComponent
    {
        [PersistentState(AllowUpdates = true)]
        public string State { get; set; }
 
        public void Attach(RenderHandle renderHandle)
        {            
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            parameters.SetParameterProperties(this);
            return Task.CompletedTask;
        }
    }
 
    private class ValueTypeTestComponent : IComponent
    {
        [PersistentState]
        public int IntValue { get; set; }
 
        [PersistentState]
        public int? NullableIntValue { get; set; }
 
        [PersistentState]
        public (string, int) TupleValue { get; set; }
 
        [PersistentState]
        public (string, int)? NullableTupleValue { get; set; }
 
        public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
        public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
    }
 
    private class MultiplePropertiesComponent : IComponent
    {
        [PersistentState]
        public string StringValue { get; set; }
 
        [PersistentState]
        public int IntValue { get; set; }
 
        [PersistentState]
        public bool BoolValue { get; set; }
 
        public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
        public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
    }
 
    private class CustomSerializerTestComponent : IComponent
    {
        [PersistentState]
        public CustomData CustomValue { get; set; }
 
        public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
        public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
    }
 
    private class CustomData
    {
        public string Value { get; set; }
    }
 
    private class TestCustomDataSerializer : PersistentComponentStateSerializer<CustomData>
    {
        public override void Persist(CustomData value, IBufferWriter<byte> writer)
        {
            var json = JsonSerializer.SerializeToUtf8Bytes($"CUSTOM:{value.Value}");
            writer.Write(json);
        }
 
        public override CustomData Restore(ReadOnlySequence<byte> data)
        {
            var json = JsonSerializer.Deserialize<string>(data.ToArray());
            var value = json.StartsWith("CUSTOM:", StringComparison.Ordinal) ? json.Substring(7) : json;
            return new CustomData { Value = value };
        }
    }
 
    private class ParentComponent : IComponent
    {
        public void Attach(RenderHandle renderHandle) => throw new NotImplementedException();
        public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
    }
 
    private class TestStore(IDictionary<string, byte[]> state) : IPersistentComponentStateStore
    {
        public Task<IDictionary<string, byte[]>> GetPersistedStateAsync() => Task.FromResult(state);
        public Task PersistStateAsync(IReadOnlyDictionary<string, byte[]> state) => throw new NotImplementedException();
    }
}