using ModelContextProtocol.Protocol; using System.Text.Json; namespace ModelContextProtocol.Tests.Protocol; public class ContentBlockTests { [Fact] public void ResourceLinkBlock_SerializationRoundTrip_PreservesAllProperties() { // Arrange var original = new ResourceLinkBlock { Uri = "https://example.com/resource", Name = "Test Resource", Description = "A test resource for validation", MimeType = "text/plain", Size = 1024 }; // Act - Serialize to JSON string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions); // Act - Deserialize back from JSON var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); // Assert Assert.NotNull(deserialized); var resourceLink = Assert.IsType(deserialized); Assert.Equal(original.Uri, resourceLink.Uri); Assert.Equal(original.Name, resourceLink.Name); Assert.Equal(original.Description, resourceLink.Description); Assert.Equal(original.MimeType, resourceLink.MimeType); Assert.Equal(original.Size, resourceLink.Size); Assert.Equal("resource_link", resourceLink.Type); } [Fact] public void ResourceLinkBlock_DeserializationWithMinimalProperties_Succeeds() { // Arrange - JSON with only required properties const string Json = """ { "type": "resource_link", "uri": "https://example.com/minimal", "name": "Minimal Resource" } """; // Act var deserialized = JsonSerializer.Deserialize(Json, McpJsonUtilities.DefaultOptions); // Assert Assert.NotNull(deserialized); var resourceLink = Assert.IsType(deserialized); Assert.Equal("https://example.com/minimal", resourceLink.Uri); Assert.Equal("Minimal Resource", resourceLink.Name); Assert.Null(resourceLink.Description); Assert.Null(resourceLink.MimeType); Assert.Null(resourceLink.Size); Assert.Equal("resource_link", resourceLink.Type); } [Fact] public void ResourceLinkBlock_DeserializationWithoutName_ThrowsJsonException() { // Arrange - JSON missing the required "name" property const string Json = """ { "type": "resource_link", "uri": "https://example.com/missing-name" } """; // Act & Assert var exception = Assert.Throws(() => JsonSerializer.Deserialize(Json, McpJsonUtilities.DefaultOptions)); Assert.Contains("Name must be provided for 'resource_link' type", exception.Message); } [Fact] public void Deserialize_IgnoresUnknownArrayProperty() { // This is a regression test where a server returned an unexpected response with // `structuredContent` as an array nested inside a content block. This should be // permitted with the `structuredContent` gracefully ignored in that location. string responseJson = @"{ ""type"": ""text"", ""text"": ""[\n {\n \""Data\"": \""1234567890\""\n }\n]"", ""structuredContent"": [ { ""Data"": ""1234567890"" } ] }"; var contentBlock = JsonSerializer.Deserialize(responseJson, McpJsonUtilities.DefaultOptions); Assert.NotNull(contentBlock); var textBlock = Assert.IsType(contentBlock); Assert.Contains("1234567890", textBlock.Text); } [Fact] public void Deserialize_IgnoresUnknownObjectProperties() { string responseJson = @"{ ""type"": ""text"", ""text"": ""Sample text"", ""unknownObject"": { ""nestedProp1"": ""value1"", ""nestedProp2"": { ""deeplyNested"": true } } }"; var contentBlock = JsonSerializer.Deserialize(responseJson, McpJsonUtilities.DefaultOptions); Assert.NotNull(contentBlock); var textBlock = Assert.IsType(contentBlock); Assert.Contains("Sample text", textBlock.Text); } [Fact] public void ToolResultContentBlock_WithError_SerializationRoundtrips() { ToolResultContentBlock toolResult = new() { ToolUseId = "call_123", Content = [new TextContentBlock { Text = "Error: City not found" }], IsError = true }; var json = JsonSerializer.Serialize(toolResult, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); var result = Assert.IsType(deserialized); Assert.Equal("call_123", result.ToolUseId); Assert.True(result.IsError); Assert.Single(result.Content); var textBlock = Assert.IsType(result.Content[0]); Assert.Equal("Error: City not found", textBlock.Text); } [Fact] public void ToolResultContentBlock_WithStructuredContent_SerializationRoundtrips() { ToolResultContentBlock toolResult = new() { ToolUseId = "call_123", Content = [ new TextContentBlock { Text = "Result data" } ], StructuredContent = JsonElement.Parse("""{"temperature":18,"condition":"cloudy"}"""), IsError = false }; var json = JsonSerializer.Serialize(toolResult, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); var result = Assert.IsType(deserialized); Assert.Equal("call_123", result.ToolUseId); Assert.Single(result.Content); var textBlock = Assert.IsType(result.Content[0]); Assert.Equal("Result data", textBlock.Text); Assert.NotNull(result.StructuredContent); Assert.Equal(18, result.StructuredContent.Value.GetProperty("temperature").GetInt32()); Assert.Equal("cloudy", result.StructuredContent.Value.GetProperty("condition").GetString()); Assert.False(result.IsError); } [Fact] public void ToolResultContentBlock_SerializationRoundTrip() { ToolResultContentBlock toolResult = new() { ToolUseId = "call_123", Content = [ new TextContentBlock { Text = "Result data" }, new ImageContentBlock { Data = "base64data", MimeType = "image/png" } ], StructuredContent = JsonElement.Parse("""{"temperature":18,"condition":"cloudy"}"""), IsError = false }; var json = JsonSerializer.Serialize(toolResult, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); var result = Assert.IsType(deserialized); Assert.Equal("call_123", result.ToolUseId); Assert.Equal(2, result.Content.Count); var textBlock = Assert.IsType(result.Content[0]); Assert.Equal("Result data", textBlock.Text); var imageBlock = Assert.IsType(result.Content[1]); Assert.Equal("base64data", imageBlock.Data); Assert.Equal("image/png", imageBlock.MimeType); Assert.NotNull(result.StructuredContent); Assert.Equal(18, result.StructuredContent.Value.GetProperty("temperature").GetInt32()); Assert.Equal("cloudy", result.StructuredContent.Value.GetProperty("condition").GetString()); Assert.False(result.IsError); } [Fact] public void ToolUseContentBlock_SerializationRoundTrip() { ToolUseContentBlock toolUse = new() { Id = "call_abc123", Name = "get_weather", Input = JsonElement.Parse("""{"city":"Paris","units":"metric"}""") }; var json = JsonSerializer.Serialize(toolUse, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); var result = Assert.IsType(deserialized); Assert.Equal("call_abc123", result.Id); Assert.Equal("get_weather", result.Name); Assert.Equal("Paris", result.Input.GetProperty("city").GetString()); Assert.Equal("metric", result.Input.GetProperty("units").GetString()); } }