Skip to content

Commit 2fc0b40

Browse files
authored
Refactor ServerTest test structure (#366)
Split ServerTest per feature test classes <!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> ## Breaking Changes <!-- Will users need to update their code or configurations? --> ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
1 parent 913c0d1 commit 2fc0b40

File tree

6 files changed

+436
-530
lines changed

6 files changed

+436
-530
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.modelcontextprotocol.kotlin.sdk.Implementation
4+
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
5+
import io.modelcontextprotocol.kotlin.sdk.client.Client
6+
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
7+
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.runBlocking
9+
import org.junit.jupiter.api.BeforeEach
10+
11+
abstract class AbstractServerFeaturesTest {
12+
13+
protected lateinit var server: Server
14+
protected lateinit var client: Client
15+
16+
abstract fun getServerCapabilities(): ServerCapabilities
17+
18+
protected open fun getServerInstructionsProvider(): (() -> String)? = null
19+
20+
@BeforeEach
21+
fun setUp() {
22+
val serverOptions = ServerOptions(
23+
capabilities = getServerCapabilities(),
24+
)
25+
26+
server = Server(
27+
serverInfo = Implementation(name = "test server", version = "1.0"),
28+
options = serverOptions,
29+
instructionsProvider = getServerInstructionsProvider(),
30+
)
31+
32+
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
33+
34+
client = Client(
35+
clientInfo = Implementation(name = "test client", version = "1.0"),
36+
)
37+
38+
runBlocking {
39+
// Connect client and server
40+
launch { client.connect(clientTransport) }
41+
launch { server.createSession(serverTransport) }
42+
}
43+
}
44+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.modelcontextprotocol.kotlin.sdk.Implementation
4+
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
5+
import io.modelcontextprotocol.kotlin.sdk.client.Client
6+
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
7+
import kotlinx.coroutines.test.runTest
8+
import org.junit.jupiter.api.Test
9+
import org.junit.jupiter.api.assertNull
10+
import kotlin.test.assertEquals
11+
12+
class ServerInstructionsTest {
13+
14+
@Test
15+
fun `Server constructor should accept instructions provider parameter`() = runTest {
16+
val serverInfo = Implementation(name = "test server", version = "1.0")
17+
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
18+
val instructions = "This is a test server. Use it for testing purposes only."
19+
20+
val server = Server(serverInfo, serverOptions, { instructions })
21+
22+
// The instructions should be stored internally and used in handleInitialize
23+
// We can't directly access the private field, but we can test it through initialization
24+
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
25+
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
26+
27+
server.createSession(serverTransport)
28+
client.connect(clientTransport)
29+
30+
assertEquals(instructions, client.serverInstructions)
31+
}
32+
33+
@Test
34+
fun `Server constructor should accept instructions parameter`() = runTest {
35+
val serverInfo = Implementation(name = "test server", version = "1.0")
36+
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
37+
val instructions = "This is a test server. Use it for testing purposes only."
38+
39+
val server = Server(serverInfo, serverOptions, instructions)
40+
41+
// The instructions should be stored internally and used in handleInitialize
42+
// We can't directly access the private field, but we can test it through initialization
43+
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
44+
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
45+
46+
server.createSession(serverTransport)
47+
client.connect(clientTransport)
48+
49+
assertEquals(instructions, client.serverInstructions)
50+
}
51+
52+
@Test
53+
fun `Server constructor should work without instructions parameter`() = runTest {
54+
val serverInfo = Implementation(name = "test server", version = "1.0")
55+
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
56+
57+
// Test that server works when instructions parameter is omitted (defaults to null)
58+
val server = Server(serverInfo, serverOptions)
59+
60+
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
61+
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
62+
63+
server.createSession(serverTransport)
64+
client.connect(clientTransport)
65+
66+
assertNull(client.serverInstructions)
67+
}
68+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
4+
import io.modelcontextprotocol.kotlin.sdk.Implementation
5+
import io.modelcontextprotocol.kotlin.sdk.Method
6+
import io.modelcontextprotocol.kotlin.sdk.Prompt
7+
import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification
8+
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
9+
import kotlinx.coroutines.CompletableDeferred
10+
import kotlinx.coroutines.test.runTest
11+
import org.junit.jupiter.api.Test
12+
import org.junit.jupiter.api.assertThrows
13+
import kotlin.test.assertEquals
14+
import kotlin.test.assertFalse
15+
import kotlin.test.assertTrue
16+
17+
class ServerPromptsTest : AbstractServerFeaturesTest() {
18+
19+
override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
20+
prompts = ServerCapabilities.Prompts(false),
21+
)
22+
23+
@Test
24+
fun `removePrompt should remove a prompt`() = runTest {
25+
// Add a prompt
26+
val testPrompt = Prompt("test-prompt", "Test Prompt", null)
27+
server.addPrompt(testPrompt) {
28+
GetPromptResult(
29+
description = "Test prompt description",
30+
messages = listOf(),
31+
)
32+
}
33+
34+
// Remove the prompt
35+
val result = server.removePrompt(testPrompt.name)
36+
37+
// Verify the prompt was removed
38+
assertTrue(result, "Prompt should be removed successfully")
39+
}
40+
41+
@Test
42+
fun `removePrompts should remove multiple prompts and send notification`() = runTest {
43+
// Add prompts
44+
val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null)
45+
val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null)
46+
server.addPrompt(testPrompt1) {
47+
GetPromptResult(
48+
description = "Test prompt description 1",
49+
messages = listOf(),
50+
)
51+
}
52+
server.addPrompt(testPrompt2) {
53+
GetPromptResult(
54+
description = "Test prompt description 2",
55+
messages = listOf(),
56+
)
57+
}
58+
59+
// Remove the prompts
60+
val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name))
61+
62+
// Verify the prompts were removed
63+
assertEquals(2, result, "Both prompts should be removed")
64+
}
65+
66+
@Test
67+
fun `removePrompt should return false when prompt does not exist`() = runTest {
68+
// Track notifications
69+
var promptListChangedNotificationReceived = false
70+
client.setNotificationHandler<PromptListChangedNotification>(Method.Defined.NotificationsPromptsListChanged) {
71+
promptListChangedNotificationReceived = true
72+
CompletableDeferred(Unit)
73+
}
74+
75+
// Try to remove a non-existent prompt
76+
val result = server.removePrompt("non-existent-prompt")
77+
78+
// Verify the result
79+
assertFalse(result, "Removing non-existent prompt should return false")
80+
assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist")
81+
}
82+
83+
@Test
84+
fun `removePrompt should throw when prompts capability is not supported`() = runTest {
85+
// Create server without prompts capability
86+
val serverOptions = ServerOptions(
87+
capabilities = ServerCapabilities(),
88+
)
89+
val server = Server(
90+
Implementation(name = "test server", version = "1.0"),
91+
serverOptions,
92+
)
93+
94+
// Verify that removing a prompt throws an exception
95+
val exception = assertThrows<IllegalStateException> {
96+
server.removePrompt("test-prompt")
97+
}
98+
assertEquals("Server does not support prompts capability.", exception.message)
99+
}
100+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.modelcontextprotocol.kotlin.sdk.Implementation
4+
import io.modelcontextprotocol.kotlin.sdk.Method
5+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
6+
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
7+
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
8+
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
9+
import kotlinx.coroutines.CompletableDeferred
10+
import kotlinx.coroutines.test.runTest
11+
import org.junit.jupiter.api.Test
12+
import org.junit.jupiter.api.assertThrows
13+
import kotlin.test.assertEquals
14+
import kotlin.test.assertFalse
15+
import kotlin.test.assertTrue
16+
17+
class ServerResourcesTest : AbstractServerFeaturesTest() {
18+
19+
override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
20+
resources = ServerCapabilities.Resources(null, null),
21+
)
22+
23+
@Test
24+
fun `removeResource should remove a resource and send notification`() = runTest {
25+
// Add a resource
26+
val testResourceUri = "test://resource"
27+
server.addResource(
28+
uri = testResourceUri,
29+
name = "Test Resource",
30+
description = "A test resource",
31+
mimeType = "text/plain",
32+
) {
33+
ReadResourceResult(
34+
contents = listOf(
35+
TextResourceContents(
36+
text = "Test resource content",
37+
uri = testResourceUri,
38+
mimeType = "text/plain",
39+
),
40+
),
41+
)
42+
}
43+
44+
// Remove the resource
45+
val result = server.removeResource(testResourceUri)
46+
47+
// Verify the resource was removed
48+
assertTrue(result, "Resource should be removed successfully")
49+
}
50+
51+
@Test
52+
fun `removeResources should remove multiple resources and send notification`() = runTest {
53+
// Add resources
54+
val testResourceUri1 = "test://resource1"
55+
val testResourceUri2 = "test://resource2"
56+
server.addResource(
57+
uri = testResourceUri1,
58+
name = "Test Resource 1",
59+
description = "A test resource 1",
60+
mimeType = "text/plain",
61+
) {
62+
ReadResourceResult(
63+
contents = listOf(
64+
TextResourceContents(
65+
text = "Test resource content 1",
66+
uri = testResourceUri1,
67+
mimeType = "text/plain",
68+
),
69+
),
70+
)
71+
}
72+
server.addResource(
73+
uri = testResourceUri2,
74+
name = "Test Resource 2",
75+
description = "A test resource 2",
76+
mimeType = "text/plain",
77+
) {
78+
ReadResourceResult(
79+
contents = listOf(
80+
TextResourceContents(
81+
text = "Test resource content 2",
82+
uri = testResourceUri2,
83+
mimeType = "text/plain",
84+
),
85+
),
86+
)
87+
}
88+
89+
// Remove the resources
90+
val result = server.removeResources(listOf(testResourceUri1, testResourceUri2))
91+
92+
// Verify the resources were removed
93+
assertEquals(2, result, "Both resources should be removed")
94+
}
95+
96+
@Test
97+
fun `removeResource should return false when resource does not exist`() = runTest {
98+
// Track notifications
99+
var resourceListChangedNotificationReceived = false
100+
client.setNotificationHandler<ResourceListChangedNotification>(
101+
Method.Defined.NotificationsResourcesListChanged,
102+
) {
103+
resourceListChangedNotificationReceived = true
104+
CompletableDeferred(Unit)
105+
}
106+
107+
// Try to remove a non-existent resource
108+
val result = server.removeResource("non-existent-resource")
109+
110+
// Verify the result
111+
assertFalse(result, "Removing non-existent resource should return false")
112+
assertFalse(
113+
resourceListChangedNotificationReceived,
114+
"No notification should be sent when resource doesn't exist",
115+
)
116+
}
117+
118+
@Test
119+
fun `removeResource should throw when resources capability is not supported`() = runTest {
120+
// Create server without resources capability
121+
val serverOptions = ServerOptions(
122+
capabilities = ServerCapabilities(),
123+
)
124+
val server = Server(
125+
Implementation(name = "test server", version = "1.0"),
126+
serverOptions,
127+
)
128+
129+
// Verify that removing a resource throws an exception
130+
val exception = assertThrows<IllegalStateException> {
131+
server.removeResource("test://resource")
132+
}
133+
assertEquals("Server does not support resources capability.", exception.message)
134+
}
135+
}

0 commit comments

Comments
 (0)