prequel/test/table.test.ts

488 lines
13 KiB
TypeScript

import { Database } from "bun:sqlite";
import { expect, test } from "bun:test";
import { ColumnOf, Prequel, Table } from "../src/index";
interface UserWithID {
user_id: number;
name: string;
}
interface UserWithBio {
user_id: number;
bio: {
name: string;
age: number;
};
}
interface Post {
post_id: number;
user_id: number;
}
interface UserWithNullable extends UserWithID {
bio: string | null;
}
function makeTestTable(): Table<UserWithID> {
const db = new Database();
return new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
}
test("constructor", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(table.name).toEqual("Users");
expect(table.db).toBe(db);
expect(table.columns).toEqual(["user_id", "name"]);
});
test("constructor -> auto-attach to a prequel isntance", () => {
const db = new Prequel();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(Object.values(db.tables)).toContain(table);
});
test(".toString() resolves to the table name for embedding in queries", () => {
const table = makeTestTable();
expect(`${table}`).toEqual('"Users"');
});
test(".reference() creates a default reference to the primary column", () => {
const table = makeTestTable();
expect(table.reference()).toEqual("Users(user_id)");
});
test(".reference() creates a reference to the specified column", () => {
const table = makeTestTable();
expect(table.reference("name")).toEqual("Users(name)");
});
test(".insert() inserts a full row", () => {
const table = makeTestTable();
table.insert({
user_id: 100,
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(100);
expect(found).toEqual({
user_id: 100,
name: "Jack",
});
});
test(".insert() is able to insert nulls to nullable fields", () => {
const table = new Table<UserWithNullable>(new Database(), "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
bio: {
type: "TEXT",
nullable: true,
},
});
table.insert({
user_id: 1,
name: "Jack",
bio: null,
});
const lookup = table.db.prepare<UserWithNullable, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(1);
expect(found?.bio).toBeNull();
});
test(".insertPartial() inserts a partial row", () => {
const table = makeTestTable();
table.insertPartial({
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
});
test(".insertWithout() inserts a partial row with type safety", () => {
const table = makeTestTable();
table.insertWithout<"user_id">({
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
});
test(".count() gets the current number of rows in the table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
});
test(".countWhere() gets the current number of rows that match a condition", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.countWhere({ name: "Jack" })).toBe(1);
});
test(".findAll() returns the full table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll();
expect(allUsers).toHaveLength(3);
expect(allUsers[0]).toEqual({ user_id: 1, name: "Jack" });
expect(allUsers[1]).toEqual({ user_id: 2, name: "Kate" });
expect(allUsers[2]).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findAll() can set a limit on returned rows", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll({ limit: 1 });
expect(allUsers).toHaveLength(1);
expect(allUsers[0]).toEqual({ user_id: 1, name: "Jack" });
});
test(".findAll() can set a skip and limit on returned rows", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll({ limit: 1, skip: 1 });
expect(allUsers).toHaveLength(1);
expect(allUsers[0]).toEqual({ user_id: 2, name: "Kate" });
});
test(".findAll() can set an order for returned rows", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll({ order: "user_id DESC" });
expect(allUsers).toHaveLength(3);
expect(allUsers[0]).toEqual({ user_id: 3, name: "Sayid" });
});
test(".query() returns a prepared statement for the table", () => {
const table = makeTestTable();
const query = table.query("SELECT * FROM Users WHERE name = ?");
expect(query).toBeDefined();
});
test(".findOneWhere() returns a row based on basic criteria", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneWhere({ name: "Sayid" });
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneWhereOrFail() throws if it finds nothing", () => {
const table = makeTestTable();
expect(() => {
table.findOneWhereOrFail({ name: "Sayid" });
}).toThrow();
});
test(".findAllWhere() returns a row based on basic criteria", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" });
expect(manyJacks).toHaveLength(3);
expect(manyJacks[0]).toEqual({ user_id: 1, name: "Jack" });
expect(manyJacks[1]).toEqual({ user_id: 2, name: "Jack" });
expect(manyJacks[2]).toEqual({ user_id: 3, name: "Jack" });
});
test(".findAllWhere() can set a limit on returned rows", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" }, { limit: 1 });
expect(manyJacks).toHaveLength(1);
expect(manyJacks[0]).toEqual({ user_id: 1, name: "Jack" });
});
test(".findAllWhere() can set an offset on a limit", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" }, { limit: 1, skip: 1 });
expect(manyJacks).toHaveLength(1);
expect(manyJacks[0]).toEqual({ user_id: 2, name: "Jack" });
});
test(".findAllWhere() can set an order on the results", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere(
{ name: "Jack" },
{ order: "user_id DESC" },
);
expect(manyJacks).toHaveLength(3);
expect(manyJacks[0]).toEqual({ user_id: 3, name: "Jack" });
});
test(".findOneById() returns a single element by its primary column", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneById() returns null when nothing was found", () => {
const table = makeTestTable();
const nobody = table.findOneById(3);
expect(nobody).toBeNull();
});
test(".findOneByIdOrFail() returns a single element by its primary column", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneByIdOrFail() throws an error when nothing is found", () => {
const table = makeTestTable();
expect(() => {
table.findOneByIdOrFail(3);
}).toThrow();
});
test(".delete() deletes a full record", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
table.delete(table.findOneByIdOrFail(2));
expect(table.count()).toBe(2);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
});
test(".deleteById() deletes the element with the given primary value", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
table.deleteById(2);
expect(table.count()).toBe(2);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
});
test(".deleteWhere() deletes all rows that match", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
table.deleteWhere({ name: "Jack" });
expect(table.count()).toBe(1);
expect(table.findAll()).toEqual([{ user_id: 3, name: "Sayid" }]);
});
test(".deleteById() respects when references are set to cascade", () => {
const pq = new Prequel();
const users = pq.table<UserWithID>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const posts = pq.table<Post>("Posts", {
post_id: {
type: "INTEGER",
primary: true,
},
user_id: {
type: "INTEGER",
references: users.reference(),
cascade: true,
},
});
const user = users.insertPartial({ name: "Bob" });
posts.insertPartial({
user_id: user.user_id,
});
users.deleteById(user.user_id);
expect(posts.count()).toBe(0);
});
test(".deleteAll() deletes all rows in the table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
table.deleteAll();
expect(table.count()).toBe(0);
});
test(".update() updates a full record", () => {
const table = makeTestTable();
const locke = table.insertPartial({ name: "Locke" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
locke.name = "Jeremy Bentham";
table.update(locke);
const newLocke = table.findOneById(1);
expect(newLocke?.name).toEqual("Jeremy Bentham");
});
test(".update() throws if no primary column is set besides ROWID", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
const user = table.insert({ user_id: 1, name: "User" });
const oops = () => {
table.update(user);
};
expect(oops).toThrow(/primary column/);
});
test(".updateWhere() updates rows based on the condition", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.updateWhere({ name: "Sawyer" }, { user_id: 2 });
expect(table.findOneById(2)?.name).toEqual("Sawyer");
});
test(".updateAll() updates all rows in the table", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.updateAll({ name: "Sawyer" });
expect(table.findAll().map((u) => u.name)).toEqual([
"Sawyer",
"Sawyer",
"Sawyer",
]);
});