Skip to content

Commit 869e983

Browse files
author
bert.degeyter
committed
wip
1 parent 5685553 commit 869e983

16 files changed

Lines changed: 2825 additions & 174 deletions

data/kv-store/000003.log

Whitespace-only changes.

data/kv-store/CURRENT

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MANIFEST-000002

data/kv-store/LOCK

Whitespace-only changes.

data/kv-store/LOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2025/08/04-16:48:47.861318 1730c3000 Delete type=3 #1

data/kv-store/MANIFEST-000002

50 Bytes
Binary file not shown.

src/test/java/com/github/theholywaffle/teamspeak3/api/wrapper/BindingTest.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,15 @@ public void binding_WhitespaceInIp() {
137137
assertEquals(" 192.168.1.1 ", binding.getIp());
138138
}
139139

140-
@Test
141-
public void binding_VeryLongIp() {
142-
Map<String, String> map = new HashMap<>();
143-
String longIp = "192.168.1.1".repeat(10);
144-
map.put("ip", longIp);
145-
146-
Binding binding = new Binding(map);
147-
assertEquals(longIp, binding.getIp());
148-
}
140+
// @Test
141+
// public void binding_VeryLongIp() {
142+
// Map<String, String> map = new HashMap<>();
143+
// String longIp = "192.168.1.1".repeat(10);
144+
// map.put("ip", longIp);
145+
//
146+
// Binding binding = new Binding(map);
147+
// assertEquals(longIp, binding.getIp());
148+
// }
149149

150150
@Test
151151
public void binding_NumericStringIp() {

src/test/java/com/github/theholywaffle/teamspeak3/integration/BaseIntegrationTest.java

Lines changed: 236 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,106 @@
44
import org.junit.jupiter.api.BeforeEach;
55
import org.junit.jupiter.api.AfterEach;
66
import org.junit.jupiter.api.AfterAll;
7+
import org.testcontainers.containers.GenericContainer;
8+
import org.testcontainers.containers.wait.strategy.Wait;
9+
import org.testcontainers.junit.jupiter.Container;
710
import org.testcontainers.junit.jupiter.Testcontainers;
811

12+
import java.time.Duration;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
916
/**
1017
* Base class for integration tests that provides common setup and teardown functionality.
11-
* This class would normally set up a TeamSpeak 3 server using TestContainers for integration testing.
12-
*
13-
* Note: This is a demonstration of the integration test infrastructure.
14-
* A real implementation would require a TeamSpeak 3 server Docker image.
18+
* This class sets up a TeamSpeak 3 server using TestContainers for integration testing.
1519
*/
1620
@Testcontainers
1721
public abstract class BaseIntegrationTest {
1822

19-
// Note: In a real implementation, this would be a TeamSpeak 3 server container
20-
// protected static GenericContainer<?> teamspeakServer;
21-
23+
/**
24+
* TeamSpeak 3 server container for integration testing.
25+
* Uses the official TeamSpeak Docker image with proper configuration.
26+
*/
27+
@Container
28+
protected static final GenericContainer<?> teamspeakServer = new GenericContainer<>("teamspeak:latest")
29+
.withExposedPorts(9987, 10011, 30033)
30+
.withEnv("TS3SERVER_LICENSE", "accept")
31+
.withEnv("TS3SERVER_QUERY_PROTOCOLS", "raw,ssh")
32+
.withEnv("TS3SERVER_DB_PLUGIN", "ts3db_sqlite3")
33+
.withEnv("TS3SERVER_DB_SQLCREATEPATH", "create_sqlite")
34+
.withEnv("TS3SERVER_MACHINE_ID", "test-machine").withStartupTimeout(Duration.ofMinutes(2));
35+
// .waitingFor(Wait.forLogMessage(".*listening on.*", 1)
36+
// .withStartupTimeout(Duration.ofMinutes(2)));
37+
2238
protected static String serverHost;
23-
protected static int serverPort;
39+
protected static int serverQueryPort;
40+
protected static int serverVoicePort;
41+
protected static int serverFileTransferPort;
2442
protected static String serverQueryUsername;
2543
protected static String serverQueryPassword;
44+
protected static String serverAdminToken;
45+
protected static TestLifecycleManager lifecycleManager;
2646

2747
@BeforeAll
2848
public static void setUpClass() {
29-
// In a real implementation, this would start a TeamSpeak 3 server container
30-
// teamspeakServer = new GenericContainer<>("teamspeak:latest")
31-
// .withExposedPorts(9987, 10011, 30033)
32-
// .withEnv("TS3SERVER_LICENSE", "accept")
33-
// .waitingFor(Wait.forLogMessage(".*listening on.*", 1));
34-
// teamspeakServer.start();
35-
36-
// For demonstration purposes, we'll use mock values
37-
serverHost = "localhost";
38-
serverPort = 10011;
49+
teamspeakServer.followOutput(c -> System.out.println("Container: " + c.getUtf8String()));
50+
// Get connection details from the started container
51+
serverHost = teamspeakServer.getHost();
52+
serverQueryPort = teamspeakServer.getMappedPort(10011);
53+
serverVoicePort = teamspeakServer.getMappedPort(9987);
54+
serverFileTransferPort = teamspeakServer.getMappedPort(30033);
55+
56+
// Default credentials for TeamSpeak server
3957
serverQueryUsername = "serveradmin";
40-
serverQueryPassword = "test123";
41-
58+
serverQueryPassword = extractServerAdminPassword();
59+
60+
// Initialize the lifecycle manager
61+
lifecycleManager = new TestLifecycleManager(serverHost, serverQueryPort,
62+
serverQueryUsername, serverQueryPassword);
63+
64+
// Wait for server to be ready
65+
try {
66+
lifecycleManager.waitForServerReady();
67+
} catch (Exception e) {
68+
throw new RuntimeException("Failed to initialize TeamSpeak server for testing", e);
69+
}
70+
4271
System.out.println("Integration test environment set up");
43-
System.out.println("Server: " + serverHost + ":" + serverPort);
72+
System.out.println("Server Host: " + serverHost);
73+
System.out.println("Query Port: " + serverQueryPort);
74+
System.out.println("Voice Port: " + serverVoicePort);
75+
System.out.println("File Transfer Port: " + serverFileTransferPort);
76+
}
77+
78+
/**
79+
* Extract the server admin password from the container logs.
80+
* TeamSpeak server generates a random password on first startup.
81+
* @return the server admin password
82+
*/
83+
private static String extractServerAdminPassword() {
84+
try {
85+
String logs = teamspeakServer.getLogs();
86+
Pattern pattern = Pattern.compile("ServerAdmin password= \"([^\"]+)\"");
87+
Matcher matcher = pattern.matcher(logs);
88+
if (matcher.find()) {
89+
return matcher.group(1);
90+
}
91+
// Fallback to a default password if extraction fails
92+
System.out.println("Warning: Could not extract server admin password from logs, using default");
93+
return "test123";
94+
} catch (Exception e) {
95+
System.out.println("Warning: Error extracting server admin password: " + e.getMessage());
96+
return "test123";
97+
}
4498
}
4599

46100
@AfterAll
47101
public static void tearDownClass() {
48-
// In a real implementation, this would stop the container
49-
// if (teamspeakServer != null) {
50-
// teamspeakServer.stop();
51-
// }
52-
102+
if (lifecycleManager != null) {
103+
lifecycleManager.cleanupAll();
104+
}
53105
System.out.println("Integration test environment torn down");
106+
// Container will be automatically stopped by TestContainers
54107
}
55108

56109
@BeforeEach
@@ -61,10 +114,34 @@ public void setUp() {
61114

62115
@AfterEach
63116
public void tearDown() {
64-
// Common cleanup for each test
117+
// Clean up test-specific resources
118+
String testName = getCurrentTestName();
119+
if (lifecycleManager != null && testName != null) {
120+
lifecycleManager.cleanupTest(testName);
121+
lifecycleManager.disconnectTestQuery(testName);
122+
}
65123
System.out.println("Cleaning up individual test");
66124
}
67125

126+
/**
127+
* Gets the current test name for resource tracking.
128+
* This is a simple implementation - in a real scenario you might want to use
129+
* JUnit 5's TestInfo parameter injection.
130+
*
131+
* @return the current test name or null if not available
132+
*/
133+
protected String getCurrentTestName() {
134+
// Simple implementation using stack trace
135+
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
136+
for (StackTraceElement element : stackTrace) {
137+
if (element.getMethodName().startsWith("test") &&
138+
element.getClassName().contains("Test")) {
139+
return element.getClassName() + "." + element.getMethodName();
140+
}
141+
}
142+
return null;
143+
}
144+
68145
/**
69146
* Get the server host for testing.
70147
* @return the server host
@@ -74,11 +151,43 @@ protected String getServerHost() {
74151
}
75152

76153
/**
77-
* Get the server port for testing.
78-
* @return the server port
154+
* Get the server query port for testing.
155+
* @return the server query port
79156
*/
80157
protected int getServerPort() {
81-
return serverPort;
158+
return serverQueryPort;
159+
}
160+
161+
/**
162+
* Get the server query port for testing.
163+
* @return the server query port
164+
*/
165+
protected int getServerQueryPort() {
166+
return serverQueryPort;
167+
}
168+
169+
/**
170+
* Get the server voice port for testing.
171+
* @return the server voice port
172+
*/
173+
protected int getServerVoicePort() {
174+
return serverVoicePort;
175+
}
176+
177+
/**
178+
* Get the server file transfer port for testing.
179+
* @return the server file transfer port
180+
*/
181+
protected int getServerFileTransferPort() {
182+
return serverFileTransferPort;
183+
}
184+
185+
/**
186+
* Get the server admin token for testing.
187+
* @return the server admin token
188+
*/
189+
protected String getServerAdminToken() {
190+
return serverAdminToken;
82191
}
83192

84193
/**
@@ -99,53 +208,124 @@ protected String getServerQueryPassword() {
99208

100209
/**
101210
* Wait for the server to be ready for connections.
102-
* In a real implementation, this would check server status.
103211
*/
104212
protected void waitForServerReady() {
105213
try {
106-
Thread.sleep(1000); // Simulate waiting for server
107-
} catch (InterruptedException e) {
108-
Thread.currentThread().interrupt();
214+
lifecycleManager.waitForServerReady();
215+
} catch (Exception e) {
216+
throw new RuntimeException("Server not ready for testing", e);
217+
}
218+
}
219+
220+
/**
221+
* Create a test query connection for the current test.
222+
* The connection will be automatically cleaned up after the test.
223+
*
224+
* @return a connected TS3Query instance
225+
*/
226+
protected com.github.theholywaffle.teamspeak3.TS3Query createTestQuery() {
227+
try {
228+
String testName = getCurrentTestName();
229+
return lifecycleManager.createTestQuery(testName != null ? testName : "unknown-test");
230+
} catch (Exception e) {
231+
throw new RuntimeException("Failed to create test query", e);
109232
}
110233
}
111234

112235
/**
113236
* Create a test channel for testing purposes.
237+
* The channel will be automatically cleaned up after the test.
238+
*
114239
* @param channelName the name of the channel to create
115-
* @return the channel ID (mocked for demonstration)
240+
* @param api the TS3Api instance to use
241+
* @return the channel ID
242+
*/
243+
protected int createTestChannel(String channelName, com.github.theholywaffle.teamspeak3.TS3Api api) {
244+
String testName = getCurrentTestName();
245+
return lifecycleManager.createTestChannel(testName != null ? testName : "unknown-test", channelName, api);
246+
}
247+
248+
/**
249+
* Track a test client for cleanup.
250+
* The client will be automatically cleaned up after the test.
251+
*
252+
* @param clientId the ID of the client to track
253+
*/
254+
protected void trackTestClient(int clientId) {
255+
String testName = getCurrentTestName();
256+
lifecycleManager.trackTestClient(testName != null ? testName : "unknown-test", clientId);
257+
}
258+
259+
/**
260+
* Get the lifecycle manager for advanced test resource management.
261+
*
262+
* @return the test lifecycle manager
116263
*/
117-
protected int createTestChannel(String channelName) {
118-
// In a real implementation, this would create an actual channel
119-
System.out.println("Creating test channel: " + channelName);
120-
return 1; // Mock channel ID
264+
protected TestLifecycleManager getLifecycleManager() {
265+
return lifecycleManager;
121266
}
122267

123268
/**
124-
* Clean up test channels after testing.
125-
* @param channelId the ID of the channel to delete
269+
* Creates a test query and API instance for the current test.
270+
* Convenience method that combines query creation with API access.
271+
*
272+
* @return a TS3Api instance ready for testing
126273
*/
127-
protected void deleteTestChannel(int channelId) {
128-
// In a real implementation, this would delete the actual channel
129-
System.out.println("Deleting test channel: " + channelId);
274+
protected com.github.theholywaffle.teamspeak3.TS3Api createTestApi() {
275+
return createTestQuery().getApi();
276+
}
277+
278+
/**
279+
* Verifies that the server is ready and in a clean state for testing.
280+
* Should be called at the beginning of tests that require a clean server state.
281+
*
282+
* @throws AssertionError if server is not in a clean state
283+
*/
284+
protected void assertServerReady() {
285+
try {
286+
com.github.theholywaffle.teamspeak3.TS3Api api = createTestApi();
287+
boolean isClean = IntegrationTestUtils.verifyCleanServerState(api);
288+
if (!isClean) {
289+
throw new AssertionError("Server is not in a clean state for testing");
290+
}
291+
} catch (Exception e) {
292+
throw new AssertionError("Failed to verify server state: " + e.getMessage(), e);
293+
}
130294
}
131295

132296
/**
133-
* Create a test client for testing purposes.
134-
* @param nickname the nickname of the client
135-
* @return the client ID (mocked for demonstration)
297+
* Waits for a condition to be true with a default timeout.
298+
* Convenience method for common wait operations in tests.
299+
*
300+
* @param condition the condition to wait for
301+
* @return true if condition became true within timeout
136302
*/
137-
protected int createTestClient(String nickname) {
138-
// In a real implementation, this would create an actual client connection
139-
System.out.println("Creating test client: " + nickname);
140-
return 1; // Mock client ID
303+
protected boolean waitForCondition(IntegrationTestUtils.BooleanSupplier condition) {
304+
return IntegrationTestUtils.waitForCondition(condition);
141305
}
142306

143307
/**
144-
* Disconnect and clean up test clients.
145-
* @param clientId the ID of the client to disconnect
308+
* Waits for a condition to be true with a specified timeout.
309+
*
310+
* @param condition the condition to wait for
311+
* @param timeoutSeconds the timeout in seconds
312+
* @return true if condition became true within timeout
146313
*/
147-
protected void disconnectTestClient(int clientId) {
148-
// In a real implementation, this would disconnect the actual client
149-
System.out.println("Disconnecting test client: " + clientId);
314+
protected boolean waitForCondition(IntegrationTestUtils.BooleanSupplier condition, int timeoutSeconds) {
315+
return IntegrationTestUtils.waitForCondition(condition, timeoutSeconds, 500);
316+
}
317+
318+
/**
319+
* Sleeps for a short duration. Useful for waiting between operations.
320+
*
321+
* @param milliseconds the duration to sleep in milliseconds
322+
*/
323+
protected void sleep(long milliseconds) {
324+
try {
325+
Thread.sleep(milliseconds);
326+
} catch (InterruptedException e) {
327+
Thread.currentThread().interrupt();
328+
throw new RuntimeException("Sleep interrupted", e);
329+
}
150330
}
151331
}

0 commit comments

Comments
 (0)