162 lines
4.1 KiB
JavaScript
162 lines
4.1 KiB
JavaScript
"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 postJSON(app, path, payload, secret) {
|
|
const rawBody = JSON.stringify(payload);
|
|
const sig = hmacSHA256Hex(rawBody, secret);
|
|
return app.handleRequest({
|
|
method: "POST",
|
|
path,
|
|
headers: { "x-signature": `sha256=${sig}` },
|
|
rawBody,
|
|
});
|
|
}
|
|
|
|
test("rejects invalid X webhook signature", () => {
|
|
const app = createApp();
|
|
const response = app.handleRequest({
|
|
method: "POST",
|
|
path: "/api/webhooks/x",
|
|
headers: { "x-signature": "sha256=deadbeef" },
|
|
rawBody: JSON.stringify({ mentionPostId: "m1", callerUserId: "u1", parentPost: {} }),
|
|
});
|
|
|
|
assert.equal(response.status, 401);
|
|
});
|
|
|
|
test("serves pwa manifest route", () => {
|
|
const app = createApp();
|
|
const response = app.handleRequest({
|
|
method: "GET",
|
|
path: "/manifest.webmanifest",
|
|
headers: {},
|
|
rawBody: "",
|
|
});
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.match(response.headers["content-type"], /application\/manifest\+json/);
|
|
const body = JSON.parse(response.body);
|
|
assert.equal(body.short_name, "XArtAudio");
|
|
});
|
|
|
|
test("X webhook returns not_article response and no charge", () => {
|
|
const app = createApp();
|
|
postJSON(app, "/api/webhooks/polar", { userId: "u1", credits: 5, eventId: "evt1" }, "polar-secret");
|
|
|
|
const response = postJSON(
|
|
app,
|
|
"/api/webhooks/x",
|
|
{ mentionPostId: "m1", callerUserId: "u1", parentPost: { id: "p1" } },
|
|
"x-secret",
|
|
);
|
|
|
|
const body = JSON.parse(response.body);
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.status, "not_article");
|
|
assert.equal(app.engine.getWalletBalance("u1"), 5);
|
|
});
|
|
|
|
test("X webhook processes article, charges caller, and exposes audio route", () => {
|
|
const app = createApp();
|
|
postJSON(app, "/api/webhooks/polar", { userId: "u1", credits: 5, eventId: "evt2" }, "polar-secret");
|
|
|
|
const response = postJSON(
|
|
app,
|
|
"/api/webhooks/x",
|
|
{
|
|
mentionPostId: "m2",
|
|
callerUserId: "u1",
|
|
parentPost: {
|
|
id: "p2",
|
|
article: {
|
|
id: "a2",
|
|
title: "Article",
|
|
body: "Hello from article",
|
|
},
|
|
},
|
|
},
|
|
"x-secret",
|
|
);
|
|
|
|
const body = JSON.parse(response.body);
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.status, "completed");
|
|
assert.equal(body.creditsCharged, 1);
|
|
|
|
const audioPageUnauthed = app.handleRequest({
|
|
method: "GET",
|
|
path: body.publicLink,
|
|
headers: {},
|
|
rawBody: "",
|
|
});
|
|
|
|
assert.equal(audioPageUnauthed.status, 200);
|
|
assert.match(audioPageUnauthed.body, /Sign in required before playback/);
|
|
});
|
|
|
|
test("non-owner can unlock with same credits then access", () => {
|
|
const app = createApp();
|
|
postJSON(app, "/api/webhooks/polar", { userId: "u1", credits: 5, eventId: "evt3" }, "polar-secret");
|
|
postJSON(app, "/api/webhooks/polar", { userId: "u2", credits: 5, eventId: "evt4" }, "polar-secret");
|
|
|
|
const makeAudio = postJSON(
|
|
app,
|
|
"/api/webhooks/x",
|
|
{
|
|
mentionPostId: "m3",
|
|
callerUserId: "u1",
|
|
parentPost: {
|
|
id: "p3",
|
|
article: {
|
|
id: "a3",
|
|
title: "Article",
|
|
body: "Hello from article",
|
|
},
|
|
},
|
|
},
|
|
"x-secret",
|
|
);
|
|
|
|
const { publicLink } = JSON.parse(makeAudio.body);
|
|
const assetId = publicLink.replace("/audio/", "");
|
|
|
|
const unlock = app.handleRequest({
|
|
method: "POST",
|
|
path: `/api/audio/${assetId}/unlock`,
|
|
headers: { "x-user-id": "u2" },
|
|
rawBody: "",
|
|
});
|
|
|
|
assert.equal(unlock.status, 200);
|
|
assert.equal(app.engine.getWalletBalance("u2"), 4);
|
|
|
|
const pageAfterUnlock = app.handleRequest({
|
|
method: "GET",
|
|
path: publicLink,
|
|
headers: { "x-user-id": "u2" },
|
|
rawBody: "",
|
|
});
|
|
|
|
assert.match(pageAfterUnlock.body, /Access granted/);
|
|
});
|