mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
tests: pin spec-match guard + bump migration to c8d1789
testAppwriteMigrationUpsertSameSpecRecreateTolerates exercises the new spec-match guard added in utopia-php/migration c8d1789. Source drops + recreates a column with the EXACT same spec as before; createdAt advances but specs match → action is forced to Tolerate. Asserts dest column's $createdAt stays at first-migration value (proving Tolerate, not DropAndRecreate). Row pass under Upsert still propagates source's new row value. Companion to testAppwriteMigrationUpsertAttributeRecreateDropsAndRecreates which exercises the spec-DIFFERS path: same precondition (drop + recreate), different outcome (DropAndRecreate vs Tolerate) gated on spec equality. composer.lock: utopia-php/migration 24fd23b -> c8d1789 (spec-match guard).
This commit is contained in:
Generated
+4
-4
@@ -4532,12 +4532,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "24fd23b676ee7c7163e583c8559f362471940954"
|
||||
"reference": "c8d17898458de4a297ba03fd1a126f7aa13227af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/24fd23b676ee7c7163e583c8559f362471940954",
|
||||
"reference": "24fd23b676ee7c7163e583c8559f362471940954",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c8d17898458de4a297ba03fd1a126f7aa13227af",
|
||||
"reference": "c8d17898458de4a297ba03fd1a126f7aa13227af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4579,7 +4579,7 @@
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/feat/skip-duplicates"
|
||||
},
|
||||
"time": "2026-04-28T02:44:46+00:00"
|
||||
"time": "2026-04-28T03:12:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
|
||||
@@ -1990,6 +1990,120 @@ trait MigrationsBase
|
||||
self::$cachedTableData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Source drops + recreates a column with the EXACT same spec. createdAt
|
||||
* advances on source, but the spec-match guard short-circuits the
|
||||
* DropAndRecreate to Tolerate — dest's column meta doc stays untouched
|
||||
* (verified via $createdAt invariance). Row pass under Upsert still
|
||||
* propagates source's new row values via upsertDocuments.
|
||||
*
|
||||
* Companion to testAppwriteMigrationUpsertAttributeRecreateDropsAndRecreates
|
||||
* which exercises the spec-DIFFERS path: same precondition, different
|
||||
* outcome based on whether spec matches.
|
||||
*/
|
||||
public function testAppwriteMigrationUpsertSameSpecRecreateTolerates(): void
|
||||
{
|
||||
$sourceHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
];
|
||||
$destHeaders = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getDestinationProject()['$id'],
|
||||
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
|
||||
];
|
||||
|
||||
$data = $this->setupMigrationTable();
|
||||
$databaseId = $data['databaseId'];
|
||||
$tableId = $data['tableId'];
|
||||
$rowId = 'row-spec-match';
|
||||
|
||||
$this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', $sourceHeaders, [
|
||||
'rowId' => $rowId,
|
||||
'data' => ['name' => 'before-recreate'],
|
||||
]);
|
||||
|
||||
$resources = [
|
||||
Resource::TYPE_DATABASE,
|
||||
Resource::TYPE_TABLE,
|
||||
Resource::TYPE_COLUMN,
|
||||
Resource::TYPE_ROW,
|
||||
];
|
||||
|
||||
$first = $this->performMigrationSync([
|
||||
'resources' => $resources,
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
$this->assertEquals('completed', $first['status']);
|
||||
|
||||
$destBefore = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $destHeaders);
|
||||
$this->assertEquals(200, $destBefore['headers']['status-code']);
|
||||
$destCreatedAtBefore = $destBefore['body']['$createdAt'];
|
||||
|
||||
sleep(1);
|
||||
|
||||
// Drop + recreate with the EXACT same spec as setupMigrationTable
|
||||
// (size=100, required=true). Source's $createdAt advances but the
|
||||
// spec is identical → spec-match guard must force Tolerate.
|
||||
$delete = $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $sourceHeaders);
|
||||
$this->assertEquals(204, $delete['headers']['status-code']);
|
||||
|
||||
$this->assertEventually(function () use ($databaseId, $tableId, $sourceHeaders) {
|
||||
$r = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $sourceHeaders);
|
||||
$this->assertEquals(404, $r['headers']['status-code']);
|
||||
}, 10000, 500);
|
||||
|
||||
$recreate = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', $sourceHeaders, [
|
||||
'key' => 'name',
|
||||
'size' => 100,
|
||||
'required' => true,
|
||||
]);
|
||||
$this->assertEquals(202, $recreate['headers']['status-code']);
|
||||
|
||||
$this->assertEventually(function () use ($databaseId, $tableId, $sourceHeaders) {
|
||||
$r = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $sourceHeaders);
|
||||
$this->assertEquals(200, $r['headers']['status-code']);
|
||||
$this->assertEquals('available', $r['body']['status']);
|
||||
}, 10000, 500);
|
||||
|
||||
$relink = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $sourceHeaders, [
|
||||
'data' => ['name' => 'after-recreate'],
|
||||
]);
|
||||
$this->assertEquals(200, $relink['headers']['status-code']);
|
||||
|
||||
$upsertResult = $this->performMigrationSync([
|
||||
'resources' => $resources,
|
||||
'endpoint' => $this->webEndpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
'onDuplicate' => 'upsert',
|
||||
]);
|
||||
$this->assertEquals('completed', $upsertResult['status']);
|
||||
|
||||
// Spec-match guard fired → dest column's $createdAt stayed at the
|
||||
// first-migration value. If DropAndRecreate had run, $createdAt
|
||||
// would have been bumped to source's NEW createdAt.
|
||||
$destAfter = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name', $destHeaders);
|
||||
$this->assertEquals(200, $destAfter['headers']['status-code']);
|
||||
$this->assertEquals($destCreatedAtBefore, $destAfter['body']['$createdAt'], 'spec-match guard must keep dest column meta untouched');
|
||||
$this->assertEquals(100, $destAfter['body']['size']);
|
||||
$this->assertTrue($destAfter['body']['required']);
|
||||
|
||||
// Row pass under Upsert still propagated source's new row value.
|
||||
$rowAfter = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders);
|
||||
$this->assertEquals(200, $rowAfter['headers']['status-code']);
|
||||
$this->assertEquals('after-recreate', $rowAfter['body']['name']);
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $destHeaders);
|
||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $sourceHeaders);
|
||||
|
||||
self::$cachedDatabaseData = [];
|
||||
self::$cachedTableData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user