Two CODE_AUDIT items in one session. §3.2 — Frontend regression net (js/tests/, 35 tests, node:test, zero deps): - harness.js loads app.js (monofile, no exports) into a node:vm with browser globals stubbed, surfacing internals via an export epilogue. - crypto: deriveKeyAndVerifier (AES key == raw PBKDF2, cross-checked vs Node pbkdf2Sync), legacy-vs-v2 verifier decoupling, encrypt/decrypt round-trip, IV uniqueness, AEAD tamper/wrong-key. - csv: parseCSV tokenizer, findColumn heuristics, Bitwarden/KeePass mapping. - merge: applyRemoteSnapshot add/update/skip (LWW), tombstone delete, resurrection arbitration (both NaN branches), local-tombstone veto, additive folder merge. Only api() is stubbed; loadEntries/encryptImportEntry run for real. - Wired as a build gate in BuildAssets.ps1 (after node --check, bypass PM_SKIP_TESTS=1). §2.2 — Unify timestamps on UTC: - Entry created_at/updated_at were written via Delphi FormatDateTime(Now) = LOCAL, while deleted_at/tombstones use SQLite CURRENT_TIMESTAMP = UTC. The tombstone-resurrection arbitration compared the two zones, skewing by the machine's UTC offset even single-device. - Add NowUTC/NowUTCStr to PM.Database, swap in at every entry/attachment write site (Entries create/update/bulk, Attachments POST echo). - No JS change needed: arbitration now compares same-zone values. - Existing rows self-heal on next edit (no destructive migration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend unit tests
Regression net for the highest-risk pure/near-pure logic in js/app.js:
crypto round-trip, KDF/verifier derivation, CSV import parsing, and the
sync-merge / tombstone-resurrection arbitration. Addresses CODE_AUDIT.md
§3.2 (aucun test automatisé).
Running
npm test
# or directly:
node --test "js/tests/**/*.test.js"
Zero dependencies — uses the Node built-in test runner (node:test) and
webcrypto. Requires Node ≥ 18 (developed on v24). Runs in ~1.7 s.
How it works — harness.js
app.js is a ~12k-line browser monofile with no module exports and one
top-level side effect (a DOMContentLoaded listener). The harness loads the
file's source into a node:vm context with browser globals stubbed
(crypto, localStorage, document, location, …) so init() never
fires, then appends an export epilogue that surfaces the internals on
globalThis.__test.
Two gotchas the harness works around, both documented inline:
const/letdon't attach to the vm global. Top-levelfunction/vardeclarations become properties of the context global, butconst state,const HASH_ALGO_V2, etc. do not — hence the explicit export epilogue.- Cross-realm prototypes. Values returned from the sandbox carry the
sandbox realm's prototypes, so
assert.deepStrictEqualtrips on the prototype check. Structural comparisons normalize through JSON first (seeeqDeepincsv.test.js).
The merge tests stub only the api() seam (a function declaration →
overridable global property) with an in-memory fake server; loadEntries,
loadFolders, and encryptImportEntry all run for real against it — so the
tests exercise the actual pull→merge path, not a re-implementation.
Suites
| File | Covers |
|---|---|
crypto.test.js |
deriveKeyAndVerifier (AES key == raw PBKDF2, cross-checked vs Node's pbkdf2Sync), legacy vs -v2 verifier decoupling, encryptPwd/decryptPwd round-trip, IV uniqueness, AEAD tamper/wrong-key → [ERROR] |
csv.test.js |
parseCSV tokenizer (quotes, escaped "", CRLF, trailing field), findColumn header heuristics, parseEntriesFromCSV for Bitwarden/KeePass shapes, note-vs-login classification |
merge.test.js |
applyRemoteSnapshot: add/update/skip (last-write-wins), tombstone delete, resurrection arbitration (both NaN branches), local-tombstone veto, additive folder merge |
Notes on encoded behavior
merge.test.js locks in one deliberately-asymmetric behavior: an unparseable
local updated_at favours KEEP (resurrection wins), but an unparseable
remote deleted_at still applies the delete. In production deleted_at
is always server-formatted (parseable), so this only matters for a corrupted
remote snapshot. If that arbitration is ever changed, the two
unparseable … tests are where to update the expectation.