[grade=pending] QA sprint: commit 103 at-risk files from multi-agent sprint work

Committed by Hermes autonomous QA sprint 2026-08-13.
These files were modified during the Aug 12 sprint but never committed.
This commit is contained in:
Krystie
2026-08-12 17:34:10 -07:00
parent 0bf4406253
commit 503de258b8
105 changed files with 14057 additions and 2632 deletions
@@ -0,0 +1,411 @@
/**
* End-to-end billing integration test.
*
* Exercises the FULL purchase → fulfillment → activation → Pro unlock flow:
*
* 1. POST /api/v1/billing/checkout → mock Stripe SDK → session { id, url }
* 2. Simulate webhook delivery → bridge.handleWebhook() with a signed
* checkout.session.completed payload
* 3. GET /api/v1/billing/lookup/:sessionId → verify license code returned
* 4. POST /api/v1/license/activate → verify code activates, Pro unlocks
*
* The bridge and the API billing routes communicate through a SHARED
* fulfillment-store file (the production IPC channel — a bind-mounted JSON
* file). This test wires both sides to the same tmp file so the lookup
* endpoint sees the license the bridge persisted, exactly as in production.
*
* The REAL license-keygen + LicenseManager are used (no HMAC mock) so the
* code generated by the bridge is cryptographically valid and activates
* through the real LicenseManager.verifyCode() path. Only Stripe's network
* surface and nodemailer are mocked.
*/
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const crypto = require('crypto');
const express = require('express');
const request = require('supertest');
// ── jest.mock must be hoisted before any require() ─────────────────────────
// Mock nodemailer so the bridge never opens a real SMTP connection. SMTP is
// left unconfigured (no SMTP_HOST/SMTP_FROM) so deliverCode() falls back to
// dev-console mode — the documented dev/test path where the license is marked
// `delivered` without actually sending email.
jest.mock('nodemailer', () => ({
createTransport: jest.fn(() => ({ sendMail: jest.fn() })),
}));
// ── Isolated tmp state (set BEFORE requiring the bridge + routes) ──────────
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'dc-e2e-billing-'));
// Shared fulfillment-store file — the IPC channel between bridge and API.
process.env.STRIPE_BRIDGE_FULFILLMENT_STORE_FILE = path.join(TMP, 'stripe-fulfillments.json');
process.env.STRIPE_BRIDGE_STATE_DIR = TMP;
process.env.STRIPE_BRIDGE_EVENTS_FILE = path.join(TMP, 'stripe-events.json');
process.env.STRIPE_WEBHOOK_SECRET = 'whsec_e2e_' + crypto.randomBytes(8).toString('hex');
// Configure Stripe products so the catalog + stripe-client can resolve price IDs.
process.env.STRIPE_SECRET_KEY = 'sk_test_e2e';
process.env.STRIPE_PRICE_PRO_30D = 'price_30d_e2e';
process.env.STRIPE_PRICE_PRO_90D = 'price_90d_e2e';
process.env.STRIPE_PRICE_PRO_180D = 'price_180d_e2e';
process.env.STRIPE_PRICE_PRO_365D = 'price_365d_e2e';
process.env.STRIPE_PUBLIC_ORIGIN = 'https://status.test';
// No SMTP → bridge uses dev-console delivery (license marked delivered, no email).
delete process.env.SMTP_HOST;
delete process.env.SMTP_FROM;
// ── Real license-keygen with a known master secret ─────────────────────────
// We write a real secret file so the bridge's loadSecret() + generateCodes()
// produce HMAC-valid codes that the LicenseManager can verify with the SAME
// secret. This makes the activation step exercise the real cryptographic path.
const E2E_SECRET = crypto.randomBytes(32).toString('hex');
const SECRET_FILE = path.join(TMP, '.license-secret');
fs.writeFileSync(SECRET_FILE, E2E_SECRET, { mode: 0o600 });
process.env.LICENSE_SECRET_FILE = SECRET_FILE;
// Real keygen — no mock. The counter file is isolated to the tmp dir.
process.env.LICENSE_COUNTER_FILE = path.join(TMP, '.license-counter');
// Now require modules (after env + mock setup).
const keygen = require('../../license-keygen');
const catalog = require('../../src/billing/catalog');
const stripeClient = require('../../src/billing/stripe-client');
const bridge = require('../../scripts/stripe-license-bridge');
const billingRoutesFactory = require('../../routes/billing');
const licenseRoutesFactory = require('../../routes/license');
const { LicenseManager } = require('../../src/managers/license-manager');
const { createFulfillmentStore } = require('../../src/billing/fulfillment-store');
// ── Test app: mounts billing + license routes the same way app.js does ─────
function makeApp(licenseManager) {
const app = express();
app.use(express.json());
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.use('/api/v1/billing', billingRoutesFactory({ asyncHandler }));
app.use('/api/v1/license', licenseRoutesFactory({ licenseManager, asyncHandler }));
// Jest/express error handler — surfaces route errors as JSON so supertest
// can assert on the body.
app.use((err, req, res, next) => {
const status = err.statusCode || 500;
res.status(status).json({ success: false, error: err.message });
});
return app;
}
// ── Helpers ────────────────────────────────────────────────────────────────
/**
* Build a signed Stripe webhook payload for checkout.session.completed.
*/
function buildSignedWebhook(sessionId, productId, customerEmail, opts = {}) {
const product = catalog.getProduct(productId);
const event = {
id: opts.eventId || `evt_e2e_${crypto.randomBytes(6).toString('hex')}`,
type: opts.type || 'checkout.session.completed',
data: {
object: {
id: sessionId,
customer_email: customerEmail,
customer_details: { email: customerEmail },
payment_status: 'paid',
amount_total: product ? product.amountCents : 0,
currency: 'usd',
metadata: { productId, product: 'dashcaddy-pro' },
},
},
};
const rawBody = Buffer.from(JSON.stringify(event));
const ts = Math.floor(Date.now() / 1000);
const sig = crypto.createHmac('sha256', process.env.STRIPE_WEBHOOK_SECRET)
.update(`${ts}.${rawBody}`, 'utf8').digest('hex');
return { rawBody, signatureHeader: `t=${ts},v1=${sig}`, event };
}
/**
* Install a mock Stripe SDK that returns a checkout session with a
* caller-chosen id + url. Captures the params passed to sessions.create().
*/
function installMockStripe(sessionId, sessionUrl) {
let capturedParams;
const mockStripe = jest.fn().mockReturnValue({
checkout: {
sessions: {
create: jest.fn().mockImplementation(async (params) => {
capturedParams = params;
return { id: sessionId, url: sessionUrl };
}),
},
},
});
stripeClient._setStripeSdk(mockStripe);
return { capturedParams: () => capturedParams };
}
// ── Cleanup ────────────────────────────────────────────────────────────────
afterAll(() => {
stripeClient._setStripeSdk(null);
try { fs.rmSync(TMP, { recursive: true, force: true }); } catch (_) { /* best effort */ }
});
// ═══════════════════════════════════════════════════════════════════════════
// THE END-TO-END FLOW
// ═══════════════════════════════════════════════════════════════════════════
describe('end-to-end billing flow: checkout → webhook → lookup → activate → Pro', () => {
const PRODUCT_ID = 'pro-90d';
const CUSTOMER_EMAIL = 'alice@example.com';
const SESSION_ID = `cs_e2e_${crypto.randomBytes(6).toString('hex')}`;
const CHECKOUT_URL = `https://checkout.stripe.com/c/pay/${SESSION_ID}`;
let app;
let licenseManager;
let activationCode; // captured during the flow
beforeAll(() => {
// Real LicenseManager, configured with the same secret the bridge uses.
licenseManager = new LicenseManager(
{
store: jest.fn().mockResolvedValue(undefined),
retrieve: jest.fn().mockResolvedValue(null),
delete: jest.fn().mockResolvedValue(undefined),
},
path.join(TMP, 'config.json'),
{ info: () => {}, warn: () => {}, error: () => {} }
);
// loadSecret reads the file and stores it as masterSecretHash for verifyCode().
licenseManager.loadSecret(SECRET_FILE);
app = makeApp(licenseManager);
});
// ── Step 1: POST /api/v1/billing/checkout ──────────────────────────────
test('Step 1: checkout creates a Stripe session via the mock SDK', async () => {
const stripe = installMockStripe(SESSION_ID, CHECKOUT_URL);
const res = await request(app)
.post('/api/v1/billing/checkout')
.send({ productId: PRODUCT_ID, customerEmail: CUSTOMER_EMAIL })
.expect(200);
expect(res.body.success).toBe(true);
expect(res.body.data.id).toBe(SESSION_ID);
expect(res.body.data.url).toBe(CHECKOUT_URL);
// The mock Stripe SDK was called with the correct product + metadata.
const params = stripe.capturedParams();
expect(params.mode).toBe('payment');
expect(params.metadata.productId).toBe(PRODUCT_ID);
expect(params.line_items[0].price).toBe('price_90d_e2e');
expect(params.customer_email).toBe(CUSTOMER_EMAIL);
});
// ── Step 2: Simulate Stripe webhook delivery ───────────────────────────
test('Step 2: webhook generates + persists + delivers the license', async () => {
const { rawBody, signatureHeader, event } = buildSignedWebhook(
SESSION_ID, PRODUCT_ID, CUSTOMER_EMAIL
);
const result = await bridge.handleWebhook({ rawBody, signatureHeader });
expect(result.status).toBe(200);
expect(result.body.delivered).toBe(true);
expect(result.body.productId).toBe(PRODUCT_ID);
expect(result.body.durationDays).toBe(90);
expect(result.body.codeId).toBeTruthy();
expect(result.body.deliveredVia).toBe('dev-console');
// Capture the code for subsequent steps.
const store = createFulfillmentStore({ filePath: process.env.STRIPE_BRIDGE_FULFILLMENT_STORE_FILE });
const record = store.readBySession(SESSION_ID);
expect(record).toBeTruthy();
expect(record.status).toBe('delivered');
expect(record.code).toBeTruthy();
activationCode = record.code;
});
// ── Step 3: GET /api/v1/billing/lookup/:sessionId ──────────────────────
test('Step 3: lookup returns the delivered license code', async () => {
const res = await request(app)
.get(`/api/v1/billing/lookup/${SESSION_ID}`)
.expect(200);
expect(res.body.success).toBe(true);
expect(res.body.data.status).toBe('delivered');
expect(res.body.data.code).toBe(activationCode);
expect(res.body.data.codeId).toBeTruthy();
expect(res.body.data.productId).toBe(PRODUCT_ID);
expect(res.body.data.durationDays).toBe(90);
expect(res.body.data.deliveredVia).toBe('dev-console');
// Bearer-style secret — must never be cached.
expect(res.headers['cache-control']).toBe('no-store');
});
// ── Step 4: POST /api/v1/license/activate → Pro unlock ─────────────────
test('Step 4: activate the license → Pro tier unlocks', async () => {
expect(activationCode).toBeTruthy();
const res = await request(app)
.post('/api/v1/license/activate')
.send({ code: activationCode })
.expect(200);
expect(res.body.success).toBe(true);
expect(res.body.license).toBeDefined();
expect(res.body.license.active).toBe(true);
expect(res.body.license.tier).toBe('premium');
expect(res.body.license.durationDays).toBe(90);
expect(res.body.license.expired).toBe(false);
// The LicenseManager itself now reports Pro (this is what gates features
// elsewhere in the app via licenseManager.isPro()).
expect(licenseManager.isPro()).toBe(true);
expect(licenseManager.hasFeature('sso')).toBe(true);
});
// ── Bonus: GET /api/v1/license/status reflects the active Pro license ──
test('Step 5: license status confirms Pro is active', async () => {
const res = await request(app)
.get('/api/v1/license/status')
.expect(200);
expect(res.body.success).toBe(true);
expect(res.body.license.active).toBe(true);
expect(res.body.license.tier).toBe('premium');
expect(res.body.license.expired).toBe(false);
expect(res.body.license.features).toEqual(
expect.arrayContaining(['sso', 'recipes', 'swarm'])
);
});
});
// ═══════════════════════════════════════════════════════════════════════════
// Additional e2e scenarios
// ═══════════════════════════════════════════════════════════════════════════
describe('e2e: lookup returns 404 before webhook delivers the license', () => {
test('lookup before webhook → 404 not found', async () => {
const app = makeApp(null);
const sessionId = `cs_notyet_${crypto.randomBytes(4).toString('hex')}`;
const res = await request(app)
.get(`/api/v1/billing/lookup/${sessionId}`)
.expect(404);
expect(res.body.success).toBe(false);
});
});
describe('e2e: each catalog product flows through to a valid activatable license', () => {
// Use a fresh app + licenseManager per product to avoid activation conflicts.
for (const product of catalog.PRODUCTS) {
test(`product ${product.id} (${product.durationDays}d) activates and unlocks Pro`, async () => {
const sessionId = `cs_e2e_${product.id}_${crypto.randomBytes(4).toString('hex')}`;
const email = `buyer_${product.id}@example.com`;
const lm = new LicenseManager(
{
store: jest.fn().mockResolvedValue(undefined),
retrieve: jest.fn().mockResolvedValue(null),
delete: jest.fn().mockResolvedValue(undefined),
},
path.join(TMP, `config-${product.id}.json`),
{ info: () => {}, warn: () => {}, error: () => {} }
);
lm.loadSecret(SECRET_FILE);
const app = makeApp(lm);
// Checkout
installMockStripe(sessionId, `https://checkout.stripe.com/c/pay/${sessionId}`);
const checkoutRes = await request(app)
.post('/api/v1/billing/checkout')
.send({ productId: product.id, customerEmail: email })
.expect(200);
expect(checkoutRes.body.data.id).toBe(sessionId);
// Webhook
const { rawBody, signatureHeader } = buildSignedWebhook(sessionId, product.id, email);
const whResult = await bridge.handleWebhook({ rawBody, signatureHeader });
expect(whResult.status).toBe(200);
expect(whResult.body.delivered).toBe(true);
expect(whResult.body.durationDays).toBe(product.durationDays);
// Lookup
const lookupRes = await request(app)
.get(`/api/v1/billing/lookup/${sessionId}`)
.expect(200);
expect(lookupRes.body.data.status).toBe('delivered');
expect(lookupRes.body.data.code).toBeTruthy();
const code = lookupRes.body.data.code;
// Activate → Pro
const activateRes = await request(app)
.post('/api/v1/license/activate')
.send({ code })
.expect(200);
expect(activateRes.body.license.tier).toBe('premium');
expect(activateRes.body.license.durationDays).toBe(product.durationDays);
expect(lm.isPro()).toBe(true);
});
}
});
describe('e2e: webhook idempotency — duplicate delivery reuses the same license', () => {
test('a second webhook for the same session does not mint a new code', async () => {
const sessionId = `cs_e2e_dedup_${crypto.randomBytes(4).toString('hex')}`;
const productId = 'pro-30d';
const email = 'dedup@example.com';
// First delivery.
const payload1 = buildSignedWebhook(sessionId, productId, email);
const r1 = await bridge.handleWebhook({
rawBody: payload1.rawBody,
signatureHeader: payload1.signatureHeader,
});
expect(r1.status).toBe(200);
expect(r1.body.delivered).toBe(true);
const store = createFulfillmentStore({ filePath: process.env.STRIPE_BRIDGE_FULFILLMENT_STORE_FILE });
const firstCode = store.readBySession(sessionId).code;
expect(firstCode).toBeTruthy();
// Same eventId (Stripe retry) → layer-1 idempotency, no regeneration.
const r2 = await bridge.handleWebhook({
rawBody: payload1.rawBody,
signatureHeader: payload1.signatureHeader,
});
expect(r2.status).toBe(200);
expect(r2.body.deduplicated).toBe(true);
const secondCode = store.readBySession(sessionId).code;
expect(secondCode).toBe(firstCode);
});
});
describe('e2e: the license code generated by the bridge verifies via the real keygen', () => {
test('bridge-generated code is cryptographically valid', async () => {
const sessionId = `cs_e2e_crypto_${crypto.randomBytes(4).toString('hex')}`;
const { rawBody, signatureHeader } = buildSignedWebhook(sessionId, 'pro-365d', 'crypto@example.com');
const result = await bridge.handleWebhook({ rawBody, signatureHeader });
expect(result.status).toBe(200);
const store = createFulfillmentStore({ filePath: process.env.STRIPE_BRIDGE_FULFILLMENT_STORE_FILE });
const code = store.readBySession(sessionId).code;
// verifyCode with the SAME secret the bridge used — this is exactly what
// LicenseManager._validateOffline does during activation.
const verification = keygen.verifyCode(E2E_SECRET, code);
expect(verification.valid).toBe(true);
expect(verification.durationDays).toBe(365);
expect(verification.expired).toBe(false);
});
});