DC-061: remove superseded status/pricing/index.html and obsolete pricing-page-catalog test
This commit is contained in:
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* DC-057 pricing-page catalog consistency test.
|
||||
*
|
||||
* The pricing page at status/pricing/index.html hard-codes the 4 product
|
||||
* IDs, prices, and labels. This test asserts that those hard-coded values
|
||||
* exactly match the catalog in src/billing/catalog.js — preventing drift
|
||||
* between the two sources.
|
||||
*
|
||||
* If a new tier is added to the catalog, this test will fail until the
|
||||
* pricing page is updated. If the pricing page is updated, the catalog
|
||||
* must change in lockstep (or this test fails the other way).
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const catalog = require('../../src/billing/catalog');
|
||||
|
||||
const PRICING_PAGE_PATH = path.join(__dirname, '..', '..', '..', 'status', 'pricing', 'index.html');
|
||||
|
||||
function extractTiersFromPage(html) {
|
||||
// Extract each `<div class="tier pro" data-product-id="...">` block, then
|
||||
// pull out the dollar amount in the `<div class="price">` element and
|
||||
// the durationDays from the "N-day Pro license" string. The regex is
|
||||
// anchored on the tier-class open + the matching buy-btn close so we
|
||||
// capture the full body of each tier card regardless of how many inner
|
||||
// divs it has.
|
||||
const tierRe = /<div class="tier pro" data-product-id="([^"]+)">([\s\S]*?)<button[^>]*class="buy-btn"[^>]*>\s*Buy/g;
|
||||
const tierBlocks = [...html.matchAll(tierRe)];
|
||||
return tierBlocks.map(([, productId, body]) => {
|
||||
const priceMatch = body.match(/<div class="price">\$(\d+)<\/div>/);
|
||||
const durMatch = body.match(/(\d+)-day Pro license/);
|
||||
return {
|
||||
productId,
|
||||
priceDollars: priceMatch ? parseInt(priceMatch[1], 10) : null,
|
||||
durationDays: durMatch ? parseInt(durMatch[1], 10) : null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the HTML body for one specific tier (from open div through the
|
||||
* buy-btn). Used by per-tier assertions that must NOT bleed across cards.
|
||||
*/
|
||||
function extractTierBody(html, productId) {
|
||||
const re = new RegExp(
|
||||
`<div class="tier pro" data-product-id="${productId}">([\\s\\S]*?)<button[^>]*class="buy-btn"[^>]*>\\s*Buy`,
|
||||
'i'
|
||||
);
|
||||
const m = html.match(re);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
describe('pricing page <-> catalog consistency (DC-057)', () => {
|
||||
let html;
|
||||
let pageTiers;
|
||||
|
||||
beforeAll(() => {
|
||||
html = fs.readFileSync(PRICING_PAGE_PATH, 'utf8');
|
||||
pageTiers = extractTiersFromPage(html);
|
||||
});
|
||||
|
||||
test('pricing page exists and is readable', () => {
|
||||
expect(html.length).toBeGreaterThan(1000);
|
||||
expect(pageTiers.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('every catalog product is rendered on the pricing page', () => {
|
||||
const catalogIds = catalog.PRODUCTS.map((p) => p.id).sort();
|
||||
const pageIds = pageTiers.map((t) => t.productId).sort();
|
||||
expect(pageIds).toEqual(catalogIds);
|
||||
});
|
||||
|
||||
test('every pricing-page productId appears in the catalog', () => {
|
||||
for (const tier of pageTiers) {
|
||||
const product = catalog.getProduct(tier.productId);
|
||||
expect(product).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
test('pricing-page dollar amounts match catalog amountCents', () => {
|
||||
for (const tier of pageTiers) {
|
||||
const product = catalog.getProduct(tier.productId);
|
||||
const expectedDollars = product.amountCents / 100;
|
||||
expect(tier.priceDollars).toBe(expectedDollars);
|
||||
}
|
||||
});
|
||||
|
||||
test('pricing-page duration strings match catalog durationDays', () => {
|
||||
for (const tier of pageTiers) {
|
||||
const product = catalog.getProduct(tier.productId);
|
||||
expect(tier.durationDays).toBe(product.durationDays);
|
||||
}
|
||||
});
|
||||
|
||||
test('catalog and pricing page agree on price label (scoped per tier card)', () => {
|
||||
// Per-tier priceLabel assertion: each tier card must include its
|
||||
// own catalog.priceLabel. A swap or misplaced label fails immediately
|
||||
// because the assertion checks the tier's own HTML body, not the page.
|
||||
for (const tier of pageTiers) {
|
||||
const product = catalog.getProduct(tier.productId);
|
||||
const body = extractTierBody(html, tier.productId);
|
||||
expect(body).not.toBeNull();
|
||||
// The priceLabel appears in the price div of THIS tier only,
|
||||
// immediately followed by the closing </div> + the duration block.
|
||||
const labelRegex = new RegExp(`<div class="price">\\s*\\${product.priceLabel}\\s*</div>\\s*<div class="duration"`);
|
||||
expect(body).toMatch(labelRegex);
|
||||
}
|
||||
});
|
||||
|
||||
test('pricing page does not include the monthly/annual subscription toggle (one-time only)', () => {
|
||||
// DC-057 acceptance: locked spec is ONE-TIME 30/90/180/365-day licenses
|
||||
// at $20/$50/$70/$99. The old monthly/annual subscription toggle
|
||||
// would contradict the spec.
|
||||
expect(html).not.toMatch(/period-monthly|period-annual/);
|
||||
expect(html).not.toMatch(/Subscribe to Pro/);
|
||||
});
|
||||
|
||||
test('pricing page references the success-page endpoint', () => {
|
||||
// The success URL is constructed server-side in stripe-client.js
|
||||
// (${origin}/billing/success?session_id=...). The pricing page itself
|
||||
// doesn't need to embed it — but the FOOTER must reference it so the
|
||||
// customer knows where to go after Stripe redirects.
|
||||
expect(html.toLowerCase()).toContain('after payment');
|
||||
expect(html).toContain('/admin/license');
|
||||
expect(html).toContain('/api/v1/billing/checkout');
|
||||
});
|
||||
|
||||
test('success page (status/billing/success.html) exists and references the lookup endpoint', () => {
|
||||
const successPath = path.join(__dirname, '..', '..', '..', 'status', 'billing', 'success.html');
|
||||
const successHtml = fs.readFileSync(successPath, 'utf8');
|
||||
expect(successHtml).toContain('/api/v1/billing/lookup/');
|
||||
expect(successHtml.length).toBeGreaterThan(1000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user