mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-11-01 11:01:01 +00:00
Closes the four spec gaps from the audit, verified against the NIPs repo. NIP-65 (kind 10002) — the one that was actually breaking things. Mute lists are published by each contact to their own write relays, so querying a fixed relay set finds only the subset who happen to write where this deployment reads. The trust graph was silently under-fed and counts came out low, which looks like "nobody muted them" rather than like a bug. Adds a relay-list codec, per-author grouping with a fallback for accounts that have published no list, and on-demand relay connections in the transport so an outbox query can reach relays that were never configured. NIP-51 private entries — the viewer's own private mutes are now decrypted through their signer (NIP-44) and applied as viewer mutes. Someone who mutes privately previously got no effect at all. The privacy boundary is asymmetric and enforced at the codec: another account's private list is never decoded, and the check lives there rather than in the caller's discipline. NIP-02 (kind 3) — the runtime can build the trust graph itself, so <bitgate-provider> no longer needs a host to supply a follow list. Set trust="manual" to opt out. NIP-56 vocabulary — SOCIAL_POLICY declared thresholds for "harassment", which is not a NIP-56 type, so no standard client could ever trigger it. Replaced with the defined vocabulary and asserted in tests. Commerce keeps its marketplace-specific categories, now documented as deliberate, with defaults so unclassified reports are never silently dropped. Two bugs found by the new tests: - checkout had no default threshold, so a flood of "other" reports did nothing at the one place money changes hands. Now earns review, never auto-denial. - describe() spread diagnostics after a derived field, so a stale zero shadowed the live relay-list count. Adds docs/nips.md recording coverage, deliberate choices, and remaining gaps — NIP-32 labelling being the largest. 811 tests.
90 lines
2.7 KiB
JavaScript
90 lines
2.7 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
CONTACT_LIST_KIND,
|
|
decodeContactList,
|
|
encodeContactList,
|
|
} from "../src/contacts.js";
|
|
|
|
const OWNER = "a1".repeat(32);
|
|
const ALICE = "b2".repeat(32);
|
|
const BOB = "c3".repeat(32);
|
|
const NOW = 1_750_000_000;
|
|
|
|
const event = (parts) => ({
|
|
id: "00".repeat(32),
|
|
pubkey: OWNER,
|
|
kind: CONTACT_LIST_KIND,
|
|
created_at: NOW,
|
|
tags: [],
|
|
content: "",
|
|
...parts,
|
|
});
|
|
|
|
describe("decodeContactList", () => {
|
|
it("reads followed pubkeys", () => {
|
|
const list = decodeContactList(event({ tags: [["p", ALICE], ["p", BOB]] }));
|
|
expect(list?.owner).toBe(OWNER);
|
|
expect(list?.contacts).toEqual([ALICE, BOB]);
|
|
});
|
|
|
|
it("captures relay hints from the third tag position", () => {
|
|
const list = decodeContactList(
|
|
event({ tags: [["p", ALICE, "wss://alicerelay.com/", "alice"]] }),
|
|
);
|
|
expect(list?.relayHints.get(ALICE)).toBe("wss://alicerelay.com/");
|
|
});
|
|
|
|
it("tolerates missing relay and petname", () => {
|
|
const list = decodeContactList(event({ tags: [["p", ALICE, "", ""]] }));
|
|
expect(list?.contacts).toEqual([ALICE]);
|
|
expect(list?.relayHints.size).toBe(0);
|
|
});
|
|
|
|
it("deduplicates repeated follows", () => {
|
|
const list = decodeContactList(event({ tags: [["p", ALICE], ["p", ALICE]] }));
|
|
expect(list?.contacts).toEqual([ALICE]);
|
|
});
|
|
|
|
it("skips malformed pubkeys", () => {
|
|
const list = decodeContactList(event({ tags: [["p", "junk"], ["p", ALICE]] }));
|
|
expect(list?.contacts).toEqual([ALICE]);
|
|
});
|
|
|
|
it("ignores content, which NIP-02 does not use", () => {
|
|
const list = decodeContactList(
|
|
event({ tags: [["p", ALICE]], content: '{"wss://legacy.example":{"read":true}}' }),
|
|
);
|
|
expect(list?.contacts).toEqual([ALICE]);
|
|
});
|
|
|
|
it("rejects the wrong kind and a malformed owner", () => {
|
|
expect(decodeContactList(event({ kind: 10000 }))).toBeNull();
|
|
expect(decodeContactList(event({ pubkey: "nope" }))).toBeNull();
|
|
});
|
|
|
|
it("returns an empty list for someone following nobody", () => {
|
|
expect(decodeContactList(event({}))?.contacts).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("encodeContactList", () => {
|
|
it("accepts bare pubkeys", () => {
|
|
expect(encodeContactList([ALICE]).tags).toEqual([["p", ALICE]]);
|
|
});
|
|
|
|
it("includes relay and petname when given", () => {
|
|
const template = encodeContactList([{ pubkey: ALICE, relay: "wss://r.example", petname: "alice" }]);
|
|
expect(template.tags).toEqual([["p", ALICE, "wss://r.example", "alice"]]);
|
|
});
|
|
|
|
it("round-trips", () => {
|
|
const template = encodeContactList([ALICE, BOB]);
|
|
expect(decodeContactList(event(template))?.contacts).toEqual([ALICE, BOB]);
|
|
});
|
|
|
|
it("skips malformed entries", () => {
|
|
expect(encodeContactList(["junk", ALICE]).tags).toEqual([["p", ALICE]]);
|
|
});
|
|
});
|