"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 createApp() { return buildApp({ config: { xWebhookSecret: "x-secret", polarWebhookSecret: "polar-secret", credit: { baseCredits: 1, includedChars: 25000, stepChars: 10000, stepCredits: 1, maxCharsPerArticle: 120000, }, }, }); } function call(app, { method, path, headers = {}, body = "", query = {} }) { return app.handleRequest({ method, path, headers, rawBody: body, query, }); } function postJSONWebhook(app, path, payload, secret) { const rawBody = JSON.stringify(payload); const sig = hmacSHA256Hex(rawBody, secret); return call(app, { method: "POST", path, headers: { "x-signature": `sha256=${sig}` }, body: rawBody, }); } test("GET / renders landing page", () => { const app = createApp(); const response = call(app, { method: "GET", path: "/" }); assert.equal(response.status, 200); assert.match(response.body, /From X Article to audiobook in one mention/); }); test("unauthenticated /app redirects to /login with returnTo", () => { const app = createApp(); const response = 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/dev-login sets cookie and redirects", () => { const app = createApp(); const response = call(app, { method: "POST", path: "/auth/dev-login", body: "userId=matiss&returnTo=%2Fapp", }); assert.equal(response.status, 303); assert.equal(response.headers.location, "/app"); assert.match(response.headers["set-cookie"], /^xartaudio_user=matiss/); }); test("authenticated dashboard topup + simulate mention flow", () => { const app = createApp(); const cookieHeader = "xartaudio_user=alice"; const topup = 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 = 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 = 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", () => { const app = createApp(); call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=owner" }, body: "amount=5", }); call(app, { method: "POST", path: "/app/actions/topup", headers: { cookie: "xartaudio_user=viewer" }, body: "amount=5", }); const generated = 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 = call(app, { method: "GET", path: audioPath, headers: { cookie: "xartaudio_user=viewer" }, }); assert.match(beforeUnlock.body, /Unlock required: 1 credits/); const unlock = call(app, { method: "POST", path: `/audio/${assetId}/unlock`, headers: { cookie: "xartaudio_user=viewer" }, }); assert.equal(unlock.status, 303); const afterUnlock = call(app, { method: "GET", path: audioPath, headers: { cookie: "xartaudio_user=viewer" }, }); assert.match(afterUnlock.body, /Access granted/); const wallet = 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("X webhook invalid signature is rejected", () => { const app = createApp(); const response = 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", () => { const app = createApp(); postJSONWebhook(app, "/api/webhooks/polar", { userId: "u1", credits: 4, eventId: "evt1" }, "polar-secret"); const response = 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); });