Skip to content

Commit 52cce6a

Browse files
committed
Add test coverage for Stdio server transport
1 parent cebe0b7 commit 52cce6a

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* SonarQube MCP Server
3+
* Copyright (C) 2025 SonarSource
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonarsource.sonarqube.mcp.transport;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import io.modelcontextprotocol.spec.McpServerSession;
21+
import java.time.Duration;
22+
import org.junit.jupiter.api.Test;
23+
import org.mockito.Mockito;
24+
import reactor.core.publisher.Mono;
25+
26+
import static org.assertj.core.api.Assertions.assertThatCode;
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.never;
29+
import static org.mockito.Mockito.times;
30+
import static org.mockito.Mockito.verify;
31+
import static org.mockito.Mockito.when;
32+
33+
class StdioServerTransportProviderTest {
34+
35+
/**
36+
* Helper to create a provider with a mock session without starting the internal transport.
37+
* We need to use reflection to set the session field directly to avoid the background threads.
38+
*/
39+
private StdioServerTransportProvider createProviderWithMockSession(McpServerSession mockSession) throws Exception {
40+
var provider = new StdioServerTransportProvider(new ObjectMapper());
41+
var sessionField = StdioServerTransportProvider.class.getDeclaredField("session");
42+
sessionField.setAccessible(true);
43+
sessionField.set(provider, mockSession);
44+
return provider;
45+
}
46+
47+
@Test
48+
void closeGracefully_should_return_empty_mono_when_session_is_null() {
49+
var provider = new StdioServerTransportProvider(new ObjectMapper());
50+
51+
var result = provider.closeGracefully();
52+
53+
assertThatCode(() -> result.block(Duration.ofSeconds(1))).doesNotThrowAnyException();
54+
}
55+
56+
@Test
57+
void closeGracefully_should_complete_successfully_when_session_closes_quickly() throws Exception {
58+
var mockSession = mock(McpServerSession.class);
59+
when(mockSession.closeGracefully()).thenReturn(Mono.empty());
60+
var provider = createProviderWithMockSession(mockSession);
61+
62+
var result = provider.closeGracefully();
63+
64+
assertThatCode(() -> result.block(Duration.ofSeconds(2))).doesNotThrowAnyException();
65+
verify(mockSession, times(1)).closeGracefully();
66+
verify(mockSession, never()).close();
67+
}
68+
69+
@Test
70+
void closeGracefully_should_timeout_and_force_close_after_10_seconds() throws Exception {
71+
var mockSession = mock(McpServerSession.class);
72+
when(mockSession.closeGracefully()).thenReturn(Mono.never());
73+
var provider = createProviderWithMockSession(mockSession);
74+
75+
var result = provider.closeGracefully();
76+
77+
assertThatCode(() -> result.block(Duration.ofSeconds(12))).doesNotThrowAnyException();
78+
verify(mockSession, times(1)).closeGracefully();
79+
verify(mockSession, times(1)).close(); // Should be force-closed
80+
}
81+
82+
@Test
83+
void closeGracefully_should_handle_error_and_complete_gracefully() throws Exception {
84+
var mockSession = mock(McpServerSession.class);
85+
when(mockSession.closeGracefully()).thenReturn(
86+
Mono.error(new RuntimeException("Close failed"))
87+
);
88+
var provider = createProviderWithMockSession(mockSession);
89+
90+
var result = provider.closeGracefully();
91+
92+
assertThatCode(() -> result.block(Duration.ofSeconds(2))).doesNotThrowAnyException();
93+
verify(mockSession, times(1)).closeGracefully();
94+
}
95+
96+
@Test
97+
void closeGracefully_should_handle_exception_during_forced_close() throws Exception {
98+
var mockSession = mock(McpServerSession.class);
99+
when(mockSession.closeGracefully()).thenReturn(Mono.never());
100+
Mockito.doThrow(new RuntimeException("Force close failed"))
101+
.when(mockSession).close();
102+
var provider = createProviderWithMockSession(mockSession);
103+
104+
var result = provider.closeGracefully();
105+
106+
assertThatCode(() -> result.block(Duration.ofSeconds(12))).doesNotThrowAnyException();
107+
verify(mockSession, times(1)).closeGracefully();
108+
verify(mockSession, times(1)).close();
109+
}
110+
111+
@Test
112+
void closeGracefully_should_complete_immediately_if_session_closes_in_5_seconds() throws Exception {
113+
var mockSession = mock(McpServerSession.class);
114+
when(mockSession.closeGracefully()).thenReturn(
115+
Mono.delay(Duration.ofSeconds(5)).then()
116+
);
117+
var provider = createProviderWithMockSession(mockSession);
118+
119+
var result = provider.closeGracefully();
120+
121+
assertThatCode(() -> result.block(Duration.ofSeconds(7))).doesNotThrowAnyException();
122+
verify(mockSession, times(1)).closeGracefully();
123+
verify(mockSession, never()).close();
124+
}
125+
126+
}
127+

0 commit comments

Comments
 (0)