// The module's migration is its own file and exactly `schemaStatements()` over its two entities // (decision AT-D20: the entity definitions are the one source of a column, so a hand-written column // cannot drift from the schema the module validates its rows with). A change to an entity changes // this snapshot, which is the moment a customer must see a new migration in review. The MariaDB // folder is the same tables in the other dialect, generated by `mysqlSchemaStatements` (spec ยง5.3: // the dialect differences are explicit, one numbered folder per dialect), and the MongoDB folder is // the same collections and indexes as declarative commands, generated by `mongoSchemaCommands`. import { readFileSync } from "node:fs" import { schemaStatements } from "@conusai-kit/data-kysely" import { mysqlSchemaStatements } from "@conusai-kit/data-kysely/mysql" import { MONGO_KERNEL_COMMANDS, mongoSchemaCommands } from "@conusai-kit/data-mongo" import { describe, expect, it } from "vitest" import { NOTES_ENTITIES, notesModule } from "../index.js" /** The statements for these tables alone: `schemaStatements` leads with the kernel's own two. */ function tablesOf(entities: Parameters[0]): string { const kernel = schemaStatements([]).length return `${schemaStatements(entities) .slice(kernel) .map((statement) => `${statement};`) .join("\n\n")}\n` } /** The same for the MariaDB folder: `mysqlSchemaStatements` leads with the kernel's own two. */ function mysqlTablesOf(entities: Parameters[0]): string { const kernel = mysqlSchemaStatements([]).length return `${mysqlSchemaStatements(entities) .slice(kernel) .map((statement) => `${statement};`) .join("\n\n")}\n` } /** The same for the MongoDB folder: `mongoSchemaCommands` leads with the kernel's own collections. */ function mongoCommandsOf(entities: Parameters[0]): ReturnType { return mongoSchemaCommands(entities).slice(MONGO_KERNEL_COMMANDS.length) } describe("the migration of the notes module", () => { it("is its own file, exactly its two tables", async () => { await expect(tablesOf(NOTES_ENTITIES)).toMatchFileSnapshot("../migrations/0001_notes.sql") }) it("is declared by the manifest, so the migrate job finds it, and every table is row-level secured", () => { expect(notesModule.migrations).toEqual({ postgres: "migrations", mariadb: "adapters/kysely/mysql/migrations", mongodb: "adapters/mongo/migrations", }) const sql = readFileSync(new URL("../migrations/0001_notes.sql", import.meta.url), "utf8") for (const table of ["notes_note", "notes_attachment"]) { expect(sql).toContain(`create table "${table}"`) expect(sql).toContain(`alter table "${table}" force row level security`) } }) it("has the same two tables as MariaDB DDL, and no policies -- MariaDB has no row-level security", async () => { const sql = readFileSync(new URL("../adapters/kysely/mysql/migrations/0001_notes.sql", import.meta.url), "utf8") await expect(mysqlTablesOf(NOTES_ENTITIES)).toMatchFileSnapshot( "../adapters/kysely/mysql/migrations/0001_notes.sql", ) for (const table of ["notes_note", "notes_attachment"]) { expect(sql).toContain(`create table \`${table}\``) } expect(sql).not.toContain("row level security") }) it("has the same two collections as MongoDB commands, each index leading with org_id (AT3.2)", () => { const file = new URL("../adapters/mongo/migrations/0001_notes.json", import.meta.url) // The file is data (Biome keeps its JSON formatting canonical), so the pin is structural: // exactly the commands `mongoSchemaCommands` generates for these entities, nothing else. const commands = JSON.parse(readFileSync(file, "utf8")) as Array> expect(commands).toEqual(mongoCommandsOf(NOTES_ENTITIES)) const created = commands .filter((command) => "createCollection" in command) .map((command) => command.createCollection) expect(created).toEqual(["notes_attachment", "notes_note"]) // MongoDB has no row-level security: the wall is the repository predicate plus these // org_id-leading indexes, and the contract suite of @conusai-kit/data-mongo is its proof. const indexes = commands.filter((command) => "createIndex" in command) for (const index of indexes) { const keys = Object.keys((index.createIndex as { keys: Record }).keys) expect(keys[0]).toBe("org_id") } // The validator is the entity's zod schema as $jsonSchema, carried by the file itself. const note = commands.find((command) => command.createCollection === "notes_note") as { validator: { $jsonSchema: { properties: Record; required: string[] } } } expect(Object.keys(note.validator.$jsonSchema.properties)).toContain("org_id") expect(note.validator.$jsonSchema.required).toContain("org_id") }) })