diff --git a/app/init/registers.php b/app/init/registers.php index 415730f936..3dc0e22dba 100644 --- a/app/init/registers.php +++ b/app/init/registers.php @@ -36,7 +36,8 @@ App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); if (!App::isProduction()) { // Allow specific domains to skip public domain validation in dev environment // Useful for existing tests involving webhooks - PublicDomain::allow(['request-catcher']); + PublicDomain::allow(['request-catcher-sms']); + PublicDomain::allow(['request-catcher-webhook']); } $register->set('logger', function () { // Register error logger diff --git a/composer.lock b/composer.lock index fd6a322076..bd6b0e959e 100644 --- a/composer.lock +++ b/composer.lock @@ -1463,16 +1463,16 @@ }, { "name": "open-telemetry/sem-conv", - "version": "1.32.0", + "version": "1.32.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sem-conv.git", - "reference": "16585cc0dbc3032a318e274043454679430d2ebf" + "reference": "94daa85ea61a8e2b7e1b0af6be0e875bedda7c22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/16585cc0dbc3032a318e274043454679430d2ebf", - "reference": "16585cc0dbc3032a318e274043454679430d2ebf", + "url": "https://api.github.com/repos/opentelemetry-php/sem-conv/zipball/94daa85ea61a8e2b7e1b0af6be0e875bedda7c22", + "reference": "94daa85ea61a8e2b7e1b0af6be0e875bedda7c22", "shasum": "" }, "require": { @@ -1516,7 +1516,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-05T03:58:53+00:00" + "time": "2025-06-24T02:32:27+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -2327,21 +2327,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -2400,9 +2399,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "spomky-labs/otphp", @@ -3494,16 +3493,16 @@ }, { "name": "utopia-php/database", - "version": "0.71.7", + "version": "0.71.8", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "aa0116b2380125907fc18c82662be8e74c54091f" + "reference": "7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/aa0116b2380125907fc18c82662be8e74c54091f", - "reference": "aa0116b2380125907fc18c82662be8e74c54091f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e", + "reference": "7dff6b67a54f1a7f9d3f210db4c6e40d7052b79e", "shasum": "" }, "require": { @@ -3544,9 +3543,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.71.7" + "source": "https://github.com/utopia-php/database/tree/0.71.8" }, - "time": "2025-06-17T23:59:10+00:00" + "time": "2025-06-26T14:48:17+00:00" }, { "name": "utopia-php/detector", @@ -3943,16 +3942,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.18.0", + "version": "0.18.1", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6" + "reference": "5d1245207a61d7ca065daddad7ac5f1d5640152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/c151aa5d4d475c788ca15c210b5b2017e21c41d6", - "reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/5d1245207a61d7ca065daddad7ac5f1d5640152f", + "reference": "5d1245207a61d7ca065daddad7ac5f1d5640152f", "shasum": "" }, "require": { @@ -3988,9 +3987,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.18.0" + "source": "https://github.com/utopia-php/messaging/tree/0.18.1" }, - "time": "2025-05-15T05:00:03+00:00" + "time": "2025-06-26T18:26:07+00:00" }, { "name": "utopia-php/migration", @@ -8237,7 +8236,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8261,5 +8260,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml index ecb46de057..f5ac47a832 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -323,7 +323,8 @@ services: depends_on: - redis - mariadb - - request-catcher + - request-catcher-sms + - request-catcher-webhook environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -1075,15 +1076,24 @@ services: networks: - appwrite - request-catcher: # used mainly for dev tests + request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook) image: appwrite/requestcatcher:1.0.0 - container_name: appwrite-requestcatcher + container_name: appwrite-requestcatcher-webhook <<: *x-logging ports: - "9504:5000" networks: - appwrite + request-catcher-sms: # used mainly for dev tests (mock SMS auth secret) + image: appwrite/requestcatcher:1.0.0 + container_name: appwrite-requestcatcher-sms + <<: *x-logging + ports: + - "9507:5000" + networks: + - appwrite + adminer: image: adminer container_name: appwrite-adminer diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 35733200c7..668993fdae 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -426,7 +426,7 @@ class Messaging extends Action $credentials = $provider->getAttribute('credentials'); return match ($provider->getAttribute('provider')) { - 'mock' => new Mock('username', 'password'), + 'mock' => (new Mock('username', 'password'))->setEndpoint('http://request-catcher-sms:5000/'), 'twilio' => new Twilio( $credentials['accountSid'] ?? '', $credentials['authToken'] ?? '', diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index e0e40f0169..df4c5fd8f5 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -188,9 +188,9 @@ class Executor array $headers, float $cpus, int $memory, - string $runtimeEntrypoint = '', bool $logging, - int $requestTimeout = null + string $runtimeEntrypoint = '', + ?int $requestTimeout = null ) { if (empty($headers['host'])) { $headers['host'] = System::getEnv('_APP_DOMAIN', ''); diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 620409bb39..bce6e7206f 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -60,7 +60,7 @@ class HTTPTest extends Scope 'origin' => 'http://localhost', ])); - $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(200, $response['headers']['status-code'], "Simple GET /robots.txt HTTP request failed: " . \json_encode($response)); $this->assertStringContainsString('# robotstxt.org/', $response['body']); } diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index a354696f53..c5152adbcc 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -140,7 +140,7 @@ trait ProjectCustom 'teams.*', 'users.*' ], - 'url' => 'http://request-catcher:5000/webhook', + 'url' => 'http://request-catcher-webhook:5000/', 'security' => false, ]); diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index dddc97e3a0..2ee0d0198e 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -13,6 +13,9 @@ abstract class Scope extends TestCase use Retryable; use Async; + public const REQUEST_TYPE_WEBHOOK = 'webhook'; + public const REQUEST_TYPE_SMS = 'sms'; + protected ?Client $client = null; protected string $endpoint = 'http://localhost/v1'; @@ -76,10 +79,16 @@ abstract class Scope extends TestCase return []; } - protected function assertLastRequest(callable $probe, $timeoutMs = 20_000, $waitMs = 500): array + protected function assertLastRequest(callable $probe, string $type, $timeoutMs = 20_000, $waitMs = 500): array { - $this->assertEventually(function () use (&$request, $probe) { - $request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); + $hostname = match ($type) { + 'webhook' => 'request-catcher-webhook', + 'sms' => 'request-catcher-sms', + default => throw new \Exception('Invalid request catcher type.'), + }; + + $this->assertEventually(function () use (&$request, $probe, $hostname) { + $request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true); $request['data'] = json_decode($request['data'], true); call_user_func($probe, $request); @@ -88,11 +97,16 @@ abstract class Scope extends TestCase return $request; } + /** + * @deprecated Use assertLastRequest instead. Used only historically in webhook tests + */ protected function getLastRequest(): array { + $hostname = 'request-catcher-webhook'; + sleep(2); - $request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); + $request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true); $request['data'] = json_decode($request['data'], true); return $request; diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 86c78d5d81..1b84ce7083 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2065,14 +2065,13 @@ class AccountCustomClientTest extends Scope $userId = $response['body']['userId']; $smsRequest = $this->assertLastRequest(function (array $request) use ($number) { - $this->assertEquals('http://request-catcher:5000/mock-sms', $request['url']); $this->assertEquals('Appwrite Mock Message Sender', $request['headers']['User-Agent']); $this->assertEquals('username', $request['headers']['X-Username']); $this->assertEquals('password', $request['headers']['X-Key']); $this->assertEquals('POST', $request['method']); $this->assertEquals('+123456789', $request['data']['from']); $this->assertEquals($number, $request['data']['to']); - }); + }, Scope::REQUEST_TYPE_SMS); $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; @@ -2416,13 +2415,21 @@ class AccountCustomClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); + $this->assertNotEmpty($response['body']['$createdAt']); $this->assertEmpty($response['body']['secret']); $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); - $smsRequest = $this->assertLastRequest(function ($request) { + $tokenCreatedAt = $response['body']['$createdAt']; + + $smsRequest = $this->assertLastRequest(function ($request) use ($tokenCreatedAt) { $this->assertArrayHasKey('data', $request); + $this->assertArrayHasKey('time', $request); $this->assertArrayHasKey('message', $request['data'], "Last request missing message: " . \json_encode($request)); - }); + + // Ensure we are not using token from last sms login + $tokenRecievedAt = $request['time']; + $this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt); + }, Scope::REQUEST_TYPE_SMS); /** * Test for FAILURE