mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
3168eb4870
* .NET: [BREAKING] Add session statebag to use for state storage instead of inside providers (#3737) * Add a StateBag to AgentSession and pass Agent and AgentSession to AIContextProvider and ChatHistoryProviders * Convert all AIContextProviders to use the statebag * Update InMemoryChatHistoryProvider to use StateBag * Update Comsos and Workflow ChatHistoryProviders * Update 3rd party chat history storage sample. * Remove serialize method from providers * Replacing provider factories with properties * Remove Providers from Session and flatten state bag serialization * Update samples to use getservice on agent * Updated additional session types to serialize statebag * Fix regression * Address PR comments * Address PR comments. * Fix formatting * Fix unit tests * Remove InMemoryAgentSession since it is not required anymore. * Address PR comments * Convert sessions for A2AAgent, ChatClientAgent, CopilotStudioAgent and GithubCopilotAgent to use regular json serialization. * Fix durable agent session jso usgae * Add jso to InMemory and Workflow ChatHistoryProviders * Update InMemoryChatHistoryProvider to use an options class for it's many optional settings. * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR feedback * Fix verification bug. * Improve state bag thread safety * Address PR comments and fix unit tests * Address PR comments * Fix unit test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add a public StateKey property to providers (#3810) * .NET: [BREAKING] Update providers in such a way that they can participate in a pipeline (#3846) * Make providers pipeline capable * Fix unit tests * Move source stamping to providers from base class * Also update samples. * Address PR comments * Rename AsAgentRequestMessageSourcedMessage to WithAgentRequestMessageSource * .NET: [BREAKING] Add consistent message filtering to all providers. (#3851) * Add consistent message filtering to all providers. * Remove old chat history filtering classes * Fix merge issues * Fix unit test * Enforce non-nullable property * Fix merging bug and make troubleshooting source info easier by adding tostring implementation * .NET: [BREAKING] Add support for multiple AIContextProviders on a ChatClientAgent (#3863) * Add support for multiple AIContextProviders on a ChatClientAgent * Address PR comments and fix tests * Address PR comments. * .NET: [BREAKING]Delay AIContext Materialization until the end of the pipeline is reached. (#3883) * Delay AIContext Materialization until the end of the pipeline is reached. * Address PR comments. * Address PR comments * Modify InvokedContext to be immutable (#3888) * .NET: Address Feedback on StateBag feature branch PR (#3910) * Address Feedback on statebag feature branch PR * Update dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR comments --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
841 lines
24 KiB
C#
841 lines
24 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Text.Json;
|
|
using Microsoft.Agents.AI.Abstractions.UnitTests.Models;
|
|
|
|
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Contains tests for the <see cref="AgentSessionStateBag"/> class.
|
|
/// </summary>
|
|
public sealed class AgentSessionStateBagTests
|
|
{
|
|
#region Constructor Tests
|
|
|
|
[Fact]
|
|
public void Constructor_Default_CreatesEmptyStateBag()
|
|
{
|
|
// Act
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Assert
|
|
Assert.False(stateBag.TryGetValue<string>("nonexistent", out _));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SetValue Tests
|
|
|
|
[Fact]
|
|
public void SetValue_WithValidKeyAndValue_StoresValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Assert
|
|
Assert.True(stateBag.TryGetValue<string>("key1", out var result));
|
|
Assert.Equal("value1", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_WithNullKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => stateBag.SetValue(null!, "value"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_WithEmptyKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.SetValue("", "value"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_WithWhitespaceKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.SetValue(" ", "value"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_OverwritesExistingValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "originalValue");
|
|
|
|
// Act
|
|
stateBag.SetValue("key1", "newValue");
|
|
|
|
// Assert
|
|
Assert.Equal("newValue", stateBag.GetValue<string>("key1"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetValue Tests
|
|
|
|
[Fact]
|
|
public void GetValue_WithExistingKey_ReturnsValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Act
|
|
var result = stateBag.GetValue<string>("key1");
|
|
|
|
// Assert
|
|
Assert.Equal("value1", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithNonexistentKey_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
var result = stateBag.GetValue<string>("nonexistent");
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithNullKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => stateBag.GetValue<string>(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithEmptyKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.GetValue<string>(""));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_CachesDeserializedValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Act
|
|
var result1 = stateBag.GetValue<string>("key1");
|
|
var result2 = stateBag.GetValue<string>("key1");
|
|
|
|
// Assert
|
|
Assert.Same(result1, result2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TryGetValue Tests
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithExistingKey_ReturnsTrueAndValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Act
|
|
var found = stateBag.TryGetValue<string>("key1", out var result);
|
|
|
|
// Assert
|
|
Assert.True(found);
|
|
Assert.Equal("value1", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithNonexistentKey_ReturnsFalseAndNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
var found = stateBag.TryGetValue<string>("nonexistent", out var result);
|
|
|
|
// Assert
|
|
Assert.False(found);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithNullKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => stateBag.TryGetValue<string>(null!, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithEmptyKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.TryGetValue<string>("", out _));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Null Value Tests
|
|
|
|
[Fact]
|
|
public void SetValue_WithNullValue_StoresNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
stateBag.SetValue<string>("key1", null);
|
|
|
|
// Assert
|
|
Assert.Equal(1, stateBag.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithNullValue_ReturnsTrueAndNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue<string>("key1", null);
|
|
|
|
// Act
|
|
var found = stateBag.TryGetValue<string>("key1", out var result);
|
|
|
|
// Assert
|
|
Assert.True(found);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithNullValue_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue<string>("key1", null);
|
|
|
|
// Act
|
|
var result = stateBag.GetValue<string>("key1");
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_OverwriteWithNull_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Act
|
|
stateBag.SetValue<string>("key1", null);
|
|
|
|
// Assert
|
|
Assert.True(stateBag.TryGetValue<string>("key1", out var result));
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetValue_OverwriteNullWithValue_ReturnsValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue<string>("key1", null);
|
|
|
|
// Act
|
|
stateBag.SetValue("key1", "newValue");
|
|
|
|
// Assert
|
|
Assert.True(stateBag.TryGetValue<string>("key1", out var result));
|
|
Assert.Equal("newValue", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void SerializeDeserialize_WithNullValue_SerializesAsNull()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue<string>("nullKey", null);
|
|
|
|
// Act
|
|
var json = stateBag.Serialize();
|
|
|
|
// Assert - null values are serialized as JSON null
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
Assert.True(json.TryGetProperty("nullKey", out var nullElement));
|
|
Assert.Equal(JsonValueKind.Null, nullElement.ValueKind);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TryRemoveValue Tests
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_ExistingKey_ReturnsTrueAndRemoves()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
|
|
// Act
|
|
var removed = stateBag.TryRemoveValue("key1");
|
|
|
|
// Assert
|
|
Assert.True(removed);
|
|
Assert.Equal(0, stateBag.Count);
|
|
Assert.False(stateBag.TryGetValue<string>("key1", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_NonexistentKey_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
var removed = stateBag.TryRemoveValue("nonexistent");
|
|
|
|
// Assert
|
|
Assert.False(removed);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_WithNullKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() => stateBag.TryRemoveValue(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_WithEmptyKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.TryRemoveValue(""));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_WithWhitespaceKey_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() => stateBag.TryRemoveValue(" "));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_DoesNotAffectOtherKeys()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "value1");
|
|
stateBag.SetValue("key2", "value2");
|
|
|
|
// Act
|
|
stateBag.TryRemoveValue("key1");
|
|
|
|
// Assert
|
|
Assert.Equal(1, stateBag.Count);
|
|
Assert.False(stateBag.TryGetValue<string>("key1", out _));
|
|
Assert.True(stateBag.TryGetValue<string>("key2", out var value));
|
|
Assert.Equal("value2", value);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryRemoveValue_ThenSetValue_Works()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "original");
|
|
|
|
// Act
|
|
stateBag.TryRemoveValue("key1");
|
|
stateBag.SetValue("key1", "replacement");
|
|
|
|
// Assert
|
|
Assert.True(stateBag.TryGetValue<string>("key1", out var result));
|
|
Assert.Equal("replacement", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Serialize/Deserialize Tests
|
|
|
|
[Fact]
|
|
public void Serialize_EmptyStateBag_ReturnsEmptyObject()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
var json = stateBag.Serialize();
|
|
|
|
// Assert
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
}
|
|
|
|
[Fact]
|
|
public void Serialize_WithStringValue_ReturnsJsonWithValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("stringKey", "stringValue");
|
|
|
|
// Act
|
|
var json = stateBag.Serialize();
|
|
|
|
// Assert
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
Assert.True(json.TryGetProperty("stringKey", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void Deserialize_FromJsonDocument_ReturnsEmptyStateBag()
|
|
{
|
|
// Arrange
|
|
var emptyJson = JsonDocument.Parse("{}").RootElement;
|
|
|
|
// Act
|
|
var stateBag = AgentSessionStateBag.Deserialize(emptyJson);
|
|
|
|
// Assert
|
|
Assert.False(stateBag.TryGetValue<string>("nonexistent", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void Deserialize_NullElement_ReturnsEmptyStateBag()
|
|
{
|
|
// Arrange
|
|
var nullJson = default(JsonElement);
|
|
|
|
// Act
|
|
var stateBag = AgentSessionStateBag.Deserialize(nullJson);
|
|
|
|
// Assert
|
|
Assert.False(stateBag.TryGetValue<string>("nonexistent", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void SerializeDeserialize_WithStringValue_Roundtrips()
|
|
{
|
|
// Arrange
|
|
var originalStateBag = new AgentSessionStateBag();
|
|
originalStateBag.SetValue("stringKey", "stringValue");
|
|
|
|
// Act
|
|
var json = originalStateBag.Serialize();
|
|
var restoredStateBag = AgentSessionStateBag.Deserialize(json);
|
|
|
|
// Assert
|
|
Assert.Equal("stringValue", restoredStateBag.GetValue<string>("stringKey"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Thread Safety Tests
|
|
|
|
[Fact]
|
|
public async System.Threading.Tasks.Task SetValue_MultipleConcurrentWrites_DoesNotThrowAsync()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var tasks = new System.Threading.Tasks.Task[100];
|
|
|
|
// Act
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
int index = i;
|
|
tasks[i] = System.Threading.Tasks.Task.Run(() => stateBag.SetValue($"key{index}", $"value{index}"));
|
|
}
|
|
|
|
await System.Threading.Tasks.Task.WhenAll(tasks);
|
|
|
|
// Assert
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
Assert.True(stateBag.TryGetValue<string>($"key{i}", out var value));
|
|
Assert.Equal($"value{i}", value);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async System.Threading.Tasks.Task ConcurrentWritesAndSerialize_DoesNotThrowAsync()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("shared", "initial");
|
|
var tasks = new System.Threading.Tasks.Task[100];
|
|
|
|
// Act - concurrently write and serialize the same key
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
int index = i;
|
|
tasks[i] = System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
stateBag.SetValue("shared", $"value{index}");
|
|
_ = stateBag.Serialize();
|
|
});
|
|
}
|
|
|
|
await System.Threading.Tasks.Task.WhenAll(tasks);
|
|
|
|
// Assert - should have some value and serialize without error
|
|
Assert.True(stateBag.TryGetValue<string>("shared", out var result));
|
|
Assert.NotNull(result);
|
|
var json = stateBag.Serialize();
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
}
|
|
|
|
[Fact]
|
|
public async System.Threading.Tasks.Task ConcurrentReadsAndWrites_DoesNotThrowAsync()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key", "initial");
|
|
var tasks = new System.Threading.Tasks.Task[200];
|
|
|
|
// Act - half readers, half writers on the same key
|
|
for (int i = 0; i < 200; i++)
|
|
{
|
|
int index = i;
|
|
tasks[i] = (index % 2 == 0)
|
|
? System.Threading.Tasks.Task.Run(() => stateBag.GetValue<string>("key"))
|
|
: System.Threading.Tasks.Task.Run(() => stateBag.SetValue("key", $"value{index}"));
|
|
}
|
|
|
|
await System.Threading.Tasks.Task.WhenAll(tasks);
|
|
|
|
// Assert - should have a consistent value
|
|
Assert.True(stateBag.TryGetValue<string>("key", out var result));
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Complex Object Tests
|
|
|
|
[Fact]
|
|
public void SetValue_WithComplexObject_StoresValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 1, FullName = "Buddy", Species = Species.Bear };
|
|
|
|
// Act
|
|
stateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Animal? result = stateBag.GetValue<Animal>("animal", TestJsonSerializerContext.Default.Options);
|
|
Assert.NotNull(result);
|
|
Assert.Equal(1, result.Id);
|
|
Assert.Equal("Buddy", result.FullName);
|
|
Assert.Equal(Species.Bear, result.Species);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithComplexObject_CachesDeserializedValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 2, FullName = "Whiskers", Species = Species.Tiger };
|
|
stateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Act
|
|
Animal? result1 = stateBag.GetValue<Animal>("animal", TestJsonSerializerContext.Default.Options);
|
|
Animal? result2 = stateBag.GetValue<Animal>("animal", TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Assert.Same(result1, result2);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithComplexObject_ReturnsTrueAndValue()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 3, FullName = "Goldie", Species = Species.Walrus };
|
|
stateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Act
|
|
bool found = stateBag.TryGetValue("animal", out Animal? result, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Assert.True(found);
|
|
Assert.NotNull(result);
|
|
Assert.Equal(3, result.Id);
|
|
Assert.Equal("Goldie", result.FullName);
|
|
Assert.Equal(Species.Walrus, result.Species);
|
|
}
|
|
|
|
[Fact]
|
|
public void SerializeDeserialize_WithComplexObject_Roundtrips()
|
|
{
|
|
// Arrange
|
|
var originalStateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 4, FullName = "Polly", Species = Species.Bear };
|
|
originalStateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Act
|
|
JsonElement json = originalStateBag.Serialize();
|
|
AgentSessionStateBag restoredStateBag = AgentSessionStateBag.Deserialize(json);
|
|
|
|
// Assert
|
|
Animal? restoredAnimal = restoredStateBag.GetValue<Animal>("animal", TestJsonSerializerContext.Default.Options);
|
|
Assert.NotNull(restoredAnimal);
|
|
Assert.Equal(4, restoredAnimal.Id);
|
|
Assert.Equal("Polly", restoredAnimal.FullName);
|
|
Assert.Equal(Species.Bear, restoredAnimal.Species);
|
|
}
|
|
|
|
[Fact]
|
|
public void Serialize_WithComplexObject_ReturnsJsonWithProperties()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 7, FullName = "Spot", Species = Species.Walrus };
|
|
stateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Act
|
|
JsonElement json = stateBag.Serialize();
|
|
|
|
// Assert
|
|
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
|
Assert.True(json.TryGetProperty("animal", out JsonElement animalElement));
|
|
Assert.Equal(JsonValueKind.Object, animalElement.ValueKind);
|
|
Assert.Equal(7, animalElement.GetProperty("id").GetInt32());
|
|
Assert.Equal("Spot", animalElement.GetProperty("fullName").GetString());
|
|
Assert.Equal("Walrus", animalElement.GetProperty("species").GetString());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Type Mismatch Tests
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithDifferentTypeAfterSet_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "hello");
|
|
|
|
// Act
|
|
var found = stateBag.TryGetValue<Animal>("key1", out var result, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Assert.False(found);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithDifferentTypeAfterSet_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "hello");
|
|
|
|
// Act & Assert
|
|
Assert.Throws<InvalidOperationException>(() => stateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_WithDifferentTypeAfterDeserializedRead_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "hello");
|
|
|
|
// First read caches the value as string
|
|
var cachedValue = stateBag.GetValue<string>("key1");
|
|
Assert.Equal("hello", cachedValue);
|
|
|
|
// Act - request as a different type
|
|
var found = stateBag.TryGetValue<Animal>("key1", out var result, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Assert.False(found);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_WithDifferentTypeAfterDeserializedRoundtrip_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
var originalStateBag = new AgentSessionStateBag();
|
|
originalStateBag.SetValue("key1", "hello");
|
|
|
|
// Round-trip through serialization
|
|
var json = originalStateBag.Serialize();
|
|
var restoredStateBag = AgentSessionStateBag.Deserialize(json);
|
|
|
|
// First read caches the value as string
|
|
var cachedValue = restoredStateBag.GetValue<string>("key1");
|
|
Assert.Equal("hello", cachedValue);
|
|
|
|
// Act & Assert - request as a different type
|
|
Assert.Throws<InvalidOperationException>(() => restoredStateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetValue_ComplexTypeAfterSetString_ReturnsFalse()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("animal", "not an animal");
|
|
|
|
// Act
|
|
var found = stateBag.TryGetValue<Animal>("animal", out var result, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Assert
|
|
Assert.False(found);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetValue_TypeMismatch_ExceptionMessageContainsBothTypeNames()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key1", "hello");
|
|
|
|
// Act
|
|
var exception = Assert.Throws<InvalidOperationException>(() => stateBag.GetValue<Animal>("key1", TestJsonSerializerContext.Default.Options));
|
|
|
|
// Assert
|
|
Assert.Contains(typeof(string).FullName!, exception.Message);
|
|
Assert.Contains(typeof(Animal).FullName!, exception.Message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region JsonSerializer Integration Tests
|
|
|
|
[Fact]
|
|
public void JsonSerializerSerialize_EmptyStateBag_ReturnsEmptyObject()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
|
|
// Act
|
|
var json = JsonSerializer.Serialize(stateBag, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
|
|
// Assert
|
|
Assert.Equal("{}", json);
|
|
}
|
|
|
|
[Fact]
|
|
public void JsonSerializerSerialize_WithStringValue_ProducesSameOutputAsSerializeMethod()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("stringKey", "stringValue");
|
|
|
|
// Act
|
|
var jsonFromSerializer = JsonSerializer.Serialize(stateBag, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
var jsonFromMethod = stateBag.Serialize().GetRawText();
|
|
|
|
// Assert
|
|
Assert.Equal(jsonFromMethod, jsonFromSerializer);
|
|
}
|
|
|
|
[Fact]
|
|
public void JsonSerializerRoundtrip_WithStringValue_PreservesData()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("greeting", "hello world");
|
|
|
|
// Act
|
|
var json = JsonSerializer.Serialize(stateBag, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
var restored = JsonSerializer.Deserialize<AgentSessionStateBag>(json, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
|
|
// Assert
|
|
Assert.NotNull(restored);
|
|
Assert.Equal("hello world", restored!.GetValue<string>("greeting"));
|
|
}
|
|
|
|
[Fact]
|
|
public void JsonSerializerRoundtrip_WithComplexObject_PreservesData()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
var animal = new Animal { Id = 10, FullName = "Rex", Species = Species.Tiger };
|
|
stateBag.SetValue("animal", animal, TestJsonSerializerContext.Default.Options);
|
|
|
|
// Act
|
|
var json = JsonSerializer.Serialize(stateBag, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
var restored = JsonSerializer.Deserialize<AgentSessionStateBag>(json, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
|
|
// Assert
|
|
Assert.NotNull(restored);
|
|
var restoredAnimal = restored!.GetValue<Animal>("animal", TestJsonSerializerContext.Default.Options);
|
|
Assert.NotNull(restoredAnimal);
|
|
Assert.Equal(10, restoredAnimal!.Id);
|
|
Assert.Equal("Rex", restoredAnimal.FullName);
|
|
Assert.Equal(Species.Tiger, restoredAnimal.Species);
|
|
}
|
|
|
|
[Fact]
|
|
public void JsonSerializerDeserialize_NullJson_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
const string Json = "null";
|
|
|
|
// Act
|
|
var stateBag = JsonSerializer.Deserialize<AgentSessionStateBag>(Json, AgentAbstractionsJsonUtilities.DefaultOptions);
|
|
|
|
// Assert
|
|
Assert.Null(stateBag);
|
|
}
|
|
|
|
#if NET10_0_OR_GREATER
|
|
[Fact]
|
|
public void JsonSerializerSerialize_WithUnknownType_Throws()
|
|
{
|
|
// Arrange
|
|
var stateBag = new AgentSessionStateBag();
|
|
stateBag.SetValue("key", new { Name = "Test" }); // Anonymous type which cannot be deserialized
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(stateBag, AgentAbstractionsJsonUtilities.DefaultOptions));
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
}
|