quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

test_sqlite3_error.js (4331B)


      1 import * as os from "os";
      2 import * as tart from "tart";
      3 
      4 const dbPath = `/tmp/qtart-sqlite3-error-${Date.now()}?.sqlite3`;
      5 let db;
      6 let stmt;
      7 
      8 function assert(condition, message) {
      9   if (!condition) {
     10     throw new Error(message);
     11   }
     12 }
     13 
     14 function expectSqlite3Error(fn, code, messagePart) {
     15   try {
     16     fn();
     17   } catch (error) {
     18     assert(error instanceof tart.Sqlite3Error,
     19            `expected Sqlite3Error, got ${error?.constructor?.name}`);
     20     assert(error instanceof Error, "Sqlite3Error must inherit from Error");
     21     assert(error.name === "Sqlite3Error",
     22            `unexpected error name: ${error.name}`);
     23     assert(error.code === code,
     24            `expected ${code}, got ${error.code}`);
     25     assert(Number.isInteger(error.errno) && error.errno !== 0,
     26            `unexpected SQLite error number: ${error.errno}`);
     27     assert(error.message.includes(messagePart),
     28            `unexpected SQLite error message: ${error.message}`);
     29     return;
     30   }
     31   throw new Error(`expected ${code} Sqlite3Error`);
     32 }
     33 
     34 function expectTypeError(fn, messagePart) {
     35   try {
     36     fn();
     37   } catch (error) {
     38     assert(error instanceof TypeError,
     39            `expected TypeError, got ${error?.constructor?.name}`);
     40     assert(error.message.includes(messagePart),
     41            `unexpected TypeError message: ${error.message}`);
     42     return;
     43   }
     44   throw new Error("expected TypeError");
     45 }
     46 
     47 try {
     48   assert(tart.apiVersion === 1,
     49          `expected Qtart API version 1, got ${tart.apiVersion}`);
     50   db = tart.sqlite3Open(dbPath);
     51   tart.sqlite3Exec(db, "CREATE TABLE entries (value TEXT UNIQUE)");
     52   tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('one')");
     53 
     54   expectSqlite3Error(
     55     () => tart.sqlite3Prepare(db, "SELECT value FROM missing_table"),
     56     "SQLITE_ERROR",
     57     "no such table: missing_table",
     58   );
     59   expectSqlite3Error(
     60     () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('one')"),
     61     "SQLITE_CONSTRAINT_UNIQUE",
     62     "UNIQUE constraint failed: entries.value",
     63   );
     64 
     65   stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries");
     66   const row = tart.sqlite3StmtGetFirst(stmt);
     67   if (row?.value !== "one") {
     68     throw Error(`unexpected first row: ${JSON.stringify(row)}`);
     69   }
     70 
     71   // Changing into WAL fails while another statement on the connection is
     72   // still active.  sqlite3StmtGetFirst must reset its SELECT before returning.
     73   tart.sqlite3Exec(db, "PRAGMA journal_mode = WAL");
     74 
     75   // Explicit close must never create a zombie connection.  A direct caller
     76   // that leaves a statement live gets SQLITE_BUSY and can still use and
     77   // finalize the statement before retrying the close.
     78   expectSqlite3Error(
     79     () => tart.sqlite3Close(db),
     80     "SQLITE_BUSY",
     81     "unable to close",
     82   );
     83   assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one",
     84          "statement must remain usable after SQLITE_BUSY from close");
     85   tart.sqlite3Finalize(stmt);
     86   tart.sqlite3Finalize(stmt);
     87   expectTypeError(
     88     () => tart.sqlite3StmtGetFirst(stmt),
     89     "invalid sqlite3 statement handle",
     90   );
     91   stmt = undefined;
     92   tart.sqlite3Close(db);
     93   db = undefined;
     94 
     95   expectTypeError(
     96     () => tart.sqlite3Open(dbPath, { readonly: "yes" }),
     97     "option 'readonly' must be a boolean",
     98   );
     99 
    100   db = tart.sqlite3Open(dbPath, { readonly: true });
    101   stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries");
    102   assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one",
    103          "read-only connection must read existing records");
    104   tart.sqlite3Finalize(stmt);
    105   stmt = undefined;
    106   expectSqlite3Error(
    107     () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('two')"),
    108     "SQLITE_READONLY",
    109     "readonly",
    110   );
    111   tart.sqlite3Close(db);
    112   db = undefined;
    113 
    114   db = tart.sqlite3Open(dbPath, { immutable: true });
    115   stmt = tart.sqlite3Prepare(db, "SELECT value FROM entries");
    116   assert(tart.sqlite3StmtGetFirst(stmt)?.value === "one",
    117          "immutable connection must preserve URI-special path characters");
    118   tart.sqlite3Finalize(stmt);
    119   stmt = undefined;
    120   expectSqlite3Error(
    121     () => tart.sqlite3Exec(db, "INSERT INTO entries VALUES ('two')"),
    122     "SQLITE_READONLY",
    123     "readonly",
    124   );
    125   tart.sqlite3Close(db);
    126   db = undefined;
    127 } finally {
    128   if (stmt !== undefined) {
    129     tart.sqlite3Finalize(stmt);
    130   }
    131   if (db !== undefined) {
    132     tart.sqlite3Close(db);
    133   }
    134   os.remove(dbPath);
    135   os.remove(`${dbPath}-wal`);
    136   os.remove(`${dbPath}-shm`);
    137 }