import { describe, expect, it } from "vitest"; import { Identity, NostrSecret, dnsTxtValue, newClaimPayload, signClaim, toCompact, } from "@kez/core"; import { ChannelError } from "../src/index.js"; import { DnsChannel, looksLikeKezTxt, type TxtResolver } from "../src/dns.js"; class CapturingResolver implements TxtResolver { constructor( private readonly records: string[], private readonly expectedName: string, ) {} async lookupTxt(name: string): Promise { expect(name).toBe(this.expectedName); return this.records; } } class FailingResolver implements TxtResolver { async lookupTxt(): Promise { throw ChannelError.unreachable("simulated network failure"); } } function signDns(subject: string) { const secret = NostrSecret.generate(); return signClaim( newClaimPayload(Identity.parse(subject), secret.identity(), new Date()), secret, ); } describe("DnsChannel", () => { it("queries _kez.", async () => { const signed = signDns("dns:jason.example.com"); const channel = new DnsChannel( new CapturingResolver([toCompact(signed)], "_kez.jason.example.com"), ); const hit = await channel.fetchAndVerify(Identity.parse("dns:jason.example.com")); expect(hit.proof).toEqual(signed); }); it("supports legacy kez1: prefix", async () => { const signed = signDns("dns:jason.example.com"); const legacy = dnsTxtValue(signed); expect(legacy.startsWith("kez1:")).toBe(true); const channel = new DnsChannel( new CapturingResolver([legacy], "_kez.jason.example.com"), ); const hit = await channel.fetchAndVerify(Identity.parse("dns:jason.example.com")); expect(hit.proof).toEqual(signed); }); it("returns NotFound when no records", async () => { const channel = new DnsChannel(new CapturingResolver([], "_kez.jason.example.com")); await expect( channel.fetchAndVerify(Identity.parse("dns:jason.example.com")), ).rejects.toMatchObject({ kind: "NotFound" }); }); it("skips non-KEZ TXT records", async () => { const channel = new DnsChannel( new CapturingResolver( ["v=spf1 -all", "google-site-verification=abc"], "_kez.jason.example.com", ), ); await expect( channel.fetchAndVerify(Identity.parse("dns:jason.example.com")), ).rejects.toMatchObject({ kind: "NotFound" }); }); it("surfaces resolver failure as Unreachable", async () => { const channel = new DnsChannel(new FailingResolver()); await expect( channel.fetchAndVerify(Identity.parse("dns:jason.example.com")), ).rejects.toMatchObject({ kind: "Unreachable" }); }); }); describe("looksLikeKezTxt", () => { it("accepts both prefixes", () => { expect(looksLikeKezTxt("kez:z1:foo")).toBe(true); expect(looksLikeKezTxt("kez1:{...}")).toBe(true); expect(looksLikeKezTxt("v=spf1")).toBe(false); expect(looksLikeKezTxt("")).toBe(false); }); });