Files
buzz/desktop/src/shared/api/invites.test.mjs
T
d500c2d5cf feat(invites): add use-limited invite links (#3141)
## Summary

- add database-backed v2 invite links with optional maximum-use limits
and atomic final-slot redemption
- preserve v1 invite compatibility while adding
exhausted/expired/invalid client handling across desktop, web, and
mobile
- emit structured claim-outcome logs with community, invite ID, outcome,
maximum uses, and post-claim count

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-db` (85 passed, 134 Postgres-dependent ignored)
- `cargo clippy -p buzz-db --all-targets -- -D warnings`
- desktop `npm run typecheck`
- push hook: desktop checks/tests, desktop Tauri tests, Rust tests, and
branch-skew passed
- Postgres integration tests were previously reviewed green at the
pre-rebase tree; local rerun on this session was unavailable because
Postgres/Docker were not running
- mobile push-hook check could not start because Flutter is unavailable
locally

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: npub1c4alndp82zyt9veaklm5d965quss79vlhk9awv7qu5erwhmf42qqlvc25c <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz>
Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-07-27 15:19:39 -07:00

212 lines
6.2 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { getJoinPolicy, mintInvite } from "./invites.ts";
function withFetch(response, run) {
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url) => {
assert.equal(url, "https://relay.example/api/join-policy");
return response;
};
return Promise.resolve(run()).finally(() => {
globalThis.fetch = originalFetch;
});
}
test("getJoinPolicy maps relay-hosted Markdown and age requirements", async () => {
await withFetch(
new Response(
JSON.stringify({
policy: {
terms_markdown: "# Terms",
privacy_markdown: "# Privacy",
age_attestation_required: true,
version: "policy-v1",
},
}),
{ status: 200 },
),
async () => {
assert.deepEqual(await getJoinPolicy("wss://relay.example", "webview"), {
termsMarkdown: "# Terms",
privacyMarkdown: "# Privacy",
ageAttestationRequired: true,
version: "policy-v1",
});
},
);
});
test("getJoinPolicy preserves opt-in behavior for unconfigured and older relays", async () => {
await withFetch(new Response(JSON.stringify({}), { status: 200 }), async () =>
assert.equal(await getJoinPolicy("wss://relay.example", "webview"), null),
);
await withFetch(new Response(null, { status: 404 }), async () =>
assert.equal(await getJoinPolicy("wss://relay.example", "webview"), null),
);
});
test("getJoinPolicy fails closed on a policy endpoint error", async () => {
await withFetch(new Response(null, { status: 503 }), async () =>
assert.rejects(getJoinPolicy("wss://relay.example", "webview"), /HTTP 503/),
);
});
test("getJoinPolicy maps the native command response", async () => {
const previousWindow = globalThis.window;
globalThis.window = {
__TAURI_INTERNALS__: {
invoke(command, args) {
assert.equal(command, "fetch_join_policy");
assert.deepEqual(args, { relayUrl: "wss://relay.example" });
return Promise.resolve({
terms_markdown: "# Terms",
privacy_markdown: "# Privacy",
age_attestation_required: true,
version: "policy-v1",
});
},
},
};
try {
assert.deepEqual(await getJoinPolicy("wss://relay.example", "native"), {
termsMarkdown: "# Terms",
privacyMarkdown: "# Privacy",
ageAttestationRequired: true,
version: "policy-v1",
});
} finally {
globalThis.window = previousWindow;
}
});
// --- mintInvite serialization ---
// The test-loader transpiles TS imports. tauri.ts imports `invoke` from
// @tauri-apps/api/core, which calls `window.__TAURI_INTERNALS__.invoke`.
// We stub that here so getRelayHttpUrl() and signRelayEvent() work in node.
function setupTauriStubs(
httpBase,
authEvent = {
id: "x",
sig: "y",
pubkey: "z",
kind: 27235,
created_at: 1,
tags: [],
},
) {
const calls = { invokeArgs: [] };
globalThis.window = globalThis.window ?? {};
globalThis.window.__TAURI_INTERNALS__ = {
invoke: async (command, args) => {
calls.invokeArgs.push({ command, args });
if (command === "get_relay_http_url") return httpBase;
if (command === "sign_event") return JSON.stringify(authEvent);
throw new Error(`Unexpected Tauri command: ${command}`);
},
};
return calls;
}
function teardownTauriStubs() {
delete globalThis.window.__TAURI_INTERNALS__;
}
test("mintInvite serializes bounded max_uses in the request body", async () => {
setupTauriStubs("https://relay.example");
try {
const originalFetch = globalThis.fetch;
let capturedBody;
globalThis.fetch = async (_url, init) => {
capturedBody = JSON.parse(init.body);
return new Response(
JSON.stringify({
code: "v2.abc123",
expires_at: 1785100000,
url: "https://relay.example/invite/v2.abc123",
max_uses: 10,
uses_remaining: 10,
}),
);
};
try {
const result = await mintInvite({ ttlSecs: 259200, maxUses: 10 });
assert.equal(capturedBody.ttl_secs, 259200);
assert.equal(capturedBody.max_uses, 10);
assert.equal(result.code, "v2.abc123");
assert.equal(result.maxUses, 10);
assert.equal(result.usesRemaining, 10);
assert.equal(result.expiresAt, 1785100000);
assert.equal(result.url, "https://relay.example/invite/v2.abc123");
} finally {
globalThis.fetch = originalFetch;
}
} finally {
teardownTauriStubs();
}
});
test("mintInvite omits max_uses when null (unlimited)", async () => {
setupTauriStubs("https://relay.example");
try {
const originalFetch = globalThis.fetch;
let capturedBody;
globalThis.fetch = async (_url, init) => {
capturedBody = JSON.parse(init.body);
return new Response(
JSON.stringify({
code: "v2.abc123",
expires_at: 1785100000,
url: "https://relay.example/invite/v2.abc123",
max_uses: null,
uses_remaining: null,
}),
);
};
try {
const result = await mintInvite({ ttlSecs: 259200, maxUses: null });
assert.equal(capturedBody.ttl_secs, 259200);
assert.equal(Object.hasOwn(capturedBody, "max_uses"), false);
assert.equal(result.maxUses, null);
assert.equal(result.usesRemaining, null);
} finally {
globalThis.fetch = originalFetch;
}
} finally {
teardownTauriStubs();
}
});
test("mintInvite omits max_uses when not provided (unlimited default)", async () => {
setupTauriStubs("https://relay.example");
try {
const originalFetch = globalThis.fetch;
let capturedBody;
globalThis.fetch = async (_url, init) => {
capturedBody = JSON.parse(init.body);
return new Response(
JSON.stringify({
code: "v2.abc123",
expires_at: 1785100000,
url: "https://relay.example/invite/v2.abc123",
max_uses: null,
uses_remaining: null,
}),
);
};
try {
await mintInvite({ ttlSecs: 86400 });
assert.equal(capturedBody.ttl_secs, 86400);
assert.equal(Object.hasOwn(capturedBody, "max_uses"), false);
} finally {
globalThis.fetch = originalFetch;
}
} finally {
teardownTauriStubs();
}
});