quorlasocial/connector/test/control.test.mjs
metatroncubeswdev 075b79f6b2 Hosted Quorla: sign-up, Stripe billing, one workspace per agency
- Control centre (QUORLA_CONTROL=true on app.quorlasocial.com): public /signup → Stripe Checkout,
  verified Stripe webhook (paid, updated, cancelled, payment failed), Admin → Agencies
  (approve without Stripe, suspend, resume, billing portal, resend welcome, remove)
- deploy/provision-tenants.sh (cron): one container per agency with its own .env and data,
  nginx map for <agency>.quorlasocial.com, upgrades agencies after a rebuild, archives removed data
- New workspaces: first admin only via the emailed one-time setup link (SETUP_TOKEN)
- Meta system user token can be saved in Settings (encrypted) for workspaces that can't edit .env
- docs/multi-tenant.md: DNS, wildcard cert, nginx, sudoers, cron, Stripe setup; tests for the control centre

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:35:23 -04:00

65 lines
4.0 KiB
JavaScript

// Hosted control centre: Stripe webhook signatures, sign-up, subscription lifecycle, provisioning tasks.
// Temp data folders only; Stripe is simulated.
import { test } from "node:test";
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "quorla-ctl-"));
Object.assign(process.env, {
QUORLA_CONTROL_DIR: path.join(TMP, "control"), QUORLA_DASHBOARD_DATA: path.join(TMP, "dash"), QUORLA_CONTROL: "true",
STRIPE_SECRET_KEY: "sk_test_x", STRIPE_PRICE_ID: "price_x", STRIPE_WEBHOOK_SECRET: "whsec_test", PROVISION_TOKEN: "p".repeat(32), SMTP_HOST: "",
});
const control = await import("../../dashboard/server/control.js");
const sign = (body, secret = "whsec_test", t = Math.floor(Date.now() / 1000)) => `t=${t},v1=${crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex")}`;
test("webhook signatures: valid accepted; wrong secret, old or missing rejected", () => {
const body = JSON.stringify({ type: "ping" });
assert.equal(control.verifyWebhook(body, sign(body)).type, "ping");
assert.throws(() => control.verifyWebhook(body, sign(body, "whsec_other")), /doesn't match/);
assert.throws(() => control.verifyWebhook(body, sign(body, "whsec_test", Math.floor(Date.now() / 1000) - 1000)), /too old/);
assert.throws(() => control.verifyWebhook(body, ""), /Bad signature/);
});
test("sign-up → Stripe Checkout → paid → provisioning → running → billing states", async () => {
const realFetch = globalThis.fetch;
let sent;
globalThis.fetch = async (url, init) => { sent = { url: String(url), body: String(init.body) }; return new Response(JSON.stringify({ url: "https://checkout.stripe.test/s1" }), { status: 200 }); };
try {
await assert.rejects(control.signup({ agencyName: "A", ownerEmail: "x", slug: "ab" }, "https://app.test"), /valid work email/);
await assert.rejects(control.signup({ agencyName: "A", ownerEmail: "a@b.co", slug: "www" }, "https://app.test"), /Workspace address/);
const r = await control.signup({ agencyName: "Northstar Digital", ownerName: "Ana", ownerEmail: "ana@northstar.test", slug: "northstar" }, "https://app.test");
assert.equal(r.url, "https://checkout.stripe.test/s1");
assert.match(sent.url, /checkout\/sessions$/);
assert.match(decodeURIComponent(sent.body), /metadata\[slug\]=northstar/);
} finally { globalThis.fetch = realFetch; }
assert.equal(control.list()[0].status, "pending_payment");
assert.deepEqual(control.tasks(), [], "nothing to do before payment");
await control.onStripeEvent({ type: "checkout.session.completed", data: { object: { client_reference_id: "northstar", metadata: { slug: "northstar" }, customer: "cus_1", subscription: "sub_1" } } });
const [task] = control.tasks();
assert.equal(task.desired, "running");
assert.equal(task.port, 4400);
assert.ok(task.setupToken);
assert.throws(() => control.checkProvisionToken("Bearer nope"), /Not allowed/);
control.checkProvisionToken(`Bearer ${"p".repeat(32)}`);
const t = await control.report("northstar", { actual: "running" });
assert.equal(t.status, "active");
assert.equal(t.setupToken, undefined, "setup token never leaves the control centre in listings");
assert.deepEqual(control.tasks(), []);
await control.onStripeEvent({ type: "invoice.payment_failed", data: { object: { subscription: "sub_1" } } });
assert.equal(control.list()[0].status, "past_due");
await control.onStripeEvent({ type: "customer.subscription.updated", data: { object: { id: "sub_1", status: "unpaid", metadata: { slug: "northstar" } } } });
assert.equal(control.list()[0].status, "suspended");
assert.equal(control.tasks()[0].desired, "stopped");
await control.report("northstar", { actual: "stopped" });
await control.onStripeEvent({ type: "customer.subscription.updated", data: { object: { id: "sub_1", status: "active", metadata: { slug: "northstar" } } } });
assert.equal(control.tasks()[0].desired, "running", "paying again restarts it");
});