swift-sdk/Tests/MCPTests/RequestTests.swift
Adam Wulf da97a3db3c
Add unit test to check that AnyRequest handles missing parameters (#24)
* Add unit test to check that AnyRequest handles missing parameters

* Conform Value to NotRequired

* Add test for decoding AnyRequest with empty parameters

---------

Co-authored-by: Mattt Zmuda <mattt@loopwork.com>
2025-03-19 05:41:50 -07:00

308 lines
11 KiB
Swift

import Testing
import class Foundation.JSONDecoder
import class Foundation.JSONEncoder
@testable import MCP
@Suite("Request Tests")
struct RequestTests {
struct TestMethod: Method {
struct Parameters: Codable, Hashable, Sendable {
let value: String
}
struct Result: Codable, Hashable, Sendable {
let success: Bool
}
static let name = "test.method"
}
struct EmptyMethod: Method {
static let name = "empty.method"
}
@Test("Request initialization with parameters")
func testRequestInitialization() throws {
let id: ID = 1
let params = CallTool.Parameters(name: "test-tool")
let request = Request<CallTool>(id: id, method: CallTool.name, params: params)
#expect(request.id == id)
#expect(request.method == CallTool.name)
#expect(request.params.name == "test-tool")
}
@Test("Request encoding and decoding")
func testRequestEncodingDecoding() throws {
let request = CallTool.request(id: 1, CallTool.Parameters(name: "test-tool"))
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(request)
let decoded = try decoder.decode(Request<CallTool>.self, from: data)
#expect(decoded.id == request.id)
#expect(decoded.method == request.method)
#expect(decoded.params.name == request.params.name)
}
@Test("Empty parameters request encoding")
func testEmptyParametersRequestEncoding() throws {
let request = EmptyMethod.request(id: 1)
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(request)
// Verify we can decode it back
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == request.id)
#expect(decoded.method == request.method)
}
@Test("Empty parameters request decoding")
func testEmptyParametersRequestDecoding() throws {
// Create a minimal JSON string
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"empty.method"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@Test("NotRequired parameters request decoding - with params")
func testNotRequiredParametersRequestDecodingWithParams() throws {
// Test decoding when params field is present
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("NotRequired parameters request decoding - without params")
func testNotRequiredParametersRequestDecodingWithoutParams() throws {
// Test decoding when params field is missing
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("NotRequired parameters request decoding - with null params")
func testNotRequiredParametersRequestDecodingWithNullParams() throws {
// Test decoding when params field is null
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("Required parameters request decoding - missing params")
func testRequiredParametersRequestDecodingMissingParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("Required parameters request decoding - null params")
func testRequiredParametersRequestDecodingNullParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("Empty parameters request decoding - with null params")
func testEmptyParametersRequestDecodingNullParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"empty.method","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@Test("Empty parameters request decoding - with empty object params")
func testEmptyParametersRequestDecodingEmptyParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"empty.method","params":{}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@Test("Initialize request decoding - requires params")
func testInitializeRequestDecodingRequiresParams() throws {
// Test missing params field
let missingParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize"}
"""
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(
Request<Initialize>.self, from: missingParams.data(using: .utf8)!)
}
// Test null params
let nullParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize","params":null}
"""
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<Initialize>.self, from: nullParams.data(using: .utf8)!)
}
// Verify that empty object params works (since fields have defaults)
let emptyParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize","params":{}}
"""
let decoded = try decoder.decode(
Request<Initialize>.self, from: emptyParams.data(using: .utf8)!)
#expect(decoded.params.protocolVersion == Version.latest)
#expect(decoded.params.clientInfo.name == "unknown")
}
@Test("Invalid parameters request decoding")
func testInvalidParametersRequestDecoding() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"invalid":"value"}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("NotRequired parameters request decoding")
func testNotRequiredParametersRequestDecoding() throws {
// Test with missing params
let missingParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
"""
let decoder = JSONDecoder()
let decodedMissing = try decoder.decode(
Request<ListTools>.self,
from: missingParams.data(using: .utf8)!)
#expect(decodedMissing.id == 1)
#expect(decodedMissing.method == ListTools.name)
#expect(decodedMissing.params.cursor == nil)
// Test with null params
let nullParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":null}
"""
let decodedNull = try decoder.decode(
Request<ListTools>.self,
from: nullParams.data(using: .utf8)!)
#expect(decodedNull.params.cursor == nil)
// Test with empty object params
let emptyParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
"""
let decodedEmpty = try decoder.decode(
Request<ListTools>.self,
from: emptyParams.data(using: .utf8)!)
#expect(decodedEmpty.params.cursor == nil)
// Test with provided cursor
let withCursor = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"cursor":"next-page"}}
"""
let decodedWithCursor = try decoder.decode(
Request<ListTools>.self,
from: withCursor.data(using: .utf8)!)
#expect(decodedWithCursor.params.cursor == "next-page")
}
@Test("AnyRequest parameters request decoding - without params")
func testAnyRequestParametersRequestDecodingWithoutParams() throws {
// Test decoding when params field is missing
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(AnyRequest.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("AnyRequest parameters request decoding - with null params")
func testAnyRequestParametersRequestDecodingWithNullParams() throws {
// Test decoding when params field is null
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("AnyRequest parameters request decoding - with empty params")
func testAnyRequestParametersRequestDecodingWithEmptyParams() throws {
// Test decoding when params field is null
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
}