UNPKG

@cloudflare/actors

Version:

An easier way to build with Cloudflare Durable Objects

207 lines 8.97 kB
import { env, listDurableObjectIds, runInDurableObject } from "cloudflare:test"; import { describe, expect, it } from "vitest"; import { SQLMigrationsDO } from "./test-worker"; import { SQLSchemaMigrations, } from "../src/sql-schema-migrations"; function makeM(state, migrations) { return new SQLSchemaMigrations({ doStorage: state.storage, migrations: migrations, }); } describe("happy paths", async () => { it("empty initial storage", async () => { // Check sending request directly to instance const id = env.SQL_MIGRATIONS_DO.idFromName("emptyDO"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { expect(instance).toBeInstanceOf(SQLMigrationsDO); const m = makeM(state, [ { idMonotonicInc: 1, description: "test default tables", sql: `SELECT * FROM sqlite_master;`, }, ]); await m.runAll(); expect(state.storage.sql.databaseSize).toEqual(8192); }); // Check direct access to instance fields and storage await runInDurableObject(stub, async (instance, state) => { expect(await state.storage.get("__sql_migrations_lastID")).toBe(1); }); // Check IDs can be listed const ids = await listDurableObjectIds(env.SQL_MIGRATIONS_DO); expect(ids.length).toBe(1); expect(ids[0].equals(id)).toBe(true); }); it("multiple DDL", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("emptyDO"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { expect(instance).toBeInstanceOf(SQLMigrationsDO); const res = await makeM(state, [ { idMonotonicInc: 1, description: "tbl1", sql: `CREATE TABLE users(name TEXT PRIMARY KEY, age INTEGER);`, }, { idMonotonicInc: 2, description: "tbl2", sql: `CREATE TABLE IF NOT EXISTS usersActivities (activityType TEXT, userName TEXT, PRIMARY KEY (userName, activityType));`, }, ]).runAll(); expect(res).toEqual({ rowsRead: 2, rowsWritten: 6, }); }); }); it("multiple DDL and data inserts", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("data-test"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { await makeM(state, [ { idMonotonicInc: 1, description: "tbl1", sql: `CREATE TABLE users(name TEXT PRIMARY KEY, age INTEGER);`, }, { idMonotonicInc: 2, description: "data", sql: `INSERT INTO users VALUES ('ironman', 100); INSERT INTO users VALUES ('thor', 9000);`, }, { idMonotonicInc: 3, description: "data2", sql: `INSERT INTO users VALUES ('hulk', 5);`, }, ]).runAll(); let rows = state.storage.sql .exec(`SELECT * FROM users;`) .toArray(); expect(rows).toEqual([ { name: "ironman", age: 100 }, { name: "thor", age: 9000 }, { name: "hulk", age: 5 }, ]); await makeM(state, [ // Edge case but even if you do not provide the previous migrations // it should run only from where it left off. // WARNING: Do not do this in production since running these for a first time // on a fresh DO will fail!!! { idMonotonicInc: 4, description: "data3", sql: `DELETE FROM users WHERE name = 'thor';`, }, ]).runAll(); rows = state.storage.sql .exec(`SELECT * FROM users;`) .toArray(); expect(rows).toEqual([ { name: "ironman", age: 100 }, { name: "hulk", age: 5 }, ]); }); }); it("run migrations multiple times", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("emptyDO"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { const m1 = [ { idMonotonicInc: 1, description: "tbl1", sql: `CREATE TABLE IF NOT EXISTS users(name TEXT PRIMARY KEY, age INTEGER);`, }, { idMonotonicInc: 2, description: "tbl2", sql: `CREATE TABLE IF NOT EXISTS usersActivities (activityType TEXT, userName TEXT, PRIMARY KEY (userName, activityType));`, }, ]; const r1 = await makeM(state, m1).runAll(); const m2 = m1.concat([ { idMonotonicInc: 3, description: "round 2", sql: ` CREATE TABLE IF NOT EXISTS marvel (heroName TEXT); CREATE TABLE IF NOT EXISTS marvelMovies (name TEXT PRIMARY KEY, releaseDateMs INTEGER); `, }, ]); const r2_1 = await makeM(state, m2).runAll(); for (let i = 0; i < 5; i++) { const r2_n = await makeM(state, m2).runAll(); // Should have no effect running the same migrations. expect(r2_n).toEqual({ rowsRead: 0, rowsWritten: 0, }); } return { r1, r2: r2_1 }; }); }); it("hasMigrationsToRun", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("hasMigrationsToRun"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { expect(makeM(state, []).hasMigrationsToRun()).toEqual(false); const m1 = [ { idMonotonicInc: 1, description: "tbl1", sql: `CREATE TABLE users(name TEXT PRIMARY KEY, age INTEGER);`, }, ]; // Reuse the same SQLSchemaMigrations instance otherwise `hasMigrationsToRun()` // will always be true before running `runAll()`. const m = makeM(state, m1); expect(m.hasMigrationsToRun()).toEqual(true); await m.runAll(); expect(m.hasMigrationsToRun()).toEqual(false); }); }); describe("expected exceptions", () => { it("duplicate IDs", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("emptyDO"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { await expect(async () => await makeM(state, [ { idMonotonicInc: 1, description: "tbl1", sql: `CREATE TABLE users(name TEXT PRIMARY KEY, age INTEGER);`, }, { idMonotonicInc: 2, description: "tbl1", sql: `CREATE TABLE IF NOT EXISTS users(name TEXT PRIMARY KEY, age INTEGER);`, }, { idMonotonicInc: 1, description: "tbl2", sql: `CREATE TABLE IF NOT EXISTS usersActivities (activityType TEXT, userName TEXT, PRIMARY KEY (userName, activityType));`, }, ]).runAll()).rejects.toThrowError("duplicate migration ID detected: 1"); }); }); it("negative IDs", async () => { const id = env.SQL_MIGRATIONS_DO.idFromName("emptyDO"); const stub = env.SQL_MIGRATIONS_DO.get(id); await runInDurableObject(stub, async (instance, state) => { await expect(async () => await makeM(state, [ { idMonotonicInc: -1, description: "tbl1", sql: `CREATE TABLE users(name TEXT PRIMARY KEY, age INTEGER);`, }, ]).runAll()).rejects.toThrowError("migration ID cannot be negative: -1"); }); }); }); }); //# sourceMappingURL=sql-schema-migrations.test.js.map