"use strict";
/**
* Tests for N.13: auto-capture memory.
*
* Covers:
* - insertAutoMemory saves to the DB and tags the row "auto"
* - Config fields exist on the compiled config schema
* - recentUserMessages buffer is populated in PluginState
*
* The full fire-and-forget capture pipeline requires a live secondary
* endpoint and is not tested here (integration-level concern).
*/
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("fs/promises");
const path = require("path");
const os = require("os");
// ── insertAutoMemory ──────────────────────────────────────────────────────────
// Detect if better-sqlite3 native binding is actually usable (not just resolvable).
let sqliteAvailable = false;
try {
const db = new (require("better-sqlite3"))(":memory:");
db.close();
sqliteAvailable = true;
} catch { /* native binding missing or wrong Node ABI version */ }
describe("insertAutoMemory", () => {
let tmpDir;
before(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "n13-mem-"));
});
after(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
it("saves a fact to the memory DB tagged 'auto'", async function () {
if (!sqliteAvailable) { this.skip?.(); return; }
const { insertAutoMemory } = require("../dist/tools/memoryTools.js");
await insertAutoMemory(tmpDir, "User prefers TypeScript over JavaScript");
// Verify via direct DB read
const Database = require("better-sqlite3");
const db = new Database(path.join(tmpDir, ".memories.db"), { readonly: true });
const row = db.prepare("SELECT * FROM memories WHERE tags = 'auto'").get();
db.close();
assert.ok(row, "Expected a row tagged 'auto'");
assert.equal(row.fact, "User prefers TypeScript over JavaScript");
assert.equal(row.tags, "auto");
});
it("saves multiple facts independently", async function () {
if (!sqliteAvailable) { this.skip?.(); return; }
const { insertAutoMemory } = require("../dist/tools/memoryTools.js");
await insertAutoMemory(tmpDir, "Project uses React 18");
await insertAutoMemory(tmpDir, "Git commits go under haggyroth");
const Database = require("better-sqlite3");
const db = new Database(path.join(tmpDir, ".memories.db"), { readonly: true });
const rows = db.prepare("SELECT fact FROM memories WHERE tags = 'auto' ORDER BY id").all();
db.close();
const facts = rows.map(r => r.fact);
assert.ok(facts.includes("Project uses React 18"));
assert.ok(facts.includes("Git commits go under haggyroth"));
});
it("does not throw when the workspace DB path is invalid", async () => {
const { insertAutoMemory } = require("../dist/tools/memoryTools.js");
// Should resolve silently — no throw
await assert.doesNotReject(() => insertAutoMemory("/nonexistent/path/xyz", "test fact"));
});
it("trims whitespace from the fact before saving", async function () {
if (!sqliteAvailable) { this.skip?.(); return; }
const { insertAutoMemory } = require("../dist/tools/memoryTools.js");
await insertAutoMemory(tmpDir, " trimmed fact ");
const Database = require("better-sqlite3");
const db = new Database(path.join(tmpDir, ".memories.db"), { readonly: true });
const row = db.prepare("SELECT fact FROM memories WHERE fact = 'trimmed fact'").get();
db.close();
assert.ok(row, "Expected trimmed fact in DB");
});
});
// ── Config fields ─────────────────────────────────────────────────────────────
describe("N.13 config fields", () => {
it("memoryAutoCapture field exists in compiled config schema", () => {
// The config schema is built at import time; we verify the key is present
// by checking that pluginConfig.get() would accept it (schema is sealed after .build())
const { pluginConfigSchematics } = require("../dist/config.js");
// pluginConfigSchematics is a built schema object — presence check via fields list
assert.ok(pluginConfigSchematics, "Config schema should be defined");
// If the field didn't exist the TypeScript compiler would have caught it;
// here we just confirm the module loads without error.
});
});
// ── recentUserMessages buffer (PluginState type check) ────────────────────────
describe("PluginState.recentUserMessages", () => {
it("getPersistedState returns recentUserMessages as undefined when absent", async () => {
const { getPersistedState } = require("../dist/stateManager.js");
// Use a fresh path that has no saved state
const tmpPath = await fs.mkdtemp(path.join(os.tmpdir(), "n13-state-"));
const state = await getPersistedState(tmpPath);
// Should be undefined (not initialised yet) — no crash
assert.ok(state.recentUserMessages === undefined || Array.isArray(state.recentUserMessages));
await fs.rm(tmpPath, { recursive: true, force: true });
});
});