Files
bitvid/tests/moderation/trust-seeds.test.mjs
T

249 lines
7.4 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { DEFAULT_TRUST_SEED_NPUBS } from "../../js/constants.js";
import { DEFAULT_TRUST_SEED_NPUBS as CONFIG_DEFAULT_TRUST_SEED_NPUBS } from "../../js/config.js";
import {
withMockedNostrTools,
createModerationServiceHarness,
createReportEvent,
} from "../helpers/moderation-test-helpers.mjs";
import { ADMIN_SUPER_NPUB } from "../../js/config.js";
import { register } from "node:module";
const [PRIMARY_SEED_NPUB] = DEFAULT_TRUST_SEED_NPUBS;
test("default trust seeds derive from config", () => {
const sanitizedConfigSeeds = Array.isArray(CONFIG_DEFAULT_TRUST_SEED_NPUBS)
? Array.from(
new Set(
CONFIG_DEFAULT_TRUST_SEED_NPUBS
.filter((value) => typeof value === "string")
.map((value) => value.trim())
.filter(Boolean)
)
)
: [];
assert.ok(Object.isFrozen(DEFAULT_TRUST_SEED_NPUBS));
assert.deepEqual(DEFAULT_TRUST_SEED_NPUBS, sanitizedConfigSeeds);
assert.notStrictEqual(DEFAULT_TRUST_SEED_NPUBS, CONFIG_DEFAULT_TRUST_SEED_NPUBS);
});
function createTrustedMuteEvent({ owner, muted, id = "m".repeat(64), createdAt = Math.floor(Date.now() / 1000) } = {}) {
return {
kind: 10000,
id,
pubkey: owner,
created_at: createdAt,
tags: [["p", muted]],
content: "",
};
}
test("trusted seeds contribute to trusted mute counts", (t) => {
withMockedNostrTools(t);
const mutedAuthor = "c".repeat(64);
const { service } = createModerationServiceHarness(t);
service.setTrustedSeeds([PRIMARY_SEED_NPUB]);
const [seedHex] = Array.from(service.trustedContacts);
assert.ok(seedHex, "trusted seeds should populate trustedContacts");
const muteEvent = createTrustedMuteEvent({ owner: seedHex, muted: mutedAuthor });
service.applyTrustedMuteEvent(seedHex, muteEvent);
assert.equal(service.isTrustedMuteOwner(seedHex), true);
assert.equal(service.isAuthorMutedByTrusted(mutedAuthor), true);
assert.deepEqual(service.getTrustedMutersForAuthor(mutedAuthor), [seedHex]);
});
test("trusted seeds contribute to trusted report counts", (t) => {
withMockedNostrTools(t);
const eventId = "f".repeat(64);
const { service } = createModerationServiceHarness(t);
service.setTrustedSeeds([PRIMARY_SEED_NPUB]);
const [seedHex] = Array.from(service.trustedContacts);
assert.ok(seedHex, "trusted seeds should populate trustedContacts");
const report = createReportEvent({
id: "r".repeat(64),
reporter: seedHex,
eventId,
createdAt: 1_700_000_111,
type: "nudity",
});
service.ingestReportEvent(report);
assert.equal(service.trustedReportCount(eventId, "nudity"), 1);
const summary = service.getTrustedReportSummary(eventId);
assert.equal(summary?.totalTrusted ?? 0, 1);
const reporters = service.getTrustedReporters(eventId, "nudity");
assert.deepEqual(reporters.map((entry) => entry.pubkey), [seedHex]);
});
test("trusted seed updates recompute guest report summaries", (t) => {
withMockedNostrTools(t);
const eventId = "a".repeat(64);
const reporterHex = "b".repeat(64);
const reporterNpub = `npub${reporterHex}`;
const { service } = createModerationServiceHarness(t);
const report = createReportEvent({
id: "c".repeat(64),
reporter: reporterHex,
eventId,
createdAt: 1_700_000_999,
type: "spam",
});
service.ingestReportEvent(report);
const preSeedSummary = service.getTrustedReportSummary(eventId);
assert.equal(preSeedSummary.totalTrusted, 0);
assert.equal(preSeedSummary.types.spam?.trusted ?? 0, 0);
service.setTrustedSeeds([reporterNpub]);
const postSeedSummary = service.getTrustedReportSummary(eventId);
assert.equal(postSeedSummary.totalTrusted, 1);
assert.equal(postSeedSummary.types.spam.trusted, 1);
assert.equal(service.trustedReportCount(eventId, "spam"), 1);
});
test("moderator seeds populate trusted contacts", (t) => {
withMockedNostrTools(t);
const moderatorHex = "d".repeat(64);
const moderatorNpub = `npub${moderatorHex}`;
const { service } = createModerationServiceHarness(t);
service.setTrustedSeeds([moderatorNpub]);
assert.ok(
service.trustedSeedContacts.has(moderatorHex),
"moderator seeds should populate trusted seed contacts"
);
assert.ok(
service.trustedContacts.has(moderatorHex),
"moderator seeds should populate trusted contacts"
);
});
test("bootstrap seeds track editor roster and ignore whitelist-only changes", async (t) => {
register(new URL("./mocks/bootstrap-trust-seeds-loader.mjs", import.meta.url));
t.after(() => {
delete globalThis.__bootstrapAccessControlMock;
delete globalThis.__bootstrapModerationServiceMock;
});
let currentEditors = [
"npub1moderatorexamplemoderatorexamplemoderatorexample1",
];
const whitelistListeners = new Set();
const editorListeners = new Set();
let ensureReadyCalls = 0;
let getEditorsCalls = 0;
const accessControlMock = {
ensureReady: async () => {
ensureReadyCalls += 1;
},
waitForReady: async () => {
ensureReadyCalls += 1;
},
getEditors: () => {
getEditorsCalls += 1;
return currentEditors;
},
onWhitelistChange: (listener) => {
if (typeof listener === "function") {
whitelistListeners.add(listener);
}
return () => {
whitelistListeners.delete(listener);
};
},
onEditorsChange: (listener) => {
if (typeof listener === "function") {
editorListeners.add(listener);
}
return () => {
editorListeners.delete(listener);
};
},
};
const setTrustedSeedsCalls = [];
const recomputeCalls = [];
const moderationServiceMock = {
setTrustedSeeds: (seeds) => {
const snapshot = [];
if (seeds && typeof seeds[Symbol.iterator] === "function") {
for (const value of seeds) {
snapshot.push(value);
}
}
setTrustedSeedsCalls.push(snapshot);
},
recomputeAllSummaries: () => {
recomputeCalls.push(true);
},
};
globalThis.__bootstrapAccessControlMock = accessControlMock;
globalThis.__bootstrapModerationServiceMock = moderationServiceMock;
const bootstrapModule = await import("../../js/services/trustBootstrap.js");
await bootstrapModule.bootstrapTrustedSeeds();
assert.equal(ensureReadyCalls, 1);
assert.ok(
getEditorsCalls >= 1,
"bootstrap should query editors during initialization"
);
assert.equal(setTrustedSeedsCalls.length, 1);
assert.equal(recomputeCalls.length, 1, "summaries should recompute after trusted seeds apply");
const [initialSeeds] = setTrustedSeedsCalls;
assert.ok(
initialSeeds.includes(ADMIN_SUPER_NPUB),
"super admin should be included in trusted seed roster"
);
assert.ok(
initialSeeds.some((value) => currentEditors.includes(value)),
"initial editor roster should seed trust"
);
assert.equal(whitelistListeners.size, 0, "creator whitelist changes must not affect trust seeds");
currentEditors = [
"npub1newmoderatorexamplemoderatorexamplemoderator",
"npub1secondmoderatorexamplemoderatorexamplemod",
];
for (const listener of editorListeners) {
listener(currentEditors);
}
assert.equal(
setTrustedSeedsCalls.length,
2,
"editor change should recompute trusted seed roster"
);
const latestSeeds = setTrustedSeedsCalls[1];
assert.deepEqual(
new Set(latestSeeds),
new Set([ADMIN_SUPER_NPUB, ...currentEditors]),
"trusted seeds should match super admin plus latest editors"
);
});