using ModelContextProtocol.Protocol; using System.Text.Json; namespace ModelContextProtocol.Tests.Protocol; public static class ResourceContentsTests { [Fact] public static void TextResourceContents_UnknownArrayProperty_IsIgnored() { // This test verifies that the ResourceContents.Converter properly skips unknown properties // even when they contain complex structures like arrays or objects. // // In this unexpected JSON, "unknownArray" appears inside text resource contents (where it doesn't belong). // The converter should gracefully ignore this unknown property and successfully deserialize // the rest of the resource contents. const string jsonWithUnknownArray = """ { "uri": "file:///test.txt", "mimeType": "text/plain", "text": "Test content", "unknownArray": [ { "nested": "value" }, { "another": "object" } ] } """; var result = JsonSerializer.Deserialize( jsonWithUnknownArray, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("file:///test.txt", textResource.Uri); Assert.Equal("text/plain", textResource.MimeType); Assert.Equal("Test content", textResource.Text); } [Fact] public static void BlobResourceContents_UnknownObjectProperty_IsIgnored() { // Test that unknown properties with nested objects are properly skipped const string jsonWithUnknownObject = """ { "uri": "file:///test.bin", "mimeType": "application/octet-stream", "blob": "AQIDBA==", "unknownObject": { "deeply": { "nested": { "value": "should be ignored" } } } } """; var result = JsonSerializer.Deserialize( jsonWithUnknownObject, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("file:///test.bin", blobResource.Uri); Assert.Equal("application/octet-stream", blobResource.MimeType); Assert.Equal("AQIDBA==", blobResource.Blob); } [Fact] public static void TextResourceContents_UnknownMixedProperties_AreIgnored() { // Test multiple unknown properties with different types const string jsonWithMixedUnknown = """ { "uri": "test://resource", "text": "content", "unknownString": "value", "unknownNumber": 42, "unknownArray": [1, 2, 3], "unknownObject": {"key": "value"}, "unknownBool": true, "unknownNull": null } """; var result = JsonSerializer.Deserialize( jsonWithMixedUnknown, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("test://resource", textResource.Uri); Assert.Equal("content", textResource.Text); } [Fact] public static void BlobResourceContents_UnknownNestedArrays_AreIgnored() { // Test complex unknown properties with arrays of objects const string jsonWithNestedArrays = """ { "uri": "blob://test", "blob": "SGVsbG8=", "mimeType": "application/custom", "unknownComplex": [ { "nested": [ {"deep": "value1"}, {"deep": "value2"} ] }, { "nested": [ {"deep": "value3"} ] } ] } """; var result = JsonSerializer.Deserialize( jsonWithNestedArrays, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("blob://test", blobResource.Uri); Assert.Equal("SGVsbG8=", blobResource.Blob); Assert.Equal("application/custom", blobResource.MimeType); } [Fact] public static void TextResourceContents_MultipleUnknownProperties_AllIgnored() { // Test that multiple unknown properties are all properly skipped const string jsonWithMultipleUnknown = """ { "uri": "file:///test", "unknownOne": {"a": 1}, "text": "Test text", "unknownTwo": [1, 2, 3], "mimeType": "text/plain", "unknownThree": {"b": {"c": "d"}} } """; var result = JsonSerializer.Deserialize( jsonWithMultipleUnknown, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("file:///test", textResource.Uri); Assert.Equal("Test text", textResource.Text); Assert.Equal("text/plain", textResource.MimeType); } [Fact] public static void BlobResourceContents_UnknownArrayOfArrays_IsIgnored() { // Test deeply nested array structures in unknown properties const string jsonWithArrayOfArrays = """ { "uri": "http://example.com/blob", "blob": "Zm9v", "unknownNested": [ [ [1, 2, 3], [4, 5, 6] ], [ [7, 8, 9] ] ] } """; var result = JsonSerializer.Deserialize( jsonWithArrayOfArrays, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("http://example.com/blob", blobResource.Uri); Assert.Equal("Zm9v", blobResource.Blob); } [Fact] public static void TextResourceContents_EmptyUnknownArray_IsIgnored() { // Test empty arrays in unknown properties const string jsonWithEmptyArray = """ { "uri": "test://text", "text": "content", "unknownEmpty": [] } """; var result = JsonSerializer.Deserialize( jsonWithEmptyArray, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("test://text", textResource.Uri); Assert.Equal("content", textResource.Text); } [Fact] public static void BlobResourceContents_EmptyUnknownObject_IsIgnored() { // Test empty objects in unknown properties const string jsonWithEmptyObject = """ { "uri": "test://blob", "blob": "YmFy", "unknownEmpty": {} } """; var result = JsonSerializer.Deserialize( jsonWithEmptyObject, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("test://blob", blobResource.Uri); Assert.Equal("YmFy", blobResource.Blob); } [Fact] public static void TextResourceContents_UnknownPropertiesBetweenRequired_AreIgnored() { // Test unknown properties interspersed with required ones const string jsonWithInterspersedUnknown = """ { "unknownFirst": {"x": 1}, "uri": "file:///document.txt", "unknownSecond": [1, 2], "text": "Document content", "unknownThird": {"nested": {"value": true}}, "mimeType": "text/plain" } """; var result = JsonSerializer.Deserialize( jsonWithInterspersedUnknown, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("file:///document.txt", textResource.Uri); Assert.Equal("Document content", textResource.Text); Assert.Equal("text/plain", textResource.MimeType); } [Fact] public static void BlobResourceContents_VeryDeeplyNestedUnknown_IsIgnored() { // Test very deeply nested structures in unknown properties const string jsonWithVeryDeepNesting = """ { "uri": "deep://blob", "blob": "ZGVlcA==", "unknownDeep": { "level1": { "level2": { "level3": { "level4": { "level5": { "value": "deep" } } } } } } } """; var result = JsonSerializer.Deserialize( jsonWithVeryDeepNesting, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("deep://blob", blobResource.Uri); Assert.Equal("ZGVlcA==", blobResource.Blob); } [Fact] public static void TextResourceContents_WithMeta_UnknownPropertiesIgnored() { // Test that _meta property works correctly alongside unknown properties const string jsonWithMetaAndUnknown = """ { "uri": "test://meta", "text": "content with meta", "unknownProp": {"data": "ignored"}, "_meta": { "customField": "metaValue" } } """; var result = JsonSerializer.Deserialize( jsonWithMetaAndUnknown, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal("test://meta", textResource.Uri); Assert.Equal("content with meta", textResource.Text); Assert.NotNull(textResource.Meta); Assert.True(textResource.Meta.ContainsKey("customField")); } [Fact] public static void TextResourceContents_SerializationRoundTrip_PreservesKnownProperties() { // Test that serialization/deserialization preserves known properties var original = new TextResourceContents { Uri = "file:///test.txt", MimeType = "text/plain", Text = "Test content" }; var json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); Assert.NotNull(deserialized); var textResource = Assert.IsType(deserialized); Assert.Equal(original.Uri, textResource.Uri); Assert.Equal(original.MimeType, textResource.MimeType); Assert.Equal(original.Text, textResource.Text); } [Fact] public static void BlobResourceContents_SerializationRoundTrip_PreservesKnownProperties() { // Test that serialization/deserialization preserves known properties var original = new BlobResourceContents { Uri = "file:///test.bin", MimeType = "application/octet-stream", Blob = "AQIDBA==" }; var json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions); var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); Assert.NotNull(deserialized); var blobResource = Assert.IsType(deserialized); Assert.Equal(original.Uri, blobResource.Uri); Assert.Equal(original.MimeType, blobResource.MimeType); Assert.Equal(original.Blob, blobResource.Blob); } [Fact] public static void ResourceContents_MissingBothTextAndBlob_ReturnsNull() { // Test that missing both text and blob properties returns null const string jsonWithoutContent = """ { "uri": "test://empty", "mimeType": "application/unknown" } """; var result = JsonSerializer.Deserialize( jsonWithoutContent, McpJsonUtilities.DefaultOptions); Assert.Null(result); } [Fact] public static void ResourceContents_WithBothTextAndBlob_PrefersBlob() { // Test that when both text and blob are present, blob takes precedence const string jsonWithBoth = """ { "uri": "test://both", "text": "text content", "blob": "YmxvYg==" } """; var result = JsonSerializer.Deserialize( jsonWithBoth, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal("test://both", blobResource.Uri); Assert.Equal("YmxvYg==", blobResource.Blob); } [Fact] public static void TextResourceContents_MissingUri_UsesEmptyString() { // Test that missing uri defaults to empty string const string jsonWithoutUri = """ { "text": "content without uri" } """; var result = JsonSerializer.Deserialize( jsonWithoutUri, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var textResource = Assert.IsType(result); Assert.Equal(string.Empty, textResource.Uri); Assert.Equal("content without uri", textResource.Text); } [Fact] public static void BlobResourceContents_MissingUri_UsesEmptyString() { // Test that missing uri defaults to empty string const string jsonWithoutUri = """ { "blob": "YmxvYg==" } """; var result = JsonSerializer.Deserialize( jsonWithoutUri, McpJsonUtilities.DefaultOptions); Assert.NotNull(result); var blobResource = Assert.IsType(result); Assert.Equal(string.Empty, blobResource.Uri); Assert.Equal("YmxvYg==", blobResource.Blob); } }