213 lines
6.9 KiB
C#
213 lines
6.9 KiB
C#
using ModelContextProtocol.Protocol;
|
|
|
|
namespace ModelContextProtocol.Tests.Client;
|
|
|
|
public class McpHeaderEncoderTests
|
|
{
|
|
[Theory]
|
|
[InlineData("us-west1", "us-west1")]
|
|
[InlineData("hello-world", "hello-world")]
|
|
[InlineData("my_tool_name", "my_tool_name")]
|
|
[InlineData("us west 1", "us west 1")]
|
|
[InlineData("", "")]
|
|
public void EncodeValue_PlainAscii_PassesThrough(string input, string expected)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(" us-west1", "=?base64?IHVzLXdlc3Qx?=")]
|
|
[InlineData("us-west1 ", "=?base64?dXMtd2VzdDEg?=")]
|
|
[InlineData(" us-west1 ", "=?base64?IHVzLXdlc3QxIA==?=")]
|
|
[InlineData("\tindented", "=?base64?CWluZGVudGVk?=")]
|
|
public void EncodeValue_LeadingTrailingWhitespace_Base64Encodes(string input, string expected)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_NonAsciiCharacters_Base64Encodes()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue("日本語");
|
|
Assert.Equal("=?base64?5pel5pys6Kqe?=", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_NewlineCharacter_Base64Encodes()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue("line1\nline2");
|
|
Assert.Equal("=?base64?bGluZTEKbGluZTI=?=", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_CarriageReturnNewline_Base64Encodes()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue("line1\r\nline2");
|
|
Assert.Equal("=?base64?bGluZTENCmxpbmUy?=", result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true, "true")]
|
|
[InlineData(false, "false")]
|
|
public void EncodeValue_Boolean_ConvertsToLowercase(bool input, string expected)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(42, "42")]
|
|
[InlineData(0, "0")]
|
|
[InlineData(-1, "-1")]
|
|
public void EncodeValue_Integer_ConvertsToString(object input, string expected)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_Null_ReturnsNull()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(null);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_UnsupportedType_ReturnsNull()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(new object());
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("us-west1", "us-west1")]
|
|
[InlineData("", "")]
|
|
public void DecodeValue_PlainAscii_ReturnsAsIs(string input, string expected)
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue(input);
|
|
Assert.Equal(expected, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_Null_ReturnsNull()
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue(null);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_ValidBase64_Decodes()
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue("=?base64?SGVsbG8=?=");
|
|
Assert.Equal("Hello", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_DegenerateWrapper_ReturnsLiteralValue()
|
|
{
|
|
// "=?base64?=" matches both the prefix "=?base64?" and the suffix "?=" because they
|
|
// overlap on the shared '?', but it is too short to contain any base64 content. It must be
|
|
// returned as-is rather than throwing when the wrapper is stripped.
|
|
var result = McpHeaderEncoder.DecodeValue("=?base64?=");
|
|
Assert.Equal("=?base64?=", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_CaseSensitivePrefix_ReturnsLiteralValue()
|
|
{
|
|
// Per SEP-2243: sentinel markers are case-sensitive and MUST appear exactly as shown (lowercase).
|
|
// An uppercase prefix should NOT be decoded as base64.
|
|
var result = McpHeaderEncoder.DecodeValue("=?BASE64?SGVsbG8=?=");
|
|
Assert.Equal("=?BASE64?SGVsbG8=?=", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_InvalidBase64_ReturnsNull()
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue("=?base64?SGVs!!!bG8=?=");
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_ValidBase64ButInvalidUtf8_ReturnsNull()
|
|
{
|
|
// "//4=" is valid Base64 that decodes to the bytes 0xFF 0xFE, which are not valid UTF-8.
|
|
// A strict decoder must reject this rather than substituting U+FFFD replacement characters.
|
|
var result = McpHeaderEncoder.DecodeValue("=?base64?//4=?=");
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_MissingPrefix_ReturnsLiteralValue()
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue("SGVsbG8=");
|
|
Assert.Equal("SGVsbG8=", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecodeValue_MissingSuffix_ReturnsLiteralValue()
|
|
{
|
|
var result = McpHeaderEncoder.DecodeValue("=?base64?SGVsbG8=");
|
|
Assert.Equal("=?base64?SGVsbG8=", result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("us-west1")]
|
|
[InlineData("Hello, 世界")]
|
|
[InlineData(" padded ")]
|
|
[InlineData("line1\nline2")]
|
|
[InlineData("\tindented")]
|
|
[InlineData("a\tb")]
|
|
public void RoundTrip_EncodeDecode_PreservesValue(string original)
|
|
{
|
|
var encoded = McpHeaderEncoder.EncodeValue(original);
|
|
Assert.NotNull(encoded);
|
|
|
|
var decoded = McpHeaderEncoder.DecodeValue(encoded);
|
|
Assert.Equal(original, decoded);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodeValue_EmbeddedTab_Base64Encodes()
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue("col1\tcol2");
|
|
Assert.StartsWith("=?base64?", result);
|
|
Assert.EndsWith("?=", result);
|
|
|
|
// Verify round-trip
|
|
var decoded = McpHeaderEncoder.DecodeValue(result);
|
|
Assert.Equal("col1\tcol2", decoded);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=?base64?literal?=")]
|
|
[InlineData("=?base64?SGVsbG8=?=")]
|
|
[InlineData("=?base64??=")]
|
|
public void EncodeValue_SentinelCollision_Base64Encodes(string input)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.NotNull(result);
|
|
Assert.StartsWith("=?base64?", result);
|
|
Assert.EndsWith("?=", result);
|
|
|
|
// The encoded value must be different from the input to avoid ambiguity
|
|
Assert.NotEqual(input, result);
|
|
|
|
// Verify round-trip: decode must recover the original literal value
|
|
var decoded = McpHeaderEncoder.DecodeValue(result);
|
|
Assert.Equal(input, decoded);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("=?BASE64?literal?=")] // Case-sensitive: uppercase prefix does not match sentinel
|
|
[InlineData("=?base64?start")] // Missing suffix: no sentinel match
|
|
[InlineData("end?=")] // Missing prefix: no sentinel match
|
|
[InlineData("plain-text")] // No sentinel pattern
|
|
public void EncodeValue_NonSentinelPattern_NotBase64Encoded(string input)
|
|
{
|
|
var result = McpHeaderEncoder.EncodeValue(input);
|
|
Assert.Equal(input, result);
|
|
}
|
|
}
|