clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

main
lofi-node / tests / relay_config_test.ts
relay_config_test.ts 4.2K
1// Relay election: persisted in config.json, validated at load and node2// start, and applied to the iroh endpoint (disabled relays still connect3// directly on the local network; custom relay maps bind cleanly).45import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert";6import { initConfig, loadConfig, validateRelay } from "../src/config.ts";7import { createSyncNode } from "../src/node.ts";8import { loadIrohAddon } from "../src/native/addon.ts";9import { IrohNode } from "../src/iroh/node.ts";10import { resolveIrohLib } from "../src/native/loader.ts";1112const available = resolveIrohLib().status === "ok";1314Deno.test("relay: absent from new configs (n0 default) and round-trips when set", async () => {15 const dir = await Deno.makeTempDir();16 try {17 const config = await initConfig(dir, {18 relay: { urls: ["https://relay.example.com", "https://relay2.example.com"] },19 });20 assertEquals(config.relay, {21 urls: ["https://relay.example.com", "https://relay2.example.com"],22 });23 const loaded = await loadConfig(dir);24 assertEquals(loaded?.relay, {25 urls: ["https://relay.example.com", "https://relay2.example.com"],26 });27 } finally {28 await Deno.remove(dir, { recursive: true });29 }30 const plain = await Deno.makeTempDir();31 try {32 const config = await initConfig(plain, {});33 assertEquals(config.relay, undefined);34 } finally {35 await Deno.remove(plain, { recursive: true });36 }37});3839Deno.test("relay: malformed elections are rejected with the offending value", () => {40 validateRelay("n0");41 validateRelay("disabled");42 validateRelay({ urls: ["http://localhost:3340"] });43 assertThrows(() => validateRelay({ urls: [] }), Error, "at least one relay URL");44 assertThrows(() => validateRelay({ urls: ["not a url"] }), Error, '"not a url"');45 assertThrows(() => validateRelay({ urls: ["ftp://relay.example.com"] }), Error, "http(s)");46});4748Deno.test("relay: a config.json with a bad relay fails at load, not at dial time", async () => {49 const dir = await Deno.makeTempDir();50 try {51 const config = await initConfig(dir, {});52 await Deno.writeTextFile(53 `${dir}/config.json`,54 JSON.stringify({ ...config, relay: { urls: [] } }),55 );56 await assertRejects(() => loadConfig(dir), Error, "at least one relay URL");57 } finally {58 await Deno.remove(dir, { recursive: true });59 }60});6162Deno.test({63 name: "relay: createSyncNode rejects a bad election before any boot work",64 sanitizeOps: false,65 sanitizeResources: false,66 fn: async () => {67 await assertRejects(68 () =>69 createSyncNode({70 appId: crypto.randomUUID(),71 backendSecret: "b",72 adminSecret: "a",73 mesh: "off",74 relay: { urls: [] },75 }),76 Error,77 "at least one relay URL",78 );79 },80});8182Deno.test({83 name: "relay: disabled endpoints still pair over direct addresses",84 ignore: !available,85 sanitizeOps: false,86 sanitizeResources: false,87 fn: async () => {88 const addon = await loadIrohAddon();89 const a = await IrohNode.open(addon, crypto.getRandomValues(new Uint8Array(32)), "disabled");90 const b = await IrohNode.open(addon, crypto.getRandomValues(new Uint8Array(32)), "disabled");91 try {92 const ticket = await a.ticket();93 const acceptPromise = a.accept();94 const dialed = await b.connect(ticket);95 const accepted = await acceptPromise;96 assert(accepted !== null, "acceptor saw the inbound connection");97 dialed.sendMsg(new TextEncoder().encode("direct only"));98 const got = await accepted.recvMsg();99 assertEquals(new TextDecoder().decode(got!), "direct only");100 await dialed.close();101 await accepted.close();102 } finally {103 await a.close();104 await b.close();105 }106 },107});108109Deno.test({110 name: "relay: a custom relay map binds cleanly (no dial attempt at boot)",111 ignore: !available,112 sanitizeOps: false,113 sanitizeResources: false,114 fn: async () => {115 const addon = await loadIrohAddon();116 const node = await IrohNode.open(addon, crypto.getRandomValues(new Uint8Array(32)), {117 urls: ["https://relay.invalid.example"],118 });119 try {120 assert(node.idString().length > 0);121 } finally {122 await node.close();123 }124 },125});