Update to .NET 10.x packages (#957)
This commit is contained in:
parent
d4ee78797b
commit
b993ae0e61
1 changed files with 298 additions and 174 deletions
|
|
@ -0,0 +1,258 @@
|
|||
using System.Text.Json;
|
||||
using ModelContextProtocol.Protocol;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Tests that custom JSON converters properly handle unknown additional properties
|
||||
/// for forward compatibility with future protocol versions.
|
||||
/// </summary>
|
||||
public class UnknownPropertiesTests
|
||||
{
|
||||
[Fact]
|
||||
public void ContentBlock_DeserializationWithUnknownProperty_SkipsProperty()
|
||||
{
|
||||
// Arrange - JSON with unknown "unknownField" property
|
||||
const string Json = """
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Hello, world!",
|
||||
"unknownField": "should be skipped"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(deserialized);
|
||||
Assert.Equal("Hello, world!", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentBlock_DeserializationWithStructuredContentInContent_SkipsProperty()
|
||||
{
|
||||
// Arrange - This was the actual bug case: structuredContent incorrectly placed
|
||||
// inside a ContentBlock instead of at CallToolResult level
|
||||
const string Json = """
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Result text",
|
||||
"structuredContent": {
|
||||
"data": "this should be ignored"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Act - Should not throw
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(deserialized);
|
||||
Assert.Equal("Result text", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentBlock_DeserializationWithMultipleUnknownProperties_SkipsAll()
|
||||
{
|
||||
// Arrange - JSON with multiple unknown properties
|
||||
const string Json = """
|
||||
{
|
||||
"type": "image",
|
||||
"data": "base64data",
|
||||
"mimeType": "image/png",
|
||||
"futureProperty1": "value1",
|
||||
"futureProperty2": {"nested": "object"},
|
||||
"futureProperty3": [1, 2, 3]
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var imageBlock = Assert.IsType<ImageContentBlock>(deserialized);
|
||||
Assert.Equal("base64data", imageBlock.Data);
|
||||
Assert.Equal("image/png", imageBlock.MimeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reference_DeserializationWithUnknownProperty_SkipsProperty()
|
||||
{
|
||||
// Arrange - JSON with unknown "metadata" property
|
||||
const string Json = """
|
||||
{
|
||||
"type": "ref/prompt",
|
||||
"name": "test-prompt",
|
||||
"metadata": {
|
||||
"version": "2.0",
|
||||
"author": "future"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<Reference>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var promptRef = Assert.IsType<PromptReference>(deserialized);
|
||||
Assert.Equal("test-prompt", promptRef.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reference_DeserializationWithMultipleUnknownProperties_SkipsAll()
|
||||
{
|
||||
// Arrange
|
||||
const string Json = """
|
||||
{
|
||||
"type": "ref/resource",
|
||||
"uri": "file:///test.txt",
|
||||
"extraField1": "ignored",
|
||||
"extraField2": 123,
|
||||
"extraField3": true
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<Reference>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var resourceRef = Assert.IsType<ResourceTemplateReference>(deserialized);
|
||||
Assert.Equal("file:///test.txt", resourceRef.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourceContents_DeserializationWithUnknownProperty_SkipsProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string Json = """
|
||||
{
|
||||
"uri": "file:///document.txt",
|
||||
"text": "File contents here",
|
||||
"mimeType": "text/plain",
|
||||
"futureEncoding": "utf-16",
|
||||
"futureChecksum": "abc123"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ResourceContents>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var textResource = Assert.IsType<TextResourceContents>(deserialized);
|
||||
Assert.Equal("file:///document.txt", textResource.Uri);
|
||||
Assert.Equal("File contents here", textResource.Text);
|
||||
Assert.Equal("text/plain", textResource.MimeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProgressNotificationParams_DeserializationWithUnknownProperty_SkipsProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string Json = """
|
||||
{
|
||||
"progressToken": "token123",
|
||||
"progress": 50,
|
||||
"newProgressFormat": "percentage",
|
||||
"estimatedTimeRemaining": 30
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ProgressNotificationParams>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal("token123", deserialized.ProgressToken.ToString());
|
||||
Assert.Equal(50.0f, deserialized.Progress.Progress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrimitiveSchemaDefinition_DeserializationWithUnknownProperty_SkipsProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string Json = """
|
||||
{
|
||||
"type": "string",
|
||||
"description": "A test string",
|
||||
"futureValidation": "regex:.*",
|
||||
"futureMaxLength": 100
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(deserialized);
|
||||
Assert.Equal("A test string", stringSchema.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CallToolResult_WithContentBlockContainingUnknownProperties_Succeeds()
|
||||
{
|
||||
// Arrange - Simulates the real-world bug scenario: a malformed response where
|
||||
// structuredContent was incorrectly nested inside content blocks
|
||||
const string Json = """
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Tool executed successfully",
|
||||
"structuredContent": {
|
||||
"result": "this was incorrectly placed here"
|
||||
}
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
""";
|
||||
|
||||
// Act - Should not throw an exception
|
||||
var deserialized = JsonSerializer.Deserialize<CallToolResult>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Single(deserialized.Content);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("Tool executed successfully", textBlock.Text);
|
||||
Assert.False(deserialized.IsError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CallToolResult_WithStructuredContentAtCorrectLevel_PreservesProperty()
|
||||
{
|
||||
// Arrange - Correct placement of structuredContent at CallToolResult level
|
||||
const string Json = """
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Tool executed"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"result": "correctly placed here",
|
||||
"value": 42
|
||||
},
|
||||
"isError": false
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<CallToolResult>(Json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.NotNull(deserialized.StructuredContent);
|
||||
Assert.Equal("correctly placed here", deserialized.StructuredContent["result"]?.ToString());
|
||||
Assert.Equal(42, (int?)deserialized.StructuredContent["value"]);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue