191 lines
4.6 KiB
TypeScript
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();
|
|
});
|
|
});
|