Files
bitvid/tests/moderation/trusted-mute-lists.test.mjs
T
google-labs-jules[bot] 85883aa5c6 feat: increase default trusted mute hide threshold to 20
- Updated `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD` in `config/instance-config.js` from 1 to 20.
- Updated `tests/moderation/trusted-mute-lists.test.mjs` to explicitly override the threshold to 1 for tests verifying the hide mechanism.
- Updated documentation (`docs/moderation/*.md`, `docs/instance-config.md`, `content/community-guidelines.md`) to reflect the new default and upstream examples.
- Verified that existing blurring logic in `js/feedEngine/stages.js` and `js/ui/components/VideoCard.js` handles the intermediate state (1-19 mutes) correctly.
2026-01-11 21:30:23 +00:00

181 lines
6.1 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import {
DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
DEFAULT_BLUR_THRESHOLD,
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
} from "../../js/constants.js";
import { createModerationStage } from "../../js/feedEngine/stages.js";
import { withMockedNostrTools, createModerationServiceHarness } from "../helpers/moderation-test-helpers.mjs";
const { userBlocks } = await import("../../js/userBlocks.js");
const { nostrClient } = await import("../../js/nostrClientFacade.js");
const { setActiveSigner, clearActiveSigner } = await import(
"../../js/nostr/client.js"
);
test("trusted mute lists from seeds hide authors for anonymous viewers", async (t) => {
withMockedNostrTools(t);
const seedHex = "1".repeat(64);
const mutedHex = "2".repeat(64);
const originalBlocked = new Set(userBlocks.blockedPubkeys);
const originalBlockEventId = userBlocks.blockEventId;
const originalBlockEventCreatedAt = userBlocks.blockEventCreatedAt;
const originalLastPublishedCreatedAt = userBlocks.lastPublishedCreatedAt;
const originalMuteEventId = userBlocks.muteEventId;
const originalMuteEventCreatedAt = userBlocks.muteEventCreatedAt;
const originalLoaded = userBlocks.loaded;
const originalLoadBlocks = userBlocks.loadBlocks;
const originalRelays = Array.isArray(nostrClient.relays)
? [...nostrClient.relays]
: nostrClient.relays;
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
? [...nostrClient.writeRelays]
: nostrClient.writeRelays;
const originalPool = nostrClient.pool;
t.after(() => {
userBlocks.blockedPubkeys = originalBlocked;
userBlocks.blockEventId = originalBlockEventId;
userBlocks.blockEventCreatedAt = originalBlockEventCreatedAt;
userBlocks.lastPublishedCreatedAt = originalLastPublishedCreatedAt;
userBlocks.muteEventId = originalMuteEventId;
userBlocks.muteEventCreatedAt = originalMuteEventCreatedAt;
userBlocks.loaded = originalLoaded;
userBlocks.loadBlocks = originalLoadBlocks;
nostrClient.relays = originalRelays;
nostrClient.writeRelays = originalWriteRelays;
nostrClient.pool = originalPool;
clearActiveSigner();
});
const publishedEvents = [];
nostrClient.relays = ["wss://seed-relay.example"];
nostrClient.writeRelays = nostrClient.relays;
nostrClient.pool = {
publish: (_targets, event) => {
publishedEvents.push(event);
return {
on(eventName, handler) {
if (eventName === "ok") {
setImmediate(handler);
return true;
}
return false;
},
};
},
list: async () => [],
};
userBlocks.loadBlocks = async () => {};
userBlocks.blockedPubkeys = new Set();
userBlocks.blockEventId = null;
userBlocks.blockEventCreatedAt = null;
userBlocks.lastPublishedCreatedAt = null;
userBlocks.muteEventId = null;
userBlocks.muteEventCreatedAt = null;
userBlocks.loaded = true;
const signedEvents = [];
setActiveSigner({
type: "private-key",
pubkey: seedHex,
nip04Encrypt: async (pubkey, plaintext) => {
assert.equal(pubkey, seedHex, "nip04 encrypt should target the seed pubkey");
return `nip04:${plaintext}`;
},
nip44Encrypt: async (pubkey, plaintext) => {
assert.equal(pubkey, seedHex, "nip44 encrypt should target the seed pubkey");
return `nip44:${plaintext}`;
},
signEvent: async (event) => {
const signed = {
...event,
id: `event-${signedEvents.length + 1}`,
created_at: event.created_at ?? Math.floor(Date.now() / 1000),
};
signedEvents.push(signed);
return signed;
},
});
await userBlocks.addBlock(mutedHex, seedHex);
const muteEvent = signedEvents.find(
(event) =>
event.kind === 10000 &&
Array.isArray(event.tags) &&
event.tags.some((tag) => Array.isArray(tag) && tag[0] === "p"),
);
assert(muteEvent, "block list updates should publish a kind 10000 mute list event");
assert(
muteEvent.tags.some((tag) => Array.isArray(tag) && tag[0] === "p" && tag[1] === mutedHex),
"mute list event should include p-tags for blocked pubkeys",
);
const publishedMuteEvents = publishedEvents.filter(
(event) =>
event?.kind === 10000 &&
Array.isArray(event.tags) &&
event.tags.some((tag) => Array.isArray(tag) && tag[0] === "p"),
);
assert(publishedMuteEvents.length >= 1, "mute list event should be published to relays");
const { service } = createModerationServiceHarness(t, { userBlocks });
service.refreshViewerFromClient = async () => {};
service.setActiveEventIds = async () => {};
service.subscribeToReports = async () => {};
service.teardownReportSubscription = () => {};
service.getAdminListSnapshot = () => ({
whitelist: new Set(),
whitelistHex: new Set(),
blacklist: new Set(),
blacklistHex: new Set(),
});
service.getAccessControlStatus = () => ({ whitelisted: false, blacklisted: false, hex: "" });
service.setTrustedSeeds([seedHex]);
service.ingestTrustedMuteEvent(muteEvent);
assert.equal(
service.isAuthorMutedByTrusted(mutedHex),
true,
"trusted mute ingestion should flag the author as muted",
);
const stage = createModerationStage({
service,
autoplayThreshold: DEFAULT_AUTOPLAY_BLOCK_THRESHOLD,
blurThreshold: DEFAULT_BLUR_THRESHOLD,
trustedMuteHideThreshold: 1, // Override to ensure hiding triggers with a single mute
reportType: "nudity",
});
const items = [{ video: { id: "video-muted", pubkey: mutedHex }, metadata: {} }];
const why = [];
const context = {
runtime: { isAuthorBlocked: () => false },
addWhy(detail) {
why.push(detail);
return detail;
},
log() {},
};
const result = await stage(items, context);
assert.equal(result.length, 0, "trusted mute threshold should hide the muted author");
const reasons = why.map((entry) => entry.reason);
assert(
reasons.includes("trusted-mute"),
"moderation stage should record trusted mute metadata",
);
assert(
reasons.includes("trusted-mute-hide"),
"moderation stage should record the trusted mute hide reason",
);
});