Files

149 lines
10 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { DatabaseSync } from "node:sqlite";
import vm from "node:vm";
import worker from "../dist/server/index.js";
const sqlite = new DatabaseSync(":memory:");
sqlite.exec("PRAGMA foreign_keys = ON");
sqlite.exec(readFileSync(new URL("../drizzle/0000_lifeos_cloud_state.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0001_lifeos_records.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0002_lifeos_db_admin.sql", import.meta.url), "utf8"));
sqlite.exec(readFileSync(new URL("../drizzle/0003_lifeos_admin_credentials.sql", import.meta.url), "utf8"));
const db = {
prepare(sql) {
return {
bind(...args) {
const statement = sqlite.prepare(sql);
return {
first: async () => statement.get(...args) ?? null,
all: async () => ({ results: statement.all(...args) }),
run: async () => ({ meta: { changes: statement.run(...args).changes } })
};
}
};
},
async batch(statements) {
sqlite.exec("BEGIN");
try {
const result = [];
for (const statement of statements) result.push(await statement.run());
sqlite.exec("COMMIT");
return result;
} catch (error) { sqlite.exec("ROLLBACK"); throw error; }
}
};
const headers = { "oai-authenticated-user-id": "alice" };
async function call(path, method = "GET", data, as = headers) {
const response = await worker.fetch(new Request("https://life-os.test" + path, {
method, headers: { ...as, "content-type": "application/json" },
body: data === undefined ? undefined : JSON.stringify(data)
}), { DB: db });
return { status: response.status, body: await response.json() };
}
const legacy = {
profile: { name: "Alice" }, settings: { theme: "dark" },
memories: [{ id: "m1", date: "2026-09-28", title: "Lab", tags: ["school", "lab"], text: "Notes" }],
museum: [{ id: "f1", memoryId: "m1", preservedAt: "2026-09-28" }],
system: { schemaVersion: 2, tasksBoard: {
goals: [{ id: "g1", title: "Study", targetDate: "2026-12-12" }],
tasks: [{ id: "t1", goalId: "g1", title: "Review", done: false }]
} }
};
sqlite.prepare("INSERT INTO lifeos_states VALUES (?, ?, ?, ?, ?, ?, ?)").run(
"alice", JSON.stringify(legacy.profile), JSON.stringify(legacy.memories),
JSON.stringify(legacy.museum), JSON.stringify(legacy.settings), JSON.stringify(legacy.system), "2026-09-28"
);
let result = await call("/api/lifeos-state");
assert.equal(result.status, 200);
assert.deepEqual(result.body.state.memories, legacy.memories);
assert.deepEqual(result.body.state.system.tasksBoard, legacy.system.tasksBoard);
assert.deepEqual(result.body.state.museum, legacy.museum);
assert.equal(result.body.versions.memories.m1, 1);
assert.deepEqual(sqlite.prepare("SELECT tag FROM lifeos_memory_tags ORDER BY tag").all().map(row => row.tag), ["lab", "school"]);
assert.equal(sqlite.prepare("SELECT COUNT(*) AS count FROM lifeos_states").get().count, 1, "legacy backup remains intact");
result = await call("/api/lifeos-sync", "POST", { kind: "memories", id: "m1", baseVersion: 1, value: { ...legacy.memories[0], tags: ["new"] } });
assert.equal(result.body.version, 2);
assert.deepEqual(sqlite.prepare("SELECT tag FROM lifeos_memory_tags").all().map(row => row.tag), ["new"]);
result = await call("/api/lifeos-sync", "POST", { kind: "memories", id: "m1", baseVersion: 1, value: legacy.memories[0] });
assert.equal(result.status, 409, "stale client cannot overwrite a record");
result = await call("/api/lifeos-sync", "POST", { kind: "tasks", id: "t2", baseVersion: 0, value: { id: "t2", title: "New task", done: false } });
assert.equal(result.status, 200);
result = await call("/api/lifeos-sync", "POST", { kind: "memories", id: "m1", baseVersion: 2, value: null });
assert.equal(result.body.version, 3);
assert.equal(sqlite.prepare("SELECT COUNT(*) AS count FROM lifeos_memory_tags").get().count, 0);
assert.equal((await call("/api/lifeos-state")).body.state.memories.length, 0);
assert.equal((await call("/api/lifeos-state", "GET", undefined, { "oai-authenticated-user-id": "bob" })).body.state, null);
assert.equal((await call("/api/lifeos-state", "PUT", { state: legacy })).status, 426, "old whole-document writes are rejected");
result = await call("/api/lifeos-initialize", "POST", { state: legacy }, { "oai-authenticated-user-id": "bob" });
assert.equal(result.status, 200);
assert.equal(result.body.state.system.tasksBoard.tasks.length, 1);
assert.equal((await call("/api/lifeos-initialize", "POST", { state: legacy }, { "oai-authenticated-user-id": "bob" })).status, 409);
const addons = readFileSync(new URL("../public/addons.js", import.meta.url), "utf8");
const syncCode = addons.slice(addons.indexOf(" const CLOUD_KEYS ="), addons.indexOf(" const originalSetItem ="));
assert.ok(syncCode.includes("function changes("));
const values = new Map();
const storage = { getItem: key => values.get(key) ?? null, setItem: (key, value) => values.set(key, String(value)) };
const client = vm.createContext({
localStorage: storage, window: {}, fetch: async (path, options = {}) => worker.fetch(
new Request("https://life-os.test" + path, { ...options, headers: { ...options.headers, ...headers } }), { DB: db }),
clearTimeout, setTimeout, confirm: () => false, t: key => key,
toast: () => {}, document: { createElement() {}, body: { append() {} } }
});
vm.runInContext(syncCode + "\nwindow.CloudTest = { ready(state, revisions) { baseline = clone(state); versions = revisions; cloudReady = true; }, changes, snapshot: cloudSnapshot, sync: uploadCloudState };", client);
const snapshot = (await call("/api/lifeos-state")).body;
for (const [kind, key] of Object.entries({ profile: "lifeos_profile", memories: "lifeos_memories", museum: "lifeos_museum", settings: "lifeos_settings", system: "lifeos_system" }))
storage.setItem(key, JSON.stringify(snapshot.state[kind]));
client.window.CloudTest.ready(snapshot.state, snapshot.versions);
const system = JSON.parse(storage.getItem("lifeos_system"));
system.tasksBoard.tasks[0].done = true;
storage.setItem("lifeos_system", JSON.stringify(system));
const pending = client.window.CloudTest.changes(snapshot.state, client.window.CloudTest.snapshot());
assert.deepEqual(Array.from(pending, item => item.kind), ["tasks"], "editing a task only writes one record");
await client.window.CloudTest.sync();
assert.equal((await call("/api/lifeos-state")).body.state.system.tasksBoard.tasks[0].done, true);
assert.equal((await call("/api/db-admin/tables/tasks/rows")).status, 401);
assert.equal((await call("/api/db-admin/config")).body.configured, false);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true })).status, 409);
const setup = await call("/api/db-admin/setup", "POST", {
username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true
});
assert.equal(setup.status, 200);
assert.equal(sqlite.prepare("SELECT iterations FROM lifeos_admin_credentials WHERE user_id = ?").get("alice").iterations, 100_000,
"password hashing stays within the deployed Workers PBKDF2 limit");
assert.equal((await call("/api/db-admin/config")).body.configured, true);
assert.equal((await call("/api/db-admin/setup", "POST", { username: "lifeos-admin", password: "replacement password 123", acknowledged: true })).status, 409);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, { ...headers, "x-lifeos-admin-session": setup.body.token })).status, 200);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "bad", acknowledged: true })).status, 401);
const login = await call("/api/db-admin/login", "POST", {
username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true
});
assert.equal(login.status, 200);
const auth = { ...headers, "x-lifeos-admin-session": login.body.token };
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, { ...auth, origin: "https://evil.example" })).status, 403);
let admin = await call("/api/db-admin/tables/tasks/rows", "GET", undefined, auth);
assert.equal(admin.status, 200);
assert.equal(admin.body.total, 2);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, { ...auth, "oai-authenticated-user-id": "bob" })).status, 401, "admin session is tied to one user");
assert.equal((await call("/api/db-admin/config", "GET", undefined, { "oai-authenticated-user-id": "bob" })).body.configured, false);
assert.equal((await call("/api/db-admin/setup", "POST", { username: "lifeos-admin", password: "bob account password 67890", acknowledged: true }, { "oai-authenticated-user-id": "bob" })).status, 200);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "bob account password 67890", acknowledged: true })).status, 401, "another user's password cannot unlock Alice");
admin = await call("/api/db-admin/tables/tasks/rows/t3", "POST", { baseVersion: 0, value: { id: "t3", title: "From database" } }, auth);
assert.equal(admin.body.version, 1);
admin = await call("/api/db-admin/tables/tasks/rows/t3", "PATCH", { baseVersion: 1, value: { id: "t3", title: "Edited" } }, auth);
assert.equal(admin.body.version, 2);
assert.equal((await call("/api/db-admin/tables/tasks/rows/t3", "PATCH", { baseVersion: 1, value: { id: "t3", title: "Stale" } }, auth)).status, 409);
assert.equal((await call("/api/db-admin/tables/tasks/rows/t3?version=2", "DELETE", undefined, auth)).status, 200);
assert.equal((await call("/api/db-admin/password", "POST", { currentPassword: "wrong password 123", newPassword: "new secure password 12345" }, auth)).status, 401);
const rotated = await call("/api/db-admin/password", "POST", {
currentPassword: "local test password with 28 chars", newPassword: "new secure password 12345"
}, auth);
assert.equal(rotated.status, 200);
assert.equal((await call("/api/db-admin/tables/tasks/rows", "GET", undefined, auth)).status, 401);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "local test password with 28 chars", acknowledged: true })).status, 401);
assert.equal((await call("/api/db-admin/login", "POST", { username: "lifeos-admin", password: "new secure password 12345", acknowledged: true })).status, 200);
assert.equal((await call("/api/db-admin/logout", "POST", {}, { ...headers, "x-lifeos-admin-session": rotated.body.token })).status, 200);
console.log("Life OS record migration, sync conflict, tags, and user isolation valid");