import { Database } from "bun:sqlite"; import { describe, expect, test } from "bun:test"; import { globSync, readFileSync } from "node:fs"; import { addTableField, createDefaultTableDocument, serializeTableDocument, updateTableCell } from "@edgeever/shared"; import { acquireMaintenanceLease, createMemoEditSession, fetchEdgeEverApp, mergeMemosRecord, updateMemoRecord, } from "./index.ts"; const executionContext = { passThroughOnException() {}, waitUntil() {}, }; const sha256 = async (value) => { const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)); return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); }; class SqlitePreparedStatement { constructor(sqlite, sql, bindings = []) { this.sqlite = sqlite; this.sql = sql; this.bindings = bindings; } bind(...bindings) { return new SqlitePreparedStatement(this.sqlite, this.sql, bindings); } async all() { return { results: this.sqlite.query(this.sql).all(...this.bindings) }; } async first() { return this.sqlite.query(this.sql).get(...this.bindings) ?? null; } async run() { this.sqlite.query(this.sql).run(...this.bindings); return { success: true, meta: {} }; } } class SqliteD1Database { constructor(sqlite) { this.sqlite = sqlite; } prepare(sql) { return new SqlitePreparedStatement(this.sqlite, sql); } async batch(statements) { const results = []; this.sqlite.transaction(() => { for (const statement of statements) { results.push(this.sqlite.query(statement.sql).run(...statement.bindings)); } })(); return results; } } const createDatabase = () => { const sqlite = new Database(":memory:"); sqlite.exec("PRAGMA foreign_keys = ON"); for (const path of globSync("migrations/*.sql").sort()) { sqlite.exec(readFileSync(path, "utf8")); } return { database: new SqliteD1Database(sqlite), sqlite }; }; const getSeedMemo = (sqlite) => sqlite.query( `SELECT m.id, m.workspace_id, m.title, m.tags_json, m.notebook_id, m.is_pinned, c.revision, c.content_hash FROM memos m INNER JOIN memo_contents c ON c.memo_id = m.id WHERE m.is_deleted = 0 ORDER BY m.id LIMIT 1`, ).get(); describe("database write optimizations", () => { test("returns the completed merge when the same source set is retried", async () => { const { database, sqlite } = createDatabase(); const first = getSeedMemo(sqlite); const secondId = "memo_merge_retry_source"; sqlite.query( `INSERT INTO memos (id, workspace_id, notebook_id, title, excerpt, tags_json, created_by, updated_by) VALUES (?, ?, ?, 'Retry source', 'retry body', '[]', 'user', 'user')`, ).run(secondId, first.workspace_id, first.notebook_id); sqlite.query( `INSERT INTO memo_contents (memo_id, content_json, content_markdown, content_text, content_hash) VALUES (?, '{"type":"doc","content":[]}', 'retry body', 'retry body', 'retry-hash')`, ).run(secondId); const input = { memoIds: [first.id, secondId], title: "Idempotent merge" }; const actor = { actorType: "user", actorId: "user_owner" }; const completed = await mergeMemosRecord(database, first.workspace_id, input, actor, "owner"); const retried = await mergeMemosRecord(database, first.workspace_id, input, actor, "owner"); expect(retried.id).toBe(completed.id); expect(retried.sourceMemoIds).toEqual(input.memoIds); expect(sqlite.query("SELECT COUNT(*) AS count FROM memos WHERE is_deleted = 0 AND title = 'Idempotent merge'").get().count).toBe(1); expect(sqlite.query("SELECT COUNT(*) AS count FROM audit_events WHERE action = 'memo.merge' AND entity_id = ?").get(completed.id).count).toBe(1); sqlite.close(); }); test("reuses a matching live edit session without another insert", async () => { const { database, sqlite } = createDatabase(); const memo = getSeedMemo(sqlite); const auth = { kind: "user", actorType: "user", actorId: "user_owner", username: "owner", displayName: "Owner", scopes: [], workspaceId: memo.workspace_id, role: "owner", }; const context = { env: { storage: { db: database, resources: {} } }, get: (name) => name === "auth" ? auth : undefined, }; const first = await createMemoEditSession(context, memo.id); const countAfterFirst = sqlite.query("SELECT COUNT(*) AS count FROM memo_edit_sessions").get().count; const second = await createMemoEditSession(context, memo.id); expect(second).toEqual(first); expect(sqlite.query("SELECT COUNT(*) AS count FROM memo_edit_sessions").get().count).toBe(countAfterFirst); sqlite.close(); }); test("returns an unchanged memo without revision, audit, or search writes", async () => { const { database, sqlite } = createDatabase(); const memo = getSeedMemo(sqlite); const auditCount = sqlite.query("SELECT COUNT(*) AS count FROM audit_events").get().count; const searchRow = sqlite.query( "SELECT id, title, content_text, tags FROM memo_search_documents WHERE memo_id = ?", ).get(memo.id); const result = await updateMemoRecord( database, memo.workspace_id, memo.id, { title: memo.title }, { actorType: "user", actorId: "user_owner" }, "owner", ); expect(result.memo.revision).toBe(memo.revision); expect(sqlite.query("SELECT COUNT(*) AS count FROM audit_events").get().count).toBe(auditCount); expect(sqlite.query( "SELECT id, title, content_text, tags FROM memo_search_documents WHERE memo_id = ?", ).get(memo.id)).toEqual(searchRow); sqlite.close(); }); test("keeps a structured table intact and retires an attachment removed from it", async () => { const { database, sqlite } = createDatabase(); const memo = getSeedMemo(sqlite); let document = addTableField(createDefaultTableDocument(), { id: "fld_files", name: "附件", type: "attachment" }); document = updateTableCell(document, "rec_sample", "fld_files", [{ resourceId: "res_table_file", filename: "brief.pdf", mimeType: "application/pdf", byteSize: 12, }]); sqlite.query( `INSERT INTO resources (id, memo_id, object_key, kind, mime_type, filename, byte_size) VALUES ('res_table_file', ?, 'workspaces/test/brief.pdf', 'attachment', 'application/pdf', 'brief.pdf', 12)`, ).run(memo.id); const actor = { actorType: "user", actorId: "user_owner" }; const saved = await updateMemoRecord( database, memo.workspace_id, memo.id, { contentMarkdown: serializeTableDocument(document) }, actor, "owner", ); expect(saved.memo.contentMarkdown).toContain("edgeever-table-v1"); const replaced = await updateMemoRecord( database, memo.workspace_id, memo.id, { contentMarkdown: "普通笔记" }, actor, "owner", ); expect(replaced.error).toBe("table_update_required"); expect(sqlite.query("SELECT content_markdown FROM memo_contents WHERE memo_id = ?").get(memo.id).content_markdown).toContain("edgeever-table-v1"); const removed = await updateMemoRecord( database, memo.workspace_id, memo.id, { contentMarkdown: serializeTableDocument(updateTableCell(document, "rec_sample", "fld_files", [])) }, actor, "owner", ); expect(removed.releasedResources?.map((resource) => resource.id)).toEqual(["res_table_file"]); expect(sqlite.query("SELECT is_deleted FROM resources WHERE id = 'res_table_file'").get().is_deleted).toBe(1); sqlite.close(); }); test("allows only one live maintenance lease owner", async () => { const { database, sqlite } = createDatabase(); const first = await acquireMaintenanceLease(database, "demo-reset-test", 60_000); const second = await acquireMaintenanceLease(database, "demo-reset-test", 60_000); expect(first).toStartWith("lease_"); expect(second).toBeNull(); sqlite.query( "UPDATE maintenance_leases SET expires_at = '2000-01-01T00:00:00.000Z' WHERE name = 'demo-reset-test'", ).run(); expect(await acquireMaintenanceLease(database, "demo-reset-test", 60_000)).toStartWith("lease_"); sqlite.close(); }); test("does not report a demo reset as successful when another reset owns the lease", async () => { const { database, sqlite } = createDatabase(); expect(await acquireMaintenanceLease(database, "demo-reset", 60_000)).toStartWith("lease_"); const response = await fetchEdgeEverApp( new Request("https://notes.example.com/api/v1/demo/reset", { method: "POST" }), { storage: { db: database, resources: {} }, EDGE_EVER_ALLOW_UNAUTHENTICATED: "true", EDGE_EVER_DEMO_MODE: "true", }, executionContext, ); expect(response.status).toBe(409); expect(response.headers.get("Retry-After")).toBe("60"); expect(await response.json()).toMatchObject({ error: { code: "demo_reset_in_progress" }, }); sqlite.close(); }); test("does not rewrite current demo seed data while listing notebooks", async () => { const { database, sqlite } = createDatabase(); const resources = { put: async () => {}, }; const request = () => fetchEdgeEverApp( new Request("https://notes.example.com/api/v1/notebooks"), { storage: { db: database, resources }, EDGE_EVER_ALLOW_UNAUTHENTICATED: "true", EDGE_EVER_DEMO_MODE: "true", }, executionContext, ); expect((await request()).status).toBe(200); expect(sqlite.query( "SELECT COUNT(*) AS count FROM memos WHERE id IN ('memo_demo_overview', 'memo_demo_overview_en')", ).get().count).toBe(2); const changesAfterSeed = sqlite.query("SELECT total_changes() AS count").get().count; expect((await request()).status).toBe(200); expect((await request()).status).toBe(200); expect(sqlite.query("SELECT total_changes() AS count").get().count).toBe(changesAfterSeed); sqlite.query("UPDATE memos SET title = 'Changed demo title' WHERE id = 'memo_demo_overview'").run(); const changesBeforeRepair = sqlite.query("SELECT total_changes() AS count").get().count; expect((await request()).status).toBe(200); expect(sqlite.query("SELECT title FROM memos WHERE id = 'memo_demo_overview'").get().title).not.toBe("Changed demo title"); expect(sqlite.query("SELECT total_changes() AS count").get().count).toBeGreaterThan(changesBeforeRepair); const changesAfterRepair = sqlite.query("SELECT total_changes() AS count").get().count; expect((await request()).status).toBe(200); expect(sqlite.query("SELECT total_changes() AS count").get().count).toBe(changesAfterRepair); sqlite.close(); }); test("touches API tokens at most once per hour", async () => { const { database, sqlite } = createDatabase(); const token = "edgeever_api_test_token"; const recent = new Date(Date.now() - 5 * 60_000).toISOString(); sqlite.query( `INSERT INTO api_tokens ( id, workspace_id, name, token_hash, token_value, scopes_json, last_used_at ) VALUES ('tok_touch_test', 'ws_default', 'Touch test', ?, ?, '["read:notebooks"]', ?)`, ).run(await sha256(token), token, recent); const request = () => fetchEdgeEverApp( new Request("https://notes.example.com/api/v1/notebooks", { headers: { Authorization: `Bearer ${token}` }, }), { storage: { db: database, resources: {} }, EDGE_EVER_AUTH_PASSWORD: "configured" }, executionContext, ); expect((await request()).status).toBe(200); expect(sqlite.query("SELECT last_used_at FROM api_tokens WHERE id = 'tok_touch_test'").get().last_used_at).toBe(recent); const stale = new Date(Date.now() - 2 * 60 * 60_000).toISOString(); sqlite.query("UPDATE api_tokens SET last_used_at = ? WHERE id = 'tok_touch_test'").run(stale); expect((await request()).status).toBe(200); expect(sqlite.query("SELECT last_used_at FROM api_tokens WHERE id = 'tok_touch_test'").get().last_used_at).not.toBe(stale); sqlite.close(); }); test("touches login sessions at most once per hour", async () => { const { database, sqlite } = createDatabase(); const token = "edgeever_session_test_token"; const recent = new Date(Date.now() - 5 * 60_000).toISOString(); const now = new Date().toISOString(); sqlite.query( `INSERT INTO users (id, username, password_hash, display_name) VALUES ('user_touch_test', 'touch-test', 'scrypt$placeholder', 'Touch Test')`, ).run(); sqlite.query( `INSERT INTO workspace_members (workspace_id, user_id, role) VALUES ('ws_default', 'user_touch_test', 'owner')`, ).run(); sqlite.query( `INSERT INTO sessions ( id, user_id, token_hash, device_id, expires_at, created_at, last_seen_at ) VALUES ('session_touch_test', 'user_touch_test', ?, 'test-device', ?, ?, ?)`, ).run(await sha256(token), new Date(Date.now() + 60 * 60_000).toISOString(), now, recent); const request = () => fetchEdgeEverApp( new Request("https://notes.example.com/api/v1/notebooks", { headers: { Authorization: `Bearer ${token}` }, }), { storage: { db: database, resources: {} }, EDGE_EVER_AUTH_PASSWORD: "configured" }, executionContext, ); expect((await request()).status).toBe(200); expect(sqlite.query("SELECT last_seen_at FROM sessions WHERE id = 'session_touch_test'").get().last_seen_at).toBe(recent); const stale = new Date(Date.now() - 61 * 60_000).toISOString(); sqlite.query("UPDATE sessions SET last_seen_at = ? WHERE id = 'session_touch_test'").run(stale); expect((await request()).status).toBe(200); expect(sqlite.query("SELECT last_seen_at FROM sessions WHERE id = 'session_touch_test'").get().last_seen_at).not.toBe(stale); sqlite.close(); }); });