chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
160
third_party/pi/packages/durable/test/sqlite-migrations.test.ts
vendored
Normal file
160
third_party/pi/packages/durable/test/sqlite-migrations.test.ts
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { idFromNumber } from "../src/ids.ts";
|
||||
import {
|
||||
applySqliteMigrations,
|
||||
CURRENT_SQLITE_SCHEMA_VERSION,
|
||||
SQLITE_MIGRATIONS,
|
||||
type SqliteMigration,
|
||||
} from "../src/storage/sqlite/index.ts";
|
||||
import { openNodeSqliteDatabase, openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { type EntryId, ROOT_CONVERSATION_ID } from "../src/types.ts";
|
||||
|
||||
const directories = new Set<string>();
|
||||
|
||||
async function databasePath(): Promise<string> {
|
||||
const directory = await mkdtemp(join(tmpdir(), "pi-durable-migrations-"));
|
||||
directories.add(directory);
|
||||
return join(directory, "storage.sqlite");
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
for (const directory of directories) await rm(directory, { recursive: true, force: true });
|
||||
directories.clear();
|
||||
});
|
||||
|
||||
describe("durable SQLite migrations", () => {
|
||||
it("creates the current schema and can be applied repeatedly", async () => {
|
||||
const database = await openNodeSqliteDatabase(await databasePath());
|
||||
try {
|
||||
await applySqliteMigrations(database);
|
||||
await applySqliteMigrations(database);
|
||||
expect(await database.get("SELECT version FROM durable_schema WHERE singleton = 1")).toEqual({
|
||||
version: CURRENT_SQLITE_SCHEMA_VERSION,
|
||||
});
|
||||
expect(await database.get("SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1")).toEqual({
|
||||
next_id: "2",
|
||||
next_seq: 1,
|
||||
});
|
||||
} finally {
|
||||
await database.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a database newer than the portable core", async () => {
|
||||
const path = await databasePath();
|
||||
const database = await openNodeSqliteDatabase(path);
|
||||
await applySqliteMigrations(database);
|
||||
await database.run(
|
||||
"UPDATE durable_schema SET version = ? WHERE singleton = 1",
|
||||
CURRENT_SQLITE_SCHEMA_VERSION + 1,
|
||||
);
|
||||
await database.close();
|
||||
|
||||
await expect(openNodeSqliteStorage(path)).rejects.toThrow("is newer than supported version");
|
||||
});
|
||||
|
||||
it("rolls initial bootstrap and every pending migration back together", async () => {
|
||||
const database = await openNodeSqliteDatabase(await databasePath());
|
||||
try {
|
||||
const failed: readonly SqliteMigration[] = [
|
||||
{
|
||||
version: 1,
|
||||
statements: [
|
||||
"CREATE TABLE migration_first (value TEXT) STRICT",
|
||||
"INSERT INTO migration_first (value) VALUES ('retained')",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: 2,
|
||||
statements: ["CREATE TABLE migration_second (value TEXT) STRICT", "THIS IS NOT SQL"],
|
||||
},
|
||||
];
|
||||
await expect(applySqliteMigrations(database, failed)).rejects.toThrow();
|
||||
expect(
|
||||
await database.get(
|
||||
"SELECT count(*) AS count FROM sqlite_schema WHERE name IN ('durable_schema', 'migration_first', 'migration_second')",
|
||||
),
|
||||
).toEqual({ count: 0 });
|
||||
|
||||
await applySqliteMigrations(database, [
|
||||
failed[0],
|
||||
{ version: 2, statements: ["CREATE TABLE migration_second (value TEXT) STRICT"] },
|
||||
]);
|
||||
expect(await database.get("SELECT version FROM durable_schema WHERE singleton = 1")).toEqual({
|
||||
version: 2,
|
||||
});
|
||||
expect(await database.get("SELECT value FROM migration_first")).toEqual({
|
||||
value: "retained",
|
||||
});
|
||||
} finally {
|
||||
await database.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rolls a failed migration back and preserves stored data for a successful retry", async () => {
|
||||
const path = await databasePath();
|
||||
const storage = await openNodeSqliteStorage(path);
|
||||
await storage.commit(
|
||||
[
|
||||
{ type: "conversation", value: { id: ROOT_CONVERSATION_ID } },
|
||||
{
|
||||
type: "entry",
|
||||
value: {
|
||||
id: idFromNumber<EntryId>(2),
|
||||
conversationId: ROOT_CONVERSATION_ID,
|
||||
kind: "retained",
|
||||
data: { retained: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
BACKGROUND_CONTEXT,
|
||||
);
|
||||
await storage.close(BACKGROUND_CONTEXT);
|
||||
|
||||
const database = await openNodeSqliteDatabase(path);
|
||||
const nextVersion = CURRENT_SQLITE_SCHEMA_VERSION + 1;
|
||||
const failedMigrations: readonly SqliteMigration[] = [
|
||||
...SQLITE_MIGRATIONS,
|
||||
{
|
||||
version: nextVersion,
|
||||
statements: ["CREATE TABLE migration_probe (value TEXT) STRICT", "THIS IS NOT SQL"],
|
||||
},
|
||||
];
|
||||
await expect(applySqliteMigrations(database, failedMigrations)).rejects.toThrow();
|
||||
expect(await database.get("SELECT version FROM durable_schema WHERE singleton = 1")).toEqual({
|
||||
version: CURRENT_SQLITE_SCHEMA_VERSION,
|
||||
});
|
||||
expect(
|
||||
await database.get(
|
||||
"SELECT count(*) AS count FROM sqlite_schema WHERE type = 'table' AND name = 'migration_probe'",
|
||||
),
|
||||
).toEqual({ count: 0 });
|
||||
|
||||
const successfulMigrations: readonly SqliteMigration[] = [
|
||||
...SQLITE_MIGRATIONS,
|
||||
{ version: nextVersion, statements: ["CREATE TABLE migration_probe (value TEXT) STRICT"] },
|
||||
];
|
||||
await applySqliteMigrations(database, successfulMigrations);
|
||||
expect(await database.get("SELECT version FROM durable_schema WHERE singleton = 1")).toEqual({
|
||||
version: nextVersion,
|
||||
});
|
||||
expect(await database.get("SELECT record, commit_seq FROM entries WHERE id = 2")).toEqual({
|
||||
record: JSON.stringify({
|
||||
id: 2,
|
||||
conversationId: ROOT_CONVERSATION_ID,
|
||||
kind: "retained",
|
||||
data: { retained: true },
|
||||
}),
|
||||
commit_seq: 1,
|
||||
});
|
||||
expect(await database.get("SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1")).toEqual({
|
||||
next_id: "3",
|
||||
next_seq: 2,
|
||||
});
|
||||
await database.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user