Files
Password-Manager/js/tests
r-zakarya 829056f1fa fix(import): normalize timestamps to the DB format at the import door
vault_entries uses SQLite's space-separated UTC format everywhere, and both
sorting and sync last-write-wins compare the strings lexically — so a foreign
JSON import carrying strict-ISO 'T'/millis/Z/offset timestamps would slot in
with a different format and subtly break ordering and merge arbitration.
normalizeImportTimestamp converts any ISO-ish variant to 'YYYY-MM-DD
HH:MM:SS' UTC (bare strings treated as UTC, garbage -> '' = server stamps
now). +1 unit test (69 total).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:13:41 +01:00
..

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

The frontend is a large browser script with no module exports and one top-level side effect (a DOMContentLoaded listener). It's being split into ordered classic-script files (§3.1); the harness concatenates the app parts in load order (APP_PARTS = app.crypto.js + app.js) into one source — node:vm doesn't share top-level const/let across separate runInContext calls the way the browser shares them across <script> tags. argon2.js is a self-contained IIFE and loads separately first. The concatenated source runs in a node:vm context with browser globals stubbed (crypto, localStorage, document, location, …) so init() never fires, then an export epilogue surfaces the internals on globalThis.__test.

Two gotchas the harness works around, both documented inline:

  • const/let don't attach to the vm global. Top-level function/var declarations become properties of the context global, but const 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.deepStrictEqual trips on the prototype check. Structural comparisons normalize through JSON first (see eqDeep in csv.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.