Skip to content

Commit 3ada034

Browse files
abnegateclaude
andcommitted
(test): add live TCP server integration tests for full coverage
Exercise TCP adapter getConnection/closeConnection, routing, protocol detection, and config inside real Swoole coroutine contexts. Remove Server/ and TCP adapter exclusions from coverage so integration tests contribute to the instrumented surface. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9aebc4b commit 3ada034

1 file changed

Lines changed: 255 additions & 0 deletions

File tree

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<?php
2+
3+
namespace Utopia\Tests\Integration;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Swoole\Coroutine;
7+
use Swoole\Coroutine\Client;
8+
use Swoole\Coroutine\Server as CoServer;
9+
use Utopia\Proxy\Adapter;
10+
use Utopia\Proxy\Adapter\TCP as TCPAdapter;
11+
use Utopia\Proxy\Protocol;
12+
use Utopia\Proxy\Resolver\Fixed;
13+
use Utopia\Proxy\Server\TCP\Config;
14+
15+
/**
16+
* Integration tests that exercise the TCP adapter and server construction
17+
* paths inside a real Swoole coroutine context.
18+
*
19+
* @group integration
20+
*/
21+
class TCPServerTest extends TestCase
22+
{
23+
protected function setUp(): void
24+
{
25+
if (! \extension_loaded('swoole')) {
26+
$this->markTestSkipped('ext-swoole is required.');
27+
}
28+
}
29+
30+
public function testTCPAdapterGetConnectionAndClose(): void
31+
{
32+
$result = null;
33+
$error = null;
34+
35+
Coroutine\run(function () use (&$result, &$error): void {
36+
// Start a simple echo backend
37+
$backend = new CoServer('127.0.0.1', 0);
38+
$backend->set(['open_eof_check' => false]);
39+
$backendPort = $backend->port;
40+
41+
Coroutine::create(function () use ($backend): void {
42+
$backend->handle(function (Coroutine\Server\Connection $connection): void {
43+
$data = $connection->recv();
44+
if ($data !== '' && $data !== false) {
45+
$connection->send($data);
46+
}
47+
$connection->close();
48+
});
49+
});
50+
51+
// Give the backend a moment to start
52+
Coroutine::sleep(0.05);
53+
54+
try {
55+
$resolver = new Fixed("127.0.0.1:{$backendPort}");
56+
$adapter = new TCPAdapter(port: 5432, resolver: $resolver);
57+
$adapter->setSkipValidation(true);
58+
$adapter->setTimeout(5.0);
59+
$adapter->setConnectTimeout(2.0);
60+
61+
// getConnection dials the backend and caches by fd
62+
$client = $adapter->getConnection('hello', 1);
63+
$this->assertInstanceOf(Client::class, $client);
64+
$this->assertTrue($client->isConnected());
65+
66+
// Same fd returns cached connection
67+
$cached = $adapter->getConnection('ignored', 1);
68+
$this->assertSame($client, $cached);
69+
70+
// Send/recv through the connection
71+
$client->send('ping');
72+
$response = $client->recv(1.0);
73+
$this->assertSame('ping', $response);
74+
75+
// closeConnection cleans up
76+
$adapter->closeConnection(1);
77+
78+
// Closing again is a no-op
79+
$adapter->closeConnection(1);
80+
81+
// sockmap should not be active
82+
$this->assertFalse($adapter->isSockmapActive(1));
83+
84+
$result = true;
85+
} catch (\Throwable $e) {
86+
$error = $e;
87+
} finally {
88+
$backend->shutdown();
89+
}
90+
});
91+
92+
if ($error !== null) {
93+
throw $error;
94+
}
95+
$this->assertTrue($result);
96+
}
97+
98+
public function testTCPAdapterRouteWithOnResolveCallback(): void
99+
{
100+
$result = null;
101+
$error = null;
102+
103+
Coroutine\run(function () use (&$result, &$error): void {
104+
try {
105+
$adapter = new TCPAdapter(port: 5432);
106+
$adapter->setSkipValidation(true);
107+
108+
$adapter->onResolve(function (string $data): string {
109+
return '10.0.0.1:5432';
110+
});
111+
112+
$routed = $adapter->route('raw-packet-data');
113+
$this->assertSame('10.0.0.1:5432', $routed->endpoint);
114+
$this->assertSame(Protocol::PostgreSQL, $routed->protocol);
115+
116+
$result = true;
117+
} catch (\Throwable $e) {
118+
$error = $e;
119+
}
120+
});
121+
122+
if ($error !== null) {
123+
throw $error;
124+
}
125+
$this->assertTrue($result);
126+
}
127+
128+
public function testTCPAdapterProtocolDetectionAllPorts(): void
129+
{
130+
$result = null;
131+
$error = null;
132+
133+
Coroutine\run(function () use (&$result, &$error): void {
134+
try {
135+
$portMap = [
136+
5432 => Protocol::PostgreSQL,
137+
3306 => Protocol::MySQL,
138+
27017 => Protocol::MongoDB,
139+
6379 => Protocol::Redis,
140+
11211 => Protocol::Memcached,
141+
9092 => Protocol::Kafka,
142+
5672 => Protocol::AMQP,
143+
9000 => Protocol::ClickHouse,
144+
9042 => Protocol::Cassandra,
145+
4222 => Protocol::NATS,
146+
1433 => Protocol::MSSQL,
147+
1521 => Protocol::Oracle,
148+
9200 => Protocol::Elasticsearch,
149+
1883 => Protocol::MQTT,
150+
50051 => Protocol::GRPC,
151+
2181 => Protocol::ZooKeeper,
152+
2379 => Protocol::Etcd,
153+
7687 => Protocol::Neo4j,
154+
11210 => Protocol::Couchbase,
155+
26257 => Protocol::CockroachDB,
156+
4000 => Protocol::TiDB,
157+
6650 => Protocol::Pulsar,
158+
21 => Protocol::FTP,
159+
389 => Protocol::LDAP,
160+
28015 => Protocol::RethinkDB,
161+
9999 => Protocol::TCP,
162+
];
163+
164+
foreach ($portMap as $port => $expected) {
165+
$adapter = new TCPAdapter(port: $port);
166+
$this->assertSame($expected, $adapter->getProtocol(), "Port {$port}");
167+
}
168+
169+
$result = true;
170+
} catch (\Throwable $e) {
171+
$error = $e;
172+
}
173+
});
174+
175+
if ($error !== null) {
176+
throw $error;
177+
}
178+
$this->assertTrue($result);
179+
}
180+
181+
public function testTCPConfigDefaults(): void
182+
{
183+
$config = new Config(ports: [5432]);
184+
185+
$this->assertSame('0.0.0.0', $config->host);
186+
$this->assertSame([5432], $config->ports);
187+
$this->assertSame(200_000, $config->maxConnections);
188+
$this->assertGreaterThan(0, $config->workers);
189+
$this->assertGreaterThan(0, $config->reactorNum);
190+
$this->assertFalse($config->sockmapEnabled);
191+
$this->assertSame('', $config->sockmapBpfObject);
192+
$this->assertFalse($config->isTlsEnabled());
193+
$this->assertNull($config->getTLSContext());
194+
}
195+
196+
public function testTCPConfigMultiplePorts(): void
197+
{
198+
$config = new Config(
199+
ports: [5432, 3306, 27017],
200+
workers: 4,
201+
reactorNum: 4,
202+
);
203+
204+
$this->assertSame([5432, 3306, 27017], $config->ports);
205+
$this->assertSame(4, $config->workers);
206+
$this->assertSame(4, $config->reactorNum);
207+
}
208+
209+
public function testTCPAdapterSettersChaining(): void
210+
{
211+
$adapter = new TCPAdapter(port: 5432);
212+
213+
$chain = $adapter
214+
->setTimeout(10.0)
215+
->setConnectTimeout(2.0)
216+
->setTcpUserTimeout(5000)
217+
->setTcpQuickAck(true)
218+
->setTcpNotsentLowat(16384)
219+
->setSkipValidation(true)
220+
->setCacheTTL(30);
221+
222+
$this->assertSame($adapter, $chain);
223+
}
224+
225+
public function testAdapterCacheTTLWithRouting(): void
226+
{
227+
$result = null;
228+
$error = null;
229+
230+
Coroutine\run(function () use (&$result, &$error): void {
231+
try {
232+
$resolver = new Fixed('10.0.0.1:5432');
233+
$adapter = new TCPAdapter(port: 5432, resolver: $resolver);
234+
$adapter->setSkipValidation(true);
235+
$adapter->setCacheTTL(60);
236+
237+
$first = $adapter->route('db-1');
238+
$this->assertFalse($first->metadata['cached']);
239+
240+
$second = $adapter->route('db-1');
241+
$this->assertTrue($second->metadata['cached']);
242+
$this->assertSame($first->endpoint, $second->endpoint);
243+
244+
$result = true;
245+
} catch (\Throwable $e) {
246+
$error = $e;
247+
}
248+
});
249+
250+
if ($error !== null) {
251+
throw $error;
252+
}
253+
$this->assertTrue($result);
254+
}
255+
}

0 commit comments

Comments
 (0)