From d76ef18b57190fa2a292f6db6e3d36c01cd1b78e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 16 May 2026 22:35:28 +1200 Subject: [PATCH] fix: provision and clean up alert records --- app/config/collections/platform.php | 9 +- src/Appwrite/Migration/Version/V24.php | 4 + src/Appwrite/Platform/Workers/Deletes.php | 11 +++ .../unit/Migration/MigrationVersionsTest.php | 47 ++++++++++ tests/unit/Platform/Workers/DeletesTest.php | 91 +++++++++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 tests/unit/Platform/Workers/DeletesTest.php diff --git a/app/config/collections/platform.php b/app/config/collections/platform.php index 43d31ee883..61e85a8f09 100644 --- a/app/config/collections/platform.php +++ b/app/config/collections/platform.php @@ -1261,9 +1261,16 @@ $platformCollections = [ '$id' => ID::custom('_key_projectInternalId'), 'type' => Database::INDEX_KEY, 'attributes' => ['projectInternalId'], - 'lengths' => [Database::LENGTH_KEY], + 'lengths' => [0], 'orders' => [Database::ORDER_ASC], ], + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectId', 'projectInternalId'], + 'lengths' => [Database::LENGTH_KEY, 0], + 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + ], [ '$id' => ID::custom('_key_project_resource'), 'type' => Database::INDEX_KEY, diff --git a/src/Appwrite/Migration/Version/V24.php b/src/Appwrite/Migration/Version/V24.php index 204a7a22e4..ffb83417ce 100644 --- a/src/Appwrite/Migration/Version/V24.php +++ b/src/Appwrite/Migration/Version/V24.php @@ -88,6 +88,10 @@ class V24 extends Migration $collections = $this->collections[$collectionType]; + if ($collectionType === 'console') { + $this->createCollection('alerts'); + } + foreach ($collections as $collection) { $id = $collection['$id']; diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index a58fc48098..78380abc87 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -732,6 +732,17 @@ class Deletes extends Action Console::error('Failed to delete schedules: ' . $th->getMessage()); } + // Delete Alerts + try { + $this->deleteByGroup('alerts', [ + Query::equal('projectId', [$projectId]), + Query::equal('projectInternalId', [$projectInternalId]), + Query::orderAsc() + ], $dbForPlatform); + } catch (Throwable $th) { + Console::error('Failed to delete alerts: ' . $th->getMessage()); + } + // Delete Advisor insights try { $this->deleteByGroup('insights', [ diff --git a/tests/unit/Migration/MigrationVersionsTest.php b/tests/unit/Migration/MigrationVersionsTest.php index 3c8bae7222..ba3a36aa5e 100644 --- a/tests/unit/Migration/MigrationVersionsTest.php +++ b/tests/unit/Migration/MigrationVersionsTest.php @@ -3,7 +3,14 @@ namespace Tests\Unit\Migration; use Appwrite\Migration\Migration; +use Appwrite\Migration\Version\V24; use PHPUnit\Framework\TestCase; +use Utopia\Cache\Adapter\None as NoCache; +use Utopia\Cache\Cache; +use Utopia\Database\Adapter\Memory; +use Utopia\Database\Database; +use Utopia\Database\Document; +use Utopia\Database\Validator\Authorization; class MigrationVersionsTest extends TestCase { @@ -24,4 +31,44 @@ class MigrationVersionsTest extends TestCase $this->assertArrayHasKey(APP_VERSION_STABLE, Migration::$versions); } } + + public function testV24CreatesAlertsCollectionForConsoleProject(): void + { + require_once __DIR__ . '/../../../app/init.php'; + + $authorization = new Authorization(); + $database = new Database(new Memory(), new Cache(new NoCache())); + $database + ->setAuthorization($authorization) + ->setDatabase('migrationV24') + ->setNamespace('migration_' . \uniqid()); + $database->create(); + + $migration = new V24(); + $migration->setProject( + new Document(['$id' => 'console', '$sequence' => 'console']), + $database, + $database, + $authorization, + ); + + $migrateCollections = new \ReflectionMethod($migration, 'migrateCollections'); + \ob_start(); + try { + $migrateCollections->invoke($migration); + } finally { + \ob_end_clean(); + } + + $collection = $database->getCollection('alerts'); + $this->assertFalse($collection->isEmpty()); + + $attributes = []; + foreach ($collection->getAttribute('attributes', []) as $attribute) { + $id = $attribute instanceof Document ? $attribute->getAttribute('$id') : ($attribute['$id'] ?? ''); + $attributes[$id] = $attribute; + } + $this->assertArrayHasKey('resourceInternalId', $attributes); + $this->assertArrayHasKey('parentResourceInternalId', $attributes); + } } diff --git a/tests/unit/Platform/Workers/DeletesTest.php b/tests/unit/Platform/Workers/DeletesTest.php new file mode 100644 index 0000000000..04941b422b --- /dev/null +++ b/tests/unit/Platform/Workers/DeletesTest.php @@ -0,0 +1,91 @@ + 'project-1', + '$sequence' => 'project-internal-1', + 'database' => 'mysql://localhost/appwrite', + ]); + + $worker = new class () extends Deletes { + /** + * @var array> + */ + public array $groups = []; + + public function runProjectDelete( + Database $database, + Document $project, + callable $getProjectDB, + callable $getDatabasesDB, + Device $device, + CertificatesAdapter $certificates, + ): void { + $this->deleteProject( + $database, + $getProjectDB, + $getDatabasesDB, + $device, + $device, + $device, + $device, + $device, + $certificates, + $project, + ); + } + + protected function deleteByGroup(string $collection, array $queries, Database $database, ?callable $callback = null): void + { + $this->groups[$collection] = $queries; + } + }; + $getProjectDB = static fn () => throw new \RuntimeException('stop'); + $getDatabasesDB = static fn () => $database; + + try { + $worker->runProjectDelete( + $database, + $project, + $getProjectDB, + $getDatabasesDB, + $this->createStub(Device::class), + $this->createStub(CertificatesAdapter::class), + ); + } catch (\RuntimeException $exception) { + $this->assertSame('stop', $exception->getMessage()); + } + + $this->assertArrayHasKey('alerts', $worker->groups); + + $queries = $worker->groups['alerts']; + $this->assertSame(Query::TYPE_EQUAL, $queries[0]->getMethod()); + $this->assertSame('projectId', $queries[0]->getAttribute()); + $this->assertSame(['project-1'], $queries[0]->getValues()); + + $this->assertSame(Query::TYPE_EQUAL, $queries[1]->getMethod()); + $this->assertSame('projectInternalId', $queries[1]->getAttribute()); + $this->assertSame(['project-internal-1'], $queries[1]->getValues()); + + $this->assertSame(Query::TYPE_ORDER_ASC, $queries[2]->getMethod()); + } +}