Files
bitvid/tests/nostr/session-actor.test.mjs
T
thePR0M3TH3ANandClaude Fable 5 99d1b1dd36 tests: spec-correct four stale tests hidden by piped exit codes
A per-file sweep (local suite runs were reading the PIPE's exit code, not
npm's) surfaced four tests failing on HEAD:

- hash-change-handler: logged-out default is deliberately TRENDING since
  04621efc; test still expected most-recent-videos
- session-actor / sessionActor / nsec-login: encoded the pre-fix "no-arg
  clearStoredSessionActor wipes EVERY account" behavior that 2026-07-02
  deliberately removed (one account's logout must not destroy other saved
  accounts' keys). Replaced with stronger assertions: multi-account survival
  on no-arg clear, targeted forget, and the blocked-key security property
  asserted on the blocked account specifically.

Integrity notes in each file; TODO #11b addendum documents the pipe lesson.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-03 21:01:45 -04:00

118 lines
4.1 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import * as sessionActor from "../../js/nostr/sessionActor.js";
import { indexedDB, IDBKeyRange } from "fake-indexeddb";
// Ensure localStorage is available
import "../test-helpers/setup-localstorage.mjs";
// Polyfill IndexedDB
globalThis.indexedDB = indexedDB;
globalThis.IDBKeyRange = IDBKeyRange;
test("SessionActor", async (t) => {
const passphrase = "correct-horse-battery-staple";
const privateKey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
await t.test("encrypts and decrypts private key", async () => {
const encrypted = await sessionActor.encryptSessionPrivateKey(privateKey, passphrase);
assert.ok(encrypted.ciphertext);
assert.ok(encrypted.salt);
assert.ok(encrypted.iv);
assert.equal(encrypted.algorithm, "AES-GCM");
const payload = {
privateKeyEncrypted: encrypted.ciphertext,
encryption: {
salt: encrypted.salt,
iv: encrypted.iv,
iterations: encrypted.iterations,
hash: encrypted.hash,
version: encrypted.version,
algorithm: encrypted.algorithm,
}
};
const decrypted = await sessionActor.decryptSessionPrivateKey(payload, passphrase);
assert.equal(decrypted, privateKey);
});
await t.test("fails to decrypt with wrong passphrase", async () => {
const encrypted = await sessionActor.encryptSessionPrivateKey(privateKey, passphrase);
const payload = {
privateKeyEncrypted: encrypted.ciphertext,
encryption: {
salt: encrypted.salt,
iv: encrypted.iv,
}
};
try {
await sessionActor.decryptSessionPrivateKey(payload, "wrong-password");
assert.fail("Should have thrown error");
} catch (error) {
assert.match(error.message, /Failed to decrypt/);
}
});
await t.test("persists and reads session actor", async () => {
// Clean up first
localStorage.clear();
sessionActor.clearStoredSessionActor();
const actor = {
pubkey: "pubkey123",
privateKeyEncrypted: "encrypteddata",
encryption: {
salt: "salt",
iv: "iv",
iterations: 1000,
},
createdAt: Date.now(),
};
sessionActor.persistSessionActor(actor);
// Verify localStorage
const stored = JSON.parse(localStorage.getItem(sessionActor.SESSION_ACTOR_STORAGE_KEY));
assert.equal(stored.pubkey, actor.pubkey);
assert.equal(stored.privateKeyEncrypted, actor.privateKeyEncrypted);
// Verify read
const read = sessionActor.readStoredSessionActorEntry();
assert.ok(read);
assert.equal(read.pubkey, actor.pubkey);
assert.equal(read.privateKeyEncrypted, actor.privateKeyEncrypted);
assert.equal(read.encryption.salt, actor.encryption.salt);
});
// spec_correction: no-arg clearStoredSessionActor() deliberately no longer
// wipes the per-account map (that wiped EVERY saved account's key on one
// account's logout — fixed 2026-07-02, same class as the NIP-46 map wipe).
// Forgetting an account is the targeted clearStoredSessionActor(pubkey).
// See tests/nostr/sessionActor.test.mjs for the full integrity note and the
// multi-account survival scenario.
await t.test("clears stored session actor (targeted; no-arg drops only the default slot)", async () => {
const actor = {
pubkey: "pubkey123",
privateKeyEncrypted: "encrypteddata",
encryption: { salt: "salt", iv: "iv" }
};
sessionActor.persistSessionActor(actor);
// No-arg clear: default slot gone, but the account's key survives.
sessionActor.clearStoredSessionActor();
assert.equal(localStorage.getItem(sessionActor.SESSION_ACTOR_STORAGE_KEY), null);
assert.ok(
sessionActor.readStoredSessionActorEntry("pubkey123"),
"no-arg clear must not delete the account's stored key",
);
// Targeted clear actually forgets it.
sessionActor.clearStoredSessionActor("pubkey123");
assert.equal(sessionActor.readStoredSessionActorEntry("pubkey123"), null);
assert.equal(sessionActor.readStoredSessionActorEntry(), null);
});
});