clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

main
bindws / test / object / dumps.test.ts
dumps.test.ts 3.8K
1// Dumps to R2, and the signed door that serves them.2import { env, runInDurableObject } from "cloudflare:test";3import { describe, it, expect } from "vitest";4import { generateSecretKey } from "nostr-tools/pure";5import type { Relay } from "../../src/relay.ts";6import { writeDump } from "../../src/dumps.ts";7import { now, ev, rpc, get } from "../helpers/relay.ts";8import { WS } from "../helpers/ws.ts";910describe("dumps", () => {11 it("writes a JSONL of every event to R2, lists it, serves it to a signature, rotates and counts as media", async () => {12 const host = "dumpy.bind.ws";13 const owner = generateSecretKey();14 const writer = generateSecretKey();15 await rpc(host, owner, "claim");16 const c = await WS.connect(host);17 for (let i = 0; i < 3; i++) expect((await c.ok(ev(writer, 1, "note " + i))).ok).toBe(true);18 expect((await rpc(host, owner, "setpolicy", { dumps: "daily", dumpsKeep: 2 })).result.dumps).toBe("daily");1920 const d = (await rpc(host, owner, "dumpnow")).result;21 expect(d.name).toMatch(/^\d{4}-\d{2}-\d{2}\.jsonl$/);22 const total = (await rpc(host, owner, "stats")).result.events;23 expect(d.events).toBe(total);24 expect(total).toBeGreaterThanOrEqual(3);25 const obj = await env.MEDIA.get(`dumpy/dumps/${d.name}`);26 expect(obj).not.toBeNull();27 const text = await obj!.text();28 const lines = text.split("\n").filter(Boolean);29 expect(lines.length).toBe(d.events);30 for (const l of lines) expect(JSON.parse(l).id).toMatch(/^[0-9a-f]{64}$/);31 expect(d.bytes).toBe(text.length);3233 const list = (await rpc(host, owner, "listdumps")).result;34 expect(list.map((x: any) => x.name)).toEqual([d.name]);35 expect(list[0].url).toBe("/dumps/" + d.name);3637 // Download needs a signature from someone with the storage action.38 const signed = await get(host, "/dumps/" + d.name, owner);39 expect(signed.status).toBe(200);40 expect(signed.headers.get("content-disposition")).toContain(d.name);41 expect(await signed.text()).toBe(text);42 expect((await get(host, "/dumps/" + d.name, null)).status).toBe(401);43 expect((await get(host, "/dumps/" + d.name, writer)).status).toBe(403);44 expect((await get(host, "/dumps/nope.jsonl", owner)).status).toBe(400);45 expect((await get(host, "/dumps/1999-01-01.jsonl", owner)).status).toBe(404);4647 const stub = env.RELAY.getByName("dumpy");48 await runInDurableObject(stub, async (r: Relay) => {49 expect(r.mediaBytes()).toBe(d.bytes);50 // Two older dumps and a keep of two: the oldest goes.51 await writeDump(r, now() - 2 * 86400);52 await writeDump(r, now() - 86400);53 });54 const after = (await rpc(host, owner, "listdumps")).result.map((x: any) => x.name);55 expect(after.length).toBe(2);56 expect(after[0]).toBe(d.name);57 expect((await env.MEDIA.list({ prefix: "dumpy/dumps/" })).objects.length).toBe(2);58 expect((await rpc(host, owner, "storagestats")).result.dumps).toBe(2);5960 expect((await rpc(host, owner, "deletedump", after[1])).result).toBe(true);61 expect((await env.MEDIA.list({ prefix: "dumpy/dumps/" })).objects.length).toBe(1);62 expect((await rpc(host, writer, "listdumps")).status).toBe(403);63 });6465 it("the alarm writes a scheduled dump once a day, not twice", async () => {66 const host = "dumpz.bind.ws";67 const owner = generateSecretKey();68 await rpc(host, owner, "claim");69 const stub = env.RELAY.getByName("dumpz");70 await runInDurableObject(stub, async (r: Relay) => r.alarm());71 expect((await rpc(host, owner, "listdumps")).result.length).toBe(0);72 await rpc(host, owner, "setpolicy", { dumps: "daily" });73 await runInDurableObject(stub, async (r: Relay) => r.alarm());74 expect((await rpc(host, owner, "listdumps")).result.length).toBe(1);75 await runInDurableObject(stub, async (r: Relay) => r.alarm());76 expect((await rpc(host, owner, "listdumps")).result.length).toBe(1);77 });78});