tests: add Appwrite->Appwrite row onDuplicate test with tolerant poller

This commit is contained in:
Prem Palanisamy
2026-04-20 16:38:59 +01:00
parent 187d0c0cf4
commit d0603c4d28
@@ -762,8 +762,12 @@ trait MigrationsBase
}
/**
* Appwrite → Appwrite row migration honoring onDuplicate=skip and onDuplicate=upsert.
* Exercises the row-buffer dispatch path via cross-project migration rather than CSV/JSON upload.
* Appwrite → Appwrite row re-migration honoring onDuplicate=skip and onDuplicate=upsert.
*
* onDuplicate only gates the row-write path in DestinationAppwrite. Re-running the
* migration with the full resource tree (database/table/column/row) always errors
* on schema creation because destination already has those. This test accepts those
* schema-level errors as expected noise and asserts row-level correctness directly.
*/
public function testAppwriteMigrationRowsOnDuplicate(): void
{
@@ -778,7 +782,6 @@ trait MigrationsBase
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
];
// Source setup: database + table + column + row
$data = $this->setupMigrationTable();
$databaseId = $data['databaseId'];
$tableId = $data['tableId'];
@@ -797,7 +800,7 @@ trait MigrationsBase
Resource::TYPE_ROW,
];
// First migration: destination is empty, all resources copied
// First migration: destination is empty, strict completion expected.
$first = $this->performMigrationSync([
'resources' => $resources,
'endpoint' => $this->webEndpoint,
@@ -806,51 +809,80 @@ trait MigrationsBase
]);
$this->assertEquals('completed', $first['status']);
// Mutate destination row so we can prove skip preserves it
// Mutate destination row to prove onDuplicate=skip preserves it.
$mutate = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders, [
'data' => ['name' => 'Mutated'],
]);
$this->assertEquals(200, $mutate['headers']['status-code']);
$this->assertEquals('Mutated', $mutate['body']['name']);
// Second migration with onDuplicate=skip: destination row must keep 'Mutated'
$second = $this->performMigrationSync([
// Re-migration with onDuplicate=skip. Overall status is expected to be 'failed'
// because schema re-create errors (database/table/column already exist) — those
// are orthogonal to onDuplicate which only affects row writes. Assert row-level
// success counter instead.
$this->runMigrationAssertingRowSuccess([
'resources' => $resources,
'endpoint' => $this->webEndpoint,
'projectId' => $this->getProject()['$id'],
'apiKey' => $this->getProject()['apiKey'],
'onDuplicate' => 'skip',
]);
$this->assertEquals('completed', $second['status']);
$rowAfterSkip = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders);
$this->assertEquals(200, $rowAfterSkip['headers']['status-code']);
$this->assertEquals('Mutated', $rowAfterSkip['body']['name'], 'onDuplicate=skip must not overwrite destination row');
// Third migration with onDuplicate=upsert: destination row must be restored to 'Original'
$third = $this->performMigrationSync([
// Re-migration with onDuplicate=upsert. Same status-tolerant approach; assert
// destination row was restored to source value.
$this->runMigrationAssertingRowSuccess([
'resources' => $resources,
'endpoint' => $this->webEndpoint,
'projectId' => $this->getProject()['$id'],
'apiKey' => $this->getProject()['apiKey'],
'onDuplicate' => 'upsert',
]);
$this->assertEquals('completed', $third['status']);
$rowAfterUpsert = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, $destHeaders);
$this->assertEquals(200, $rowAfterUpsert['headers']['status-code']);
$this->assertEquals('Original', $rowAfterUpsert['body']['name'], 'onDuplicate=upsert must restore source value');
// Cleanup on destination
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $destHeaders);
// Cleanup on source
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, $sourceHeaders);
self::$cachedDatabaseData = [];
self::$cachedTableData = [];
}
/**
* Helper for Appwrite→Appwrite re-migrations where onDuplicate applies only to rows.
* Accepts migration stages of 'finished' regardless of overall status, then asserts
* the row-level counter has zero errors (and at least one success).
*
* @param array<string,mixed> $body
*/
private function runMigrationAssertingRowSuccess(array $body): void
{
$migration = $this->client->call(Client::METHOD_POST, '/migrations/appwrite', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getDestinationProject()['$id'],
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
], $body);
$this->assertEquals(202, $migration['headers']['status-code']);
$this->assertEventually(function () use ($migration) {
$response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migration['body']['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getDestinationProject()['$id'],
'x-appwrite-key' => $this->getDestinationProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('finished', $response['body']['stage']);
$this->assertArrayHasKey(Resource::TYPE_ROW, $response['body']['statusCounters']);
$this->assertEquals(0, $response['body']['statusCounters'][Resource::TYPE_ROW]['error']);
$this->assertGreaterThanOrEqual(1, $response['body']['statusCounters'][Resource::TYPE_ROW]['success']);
}, 60_000, 500);
}
/**
* Storage
*/