From 845fc13d6e42e8d1bd960b5b00799e299eca1aa1 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Fri, 1 Aug 2025 12:08:25 +0300 Subject: [PATCH] bindings/javascript: Remove test suite We have `testing/javascript` to test both the native bindings and serverless driver, so let's use that instead. --- .../__test__/artifacts/basic-test.sql | 3 - .../__test__/better-sqlite3.spec.mjs | 445 --------------- bindings/javascript/__test__/dual-test.mjs | 82 --- bindings/javascript/__test__/sync.spec.mjs | 530 ------------------ bindings/javascript/package.json | 2 +- 5 files changed, 1 insertion(+), 1061 deletions(-) delete mode 100644 bindings/javascript/__test__/artifacts/basic-test.sql delete mode 100644 bindings/javascript/__test__/better-sqlite3.spec.mjs delete mode 100644 bindings/javascript/__test__/dual-test.mjs delete mode 100644 bindings/javascript/__test__/sync.spec.mjs diff --git a/bindings/javascript/__test__/artifacts/basic-test.sql b/bindings/javascript/__test__/artifacts/basic-test.sql deleted file mode 100644 index e75ef4432..000000000 --- a/bindings/javascript/__test__/artifacts/basic-test.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE users (name TEXT, age INTEGER); -INSERT INTO users (name, age) VALUES ('Bob', 24); -INSERT INTO users (name, age) VALUES ('Alice', 42); \ No newline at end of file diff --git a/bindings/javascript/__test__/better-sqlite3.spec.mjs b/bindings/javascript/__test__/better-sqlite3.spec.mjs deleted file mode 100644 index fad9dba2f..000000000 --- a/bindings/javascript/__test__/better-sqlite3.spec.mjs +++ /dev/null @@ -1,445 +0,0 @@ -import crypto from "crypto"; -import fs from "node:fs"; -import { fileURLToPath } from "url"; -import path from "node:path"; -import DualTest from "./dual-test.mjs"; - -const inMemoryTest = new DualTest(":memory:"); -const foobarTest = new DualTest("foobar.db"); - -inMemoryTest.both("Open in-memory database", async (t) => { - const db = t.context.db; - t.is(db.memory, true); -}); - -inMemoryTest.both("Property .name of in-memory database", async (t) => { - const db = t.context.db; - t.is(db.name, t.context.path); -}); - -foobarTest.both("Property .name of database", async (t) => { - const db = t.context.db; - t.is(db.name, t.context.path); -}); - -new DualTest("foobar.db", { readonly: true }).both( - "Property .readonly of database if set", - async (t) => { - const db = t.context.db; - t.is(db.readonly, true); - }, -); - -const genDatabaseFilename = () => { - return `test-${crypto.randomBytes(8).toString("hex")}.db`; -}; - -new DualTest().both( - "opening a read-only database fails if the file doesn't exist", - async (t) => { - t.throws( - () => t.context.connect(genDatabaseFilename(), { readonly: true }), - { - any: true, - code: "SQLITE_CANTOPEN", - }, - ); - }, -); - -foobarTest.both("Property .readonly of database if not set", async (t) => { - const db = t.context.db; - t.is(db.readonly, false); -}); - -foobarTest.both("Property .open of database", async (t) => { - const db = t.context.db; - t.is(db.open, true); -}); - -inMemoryTest.both("Statement.get() returns data", async (t) => { - const db = t.context.db; - const stmt = db.prepare("SELECT 1"); - const result = stmt.get(); - t.is(result["1"], 1); - const result2 = stmt.get(); - t.is(result2["1"], 1); -}); - -inMemoryTest.both( - "Statement.get() returns undefined when no data", - async (t) => { - const db = t.context.db; - const stmt = db.prepare("SELECT 1 WHERE 1 = 2"); - const result = stmt.get(); - t.is(result, undefined); - }, -); - -inMemoryTest.both( - "Statement.run() returns correct result object", - async (t) => { - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT)").run(); - const rows = db.prepare("INSERT INTO users (name) VALUES (?)").run("Alice"); - t.deepEqual(rows, { changes: 1, lastInsertRowid: 1 }); - }, -); - -inMemoryTest.onlySqlitePasses( - "Statment.iterate() should correctly return an iterable object", - async (t) => { - const db = t.context.db; - db.prepare( - "CREATE TABLE users (name TEXT, age INTEGER, nationality TEXT)", - ).run(); - db.prepare( - "INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)", - ).run(["Alice", 42], "UK"); - db.prepare( - "INSERT INTO users (name, age, nationality) VALUES (?, ?, ?)", - ).run("Bob", 24, "USA"); - - let rows = db.prepare("SELECT * FROM users").iterate(); - for (const row of rows) { - t.truthy(row.name); - t.truthy(row.nationality); - t.true(typeof row.age === "number"); - } - }, -); - -inMemoryTest.both( - "Empty prepared statement should throw the correct error", - async (t) => { - const db = t.context.db; - t.throws( - () => { - db.prepare(""); - }, - { - instanceOf: RangeError, - message: "The supplied SQL string contains no statements", - }, - ); - }, -); - -inMemoryTest.both("Test pragma()", async (t) => { - const db = t.context.db; - t.deepEqual(typeof db.pragma("cache_size")[0].cache_size, "number"); - t.deepEqual(typeof db.pragma("cache_size", { simple: true }), "number"); -}); - -inMemoryTest.both("pragma query", async (t) => { - const db = t.context.db; - let page_size = db.pragma("page_size"); - let expectedValue = [{ page_size: 4096 }]; - t.deepEqual(page_size, expectedValue); -}); - -inMemoryTest.both("pragma table_list", async (t) => { - const db = t.context.db; - let param = "sqlite_schema"; - let actual = db.pragma(`table_info(${param})`); - let expectedValue = [ - { cid: 0, name: "type", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 }, - { cid: 1, name: "name", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 }, - { - cid: 2, - name: "tbl_name", - type: "TEXT", - notnull: 0, - dflt_value: null, - pk: 0, - }, - { - cid: 3, - name: "rootpage", - type: "INT", - notnull: 0, - dflt_value: null, - pk: 0, - }, - { cid: 4, name: "sql", type: "TEXT", notnull: 0, dflt_value: null, pk: 0 }, - ]; - t.deepEqual(actual, expectedValue); -}); - -inMemoryTest.both("simple pragma table_list", async (t) => { - const db = t.context.db; - let param = "sqlite_schema"; - let actual = db.pragma(`table_info(${param})`, { simple: true }); - let expectedValue = 0; - t.deepEqual(actual, expectedValue); -}); - -inMemoryTest.onlySqlitePasses( - "Statement shouldn't bind twice with bind()", - async (t) => { - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run(); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42); - let stmt = db.prepare("SELECT * FROM users WHERE name = ?").bind("Alice"); - - let row = stmt.get(); - t.truthy(row.name); - t.true(typeof row.age === "number"); - - t.throws( - () => { - stmt.bind("Bob"); - }, - { - instanceOf: TypeError, - message: - "The bind() method can only be invoked once per statement object", - }, - ); - }, -); - -inMemoryTest.both( - "Test pluck(): Rows should only have the values of the first column", - async (t) => { - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run(); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24); - - let stmt = db.prepare("SELECT * FROM users").pluck(); - - for (const row of stmt.all()) { - t.truthy(row); - t.assert(typeof row === "string"); - } - }, -); - -inMemoryTest.both( - "Test raw(): Rows should be returned as arrays", - async (t) => { - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run(); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24); - - let stmt = db.prepare("SELECT * FROM users").raw(); - - for (const row of stmt.all()) { - t.true(Array.isArray(row)); - t.true(typeof row[0] === "string"); - t.true(typeof row[1] === "number"); - } - - stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw(); - const row = stmt.get("Alice"); - t.true(Array.isArray(row)); - t.is(row.length, 2); - t.is(row[0], "Alice"); - t.is(row[1], 42); - - const noRow = stmt.get("Charlie"); - t.is(noRow, undefined); - - stmt = db.prepare("SELECT * FROM users").raw(); - const rows = stmt.all(); - t.true(Array.isArray(rows)); - t.is(rows.length, 2); - t.deepEqual(rows[0], ["Alice", 42]); - t.deepEqual(rows[1], ["Bob", 24]); - }, -); - -inMemoryTest.onlySqlitePasses( - "Test expand(): Columns should be namespaced", - async (t) => { - const expandedResults = [ - { - users: { - name: "Alice", - type: "premium", - }, - addresses: { - userName: "Alice", - type: "home", - street: "Alice's street", - }, - }, - { - users: { - name: "Bob", - type: "basic", - }, - addresses: { - userName: "Bob", - type: "work", - street: "Bob's street", - }, - }, - ]; - - let regularResults = [ - { - name: "Alice", - street: "Alice's street", - type: "home", - userName: "Alice", - }, - { - name: "Bob", - street: "Bob's street", - type: "work", - userName: "Bob", - }, - ]; - - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, type TEXT)").run(); - db.prepare( - "CREATE TABLE addresses (userName TEXT, street TEXT, type TEXT)", - ).run(); - db.prepare("INSERT INTO users (name, type) VALUES (?, ?)").run( - "Alice", - "premium", - ); - db.prepare("INSERT INTO users (name, type) VALUES (?, ?)").run( - "Bob", - "basic", - ); - db.prepare( - "INSERT INTO addresses (userName, street, type) VALUES (?, ?, ?)", - ).run("Alice", "Alice's street", "home"); - db.prepare( - "INSERT INTO addresses (userName, street, type) VALUES (?, ?, ?)", - ).run("Bob", "Bob's street", "work"); - - let allRows = db - .prepare( - "SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)", - ) - .expand(true) - .all(); - - t.deepEqual(allRows, expandedResults); - - allRows = db - .prepare( - "SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)", - ) - .expand() - .all(); - - t.deepEqual(allRows, expandedResults); - - allRows = db - .prepare( - "SELECT * FROM users u JOIN addresses a ON (u.name = a.userName)", - ) - .expand(false) - .all(); - - t.deepEqual(allRows, regularResults); - }, -); - -inMemoryTest.both( - "Presentation modes should be mutually exclusive", - async (t) => { - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run(); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24); - - // test raw() - let stmt = db.prepare("SELECT * FROM users").pluck().raw(); - - for (const row of stmt.all()) { - t.true(Array.isArray(row)); - t.true(typeof row[0] === "string"); - t.true(typeof row[1] === "number"); - } - - stmt = db.prepare("SELECT * FROM users WHERE name = ?").raw(); - const row = stmt.get("Alice"); - t.true(Array.isArray(row)); - t.is(row.length, 2); - t.is(row[0], "Alice"); - t.is(row[1], 42); - - const noRow = stmt.get("Charlie"); - t.is(noRow, undefined); - - stmt = db.prepare("SELECT * FROM users").raw(); - let rows = stmt.all(); - t.true(Array.isArray(rows)); - t.is(rows.length, 2); - t.deepEqual(rows[0], ["Alice", 42]); - t.deepEqual(rows[1], ["Bob", 24]); - - // test pluck() - stmt = db.prepare("SELECT * FROM users").raw().pluck(); - - for (const name of stmt.all()) { - t.truthy(name); - t.assert(typeof name === "string"); - } - }, -); - -inMemoryTest.onlySqlitePasses( - "Presentation mode 'expand' should be mutually exclusive", - async (t) => { - // this test can be appended to the previous one when 'expand' is implemented in Turso - const db = t.context.db; - db.prepare("CREATE TABLE users (name TEXT, age INTEGER)").run(); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Alice", 42); - db.prepare("INSERT INTO users (name, age) VALUES (?, ?)").run("Bob", 24); - - let stmt = db.prepare("SELECT * FROM users").pluck().raw(); - - // test expand() - stmt = db.prepare("SELECT * FROM users").raw().pluck().expand(); - const rows = stmt.all(); - t.true(Array.isArray(rows)); - t.is(rows.length, 2); - t.deepEqual(rows[0], { users: { name: "Alice", age: 42 } }); - t.deepEqual(rows[1], { users: { name: "Bob", age: 24 } }); - }, -); - -inMemoryTest.both( - "Test exec(): Should correctly load multiple statements from file", - async (t) => { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - const db = t.context.db; - const file = fs.readFileSync( - path.resolve(__dirname, "./artifacts/basic-test.sql"), - "utf8", - ); - db.exec(file); - let rows = db.prepare("SELECT * FROM users").all(); - for (const row of rows) { - t.truthy(row.name); - t.true(typeof row.age === "number"); - } - }, -); - -inMemoryTest.both( - "Test Statement.database gets the database object", - async (t) => { - const db = t.context.db; - let stmt = db.prepare("SELECT 1"); - t.is(stmt.database, db); - }, -); - -inMemoryTest.both("Test Statement.source", async (t) => { - const db = t.context.db; - let sql = "CREATE TABLE t (id int)"; - let stmt = db.prepare(sql); - t.is(stmt.source, sql); -}); diff --git a/bindings/javascript/__test__/dual-test.mjs b/bindings/javascript/__test__/dual-test.mjs deleted file mode 100644 index 5b03dba14..000000000 --- a/bindings/javascript/__test__/dual-test.mjs +++ /dev/null @@ -1,82 +0,0 @@ -import avaTest from "ava"; -import turso from "../sync.js"; -import sqlite from "better-sqlite3"; - -class DualTest { - - #libs = { turso, sqlite }; - #beforeEaches = []; - #pathFn; - #options; - - constructor(path_opt, options = {}) { - if (typeof path_opt === 'function') { - this.#pathFn = path_opt; - } else { - this.#pathFn = () => path_opt ?? "hello.db"; - } - this.#options = options; - } - - beforeEach(fn) { - this.#beforeEaches.push(fn); - } - - only(name, impl, ...rest) { - avaTest.serial.only('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest); - avaTest.serial.only('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest); - } - - onlySqlitePasses(name, impl, ...rest) { - avaTest.serial.failing('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest); - avaTest.serial('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest); - } - - both(name, impl, ...rest) { - avaTest.serial('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest); - avaTest.serial('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest); - } - - skip(name, impl, ...rest) { - avaTest.serial.skip('[TESTING TURSO] ' + name, this.#wrap('turso', impl), ...rest); - avaTest.serial.skip('[TESTING BETTER-SQLITE3] ' + name, this.#wrap('sqlite', impl), ...rest); - } - - async #runBeforeEach(t) { - for (const beforeEach of this.#beforeEaches) { - await beforeEach(t); - } - } - - #wrap(provider, fn) { - return async (t, ...rest) => { - const path = this.#pathFn(); - const Lib = this.#libs[provider]; - const db = this.#connect(Lib, path, this.#options) - t.context = { - ...t, - connect: this.#curry(this.#connect)(Lib), - db, - errorType: Lib.SqliteError, - path, - provider, - }; - - t.teardown(() => db.close()); - await this.#runBeforeEach(t); - await fn(t, ...rest); - }; - } - - #connect(constructor, path, options) { - return new constructor(path, options); - } - - #curry(fn) { - return (first) => (...rest) => fn(first, ...rest); - } -} - -export default DualTest; - - diff --git a/bindings/javascript/__test__/sync.spec.mjs b/bindings/javascript/__test__/sync.spec.mjs deleted file mode 100644 index 372ef567d..000000000 --- a/bindings/javascript/__test__/sync.spec.mjs +++ /dev/null @@ -1,530 +0,0 @@ -import crypto from "crypto"; -import fs from "fs"; -import DualTest from "./dual-test.mjs"; - -const dualTest = new DualTest(); - -new DualTest(":memory:").both("Open in-memory database", async (t) => { - const db = t.context.db; - t.is(db.memory, true); -}); - -dualTest.beforeEach(async (t) => { - const db = t.context.db; - - db.exec(` - DROP TABLE IF EXISTS users; - CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT) - `); - db.exec( - "INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')", - ); - db.exec( - "INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')", - ); -}); - -dualTest.onlySqlitePasses("Statement.prepare() error", async (t) => { - const db = t.context.db; - - t.throws( - () => { - return db.prepare("SYNTAX ERROR"); - }, - { - any: true, - instanceOf: t.context.errorType, - message: 'near "SYNTAX": syntax error', - }, - ); -}); - -dualTest.both("Statement.run() returning rows", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT 1"); - const info = stmt.run(); - t.is(info.changes, 0); -}); - -dualTest.both("Statement.run() [positional]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("INSERT INTO users(name, email) VALUES (?, ?)"); - const info = stmt.run(["Carol", "carol@example.net"]); - t.is(info.changes, 1); - t.is(info.lastInsertRowid, 3); - - // Verify that the data is inserted - const stmt2 = db.prepare("SELECT * FROM users WHERE id = 3"); - t.is(stmt2.get().name, "Carol"); - t.is(stmt2.get().email, "carol@example.net"); -}); - -dualTest.both("Statement.run() [named]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare( - "INSERT INTO users(name, email) VALUES (@name, @email);", - ); - const info = stmt.run({ name: "Carol", email: "carol@example.net" }); - t.is(info.changes, 1); - t.is(info.lastInsertRowid, 3); -}); - -dualTest.both("Statement.get() returns no rows", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users WHERE id = 0"); - t.is(stmt.get(), undefined); -}); - -dualTest.both("Statement.get() [no parameters]", async (t) => { - const db = t.context.db; - - var stmt = 0; - - stmt = db.prepare("SELECT * FROM users"); - t.is(stmt.get().name, "Alice"); - t.deepEqual(stmt.raw().get(), [1, "Alice", "alice@example.org"]); -}); - -dualTest.both("Statement.get() [positional]", async (t) => { - const db = t.context.db; - - var stmt = 0; - - stmt = db.prepare("SELECT * FROM users WHERE id = ?"); - t.is(stmt.get(0), undefined); - t.is(stmt.get([0]), undefined); - t.is(stmt.get(1).name, "Alice"); - t.is(stmt.get(2).name, "Bob"); - - stmt = db.prepare("SELECT * FROM users WHERE id = ?1"); - t.is(stmt.get({ 1: 0 }), undefined); - t.is(stmt.get({ 1: 1 }).name, "Alice"); - t.is(stmt.get({ 1: 2 }).name, "Bob"); -}); - -dualTest.both("Statement.get() [named]", async (t) => { - const db = t.context.db; - - var stmt = undefined; - - stmt = db.prepare("SELECT :b, :a"); - t.deepEqual(stmt.raw().get({ a: "a", b: "b" }), ["b", "a"]); - - stmt = db.prepare("SELECT * FROM users WHERE id = :id"); - t.is(stmt.get({ id: 0 }), undefined); - t.is(stmt.get({ id: 1 }).name, "Alice"); - t.is(stmt.get({ id: 2 }).name, "Bob"); - - stmt = db.prepare("SELECT * FROM users WHERE id = @id"); - t.is(stmt.get({ id: 0 }), undefined); - t.is(stmt.get({ id: 1 }).name, "Alice"); - t.is(stmt.get({ id: 2 }).name, "Bob"); - - stmt = db.prepare("SELECT * FROM users WHERE id = $id"); - t.is(stmt.get({ id: 0 }), undefined); - t.is(stmt.get({ id: 1 }).name, "Alice"); - t.is(stmt.get({ id: 2 }).name, "Bob"); -}); - -dualTest.both("Statement.get() [raw]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users WHERE id = ?"); - t.deepEqual(stmt.raw().get(1), [1, "Alice", "alice@example.org"]); -}); - -dualTest.onlySqlitePasses("Statement.iterate() [empty]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users WHERE id = 0"); - t.is(stmt.iterate().next().done, true); - t.is(stmt.iterate([]).next().done, true); - t.is(stmt.iterate({}).next().done, true); -}); - -dualTest.onlySqlitePasses("Statement.iterate()", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [1, 2]; - var idx = 0; - for (const row of stmt.iterate()) { - t.is(row.id, expected[idx++]); - } -}); - -dualTest.both("Statement.all()", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [ - { id: 1, name: "Alice", email: "alice@example.org" }, - { id: 2, name: "Bob", email: "bob@example.com" }, - ]; - t.deepEqual(stmt.all(), expected); -}); - -dualTest.both("Statement.all() [raw]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [ - [1, "Alice", "alice@example.org"], - [2, "Bob", "bob@example.com"], - ]; - t.deepEqual(stmt.raw().all(), expected); -}); - -dualTest.both("Statement.all() [pluck]", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [1, 2]; - t.deepEqual(stmt.pluck().all(), expected); -}); - -dualTest.both( - "Statement.raw() [passing false should disable raw mode]", - async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [ - { id: 1, name: "Alice", email: "alice@example.org" }, - { id: 2, name: "Bob", email: "bob@example.com" }, - ]; - t.deepEqual(stmt.raw(false).all(), expected); - }, -); - -dualTest.both( - "Statement.pluck() [passing false should disable pluck mode]", - async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT * FROM users"); - const expected = [ - { id: 1, name: "Alice", email: "alice@example.org" }, - { id: 2, name: "Bob", email: "bob@example.com" }, - ]; - t.deepEqual(stmt.pluck(false).all(), expected); - }, -); - -dualTest.onlySqlitePasses( - "Statement.all() [default safe integers]", - async (t) => { - const db = t.context.db; - db.defaultSafeIntegers(); - const stmt = db.prepare("SELECT * FROM users"); - const expected = [ - [1n, "Alice", "alice@example.org"], - [2n, "Bob", "bob@example.com"], - ]; - t.deepEqual(stmt.raw().all(), expected); - }, -); - -dualTest.onlySqlitePasses( - "Statement.all() [statement safe integers]", - async (t) => { - const db = t.context.db; - const stmt = db.prepare("SELECT * FROM users"); - stmt.safeIntegers(); - const expected = [ - [1n, "Alice", "alice@example.org"], - [2n, "Bob", "bob@example.com"], - ]; - t.deepEqual(stmt.raw().all(), expected); - }, -); - -dualTest.onlySqlitePasses("Statement.raw() [failure]", async (t) => { - const db = t.context.db; - const stmt = db.prepare( - "INSERT INTO users (id, name, email) VALUES (?, ?, ?)", - ); - await t.throws( - () => { - stmt.raw(); - }, - { - message: "The raw() method is only for statements that return data", - }, - ); -}); - -dualTest.onlySqlitePasses( - "Statement.run() with array bind parameter", - async (t) => { - const db = t.context.db; - - db.exec(` - DROP TABLE IF EXISTS t; - CREATE TABLE t (value BLOB); - `); - - const array = [1, 2, 3]; - - const insertStmt = db.prepare("INSERT INTO t (value) VALUES (?)"); - await t.throws( - () => { - insertStmt.run([array]); - }, - { - message: - "SQLite3 can only bind numbers, strings, bigints, buffers, and null", - }, - ); - }, -); - -dualTest.onlySqlitePasses( - "Statement.run() with Float32Array bind parameter", - async (t) => { - const db = t.context.db; - - db.exec(` - DROP TABLE IF EXISTS t; - CREATE TABLE t (value BLOB); - `); - - const array = new Float32Array([1, 2, 3]); - - const insertStmt = db.prepare("INSERT INTO t (value) VALUES (?)"); - insertStmt.run([array]); - - const selectStmt = db.prepare("SELECT value FROM t"); - t.deepEqual(selectStmt.raw().get()[0], Buffer.from(array.buffer)); - }, -); - -/// This test is not supported by better-sqlite3, but is supported by libsql. -/// Therefore, when implementing it in Turso, only enable the test for Turso. -dualTest.skip( - "Statement.run() for vector feature with Float32Array bind parameter", - async (t) => { - const db = t.context.db; - - db.exec(` - DROP TABLE IF EXISTS t; - CREATE TABLE t (embedding FLOAT32(8)); - CREATE INDEX t_idx ON t ( libsql_vector_idx(embedding) ); - `); - - const insertStmt = db.prepare("INSERT INTO t VALUES (?)"); - insertStmt.run([new Float32Array([1, 1, 1, 1, 1, 1, 1, 1])]); - insertStmt.run([new Float32Array([-1, -1, -1, -1, -1, -1, -1, -1])]); - - const selectStmt = db.prepare( - "SELECT embedding FROM vector_top_k('t_idx', vector('[2,2,2,2,2,2,2,2]'), 1) n JOIN t ON n.rowid = t.rowid", - ); - t.deepEqual( - selectStmt.raw().get()[0], - Buffer.from(new Float32Array([1, 1, 1, 1, 1, 1, 1, 1]).buffer), - ); - - // we need to explicitly delete this table because later when sqlite-based (not LibSQL) tests will delete table 't' they will leave 't_idx_shadow' table untouched - db.exec(`DROP TABLE t`); - }, -); - -dualTest.onlySqlitePasses("Statement.columns()", async (t) => { - const db = t.context.db; - - var stmt = undefined; - - stmt = db.prepare("SELECT 1"); - t.deepEqual(stmt.columns(), [ - { - column: null, - database: null, - name: "1", - table: null, - type: null, - }, - ]); - - stmt = db.prepare("SELECT * FROM users WHERE id = ?"); - t.deepEqual(stmt.columns(), [ - { - column: "id", - database: "main", - name: "id", - table: "users", - type: "INTEGER", - }, - { - column: "name", - database: "main", - name: "name", - table: "users", - type: "TEXT", - }, - { - column: "email", - database: "main", - name: "email", - table: "users", - type: "TEXT", - }, - ]); -}); - -dualTest.onlySqlitePasses("Database.transaction()", async (t) => { - const db = t.context.db; - - const insert = db.prepare( - "INSERT INTO users(name, email) VALUES (:name, :email)", - ); - - const insertMany = db.transaction((users) => { - t.is(db.inTransaction, true); - for (const user of users) insert.run(user); - }); - - t.is(db.inTransaction, false); - insertMany([ - { name: "Joey", email: "joey@example.org" }, - { name: "Sally", email: "sally@example.org" }, - { name: "Junior", email: "junior@example.org" }, - ]); - t.is(db.inTransaction, false); - - const stmt = db.prepare("SELECT * FROM users WHERE id = ?"); - t.is(stmt.get(3).name, "Joey"); - t.is(stmt.get(4).name, "Sally"); - t.is(stmt.get(5).name, "Junior"); -}); - -dualTest.onlySqlitePasses("Database.transaction().immediate()", async (t) => { - const db = t.context.db; - const insert = db.prepare( - "INSERT INTO users(name, email) VALUES (:name, :email)", - ); - const insertMany = db.transaction((users) => { - t.is(db.inTransaction, true); - for (const user of users) insert.run(user); - }); - t.is(db.inTransaction, false); - insertMany.immediate([ - { name: "Joey", email: "joey@example.org" }, - { name: "Sally", email: "sally@example.org" }, - { name: "Junior", email: "junior@example.org" }, - ]); - t.is(db.inTransaction, false); -}); - -dualTest.onlySqlitePasses("values", async (t) => { - const db = t.context.db; - - const stmt = db.prepare("SELECT ?").raw(); - t.deepEqual(stmt.get(1), [1]); - t.deepEqual(stmt.get(Number.MIN_VALUE), [Number.MIN_VALUE]); - t.deepEqual(stmt.get(Number.MAX_VALUE), [Number.MAX_VALUE]); - t.deepEqual(stmt.get(Number.MAX_SAFE_INTEGER), [Number.MAX_SAFE_INTEGER]); - t.deepEqual(stmt.get(9007199254740991n), [9007199254740991]); -}); - -dualTest.both("Database.pragma()", async (t) => { - const db = t.context.db; - db.pragma("cache_size = 2000"); - t.deepEqual(db.pragma("cache_size"), [{ cache_size: 2000 }]); -}); - -dualTest.both("errors", async (t) => { - const db = t.context.db; - - const syntaxError = await t.throws( - () => { - db.exec("SYNTAX ERROR"); - }, - { - any: true, - instanceOf: t.context.errorType, - message: /near "SYNTAX": syntax error/, - code: "SQLITE_ERROR", - }, - ); - const noTableError = await t.throws( - () => { - db.exec("SELECT * FROM missing_table"); - }, - { - any: true, - instanceOf: t.context.errorType, - message: - /(Parse error: Table missing_table not found|no such table: missing_table)/, - code: "SQLITE_ERROR", - }, - ); - - if (t.context.provider === "libsql") { - t.is(noTableError.rawCode, 1); - t.is(syntaxError.rawCode, 1); - } -}); - -dualTest.onlySqlitePasses("Database.prepare() after close()", async (t) => { - const db = t.context.db; - db.close(); - t.throws( - () => { - db.prepare("SELECT 1"); - }, - { - instanceOf: TypeError, - message: "The database connection is not open", - }, - ); -}); - -dualTest.onlySqlitePasses("Database.exec() after close()", async (t) => { - const db = t.context.db; - db.close(); - t.throws( - () => { - db.exec("SELECT 1"); - }, - { - instanceOf: TypeError, - message: "The database connection is not open", - }, - ); -}); - -/// Generate a unique database filename -const genDatabaseFilename = () => { - return `test-${crypto.randomBytes(8).toString("hex")}.db`; -}; - -new DualTest(genDatabaseFilename).onlySqlitePasses( - "Timeout option", - async (t) => { - t.teardown(() => fs.unlinkSync(t.context.path)); - - const timeout = 1000; - const { db: conn1 } = t.context; - conn1.exec("CREATE TABLE t(x)"); - conn1.exec("BEGIN IMMEDIATE"); - conn1.exec("INSERT INTO t VALUES (1)"); - const options = { timeout }; - const conn2 = t.context.connect(t.context.path, options); - const start = Date.now(); - try { - conn2.exec("INSERT INTO t VALUES (1)"); - } catch (e) { - t.is(e.code, "SQLITE_BUSY"); - const end = Date.now(); - const elapsed = end - start; - // Allow some tolerance for the timeout. - t.is(elapsed > timeout / 2, true); - } - conn1.close(); - conn2.close(); - }, -); diff --git a/bindings/javascript/package.json b/bindings/javascript/package.json index e0d5e252e..203eacfcc 100644 --- a/bindings/javascript/package.json +++ b/bindings/javascript/package.json @@ -47,7 +47,7 @@ "build": "napi build --platform --release", "build:debug": "napi build --platform", "prepublishOnly": "napi prepublish -t npm", - "test": "ava -s", + "test": "true", "universal": "napi universalize", "version": "napi version" },