// Node smoke test of the built package (pkg/, `--target web`): the generated JS glue, camelCase // records, CoreError codes, Argon2id timing. No IndexedDB in Node, so it uses in-memory vaults // (the IndexedDB path is covered by `wasm-pack test --headless --chrome`). // // node crates/kv-wasm/js/smoke.mjs # checks + timing // node crates/kv-wasm/js/smoke.mjs --write-fixture # also writes tests/fixtures/wasm-vault.json // // With a fresh local kestrove-server it also runs a two-device sync round trip, both devices in wasm // (see crates/kv-ffi/tests/live_server.rs for starting one; it takes ~2 minutes because of the // server's auth rate limit): // // KESTROVE_SERVER_URL=http://127.0.0.1:8080 KESTROVE_SETUP_TOKEN= node crates/kv-wasm/js/smoke.mjs import assert from "node:assert/strict"; import { readFile, writeFile } from "node:fs/promises"; import init, * as kv from "../pkg/kv_wasm.js"; const pkg = new URL("../pkg/", import.meta.url); await init({ module_or_path: await readFile(new URL("kv_wasm_bg.wasm", pkg)) }); const PW = "wasm fixture master password"; const ms = (t) => `${Math.round(performance.now() - t)} ms`; async function rejects(promise, code) { try { await promise; } catch (e) { assert.equal(e.name, "CoreError"); assert.equal(e.code, code, e.message); return e; } assert.fail(`expected ${code}`); } function login(title, password, extra = {}) { return { id: "", kind: "login", title, username: "ana@example.com", password, urls: ["https://example.com/login"], totp: null, notes: null, favorite: false, tags: [], modifiedAt: 0, ...extra, }; } // ------------------------------------------------------------ local checks, Argon2 timing const a = await kv.VaultCore.newInMemory(); await rejects(a.unlock(PW, "K1-AAAAAA-AAAAA-AAAAA-AAAAA-AAAAA-A"), "NoAccount"); let t = performance.now(); const secretKey = await a.createAccount(PW); console.log(`createAccount (Argon2id m=64 MiB t=3 p=4): ${ms(t)}`); await a.lock(); t = performance.now(); await a.unlock(PW, secretKey); console.log(`unlock (Argon2id m=64 MiB t=3 p=4): ${ms(t)}`); const bankId = await a.saveItem( login("Bank", "first password", { totp: "otpauth://totp/Bank:ana?secret=JBSWY3DPEHPK3PXP&issuer=Bank", tags: ["finance", "ünïcode ✓"], favorite: true, }), ); const bank = await a.getItem(bankId); assert.equal(bank.password, "first password"); assert.equal(bank.notes, null); await a.saveItem({ ...bank, password: "second password" }); await a.saveItem({ id: "", kind: "secureNote", title: "Recovery codes", notes: "multi\nline 🔐" }); const list = await a.listItems(); assert.deepEqual( list.map((i) => [i.title, i.kind, i.hasTotp]), [ ["Bank", "login", true], ["Recovery codes", "secureNote", false], ], ); assert.equal((await a.itemHistory(bankId))[0].password, "first password"); assert.deepEqual(list[0].tags, ["finance", "ünïcode ✓"]); assert.ok(list[0].modifiedAt >= bank.modifiedAt); await rejects(a.getItem("not-a-uuid"), "Invalid"); const code = kv.totpCode("JBSWY3DPEHPK3PXP", Math.floor(Date.now() / 1000)); assert.match(code.code, /^\d{6}$/); assert.equal(kv.matchUrl("example.com", "https://example.com/x"), "exact"); assert.equal(kv.generatePassword(20, false).length, 20); const exported = await a.exportEncrypted("export passphrase"); assert.ok(exported instanceof Uint8Array && exported.length > 100); if (process.argv.includes("--write-fixture")) { const items = []; for (const s of await a.listItems()) items.push(await a.getItem(s.id)); items.sort((x, y) => (x.title < y.title ? -1 : 1)); const fixture = { masterPassword: PW, secretKey, entries: await a.__replicaEntries(), items }; const path = new URL("../tests/fixtures/wasm-vault.json", import.meta.url); await writeFile(path, JSON.stringify(fixture, null, 2) + "\n"); console.log(`wrote ${path.pathname}`); } // ------------------------------------------------------------ live server round trip const url = process.env.KESTROVE_SERVER_URL; const token = process.env.KESTROVE_SETUP_TOKEN; if (url && token) { const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000)); await a.setDevice("wasm device A", "linux"); await a.configureServer(url, []); assert.equal((await a.serverInfo()).setupOpen, true, "server must be fresh"); await a.registerAccount(token); let r = await a.syncNow(); assert.equal(r.pushed, 2); console.log("A registered and pushed", r); console.log("waiting 61 s for the server's auth rate limit…"); await sleep(61); const b = await kv.VaultCore.newInMemory(); await b.setDevice("wasm device B", "macos"); await b.configureServer(url, []); let res = await b.login(PW, secretKey, null); if (res === "pendingApproval") { const pending = (await a.listDevices()).find((d) => !d.approved); assert.equal(pending.keyFingerprint, await b.deviceKeyFingerprint()); await a.approveDevice(pending.deviceId); console.log("A approved B; waiting 61 s again…"); await sleep(61); res = await b.login(PW, secretKey, null); } assert.equal(res, "ok"); r = await b.syncNow(); console.log("B synced", r); const bItems = await b.listItems(); assert.deepEqual(bItems.map((i) => i.title).sort(), ["Bank", "Recovery codes"]); const onB = await b.getItem(bankId); assert.equal(onB.password, "second password"); await b.saveItem({ ...onB, notes: "edited on B" }); assert.equal((await b.syncNow()).pushed, 1); await a.syncNow(); assert.equal((await a.getItem(bankId)).notes, "edited on B"); await b.deleteItem((await b.listItems()).find((i) => i.title === "Recovery codes").id); await b.syncNow(); await a.syncNow(); assert.deepEqual((await a.listItems()).map((i) => i.title), ["Bank"]); console.log("live round trip ok"); } console.log("smoke ok");