muddle/tests/orm.test.ts
2026-10-23 09:12:49 -04:00

191 lines
4.6 KiB
TypeScript

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<Database> {
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>(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>(User as never, {
id: "1",
email: "ada@example.com",
name: null,
age: 36,
});
expect(hydrated.domain).toBe("example.com");
expect(hydrated.name).toBeNull();
});
});