test: add frontend unit suite + fix mixed local/UTC timestamps

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>
This commit is contained in:
r-zakarya
2026-07-04 19:17:33 +01:00
parent 73e4e37f19
commit d9397881dc
12 changed files with 818 additions and 26 deletions
+27 -1
View File
@@ -102,8 +102,34 @@ if ($jsFiles) {
}
}
Log "JS syntax OK."
# --- Unit test gate ---------------------------------------------------
# Run the frontend regression suite (crypto round-trip, CSV import,
# sync-merge arbitration) before embedding. A broken crypto/merge
# invariant now blocks the build the same way a syntax error does,
# instead of surfacing only after a full Delphi rebuild + manual test.
# ~1.7 s, zero deps (node:test). Skipped automatically if the suite
# isn't present. Set PM_SKIP_TESTS=1 to bypass during rapid iteration.
if ($env:PM_SKIP_TESTS -eq '1') {
Log "PM_SKIP_TESTS=1 - skipping unit tests."
} elseif (Test-Path (Join-Path $WebRoot 'js\tests')) {
Log "Running frontend unit tests..."
Push-Location $WebRoot
try {
$testOut = $null | & $node.Source --test 'js/tests/**/*.test.js' 2>&1
$testExit = $LASTEXITCODE
} finally {
Pop-Location
}
if ($testExit -ne 0) {
Log "UNIT TESTS FAILED:"
Log ($testOut | Out-String)
throw "Frontend unit tests failed - aborting asset build. (Set PM_SKIP_TESTS=1 to bypass.)"
}
Log "Unit tests OK."
}
} else {
Log "WARNING: node not found - skipping JS syntax check. Install Node to enable it."
Log "WARNING: node not found - skipping JS syntax check + unit tests. Install Node to enable them."
}
}