import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { withQueryContext } from "@executor-js/fumadb/query";
import { collectTables } from "./executor";
import { createSqliteTestFumaDb, type SqliteTestFumaDb } from "./sqlite-test-db";
// The subject table has no readers or writers yet (it is populated in a later
// change), so this pins the two properties the schema alone is responsible
// for: the boot bring-up actually creates it, and it is TENANT-scoped rather
// than owner-scoped — any executor bound to the tenant may read it, which is
// what a cross-subject view needs. Written against the real SQLite bring-up
// (`createSqliteTestFumaDb` runs the same statements the local/self-host/D1
// hosts boot with) instead of asserting on the table definition object.
const TENANT = "t1";
const OTHER_TENANT = "t2";
const withDb = (body: (db: SqliteTestFumaDb) => Promise): Promise =>
Effect.runPromise(
Effect.acquireUseRelease(
Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() })),
(db) => Effect.promise(() => body(db)),
(db) => Effect.promise(() => db.close()),
),
);
const insertSubject = (
db: SqliteTestFumaDb,
row: { readonly rowId: string; readonly tenant: string; readonly externalId: string },
): Promise =>
db.client.execute({
sql: `INSERT INTO subject (row_id, tenant, external_id, created_at, last_seen_at, status)
VALUES (?, ?, ?, ?, ?, ?)`,
args: [row.rowId, row.tenant, row.externalId, Date.now(), null, null],
});
describe("subject table", () => {
it.effect("is created by the runtime schema bring-up", () =>
Effect.promise(() =>
withDb(async (db) => {
const info = await db.client.execute("PRAGMA table_info('subject')");
const columns = info.rows.map((row) => String(row["name"]));
expect(columns).toEqual(
expect.arrayContaining([
"row_id",
"tenant",
"external_id",
"created_at",
"last_seen_at",
"status",
]),
);
}),
),
);
it.effect("keys one row per (tenant, external_id)", () =>
Effect.promise(() =>
withDb(async (db) => {
await insertSubject(db, { rowId: "s1", tenant: TENANT, externalId: "user_a" });
// The same principal id under a DIFFERENT tenant is a different row —
// external ids are only unique within their tenant.
await insertSubject(db, { rowId: "s2", tenant: OTHER_TENANT, externalId: "user_a" });
await expect(
insertSubject(db, { rowId: "s3", tenant: TENANT, externalId: "user_a" }),
).rejects.toThrow();
const rows = await db.client.execute("SELECT row_id FROM subject ORDER BY row_id");
expect(rows.rows.map((row) => String(row["row_id"]))).toEqual(["s1", "s2"]);
}),
),
);
it.effect("tolerates the host sentinel subject id", () =>
Effect.promise(() =>
withDb(async (db) => {
// Local/self-host bind every request to the literal subject "local";
// nothing may assume external ids are opaque provider identifiers.
await insertSubject(db, { rowId: "s1", tenant: TENANT, externalId: "local" });
const rows = await db.client.execute("SELECT external_id FROM subject");
expect(rows.rows.map((row) => String(row["external_id"]))).toEqual(["local"]);
}),
),
);
it.effect("reads only within the bound tenant", () =>
Effect.promise(() =>
withDb(async (db) => {
await insertSubject(db, { rowId: "s1", tenant: TENANT, externalId: "user_a" });
await insertSubject(db, { rowId: "s2", tenant: OTHER_TENANT, externalId: "user_b" });
// No `subject` in the context: a pure-org executor still reads the
// table, which is the whole point of tenant scoping.
const scoped = withQueryContext(db.db, { tenant: TENANT, subject: null });
const visible = await scoped.findMany("subject", {});
expect(visible.map((row) => row.external_id)).toEqual(["user_a"]);
}),
),
);
});