mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
421 lines
16 KiB
TypeScript
421 lines
16 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { resolveFilterAliases } from "./nostr-utils";
|
|
import type { NostrFilter } from "@/types/nostr";
|
|
|
|
describe("resolveFilterAliases", () => {
|
|
describe("$me alias resolution", () => {
|
|
it("should replace $me with account pubkey in authors", () => {
|
|
const filter: NostrFilter = { authors: ["$me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result.authors).toEqual([accountPubkey]);
|
|
expect(result.authors).not.toContain("$me");
|
|
});
|
|
|
|
it("should replace $me with account pubkey in #p tags", () => {
|
|
const filter: NostrFilter = { "#p": ["$me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result["#p"]).toEqual([accountPubkey]);
|
|
expect(result["#p"]).not.toContain("$me");
|
|
});
|
|
|
|
it("should handle $me when no account is set", () => {
|
|
const filter: NostrFilter = { authors: ["$me"] };
|
|
const result = resolveFilterAliases(filter, undefined, []);
|
|
|
|
expect(result.authors).toEqual([]);
|
|
});
|
|
|
|
it("should preserve other pubkeys when resolving $me", () => {
|
|
const hex = "b".repeat(64);
|
|
const filter: NostrFilter = { authors: ["$me", hex] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result.authors).toContain(accountPubkey);
|
|
expect(result.authors).toContain(hex);
|
|
expect(result.authors).not.toContain("$me");
|
|
});
|
|
});
|
|
|
|
describe("$contacts alias resolution", () => {
|
|
it("should replace $contacts with contact pubkeys in authors", () => {
|
|
const filter: NostrFilter = { authors: ["$contacts"] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result.authors).toEqual(contacts);
|
|
expect(result.authors).not.toContain("$contacts");
|
|
});
|
|
|
|
it("should replace $contacts with contact pubkeys in #p tags", () => {
|
|
const filter: NostrFilter = { "#p": ["$contacts"] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result["#p"]).toEqual(contacts);
|
|
expect(result["#p"]).not.toContain("$contacts");
|
|
});
|
|
|
|
it("should handle $contacts with empty contact list", () => {
|
|
const filter: NostrFilter = { authors: ["$contacts"] };
|
|
const result = resolveFilterAliases(filter, undefined, []);
|
|
|
|
expect(result.authors).toEqual([]);
|
|
});
|
|
|
|
it("should preserve other pubkeys when resolving $contacts", () => {
|
|
const hex = "d".repeat(64);
|
|
const filter: NostrFilter = { authors: ["$contacts", hex] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result.authors).toContain(hex);
|
|
expect(result.authors).toContain(contacts[0]);
|
|
expect(result.authors).toContain(contacts[1]);
|
|
expect(result.authors).not.toContain("$contacts");
|
|
});
|
|
});
|
|
|
|
describe("combined $me and $contacts", () => {
|
|
it("should resolve both $me and $contacts in authors", () => {
|
|
const filter: NostrFilter = { authors: ["$me", "$contacts"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result.authors).toContain(accountPubkey);
|
|
expect(result.authors).toContain(contacts[0]);
|
|
expect(result.authors).toContain(contacts[1]);
|
|
expect(result.authors).not.toContain("$me");
|
|
expect(result.authors).not.toContain("$contacts");
|
|
});
|
|
|
|
it("should resolve aliases in both authors and #p tags", () => {
|
|
const filter: NostrFilter = {
|
|
authors: ["$me"],
|
|
"#p": ["$contacts"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result.authors).toEqual([accountPubkey]);
|
|
expect(result["#p"]).toEqual(contacts);
|
|
});
|
|
|
|
it("should handle mix of aliases and regular pubkeys", () => {
|
|
const hex1 = "d".repeat(64);
|
|
const hex2 = "e".repeat(64);
|
|
const filter: NostrFilter = {
|
|
authors: ["$me", hex1, "$contacts"],
|
|
"#p": [hex2, "$me"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result.authors).toContain(accountPubkey);
|
|
expect(result.authors).toContain(hex1);
|
|
expect(result.authors).toContain(contacts[0]);
|
|
expect(result.authors).toContain(contacts[1]);
|
|
expect(result["#p"]).toContain(hex2);
|
|
expect(result["#p"]).toContain(accountPubkey);
|
|
});
|
|
});
|
|
|
|
describe("deduplication", () => {
|
|
it("should deduplicate when $me is in contacts", () => {
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = [accountPubkey, "b".repeat(64), "c".repeat(64)];
|
|
const filter: NostrFilter = { authors: ["$me", "$contacts"] };
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
// Account pubkey should appear once (deduplicated)
|
|
const accountCount = result.authors?.filter(
|
|
(a) => a === accountPubkey,
|
|
).length;
|
|
expect(accountCount).toBe(1);
|
|
expect(result.authors?.length).toBe(3); // account + 2 other contacts
|
|
});
|
|
|
|
it("should deduplicate regular pubkeys that appear multiple times", () => {
|
|
const hex = "d".repeat(64);
|
|
const filter: NostrFilter = { authors: [hex, hex, hex] };
|
|
const result = resolveFilterAliases(filter, undefined, []);
|
|
|
|
expect(result.authors).toEqual([hex]);
|
|
});
|
|
|
|
it("should deduplicate across resolved contacts and explicit pubkeys", () => {
|
|
const hex1 = "a".repeat(64);
|
|
const hex2 = "b".repeat(64);
|
|
const contacts = [hex1, hex2, "c".repeat(64)];
|
|
const filter: NostrFilter = { authors: ["$contacts", hex1, hex2] };
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
// Each pubkey should appear once
|
|
expect(result.authors?.filter((a) => a === hex1).length).toBe(1);
|
|
expect(result.authors?.filter((a) => a === hex2).length).toBe(1);
|
|
expect(result.authors?.length).toBe(3); // 3 unique contacts
|
|
});
|
|
});
|
|
|
|
describe("filter preservation", () => {
|
|
it("should preserve other filter properties", () => {
|
|
const filter: NostrFilter = {
|
|
authors: ["$me"],
|
|
kinds: [1, 3, 7],
|
|
limit: 50,
|
|
since: 1234567890,
|
|
"#t": ["nostr", "bitcoin"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result.kinds).toEqual([1, 3, 7]);
|
|
expect(result.limit).toBe(50);
|
|
expect(result.since).toBe(1234567890);
|
|
expect(result["#t"]).toEqual(["nostr", "bitcoin"]);
|
|
});
|
|
|
|
it("should not modify original filter", () => {
|
|
const filter: NostrFilter = { authors: ["$me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
// Original filter should still have $me
|
|
expect(filter.authors).toContain("$me");
|
|
});
|
|
|
|
it("should handle filters without aliases", () => {
|
|
const hex = "a".repeat(64);
|
|
const filter: NostrFilter = {
|
|
authors: [hex],
|
|
kinds: [1],
|
|
};
|
|
const result = resolveFilterAliases(filter, "b".repeat(64), []);
|
|
|
|
expect(result.authors).toEqual([hex]);
|
|
expect(result.kinds).toEqual([1]);
|
|
});
|
|
|
|
it("should handle empty filter", () => {
|
|
const filter: NostrFilter = {};
|
|
const result = resolveFilterAliases(filter, "a".repeat(64), []);
|
|
|
|
expect(result).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
it("should handle undefined authors array", () => {
|
|
const filter: NostrFilter = { kinds: [1] };
|
|
const result = resolveFilterAliases(filter, "a".repeat(64), []);
|
|
|
|
expect(result.authors).toBeUndefined();
|
|
});
|
|
|
|
it("should handle undefined #p array", () => {
|
|
const filter: NostrFilter = { authors: ["$me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result["#p"]).toBeUndefined();
|
|
});
|
|
|
|
it("should handle large contact lists", () => {
|
|
const contacts = Array.from({ length: 5000 }, (_, i) =>
|
|
i.toString(16).padStart(64, "0"),
|
|
);
|
|
const filter: NostrFilter = { authors: ["$contacts"] };
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result.authors?.length).toBe(5000);
|
|
});
|
|
|
|
it("should handle mixed case aliases (case-insensitive)", () => {
|
|
// Aliases are case-insensitive for user convenience
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const filter: NostrFilter = { authors: ["$Me", "$CONTACTS"] };
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
// These should be resolved despite case differences
|
|
expect(result.authors).toContain(accountPubkey);
|
|
expect(result.authors).toContain(contacts[0]);
|
|
expect(result.authors).toContain(contacts[1]);
|
|
expect(result.authors).not.toContain("$Me");
|
|
expect(result.authors).not.toContain("$CONTACTS");
|
|
});
|
|
});
|
|
|
|
describe("case-insensitive alias resolution", () => {
|
|
it("should resolve $ME (uppercase) in authors", () => {
|
|
const filter: NostrFilter = { authors: ["$ME"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result.authors).toEqual([accountPubkey]);
|
|
expect(result.authors).not.toContain("$ME");
|
|
});
|
|
|
|
it("should resolve $Me (mixed case) in #p tags", () => {
|
|
const filter: NostrFilter = { "#p": ["$Me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result["#p"]).toEqual([accountPubkey]);
|
|
expect(result["#p"]).not.toContain("$Me");
|
|
});
|
|
|
|
it("should resolve $CONTACTS (uppercase) in authors", () => {
|
|
const filter: NostrFilter = { authors: ["$CONTACTS"] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result.authors).toEqual(contacts);
|
|
expect(result.authors).not.toContain("$CONTACTS");
|
|
});
|
|
|
|
it("should resolve $Contacts (mixed case) in #P tags", () => {
|
|
const filter: NostrFilter = { "#P": ["$Contacts"] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result["#P"]).toEqual(contacts);
|
|
expect(result["#P"]).not.toContain("$Contacts");
|
|
});
|
|
|
|
it("should handle multiple case variations in same filter", () => {
|
|
const filter: NostrFilter = {
|
|
authors: ["$me", "$ME", "$Me"],
|
|
"#p": ["$contacts", "$CONTACTS", "$Contacts"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
// Should deduplicate all variants of $me to single pubkey
|
|
expect(result.authors).toEqual([accountPubkey]);
|
|
// Should deduplicate all variants of $contacts
|
|
expect(result["#p"]).toEqual(contacts);
|
|
});
|
|
|
|
it("should handle sloppy typing with whitespace-like patterns", () => {
|
|
const filter: NostrFilter = {
|
|
authors: ["$ME", "$me", "$Me"],
|
|
"#P": ["$CONTACTS", "$contacts", "$Contacts"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result.authors?.length).toBe(1);
|
|
expect(result.authors).toContain(accountPubkey);
|
|
expect(result["#P"]).toEqual(contacts);
|
|
});
|
|
});
|
|
|
|
describe("uppercase #P tag resolution", () => {
|
|
it("should replace $me with account pubkey in #P tags", () => {
|
|
const filter: NostrFilter = { "#P": ["$me"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result["#P"]).toEqual([accountPubkey]);
|
|
expect(result["#P"]).not.toContain("$me");
|
|
});
|
|
|
|
it("should replace $contacts with contact pubkeys in #P tags", () => {
|
|
const filter: NostrFilter = { "#P": ["$contacts"] };
|
|
const contacts = ["a".repeat(64), "b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, undefined, contacts);
|
|
|
|
expect(result["#P"]).toEqual(contacts);
|
|
expect(result["#P"]).not.toContain("$contacts");
|
|
});
|
|
|
|
it("should handle $me when no account is set in #P", () => {
|
|
const filter: NostrFilter = { "#P": ["$me"] };
|
|
const result = resolveFilterAliases(filter, undefined, []);
|
|
|
|
expect(result["#P"]).toEqual([]);
|
|
});
|
|
|
|
it("should preserve other pubkeys when resolving $me in #P", () => {
|
|
const hex = "b".repeat(64);
|
|
const filter: NostrFilter = { "#P": ["$me", hex] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const result = resolveFilterAliases(filter, accountPubkey, []);
|
|
|
|
expect(result["#P"]).toContain(accountPubkey);
|
|
expect(result["#P"]).toContain(hex);
|
|
expect(result["#P"]).not.toContain("$me");
|
|
});
|
|
|
|
it("should handle mix of $me, $contacts, and regular pubkeys in #P", () => {
|
|
const hex1 = "d".repeat(64);
|
|
const filter: NostrFilter = { "#P": ["$me", hex1, "$contacts"] };
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result["#P"]).toContain(accountPubkey);
|
|
expect(result["#P"]).toContain(hex1);
|
|
expect(result["#P"]).toContain(contacts[0]);
|
|
expect(result["#P"]).toContain(contacts[1]);
|
|
});
|
|
|
|
it("should deduplicate when $me is in contacts for #P", () => {
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = [accountPubkey, "b".repeat(64), "c".repeat(64)];
|
|
const filter: NostrFilter = { "#P": ["$me", "$contacts"] };
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
// Account pubkey should appear once (deduplicated)
|
|
const accountCount = result["#P"]?.filter(
|
|
(a) => a === accountPubkey,
|
|
).length;
|
|
expect(accountCount).toBe(1);
|
|
expect(result["#P"]?.length).toBe(3); // account + 2 other contacts
|
|
});
|
|
});
|
|
|
|
describe("mixed #p and #P tag resolution", () => {
|
|
it("should resolve aliases in both #p and #P independently", () => {
|
|
const filter: NostrFilter = {
|
|
"#p": ["$me"],
|
|
"#P": ["$contacts"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
expect(result["#p"]).toEqual([accountPubkey]);
|
|
expect(result["#P"]).toEqual(contacts);
|
|
});
|
|
|
|
it("should handle same aliases in both tags without interference", () => {
|
|
const filter: NostrFilter = {
|
|
"#p": ["$me", "$contacts"],
|
|
"#P": ["$me", "$contacts"],
|
|
};
|
|
const accountPubkey = "a".repeat(64);
|
|
const contacts = ["b".repeat(64), "c".repeat(64)];
|
|
const result = resolveFilterAliases(filter, accountPubkey, contacts);
|
|
|
|
// Both should have same resolved values
|
|
expect(result["#p"]).toContain(accountPubkey);
|
|
expect(result["#p"]).toContain(contacts[0]);
|
|
expect(result["#p"]).toContain(contacts[1]);
|
|
expect(result["#P"]).toContain(accountPubkey);
|
|
expect(result["#P"]).toContain(contacts[0]);
|
|
expect(result["#P"]).toContain(contacts[1]);
|
|
});
|
|
});
|
|
});
|