Fourth slice of the app.js split. Moves TOTP (base32Decode, generateTOTP, parseOtpAuthUri) plus the TOTP-secret and custom-field AES-GCM wrappers to js/app.totp.js. Loads before app.js (pure declarations), after app.crypto.js (uses encryptPwd/decryptPwd). Also called by app.import.js and app.sync.js via shared global scope. - Byte-for-byte identical extraction; no duplicate const; syntax OK on all five app parts. - NEW: js/tests/totp.test.js — 13 tests including the 5 RFC 6238 Appendix B reference vectors (generateTOTP reads Date.now(), so each case stubs the sandbox clock to the vector's fixed time), base32 decode edge cases, and parseOtpAuthUri. Extraction AND new coverage in one slice. - Suite: 42 → 55 tests, all green. - Assets regenerated (manifest now embeds all 6 ordered JS files: argon2 → crypto → totp → import → app → sync); also fixes the previous import commit's not-yet-rebuilt manifest. - Delphi build artifacts (*.vrc, *.$manifest) gitignored. app.js: 11936 → 10138 lines (4 modules extracted, ~1800 lines). 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
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/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.