import { Database } from "bun:sqlite"; import { expect, test } from "bun:test"; import { 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 { const db = new Database(); return new Table(db, "Users", { user_id: { type: "INTEGER", primary: true, }, name: "TEXT", }); } test("constructor", () => { const db = new Database(); const table = new Table(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(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( `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(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( `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( `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( `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(".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(".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.size()).toBe(3); table.delete(table.findOneByIdOrFail(2)); expect(table.size()).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.size()).toBe(3); table.deleteById(2); expect(table.size()).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.size()).toBe(3); table.deleteWhere({ name: "Jack" }); expect(table.size()).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("Users", { user_id: { type: "INTEGER", primary: true, }, name: "TEXT", }); const posts = pq.table("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.size()).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(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(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(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", ]); });