|
3 | 3 | namespace Utopia\Tests; |
4 | 4 |
|
5 | 5 | use PHPUnit\Framework\TestCase; |
6 | | -use Utopia\Platform\Action; |
7 | 6 | use Utopia\Proxy\Adapter\HTTP\Swoole as HTTPAdapter; |
8 | 7 | use Utopia\Proxy\Adapter\SMTP\Swoole as SMTPAdapter; |
9 | 8 | use Utopia\Proxy\Adapter\TCP\Swoole as TCPAdapter; |
10 | | -use Utopia\Proxy\Service\HTTP as HTTPService; |
11 | | -use Utopia\Proxy\Service\SMTP as SMTPService; |
12 | | -use Utopia\Proxy\Service\TCP as TCPService; |
| 9 | +use Utopia\Proxy\Resolver\Exception as ResolverException; |
13 | 10 |
|
14 | 11 | class AdapterActionsTest extends TestCase |
15 | 12 | { |
| 13 | + protected MockResolver $resolver; |
| 14 | + |
16 | 15 | protected function setUp(): void |
17 | 16 | { |
18 | | - if (!\extension_loaded('swoole')) { |
| 17 | + if (! \extension_loaded('swoole')) { |
19 | 18 | $this->markTestSkipped('ext-swoole is required to run adapter tests.'); |
20 | 19 | } |
| 20 | + |
| 21 | + $this->resolver = new MockResolver(); |
21 | 22 | } |
22 | 23 |
|
23 | | - public function testDefaultServicesAreAssigned(): void |
| 24 | + public function test_resolver_is_assigned_to_adapters(): void |
24 | 25 | { |
25 | | - $http = new HTTPAdapter(); |
26 | | - $tcp = new TCPAdapter(port: 5432); |
27 | | - $smtp = new SMTPAdapter(); |
| 26 | + $http = new HTTPAdapter($this->resolver); |
| 27 | + $tcp = new TCPAdapter($this->resolver, port: 5432); |
| 28 | + $smtp = new SMTPAdapter($this->resolver); |
28 | 29 |
|
29 | | - $this->assertInstanceOf(HTTPService::class, $http->getService()); |
30 | | - $this->assertInstanceOf(TCPService::class, $tcp->getService()); |
31 | | - $this->assertInstanceOf(SMTPService::class, $smtp->getService()); |
| 30 | + $this->assertSame($this->resolver, $http->getResolver()); |
| 31 | + $this->assertSame($this->resolver, $tcp->getResolver()); |
| 32 | + $this->assertSame($this->resolver, $smtp->getResolver()); |
32 | 33 | } |
33 | 34 |
|
34 | | - public function testResolveActionRoutesAndRunsLifecycleActions(): void |
| 35 | + public function test_resolve_routes_and_returns_endpoint(): void |
35 | 36 | { |
36 | | - $adapter = new HTTPAdapter(); |
37 | | - $service = new HTTPService(); |
38 | | - |
39 | | - $initHost = null; |
40 | | - $shutdownEndpoint = null; |
41 | | - |
42 | | - $service->addAction('resolve', (new class extends Action {}) |
43 | | - ->callback(function (string $hostname): string { |
44 | | - return "127.0.0.1:8080"; |
45 | | - })); |
46 | | - |
47 | | - $service->addAction('beforeRoute', (new class extends Action {}) |
48 | | - ->setType(Action::TYPE_INIT) |
49 | | - ->callback(function (string $hostname) use (&$initHost) { |
50 | | - $initHost = $hostname; |
51 | | - })); |
52 | | - |
53 | | - $service->addAction('afterRoute', (new class extends Action {}) |
54 | | - ->setType(Action::TYPE_SHUTDOWN) |
55 | | - ->callback(function (string $hostname, string $endpoint, $result) use (&$shutdownEndpoint) { |
56 | | - $shutdownEndpoint = $endpoint; |
57 | | - })); |
58 | | - |
59 | | - $adapter->setService($service); |
| 37 | + $this->resolver->setEndpoint('127.0.0.1:8080'); |
| 38 | + $adapter = new HTTPAdapter($this->resolver); |
| 39 | + $adapter->setSkipValidation(true); |
60 | 40 |
|
61 | 41 | $result = $adapter->route('api.example.com'); |
62 | 42 |
|
63 | 43 | $this->assertSame('127.0.0.1:8080', $result->endpoint); |
64 | | - $this->assertSame('api.example.com', $initHost); |
65 | | - $this->assertSame('127.0.0.1:8080', $shutdownEndpoint); |
| 44 | + $this->assertSame('http', $result->protocol); |
66 | 45 | } |
67 | 46 |
|
68 | | - public function testErrorActionRunsOnRoutingFailure(): void |
| 47 | + public function test_notify_connect_delegates_to_resolver(): void |
69 | 48 | { |
70 | | - $adapter = new HTTPAdapter(); |
71 | | - $service = new HTTPService(); |
72 | | - |
73 | | - $errorMessage = null; |
74 | | - $errorHost = null; |
75 | | - |
76 | | - $service->addAction('resolve', (new class extends Action {}) |
77 | | - ->callback(function (string $hostname): string { |
78 | | - throw new \Exception("No backend"); |
79 | | - })); |
80 | | - |
81 | | - $service->addAction('onRoutingError', (new class extends Action {}) |
82 | | - ->setType(Action::TYPE_ERROR) |
83 | | - ->callback(function (string $hostname, \Exception $e) use (&$errorMessage, &$errorHost) { |
84 | | - $errorHost = $hostname; |
85 | | - $errorMessage = $e->getMessage(); |
86 | | - })); |
87 | | - |
88 | | - $adapter->setService($service); |
89 | | - |
90 | | - try { |
91 | | - $adapter->route('api.example.com'); |
92 | | - $this->fail('Expected routing error was not thrown.'); |
93 | | - } catch (\Exception $e) { |
94 | | - $this->assertSame('No backend', $e->getMessage()); |
95 | | - } |
| 49 | + $adapter = new HTTPAdapter($this->resolver); |
| 50 | + |
| 51 | + $adapter->notifyConnect('resource-123', ['extra' => 'data']); |
96 | 52 |
|
97 | | - $this->assertSame('api.example.com', $errorHost); |
98 | | - $this->assertSame('No backend', $errorMessage); |
| 53 | + $connects = $this->resolver->getConnects(); |
| 54 | + $this->assertCount(1, $connects); |
| 55 | + $this->assertSame('resource-123', $connects[0]['resourceId']); |
| 56 | + $this->assertSame(['extra' => 'data'], $connects[0]['metadata']); |
99 | 57 | } |
100 | 58 |
|
101 | | - public function testMissingResolveActionThrows(): void |
| 59 | + public function test_notify_close_delegates_to_resolver(): void |
102 | 60 | { |
103 | | - $adapter = new HTTPAdapter(); |
104 | | - $adapter->setService(new HTTPService()); |
| 61 | + $adapter = new HTTPAdapter($this->resolver); |
105 | 62 |
|
106 | | - $this->expectException(\Exception::class); |
107 | | - $this->expectExceptionMessage('No resolve action registered'); |
| 63 | + $adapter->notifyClose('resource-123', ['extra' => 'data']); |
108 | 64 |
|
109 | | - $adapter->route('api.example.com'); |
| 65 | + $disconnects = $this->resolver->getDisconnects(); |
| 66 | + $this->assertCount(1, $disconnects); |
| 67 | + $this->assertSame('resource-123', $disconnects[0]['resourceId']); |
| 68 | + $this->assertSame(['extra' => 'data'], $disconnects[0]['metadata']); |
110 | 69 | } |
111 | 70 |
|
112 | | - public function testResolveActionRejectsEmptyEndpoint(): void |
| 71 | + public function test_track_activity_delegates_to_resolver_with_throttling(): void |
113 | 72 | { |
114 | | - $adapter = new HTTPAdapter(); |
115 | | - $service = new HTTPService(); |
| 73 | + $adapter = new HTTPAdapter($this->resolver); |
| 74 | + $adapter->setActivityInterval(1); // 1 second throttle |
116 | 75 |
|
117 | | - $service->addAction('resolve', (new class extends Action {}) |
118 | | - ->callback(function (string $hostname): string { |
119 | | - return ''; |
120 | | - })); |
| 76 | + // First call should trigger activity tracking |
| 77 | + $adapter->trackActivity('resource-123'); |
| 78 | + $this->assertCount(1, $this->resolver->getActivities()); |
121 | 79 |
|
122 | | - $adapter->setService($service); |
| 80 | + // Immediate second call should be throttled |
| 81 | + $adapter->trackActivity('resource-123'); |
| 82 | + $this->assertCount(1, $this->resolver->getActivities()); |
123 | 83 |
|
124 | | - $this->expectException(\Exception::class); |
125 | | - $this->expectExceptionMessage('Resolve action returned empty endpoint'); |
| 84 | + // Wait for throttle interval to pass |
| 85 | + sleep(2); |
126 | 86 |
|
127 | | - $adapter->route('api.example.com'); |
| 87 | + // Third call should trigger activity tracking |
| 88 | + $adapter->trackActivity('resource-123'); |
| 89 | + $this->assertCount(2, $this->resolver->getActivities()); |
128 | 90 | } |
129 | 91 |
|
130 | | - public function testInitActionsRunInRegistrationOrder(): void |
| 92 | + public function test_routing_error_throws_exception(): void |
131 | 93 | { |
132 | | - $adapter = new HTTPAdapter(); |
133 | | - $service = new HTTPService(); |
| 94 | + $this->resolver->setException(new ResolverException('No backend found')); |
| 95 | + $adapter = new HTTPAdapter($this->resolver); |
134 | 96 |
|
135 | | - $calls = []; |
| 97 | + $this->expectException(ResolverException::class); |
| 98 | + $this->expectExceptionMessage('No backend found'); |
136 | 99 |
|
137 | | - $service->addAction('resolve', (new class extends Action {}) |
138 | | - ->callback(function (string $hostname): string { |
139 | | - return '127.0.0.1:8080'; |
140 | | - })); |
| 100 | + $adapter->route('api.example.com'); |
| 101 | + } |
141 | 102 |
|
142 | | - $service->addAction('first', (new class extends Action {}) |
143 | | - ->setType(Action::TYPE_INIT) |
144 | | - ->callback(function () use (&$calls) { |
145 | | - $calls[] = 'first'; |
146 | | - })); |
| 103 | + public function test_empty_endpoint_throws_exception(): void |
| 104 | + { |
| 105 | + $this->resolver->setEndpoint(''); |
| 106 | + $adapter = new HTTPAdapter($this->resolver); |
147 | 107 |
|
148 | | - $service->addAction('second', (new class extends Action {}) |
149 | | - ->setType(Action::TYPE_INIT) |
150 | | - ->callback(function () use (&$calls) { |
151 | | - $calls[] = 'second'; |
152 | | - })); |
| 108 | + $this->expectException(ResolverException::class); |
| 109 | + $this->expectExceptionMessage('Resolver returned empty endpoint'); |
153 | 110 |
|
154 | | - $adapter->setService($service); |
155 | 111 | $adapter->route('api.example.com'); |
| 112 | + } |
| 113 | + |
| 114 | + public function test_skip_validation_allows_private_i_ps(): void |
| 115 | + { |
| 116 | + // 10.0.0.1 is a private IP that would normally be blocked |
| 117 | + $this->resolver->setEndpoint('10.0.0.1:8080'); |
| 118 | + $adapter = new HTTPAdapter($this->resolver); |
| 119 | + $adapter->setSkipValidation(true); |
156 | 120 |
|
157 | | - $this->assertSame(['first', 'second'], $calls); |
| 121 | + // Should not throw exception with validation disabled |
| 122 | + $result = $adapter->route('api.example.com'); |
| 123 | + $this->assertSame('10.0.0.1:8080', $result->endpoint); |
158 | 124 | } |
159 | 125 | } |
0 commit comments