"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const { buildApp } = require("../src/app"); const { hmacSHA256Hex } = require("../src/lib/signature"); function getTestCookieValue(cookieHeader, name) { const parts = String(cookieHeader || "").split(";").map((part) => part.trim()); const prefix = `${name}=`; const valuePart = parts.find((part) => part.startsWith(prefix)); if (!valuePart) { return null; } try { return decodeURIComponent(valuePart.slice(prefix.length)); } catch { return valuePart.slice(prefix.length); } } function createTestAuthAdapter() { return { isConfigured() { return true; }, isXOAuthConfigured() { return true; }, handlesPath() { return false; }, async handleRoute() { return { status: 404, headers: {}, body: "not_found" }; }, async getAuthenticatedUserId(headers = {}) { return getTestCookieValue(headers.cookie, "xartaudio_user"); }, async signInWithEmail({ email }) { return { setCookie: `xartaudio_user=${encodeURIComponent(String(email))}; Path=/; HttpOnly`, }; }, async signUpWithEmail({ email }) { return { setCookie: `xartaudio_user=${encodeURIComponent(String(email))}; Path=/; HttpOnly`, }; }, async getXAuthorizationUrl() { return { url: "https://x.com/i/oauth2/authorize?state=test", setCookie: "xartaudio_oauth_state=test; Path=/; HttpOnly", }; }, async signOut() { return { ok: true, setCookie: "xartaudio_user=; Path=/; Max-Age=0", }; }, }; } function createApp(options = {}) { const baseConfig = { xWebhookSecret: "x-secret", polarWebhookSecret: "polar-secret", xBearerToken: "", xBotUserId: "", polarAccessToken: "", polarServer: "production", polarProductIds: [], appBaseUrl: "http://localhost:3000", betterAuthSecret: "test-better-auth-secret", betterAuthBasePath: "/api/auth", xOAuthClientId: "x-client-id", xOAuthClientSecret: "x-client-secret", internalApiToken: "internal-token", convexDeploymentUrl: "", convexAuthToken: "", convexStateQuery: "state:getLatestSnapshot", convexStateMutation: "state:saveSnapshot", qwenTtsApiKey: "", qwenTtsBaseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", qwenTtsModel: "qwen-tts-latest", qwenTtsVoice: "Cherry", qwenTtsFormat: "mp3", minioBucket: "", minioEndPoint: "", minioPort: 443, minioUseSSL: true, minioRegion: "us-east-1", minioAccessKey: "", minioSecretKey: "", minioSignedUrlTtlSec: 3600, rateLimits: { webhookPerMinute: 120, authPerMinute: 30, actionPerMinute: 60, }, abuse: { maxJobsPerUserPerDay: 0, cooldownSec: 0, denyUserIds: [], }, credit: { baseCredits: 1, includedChars: 25000, stepChars: 10000, stepCredits: 1, maxCharsPerArticle: 120000, }, }; const overrideConfig = options.config || {}; const mergedConfig = { ...baseConfig, ...overrideConfig, rateLimits: { ...baseConfig.rateLimits, ...(overrideConfig.rateLimits || {}), }, abuse: { ...baseConfig.abuse, ...(overrideConfig.abuse || {}), }, credit: { ...baseConfig.credit, ...(overrideConfig.credit || {}), }, }; const appOptions = { ...options }; delete appOptions.config; if (!appOptions.authAdapter) { appOptions.authAdapter = createTestAuthAdapter(); } return buildApp({ config: mergedConfig, ...appOptions, }); } async function call(app, { method, path, headers = {}, body = "", query = {} }) { return app.handleRequest({ method, path, headers, rawBody: body, query, }); } async function postJSONWebhook(app, path, payload, secret, extraHeaders) { const rawBody = JSON.stringify(payload); const sig = hmacSHA256Hex(rawBody, secret); return call(app, { method: "POST", path, headers: { "x-signature": `sha256=${sig}`, ...(extraHeaders || {}) }, body: rawBody, }); } test("GET / renders landing page", async () => { const app = createApp(); const response = await call(app, { method: "GET", path: "/" }); assert.equal(response.status, 200); assert.match(response.body, /Listen to X Articles/); }); test("GET /assets/styles.css serves compiled stylesheet", async () => { const app = createApp(); const response = await call(app, { method: "GET", path: "/assets/styles.css" }); assert.equal(response.status, 200); assert.match(response.headers["content-type"], /text\/css/); assert.match(response.body, /\.btn/); }); test("unauthenticated /app redirects to /login with returnTo", async () => { const app = createApp(); const response = await call(app, { method: "GET", path: "/app" }); assert.equal(response.status, 303); assert.match(response.headers.location, /^\/login\?/); assert.match(response.headers.location, /returnTo=%2Fapp/); }); test("POST /auth/email/sign-in sets cookie and redirects", async () => { const app = createApp(); const response = await call(app, { method: "POST", path: "/auth/email/sign-in", body: "email=matiss%40example.com&password=password123&returnTo=%2Fapp", }); assert.equal(response.status, 303); assert.equal(response.headers.location, "/app"); assert.match(String(response.headers["set-cookie"]), /xartaudio_user=matiss%40example\.com/); }); test("authenticated dashboard topup + simulate mention flow", async () => { const app = createApp(); const cookieHeader = "xartaudio_user=alice"; const topup = await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: cookieHeader }, body: "amount=8", }); assert.equal(topup.status, 303); assert.match(topup.headers.location, /Added%208%20credits/); const simulate = await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: cookieHeader }, body: "title=Hello&body=This+is+the+article+body", }); assert.equal(simulate.status, 303); assert.match(simulate.headers.location, /^\/audio\//); const dashboard = await call(app, { method: "GET", path: "/app", headers: { cookie: cookieHeader }, }); assert.equal(dashboard.status, 200); assert.match(dashboard.body, /Recent Audiobooks/); assert.match(dashboard.body, /Hello/); }); test("audio flow requires auth for unlock and supports permanent unlock", async () => { const app = createApp(); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=owner" }, body: "amount=5", }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=viewer" }, body: "amount=5", }); const generated = await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=owner" }, body: "title=Owned+Audio&body=Body", }); const audioPath = generated.headers.location.split("?")[0]; const assetId = audioPath.replace("/audio/", ""); const beforeUnlock = await call(app, { method: "GET", path: audioPath, headers: { cookie: "xartaudio_user=viewer" }, }); assert.match(beforeUnlock.body, /Unlock this audiobook permanently for/); const unlock = await call(app, { method: "POST", path: `/audio/${assetId}/unlock`, headers: { cookie: "xartaudio_user=viewer" }, }); assert.equal(unlock.status, 303); const afterUnlock = await call(app, { method: "GET", path: audioPath, headers: { cookie: "xartaudio_user=viewer" }, }); assert.match(afterUnlock.body, /Unlocked!/); const wallet = await call(app, { method: "GET", path: "/api/me/wallet", headers: { cookie: "xartaudio_user=viewer" }, }); const walletData = JSON.parse(wallet.body); assert.equal(walletData.balance, 4); }); test("owner can delete audio while non owner is forbidden", async () => { const app = createApp(); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=owner-delete" }, body: "amount=5", }); const generated = await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=owner-delete" }, body: "title=Delete+Me&body=Body", }); const assetId = generated.headers.location.split("?")[0].replace("/audio/", ""); const forbidden = await call(app, { method: "DELETE", path: `/api/audio/${assetId}`, headers: { cookie: "xartaudio_user=someone-else" }, }); assert.equal(forbidden.status, 403); const deleted = await call(app, { method: "DELETE", path: `/api/audio/${assetId}`, headers: { cookie: "xartaudio_user=owner-delete" }, }); assert.equal(deleted.status, 200); assert.equal(JSON.parse(deleted.body).status, "deleted"); const pageAfterDelete = await call(app, { method: "GET", path: `/audio/${assetId}`, headers: { cookie: "xartaudio_user=owner-delete" }, }); assert.match(pageAfterDelete.body, /Audio not found/); }); test("audio page uses signed storage URL when storage adapter is configured", async () => { const app = createApp({ storageAdapter: { isConfigured() { return true; }, async getSignedDownloadUrl(key) { return `https://signed.local/${key}`; }, async uploadAudio() {}, }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=owner" }, body: "amount=2", }); const generated = await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=owner" }, body: "title=Signed&body=Audio+Body", }); const audioPath = generated.headers.location.split("?")[0]; const page = await call(app, { method: "GET", path: audioPath, headers: { cookie: "xartaudio_user=owner" }, }); assert.match(page.body, /https:\/\/signed\.local\/audio\/1\.mp3/); }); test("/api/x/mentions returns upstream mentions when configured", async () => { const app = createApp({ xAdapter: { isConfigured() { return true; }, async listMentions({ sinceId }) { return [{ id: "m1", sinceId: sinceId || null }]; }, }, }); const response = await call(app, { method: "GET", path: "/api/x/mentions", headers: { "x-internal-token": "internal-token" }, query: { sinceId: "100" }, }); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.equal(body.mentions.length, 1); assert.equal(body.mentions[0].id, "m1"); }); test("/api/x/mentions requires internal token", async () => { const app = createApp({ xAdapter: { isConfigured() { return true; }, async listMentions() { return []; }, }, }); const response = await call(app, { method: "GET", path: "/api/x/mentions", query: { sinceId: "1" }, }); assert.equal(response.status, 401); }); test("cross-site browser posts are blocked", async () => { const app = createApp(); const response = await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice", origin: "https://evil.example", "sec-fetch-site": "cross-site", }, body: "amount=5", }); assert.equal(response.status, 403); assert.match(response.body, /csrf_blocked|invalid_origin/); }); test("dev dashboard routes can be disabled", async () => { const app = createApp({ config: { enableDevRoutes: false, }, }); const response = await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice" }, body: "amount=5", }); assert.equal(response.status, 404); }); test("simulate mention schedules background audio generation when service is configured", async () => { const queued = []; const app = createApp({ audioGenerationService: { isConfigured() { return true; }, async enqueueJob(payload) { queued.push(payload); }, }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice" }, body: "amount=3", }); const simulate = await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=alice" }, body: "title=T&body=hello+world", }); assert.equal(simulate.status, 303); assert.equal(queued.length, 1); assert.equal(typeof queued[0].assetId, "string"); assert.equal(queued[0].text, "hello world"); }); test("failed background generation refunds charged credits", async () => { const app = createApp({ audioGenerationService: { isConfigured() { return true; }, async enqueueJob(payload) { payload.onFailed(new Error("tts_outage")); }, }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice" }, body: "amount=3", }); await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=alice" }, body: "title=T&body=hello+world", }); const wallet = await call(app, { method: "GET", path: "/api/me/wallet", headers: { cookie: "xartaudio_user=alice" }, }); assert.equal(JSON.parse(wallet.body).balance, 3); }); test("internal worker endpoints require token and can complete jobs", async () => { const queued = []; const app = createApp({ config: { internalApiToken: "internal-token", }, audioGenerationService: { isConfigured() { return true; }, async enqueueJob(payload) { queued.push(payload); }, }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice" }, body: "amount=2", }); await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=alice" }, body: "title=Queued&body=hello", }); const job = app.engine.listJobsForUser("alice")[0]; assert.equal(job.status, "synthesizing"); const denied = await call(app, { method: "POST", path: `/internal/jobs/${job.id}/complete`, body: JSON.stringify({}), }); assert.equal(denied.status, 401); const completed = await call(app, { method: "POST", path: `/internal/jobs/${job.id}/complete`, headers: { "x-internal-token": "internal-token" }, body: JSON.stringify({ asset: { storageKey: "audio/worker.mp3", sizeBytes: 999, }, }), }); assert.equal(completed.status, 200); const completedBody = JSON.parse(completed.body); assert.equal(completedBody.job.status, "completed"); const readJob = await call(app, { method: "GET", path: `/api/jobs/${job.id}`, headers: { cookie: "xartaudio_user=alice" }, }); assert.equal(readJob.status, 200); assert.equal(JSON.parse(readJob.body).job.status, "completed"); }); test("internal retention endpoint prunes stale content and assets", async () => { const app = createApp({ config: { internalApiToken: "internal-token", }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=retention-owner" }, body: "amount=2", }); await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=retention-owner" }, body: "title=Retention&body=Body", }); const job = app.engine.listJobsForUser("retention-owner")[0]; const asset = app.engine.getAsset(job.assetId, { includeDeleted: true }); job.createdAt = "2020-01-01T00:00:00.000Z"; asset.createdAt = "2020-01-01T00:00:00.000Z"; const denied = await call(app, { method: "POST", path: "/internal/retention/run", body: JSON.stringify({ rawArticleHours: 1, audioDays: 1 }), }); assert.equal(denied.status, 401); const run = await call(app, { method: "POST", path: "/internal/retention/run", headers: { "x-internal-token": "internal-token" }, body: JSON.stringify({ rawArticleHours: 1, audioDays: 1 }), }); assert.equal(run.status, 200); const summary = JSON.parse(run.body).summary; assert.equal(summary.prunedArticleBodies >= 1, true); assert.equal(summary.deletedAssets >= 1, true); const page = await call(app, { method: "GET", path: `/audio/${job.assetId}`, headers: { cookie: "xartaudio_user=retention-owner" }, }); assert.match(page.body, /Audio not found/); }); test("internal endpoints are disabled when no token configured", async () => { const app = createApp({ config: { internalApiToken: "", }, }); const response = await call(app, { method: "POST", path: "/internal/retention/run", body: "{}", }); assert.equal(response.status, 503); assert.equal(JSON.parse(response.body).error, "internal_api_disabled"); }); test("/api/payments/create-checkout returns 503 when Polar is not configured", async () => { const app = createApp(); const response = await call(app, { method: "POST", path: "/api/payments/create-checkout", headers: { cookie: "xartaudio_user=viewer" }, }); assert.equal(response.status, 503); assert.equal(JSON.parse(response.body).error, "polar_checkout_not_configured"); }); test("/api/payments/create-checkout returns checkout URL when adapter is configured", async () => { const app = createApp({ polarAdapter: { isConfigured() { return true; }, async createCheckoutSession() { return { id: "chk_1", url: "https://polar.sh/checkout/chk_1" }; }, parseWebhookEvent() { return null; }, extractTopUp(payload) { return payload; }, }, }); const response = await call(app, { method: "POST", path: "/api/payments/create-checkout", headers: { cookie: "xartaudio_user=buyer" }, }); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.equal(body.checkoutId, "chk_1"); assert.equal(body.checkoutUrl, "https://polar.sh/checkout/chk_1"); }); test("X webhook invalid signature is rejected", async () => { const app = createApp(); const response = await call(app, { method: "POST", path: "/api/webhooks/x", headers: { "x-signature": "sha256=deadbeef" }, body: JSON.stringify({ mentionPostId: "m1", callerUserId: "u1", parentPost: {} }), }); assert.equal(response.status, 401); }); test("X webhook valid flow processes article", async () => { const app = createApp(); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u1", credits: 4, eventId: "evt1" }, "polar-secret"); const response = await postJSONWebhook( app, "/api/webhooks/x", { mentionPostId: "m2", callerUserId: "u1", parentPost: { id: "p2", article: { id: "a2", title: "T", body: "Hello" }, }, }, "x-secret", ); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.equal(body.status, "completed"); assert.equal(body.creditsCharged, 1); }); test("X webhook supports CRC challenge response", async () => { const app = createApp(); const response = await call(app, { method: "GET", path: "/api/webhooks/x", query: { crc_token: "token-123" }, }); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.match(body.response_token, /^sha256=/); }); test("X webhook can normalize mentionTweetId payload and reply via adapter", async () => { const replies = []; const app = createApp({ xAdapter: { isConfigured() { return true; }, async listMentions() { return []; }, async fetchParentPostFromMention() { return { id: "parent-1", authorId: "author-1", article: { id: "article-1", title: "From X", body: "Article body" }, }; }, async replyToMention(payload) { replies.push(payload); }, }, }); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u10", credits: 5, eventId: "evt10" }, "polar-secret"); const response = await postJSONWebhook(app, "/api/webhooks/x", { mentionTweetId: "mention-10", callerUserId: "u10", }, "x-secret"); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.equal(body.status, "completed"); assert.equal(body.replied, true); assert.equal(replies.length, 1); assert.equal(replies[0].mentionTweetId, "mention-10"); assert.match(replies[0].text, /audiobook/i); }); test("X webhook replies with not article message when parent is not article", async () => { const replies = []; const app = createApp({ xAdapter: { isConfigured() { return true; }, async listMentions() { return []; }, async fetchParentPostFromMention() { return { id: "parent-2", text: "not article" }; }, async replyToMention(payload) { replies.push(payload); }, }, }); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u11", credits: 5, eventId: "evt11" }, "polar-secret"); const response = await postJSONWebhook(app, "/api/webhooks/x", { mentionTweetId: "mention-11", callerUserId: "u11", }, "x-secret"); assert.equal(response.status, 200); const body = JSON.parse(response.body); assert.equal(body.status, "not_article"); assert.equal(body.replied, true); assert.equal(replies.length, 1); assert.match(replies[0].text, /not an X Article/); }); test("X webhook can normalize tweet_create_events payload", async () => { const app = createApp({ xAdapter: { isConfigured() { return true; }, async listMentions() { return []; }, async fetchParentPostFromMention(mentionTweetId) { assert.equal(mentionTweetId, "mention-evt-1"); return { id: "parent-evt-1", authorId: "author-evt-1", article: { id: "article-evt-1", title: "Evt", body: "Body" }, }; }, async replyToMention() {}, }, }); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u-evt", credits: 4, eventId: "evt-seed" }, "polar-secret"); const response = await postJSONWebhook(app, "/api/webhooks/x", { tweet_create_events: [ { id_str: "mention-evt-1", user: { id_str: "u-evt" }, }, ], }, "x-secret"); assert.equal(response.status, 200); assert.equal(JSON.parse(response.body).status, "completed"); }); test("Polar webhook uses adapter parsing for standard webhook headers", async () => { const app = createApp({ polarAdapter: { isConfigured() { return false; }, async createCheckoutSession() { return null; }, parseWebhookEvent() { return { type: "order.paid", data: { id: "ord_1", metadata: { xartaudio_user_id: "u9", xartaudio_credits: "7" }, }, }; }, extractTopUp(event) { return { userId: event.data.metadata.xartaudio_user_id, credits: Number.parseInt(event.data.metadata.xartaudio_credits, 10), eventId: event.data.id, }; }, }, }); const response = await call(app, { method: "POST", path: "/api/webhooks/polar", headers: { "webhook-id": "wh_1", "webhook-timestamp": "1", "webhook-signature": "sig", }, body: "{\"type\":\"order.paid\"}", }); assert.equal(response.status, 200); const wallet = await call(app, { method: "GET", path: "/api/me/wallet", headers: { cookie: "xartaudio_user=u9" }, }); assert.equal(JSON.parse(wallet.body).balance, 7); }); test("emits persistence snapshots on mutating actions", async () => { const snapshots = []; const app = createApp({ onMutation(state) { snapshots.push(state); }, }); await call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=alice" }, body: "amount=5", }); await call(app, { method: "POST", path: "/app/actions/simulate-mention", headers: { cookie: "xartaudio_user=alice" }, body: "title=Persisted&body=hello", }); assert.equal(snapshots.length >= 2, true); const latest = snapshots[snapshots.length - 1]; assert.equal(latest.version, 1); assert.equal(typeof latest.updatedAt, "string"); assert.equal(typeof latest.engine, "object"); }); test("can boot app from previously persisted state snapshot", async () => { const snapshots = []; const app1 = createApp({ onMutation(state) { snapshots.push(state); }, }); await call(app1, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=restart-user" }, body: "amount=6", }); const persistedState = snapshots[snapshots.length - 1]; const app2 = createApp({ initialState: persistedState, }); const wallet = await call(app2, { method: "GET", path: "/api/me/wallet", headers: { cookie: "xartaudio_user=restart-user" }, }); assert.equal(wallet.status, 200); assert.equal(JSON.parse(wallet.body).balance, 6); }); test("rate limits repeated webhook calls", async () => { const app = createApp({ config: { rateLimits: { webhookPerMinute: 1, }, }, }); const first = await call(app, { method: "POST", path: "/api/webhooks/x", headers: { "x-forwarded-for": "1.2.3.4", "x-signature": "sha256=deadbeef" }, body: JSON.stringify({}), }); const second = await call(app, { method: "POST", path: "/api/webhooks/x", headers: { "x-forwarded-for": "1.2.3.4", "x-signature": "sha256=deadbeef" }, body: JSON.stringify({}), }); assert.equal(first.status, 401); assert.equal(second.status, 429); }); test("anti abuse deny list blocks webhook generation", async () => { const app = createApp({ config: { abuse: { denyUserIds: ["blocked-user"], }, }, }); const response = await postJSONWebhook(app, "/api/webhooks/x", { mentionPostId: "m-deny", callerUserId: "blocked-user", parentPost: { id: "p-deny", article: { id: "a-deny", title: "T", body: "hello" }, }, }, "x-secret"); assert.equal(response.status, 429); assert.equal(JSON.parse(response.body).error, "user_denied"); }); test("anti abuse daily limit blocks second generated job", async () => { const app = createApp({ config: { abuse: { maxJobsPerUserPerDay: 1, }, }, }); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u-limit", credits: 4, eventId: "evt-limit" }, "polar-secret"); const first = await postJSONWebhook(app, "/api/webhooks/x", { mentionPostId: "m-limit-1", callerUserId: "u-limit", parentPost: { id: "p-limit-1", article: { id: "a-limit-1", title: "T1", body: "hello" }, }, }, "x-secret"); const second = await postJSONWebhook(app, "/api/webhooks/x", { mentionPostId: "m-limit-2", callerUserId: "u-limit", parentPost: { id: "p-limit-2", article: { id: "a-limit-2", title: "T2", body: "hello" }, }, }, "x-secret"); assert.equal(first.status, 200); assert.equal(second.status, 429); assert.equal(JSON.parse(second.body).error, "daily_limit_exceeded"); }); test("anti abuse cooldown reports retry delay", async () => { const app = createApp({ config: { abuse: { cooldownSec: 60, }, }, }); await postJSONWebhook(app, "/api/webhooks/polar", { userId: "u-cool", credits: 4, eventId: "evt-cool" }, "polar-secret"); const first = await postJSONWebhook(app, "/api/webhooks/x", { mentionPostId: "m-cool-1", callerUserId: "u-cool", parentPost: { id: "p-cool-1", article: { id: "a-cool-1", title: "T1", body: "hello" }, }, }, "x-secret"); const second = await postJSONWebhook(app, "/api/webhooks/x", { mentionPostId: "m-cool-2", callerUserId: "u-cool", parentPost: { id: "p-cool-2", article: { id: "a-cool-2", title: "T2", body: "hello" }, }, }, "x-secret"); assert.equal(first.status, 200); assert.equal(second.status, 429); const body = JSON.parse(second.body); assert.equal(body.error, "cooldown_active"); assert.equal(typeof body.retryAfterSec, "number"); }); test("rate limits repeated login attempts from same IP", async () => { const app = createApp({ config: { rateLimits: { authPerMinute: 1, }, }, }); const first = await call(app, { method: "POST", path: "/auth/email/sign-in", headers: { "x-forwarded-for": "5.5.5.5" }, body: "email=alice%40example.com&password=password123&returnTo=%2Fapp", }); const second = await call(app, { method: "POST", path: "/auth/email/sign-in", headers: { "x-forwarded-for": "5.5.5.5" }, body: "email=alice%40example.com&password=password123&returnTo=%2Fapp", }); assert.equal(first.status, 303); assert.equal(second.status, 303); assert.match(second.headers.location, /Too%20many%20requests/); });