115 lines
5.2 KiB
TypeScript
115 lines
5.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const fixture = JSON.parse(fs.readFileSync(
|
|
path.resolve(import.meta.dirname, "../../../tests/golden/M12-01D/catalog-v1.json"),
|
|
"utf8",
|
|
)) as unknown;
|
|
|
|
test("M12-01G aborts catalog migration without changing the old IndexedDB index", async ({ page }) => {
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async (source) => {
|
|
const storage = await import("/src/storage/asset-catalog-indexeddb.ts");
|
|
const open = (name: string): Promise<IDBDatabase> => new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(name, 1);
|
|
request.onupgradeneeded = () => {
|
|
request.result.createObjectStore("setting", { keyPath: "id" });
|
|
request.result.createObjectStore("migration", { keyPath: "id" });
|
|
};
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
const complete = (transaction: IDBTransaction): Promise<void> => new Promise((resolve, reject) => {
|
|
transaction.oncomplete = () => resolve();
|
|
transaction.onerror = () => reject(transaction.error);
|
|
transaction.onabort = () => reject(transaction.error);
|
|
});
|
|
const seed = async (database: IDBDatabase): Promise<void> => {
|
|
const transaction = database.transaction(["setting", "migration"], "readwrite");
|
|
transaction.objectStore("setting").put({ id: storage.ASSET_CATALOG_INDEX_V1_ID, value: source });
|
|
transaction.objectStore("migration").put({ id: "schema-legacy", version: 1, marker: "unchanged" });
|
|
await complete(transaction);
|
|
};
|
|
const get = (store: IDBObjectStore, id: string): Promise<unknown> => new Promise((resolve, reject) => {
|
|
const request = store.get(id);
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
const snapshot = async (database: IDBDatabase) => {
|
|
const transaction = database.transaction(["setting", "migration"], "readonly");
|
|
const settings = transaction.objectStore("setting");
|
|
const migrations = transaction.objectStore("migration");
|
|
const values = await Promise.all([
|
|
get(settings, storage.ASSET_CATALOG_INDEX_V1_ID),
|
|
get(settings, storage.ASSET_CATALOG_INDEX_V2_ID),
|
|
get(migrations, storage.ASSET_CATALOG_MIGRATION_ID),
|
|
get(migrations, "schema-legacy"),
|
|
]);
|
|
await complete(transaction);
|
|
return { version: database.version, source: values[0], target: values[1], receipt: values[2], legacy: values[3] };
|
|
};
|
|
const faultCode = async (database: IDBDatabase, faultAt: "AFTER_TARGET_PUT" | "AFTER_SOURCE_DELETE"): Promise<string> => {
|
|
try {
|
|
await storage.migrateAssetCatalogIndexedDB(database, { faultAt });
|
|
return "";
|
|
}
|
|
catch (error) {
|
|
return String((error as Error & { code?: string }).code ?? "");
|
|
}
|
|
};
|
|
|
|
const databaseName = `m12-01g-${crypto.randomUUID()}`;
|
|
let database = await open(databaseName);
|
|
await seed(database);
|
|
const baseline = await snapshot(database);
|
|
const afterTargetCode = await faultCode(database, "AFTER_TARGET_PUT");
|
|
database.close();
|
|
database = await open(databaseName);
|
|
const afterTargetFault = await snapshot(database);
|
|
const afterDeleteCode = await faultCode(database, "AFTER_SOURCE_DELETE");
|
|
database.close();
|
|
database = await open(databaseName);
|
|
const afterDeleteFault = await snapshot(database);
|
|
const migrated = await storage.migrateAssetCatalogIndexedDB(database);
|
|
const committed = await snapshot(database);
|
|
database.close();
|
|
indexedDB.deleteDatabase(databaseName);
|
|
return {
|
|
baseline,
|
|
afterTargetCode,
|
|
afterTargetFault,
|
|
afterDeleteCode,
|
|
afterDeleteFault,
|
|
migrated: {
|
|
status: migrated.status,
|
|
revision: migrated.manifest.revision,
|
|
sourceHash: migrated.receipt.sourceManifestSha256,
|
|
targetHash: migrated.receipt.targetManifestSha256,
|
|
},
|
|
committed,
|
|
};
|
|
}, fixture);
|
|
|
|
expect(result.afterTargetCode).toBe("STORAGE_TRANSACTION");
|
|
expect(result.afterDeleteCode).toBe("STORAGE_TRANSACTION");
|
|
expect(result.afterTargetFault).toEqual(result.baseline);
|
|
expect(result.afterDeleteFault).toEqual(result.baseline);
|
|
expect(result.baseline.version).toBe(1);
|
|
expect(result.baseline.source).toEqual({ id: "asset-catalog:index:v1", value: fixture });
|
|
expect(result.baseline.target).toBeUndefined();
|
|
expect(result.baseline.receipt).toBeUndefined();
|
|
expect(result.baseline.legacy).toEqual({ id: "schema-legacy", version: 1, marker: "unchanged" });
|
|
expect(result.migrated).toEqual({
|
|
status: "MIGRATED",
|
|
revision: 7,
|
|
sourceHash: "cbdc8b03dc9ce1f38b44bae1c7459eb273126cbd6c7dd33115d2b1966e975881",
|
|
targetHash: "0c08ed1af1dd0998c809f76d969fd21d8fdae28ad37cf5571bdc99b16bf3ec90",
|
|
});
|
|
expect(result.committed.version).toBe(1);
|
|
expect(result.committed.source).toBeUndefined();
|
|
expect(result.committed.target).toMatchObject({ id: "asset-catalog:index:v2", value: { schemaVersion: 2, revision: 7 } });
|
|
expect(result.committed.receipt).toMatchObject({ id: "asset-catalog:migration:v1-to-v2", task: "M12-01G", status: "MIGRATED" });
|
|
expect(result.committed.legacy).toEqual(result.baseline.legacy);
|
|
});
|