From cd21140572a5fa0099c30a697bf35cfbcde8a049 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 13 Jan 2026 22:01:16 +1300 Subject: [PATCH] Fix request fetching --- composer.lock | 50 ++++---- docker-compose.yml | 4 +- tests/e2e/Scopes/Scope.php | 109 +++++++++++++++--- .../Account/AccountCustomClientTest.php | 56 ++++++--- 4 files changed, 162 insertions(+), 57 deletions(-) diff --git a/composer.lock b/composer.lock index 720f3047b1..ed25076f1a 100644 --- a/composer.lock +++ b/composer.lock @@ -756,16 +756,16 @@ }, { "name": "google/protobuf", - "version": "v4.33.3", + "version": "v4.33.4", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "281537d44d6c270606354e65bfa75a0969dbd629" + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/281537d44d6c270606354e65bfa75a0969dbd629", - "reference": "281537d44d6c270606354e65bfa75a0969dbd629", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/22d28025cda0d223a2e48c2e16c5284ecc9f5402", + "reference": "22d28025cda0d223a2e48c2e16c5284ecc9f5402", "shasum": "" }, "require": { @@ -794,9 +794,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.3" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.4" }, - "time": "2026-01-09T21:49:37+00:00" + "time": "2026-01-12T17:58:43+00:00" }, { "name": "league/csv", @@ -4267,16 +4267,16 @@ }, { "name": "utopia-php/framework", - "version": "0.33.35", + "version": "0.33.36", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "82b139fb04f30045db51b0d322224f222da32313" + "reference": "fd835ed77e1cdf327067ce4e650cce86304e7098" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313", - "reference": "82b139fb04f30045db51b0d322224f222da32313", + "url": "https://api.github.com/repos/utopia-php/http/zipball/fd835ed77e1cdf327067ce4e650cce86304e7098", + "reference": "fd835ed77e1cdf327067ce4e650cce86304e7098", "shasum": "" }, "require": { @@ -4309,9 +4309,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.35" + "source": "https://github.com/utopia-php/http/tree/0.33.36" }, - "time": "2025-12-12T08:33:52+00:00" + "time": "2026-01-12T07:32:29+00:00" }, { "name": "utopia-php/image", @@ -5014,22 +5014,22 @@ }, { "name": "utopia-php/swoole", - "version": "0.8.5", + "version": "0.8.6", "source": { "type": "git", "url": "https://github.com/utopia-php/swoole.git", - "reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58" + "reference": "14b00277c35a258cb263706fd4e05c50368feb4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/swoole/zipball/e42b6b8e44c457a7b35d8a857d7af1d67d667c58", - "reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/14b00277c35a258cb263706fd4e05c50368feb4f", + "reference": "14b00277c35a258cb263706fd4e05c50368feb4f", "shasum": "" }, "require": { "ext-swoole": "*", "php": ">=8.0", - "utopia-php/framework": "0.33.35" + "utopia-php/framework": "0.33.36" }, "require-dev": { "laravel/pint": "1.2.*", @@ -5059,9 +5059,9 @@ ], "support": { "issues": "https://github.com/utopia-php/swoole/issues", - "source": "https://github.com/utopia-php/swoole/tree/0.8.5" + "source": "https://github.com/utopia-php/swoole/tree/0.8.6" }, - "time": "2025-12-15T14:03:23+00:00" + "time": "2026-01-12T07:57:35+00:00" }, { "name": "utopia-php/system", @@ -5439,16 +5439,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.8.10", + "version": "1.8.11", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "28217b1d722ad2b217559c3a371eafd4d070614d" + "reference": "936404bbcbf4cd692bac102f2912b6c97ac87215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/28217b1d722ad2b217559c3a371eafd4d070614d", - "reference": "28217b1d722ad2b217559c3a371eafd4d070614d", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/936404bbcbf4cd692bac102f2912b6c97ac87215", + "reference": "936404bbcbf4cd692bac102f2912b6c97ac87215", "shasum": "" }, "require": { @@ -5484,9 +5484,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.8.10" + "source": "https://github.com/appwrite/sdk-generator/tree/1.8.11" }, - "time": "2026-01-09T11:40:35+00:00" + "time": "2026-01-12T08:41:56+00:00" }, { "name": "brianium/paratest", diff --git a/docker-compose.yml b/docker-compose.yml index 20c0ad8f79..2179a47288 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1211,7 +1211,7 @@ services: - MAILDEV_INCOMING_PASS=${_APP_SMTP_PASSWORD} request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook) - image: appwrite/requestcatcher:1.0.0 + image: appwrite/requestcatcher:1.1.0 container_name: appwrite-requestcatcher-webhook <<: *x-logging ports: @@ -1220,7 +1220,7 @@ services: - appwrite request-catcher-sms: # used mainly for dev tests (mock SMS auth secret) - image: appwrite/requestcatcher:1.0.0 + image: appwrite/requestcatcher:1.1.0 container_name: appwrite-requestcatcher-sms <<: *x-logging ports: diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index b41ea8fc76..1b597e7ddf 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -152,7 +152,7 @@ abstract class Scope extends TestCase } /** - * @deprecated Use assertLastRequest instead. Used only historically in webhook tests + * @deprecated Use getLastRequestForProject instead. Used only historically in webhook tests */ protected function getLastRequest(): array { @@ -163,31 +163,112 @@ abstract class Scope extends TestCase * Get the last webhook request for a specific project. * Polls with retry to handle parallel test race conditions. */ - protected function getLastRequestForProject(string $projectId, int $maxAttempts = 10, int $delayMs = 500): array - { - $hostname = 'request-catcher-webhook'; + protected function getLastRequestForProject( + string $projectId, + string $type = self::REQUEST_TYPE_WEBHOOK, + array $queryParams = [], + int $maxAttempts = 10, + int $delayMs = 500, + ?callable $probe = null + ): array { + $hostname = match ($type) { + self::REQUEST_TYPE_WEBHOOK => 'request-catcher-webhook', + self::REQUEST_TYPE_SMS => 'request-catcher-sms', + default => throw new \Exception('Invalid request catcher type.'), + }; + $enforceProjectId = $type === self::REQUEST_TYPE_WEBHOOK; + + if (empty($queryParams)) { + $queryParams = [ + 'header_X-Appwrite-Webhook-Project-Id' => $projectId, + ]; + } + + $query = http_build_query($queryParams); sleep(2); for ($attempt = 0; $attempt < $maxAttempts; $attempt++) { - $request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true); - if ($request) { - $request['data'] = json_decode($request['data'], true); + $requests = json_decode(file_get_contents('http://' . $hostname . ':5000/__find_request__?' . $query), true); + if (is_array($requests)) { + for ($i = count($requests) - 1; $i >= 0; $i--) { + $request = $this->decodeRequestData($requests[$i]); + if ($probe !== null) { + try { + $probe($request); + return $request; + } catch (\Throwable $error) { + continue; + } + } - $requestProjectId = $request['headers']['X-Appwrite-Webhook-Project-Id'] ?? ''; - if ($requestProjectId === $projectId) { - return $request; + if ($enforceProjectId) { + $requestProjectId = $request['headers']['X-Appwrite-Webhook-Project-Id'] ?? ''; + if ($requestProjectId === $projectId) { + return $request; + } + } else { + return $request; + } } } usleep($delayMs * 1000); } - $request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true); - if ($request) { - $request['data'] = json_decode($request['data'], true); + $requests = json_decode(file_get_contents('http://' . $hostname . ':5000/__find_request__?' . $query), true); + if (is_array($requests)) { + for ($i = count($requests) - 1; $i >= 0; $i--) { + $request = $this->decodeRequestData($requests[$i]); + if ($probe !== null) { + try { + $probe($request); + return $request; + } catch (\Throwable $error) { + continue; + } + } + + if ($enforceProjectId) { + $requestProjectId = $request['headers']['X-Appwrite-Webhook-Project-Id'] ?? ''; + if ($requestProjectId === $projectId) { + return $request; + } + } else { + return $request; + } + } } - return $request ?? []; + + return []; + } + + protected function decodeRequestData(array $request): array + { + if (!array_key_exists('data', $request)) { + return $request; + } + + if (is_array($request['data'])) { + return $request; + } + + if (!is_string($request['data']) || $request['data'] === '') { + return $request; + } + + $decoded = json_decode($request['data'], true); + if (json_last_error() === JSON_ERROR_NONE) { + $request['data'] = $decoded; + return $request; + } + + parse_str($request['data'], $parsed); + if (!empty($parsed)) { + $request['data'] = $parsed; + } + + return $request; } /** diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 773cf2edb9..b0d95450d1 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2530,14 +2530,23 @@ class AccountCustomClientTest extends Scope $userId = $response['body']['userId']; - $smsRequest = $this->assertLastRequest(function (array $request) use ($number) { - $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); + $smsRequest = $this->getLastRequestForProject( + $this->getProject()['$id'], + Scope::REQUEST_TYPE_SMS, + [ + 'header_X-Username' => 'username', + 'header_X-Key' => 'password', + 'method' => 'POST', + ], + probe: function (array $request) use ($number) { + $this->assertEquals('Appwrite Mock Message Sender', $request['headers']['User-Agent'] ?? null); + $this->assertEquals('username', $request['headers']['X-Username'] ?? null); + $this->assertEquals('password', $request['headers']['X-Key'] ?? null); + $this->assertEquals('POST', $request['method'] ?? null); + $this->assertEquals('+123456789', $request['data']['from'] ?? null); + $this->assertEquals($number, $request['data']['to'] ?? null); + } + ); $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; @@ -2887,15 +2896,30 @@ class AccountCustomClientTest extends Scope $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)); + $phone = $data['phone'] ?? ''; + $smsQuery = [ + 'header_X-Username' => 'username', + 'header_X-Key' => 'password', + 'method' => 'POST', + ]; - // Ensure we are not using token from last sms login - $tokenRecievedAt = $request['time']; - $this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt); - }, Scope::REQUEST_TYPE_SMS); + $smsRequest = $this->getLastRequestForProject( + $this->getProject()['$id'], + Scope::REQUEST_TYPE_SMS, + $smsQuery, + probe: function (array $request) use ($tokenCreatedAt, $phone) { + $this->assertArrayHasKey('data', $request); + $this->assertArrayHasKey('time', $request); + $this->assertArrayHasKey('message', $request['data'], "Last request missing message: " . \json_encode($request)); + if (!empty($phone)) { + $this->assertEquals($phone, $request['data']['to'] ?? null); + } + + // Ensure we are not using token from last sms login + $tokenRecievedAt = $request['time']; + $this->assertGreaterThan($tokenCreatedAt, $tokenRecievedAt); + } + ); /** * Test for FAILURE