Read automx's rows with a mode its image's sqlite3 knows, since it refuses -json (issue 1630)
mesh/merge-gate head 1f7dd702d283 merged into trunk 90ecba4e50a3 — pass: builds mailu, sent nowhere; no bus step; 1 wait(s) for a person; every machine com…
mesh/repo-check head 1f7dd702d283 merged into trunk 90ecba4e50a3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

This commit is contained in:
2026-10-21 16:24:58 +02:00
parent 90ecba4e50
commit 1f7dd702d2
2 changed files with 77 additions and 27 deletions
+31 -9
View File
@@ -2,13 +2,14 @@
// sqlite3 inside its container. The database here is a real SQLite file made by the test with the tables
// automx/files/setup-db creates and the rows add-domains writes — with the IMAP server's name empty, the case
// the live instance is in. The fake container runs each command's statement against that database and prints
// it the way `sqlite3 -json` does (one JSON array, nothing for no rows). Run against the compiled module.
// it the way the image's sqlite3 does in list mode (one line per row, cells apart by "|", nothing for no rows); that sqlite3
// refuses `-json` ("sqlite3: Error: unknown option: -json", captured from novox/mailu.automx_rows, issue 1630). Run against the compiled module.
import { test } from "node:test";
import assert from "node:assert/strict";
import { DatabaseSync } from "node:sqlite";
import { readAutomx, readCommand, AUTOMX_DATABASE } from "../dist/automx.js";
import { readAutomx, readCommand, selectOf, parseRows, AUTOMX_DATABASE } from "../dist/automx.js";
function database(imapName: string): DatabaseSync {
const db = new DatabaseSync(":memory:");
@@ -32,8 +33,9 @@ function database(imapName: string): DatabaseSync {
function container(db: DatabaseSync, asked: string[][] = []) {
return async (_name: string, command: string[]) => {
asked.push(command);
const rows = db.prepare(command[command.length - 1]).all();
return rows.length === 0 ? "" : JSON.stringify(rows) + "\n";
if (command.includes("-json")) throw Object.assign(new Error("Command failed: docker exec"), { stderr: "sqlite3: Error: unknown option: -json\n" });
const rows = db.prepare(command[command.length - 1]).all() as unknown as Record<string, string>[];
return rows.length === 0 ? "" : rows.map((r) => Object.values(r).join("|")).join("\n") + "\n";
};
}
@@ -61,17 +63,18 @@ test("every command opens the file read-only and runs one fixed SELECT", async (
await readAutomx(container(database("x"), asked), "mailu-automx");
assert.equal(asked.length, 4);
for (const c of asked) {
assert.deepEqual(c.slice(0, 4), ["sqlite3", "-readonly", "-json", AUTOMX_DATABASE]);
assert.match(c[4], /^SELECT /);
assert.equal(c.length, 5);
assert.deepEqual(c.slice(0, 3), ["sqlite3", "-readonly", AUTOMX_DATABASE]);
assert.match(c[3], /^SELECT /);
assert.equal(c.length, 4);
assert.ok(!c.includes("-json"));
}
assert.deepEqual(readCommand("SELECT 1").length, 5);
assert.deepEqual(readCommand("provider"), ["sqlite3", "-readonly", AUTOMX_DATABASE, selectOf("provider")]);
});
test("a table that cannot be read is said and does not hide the others", async () => {
const db = database("x");
const exec = async (name: string, command: string[]) => {
if (command[4].includes("FROM domain ")) throw Object.assign(new Error("Command failed: docker exec"), { stderr: "Error: unable to open database file\nmore" });
if (command[3].includes("FROM domain ")) throw Object.assign(new Error("Command failed: docker exec"), { stderr: "Error: unable to open database file\nmore" });
return container(db)(name, command);
};
const answer = await readAutomx(exec, "mailu-automx");
@@ -79,3 +82,22 @@ test("a table that cannot be read is said and does not hide the others", async (
assert.equal(answer.tables.provider.length, 1);
assert.equal(answer.tables.domain, undefined);
});
test("a NULL, an empty text, a number and a text holding a bar or a newline are read apart", () => {
const hex = (t: string) => Buffer.from(t).toString("hex").toUpperCase();
const line = [`integer:${hex("5")}`, `text:${hex("a|b\nc")}`, "text:", "null:"].join("|");
const rows = parseRows("domain", line + "\n");
assert.deepEqual(rows, [{ id: 5, name: "a|b\nc", provider_id: "", ldapserver_id: null }]);
});
test("output that is not cells is an unreadable table, not a guess", async () => {
const exec = async () => "Error: something else\n";
const answer = await readAutomx(exec, "mailu-automx");
assert.equal(Object.keys(answer.unreadable).length, 4);
});
test("the image's refusal of -json is said as the table's reason", async () => {
const exec = async (_n: string, command: string[]) => container(database("x"))("", ["sqlite3", "-json", ...command.slice(1)]);
const answer = await readAutomx(exec, "mailu-automx");
assert.deepEqual(answer.unreadable.domain, "sqlite3: Error: unknown option: -json");
});