ca8081987d
First slice of the app.js split. Approach: ordered classic-script files loaded via separate <script> tags (argon2.js → app.crypto.js → app.js), NOT ES modules / a bundler. Classic scripts share one global lexical environment, so consts/functions cross-reference across files exactly as in the monofile — zero call-site rewrites, near-zero risk. Chosen over the audit's esbuild/ES-module suggestion because the code is written entirely in global scope (functions call each other by bare name everywhere). - js/app.crypto.js: KDF (PBKDF2 + Argon2id), verifier, AES-GCM encrypt/ decrypt, key persist/restore. Verified byte-for-byte identical to the original block before removal; no duplicate const across the two scripts. - index.html + BuildAssets whitelist + test harness updated for the load order. Harness CONCATENATES app.crypto.js + app.js (node:vm doesn't share top-level const across separate runInContext calls the way browsers share it across <script> tags); argon2.js stays a separate IIFE. - Runtime-validated: rebuilt exe unlocks via quick-unlock and loads/decrypts entries — the extracted crypto (restoreCryptoKey, verifierFromKeyHex, decryptPwd) works from the separate file. 42/42 tests green. - Docs: CLAUDE.md "Découpage frontend" (pattern + rules), file map, tests README, CODE_AUDIT §3.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
63 lines
3.2 KiB
Markdown
63 lines
3.2 KiB
Markdown
# 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.
|