import { describe, expect, test } from "bun:test"; import { createDatabase, Database } from "../modules/database/src/index.ts"; import { Field } from "../modules/database/src/decorators/Field.ts"; import { Hydrate } from "../modules/database/src/decorators/Hydrate.ts"; import { Table } from "../modules/database/src/decorators/Table.ts"; import { encodeRow, hydrateRow } from "../modules/database/src/hydration.ts"; /** * Tests of the ORM: the fluent repository, the hydration of the rows and the * encoding of the values. */ @Table("users") class User { @Field({ type: "text", primary: true }) id: string; @Field({ type: "text", unique: true, encode: (value: string) => value.trim().toLowerCase(), }) email: string; @Field({ type: "text", nullable: true, encode: (value: string | null) => value?.trim() ?? null, decode: (value: unknown) => (value === null ? null : String(value)), }) name: string | null; @Field({ type: "int", default: 0 }) age: number; @Hydrate({ decode: (row) => `${String(row.email).split("@")[1] ?? ""}`, }) domain: string; } async function database(): Promise { const db = createDatabase({ dialect: "sqlite", path: ":memory:" }); db.model(User); await db.migrate(); return db; } describe("orm", () => { test("creates the table of a model", async () => { const db = await database(); const rows = await db .model(User) .query("SELECT name FROM sqlite_master WHERE type = 'table'"); expect(rows).toHaveLength(1); await db.close(); }); test("inserts and reads a row", async () => { const db = await database(); const users = db.model(User); const created = await users.create({ id: "1", email: "ADA@Example.COM", name: "Ada", age: 36, }); expect(created.id).toBe("1"); const found = await users.where("id", "1").first(); expect(found?.email).toBe("ada@example.com"); expect(found?.name).toBe("Ada"); await db.close(); }); test("encodes the values before the commit", async () => { const db = await database(); const users = db.model(User); await users.create({ id: "1", email: "ada@example.com", name: " Ada ", age: 36, }); const rows = await users.query( "SELECT name, email FROM users WHERE id = '1'", ); expect(rows[0]?.name).toBe("Ada"); expect(rows[0]?.email).toBe("ada@example.com"); await db.close(); }); test("hydrates the rows before they are read", async () => { const db = await database(); const users = db.model(User); await users.create({ id: "1", email: "ada@example.com", name: null, age: 36, }); const found = await users.where("id", "1").first(); expect(found?.domain).toBe("example.com"); expect(found?.name).toBeNull(); await db.close(); }); test("queries with conditions, order and limit", async () => { const db = await database(); const users = db.model(User); for (const [index, name] of ["Ada", "Grace", "Alan"].entries()) { await users.create({ id: String(index), email: `${name.toLowerCase()}@example.com`, name, age: 30 + index, }); } expect(await users.count()).toBe(3); expect(await users.where("age", ">", 30).count()).toBe(2); expect((await users.orderBy("age", "desc").limit(1).all())[0]?.name).toBe( "Alan", ); expect( await users .where([{ column: "name", operator: "=", value: "Ada" }]) .count(), ).toBe(1); await db.close(); }); test("updates and deletes rows", async () => { const db = await database(); const users = db.model(User); await users.create({ id: "1", email: "ada@example.com", name: "Ada", age: 36, }); expect(await users.where("id", "1").update({ age: 37 })).toBe(1); expect((await users.where("id", "1").first())?.age).toBe(37); expect(await users.where("id", "1").delete()).toBe(1); expect(await users.count()).toBe(0); await db.close(); }); test("runs a transaction", async () => { const db = await database(); const users = db.model(User); await db.transaction(async (tx) => { await tx.model(User).create({ id: "1", email: "ada@example.com", name: "Ada", age: 36, }); }); expect(await users.count()).toBe(1); await db.close(); }); test("encodes and hydrates a row on its own", () => { const encoded = encodeRow(User as never, { email: " A@B.COM ", name: " x ", }); expect(encoded.email).toBe("a@b.com"); expect(encoded.name).toBe("x"); const hydrated = hydrateRow(User as never, { id: "1", email: "ada@example.com", name: null, age: 36, }); expect(hydrated.domain).toBe("example.com"); expect(hydrated.name).toBeNull(); }); });