Provide list/read/summarize plus reason-gated archive, label, draft, and forward-as-draft, with SQLite audit logging and default-mailbox resolution. Co-authored-by: Cursor <cursoragent@cursor.com>
112 lines
3.1 KiB
JavaScript
112 lines
3.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import Database from "better-sqlite3";
|
|
|
|
/**
|
|
* Exercise audit_log insert/list against an in-memory schema that mirrors
|
|
* apps/watcher/src/db.js (without opening the live app.db).
|
|
*/
|
|
function openAuditDb() {
|
|
const db = new Database(":memory:");
|
|
db.pragma("foreign_keys = ON");
|
|
db.exec(`
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
google_sub TEXT NOT NULL UNIQUE,
|
|
email TEXT NOT NULL,
|
|
name TEXT,
|
|
picture TEXT,
|
|
is_default INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE TABLE audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
mailbox TEXT NOT NULL,
|
|
actor TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
reason TEXT,
|
|
message_ids TEXT,
|
|
payload TEXT,
|
|
status TEXT NOT NULL,
|
|
error TEXT
|
|
);
|
|
`);
|
|
return db;
|
|
}
|
|
|
|
describe("audit_log schema behavior", () => {
|
|
it("inserts and lists with reason + status", () => {
|
|
const db = openAuditDb();
|
|
const { lastInsertRowid: userId } = db
|
|
.prepare(
|
|
`INSERT INTO users (google_sub, email, is_default) VALUES ('sub1', 'a@example.com', 1)`,
|
|
)
|
|
.run();
|
|
|
|
const insert = db.prepare(`
|
|
INSERT INTO audit_log (
|
|
user_id, mailbox, actor, action, reason, message_ids, payload, status, error
|
|
) VALUES (?, ?, 'cli', ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
insert.run(
|
|
userId,
|
|
"a@example.com",
|
|
"archive",
|
|
"newsletter noise",
|
|
JSON.stringify(["msg1"]),
|
|
JSON.stringify({ removeLabelIds: ["INBOX"] }),
|
|
"ok",
|
|
null,
|
|
);
|
|
insert.run(
|
|
userId,
|
|
"a@example.com",
|
|
"archive",
|
|
"preview",
|
|
JSON.stringify(["msg2"]),
|
|
JSON.stringify({ dryRun: true }),
|
|
"dry-run",
|
|
null,
|
|
);
|
|
|
|
const rows = db
|
|
.prepare(
|
|
`SELECT * FROM audit_log WHERE action = ? ORDER BY id DESC LIMIT 10`,
|
|
)
|
|
.all("archive");
|
|
assert.equal(rows.length, 2);
|
|
assert.equal(rows[0].status, "dry-run");
|
|
assert.equal(rows[1].reason, "newsletter noise");
|
|
assert.deepEqual(JSON.parse(rows[1].message_ids), ["msg1"]);
|
|
});
|
|
|
|
it("setDefault clears other defaults", () => {
|
|
const db = openAuditDb();
|
|
db.prepare(
|
|
`INSERT INTO users (google_sub, email, is_default) VALUES ('s1', 'a@example.com', 1)`,
|
|
).run();
|
|
db.prepare(
|
|
`INSERT INTO users (google_sub, email, is_default) VALUES ('s2', 'b@example.com', 0)`,
|
|
).run();
|
|
|
|
const clearAndSet = db.transaction((id) => {
|
|
db.prepare(`UPDATE users SET is_default = 0`).run();
|
|
db.prepare(`UPDATE users SET is_default = 1 WHERE id = ?`).run(id);
|
|
});
|
|
const b = db
|
|
.prepare(`SELECT id FROM users WHERE email = 'b@example.com'`)
|
|
.get();
|
|
clearAndSet(b.id);
|
|
|
|
const defaults = db
|
|
.prepare(`SELECT email FROM users WHERE is_default = 1`)
|
|
.all();
|
|
assert.deepEqual(
|
|
defaults.map((r) => r.email),
|
|
["b@example.com"],
|
|
);
|
|
});
|
|
});
|