CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | /**
* Block L8 — public /pricing page tests.
*
* Anonymous-safe route. Verifies:
* - GET /pricing returns 200 HTML to a logged-out visitor
* - All four plan names (Free, Pro, Team, Enterprise) render
* - The "what you get on free" block contains the AI features
* - All five FAQ questions are present verbatim
* - CTA links resolve to /register?next=... or /settings/billing
* - The self-host column mentions the curl install line
*/
import { describe, it, expect } from "bun:test";
import app from "../app";
import { FALLBACK_PLANS } from "../lib/billing";
describe("L8 — /pricing public page", () => {
it("returns 200 HTML to an anonymous visitor", async () => {
const res = await app.request("/pricing");
expect(res.status).toBe(200);
const ct = res.headers.get("content-type") || "";
expect(ct.toLowerCase()).toContain("text/html");
});
it("renders all four plan names (Free, Pro, Team, Enterprise)", async () => {
const res = await app.request("/pricing");
const body = await res.text();
// FALLBACK_PLANS guarantees the four names exist regardless of whether
// the DB seeds are loaded — listPlans() falls back to these.
for (const slug of Object.keys(FALLBACK_PLANS)) {
const name = FALLBACK_PLANS[slug].name;
expect(body).toContain(name);
}
});
it("the free-tier block lists at least 6 of the included AI features", async () => {
const res = await app.request("/pricing");
const body = await res.text();
const features = [
"Unlimited public repos",
"AI code review on every PR",
"AI auto-merge",
"ai:build label",
"Sleep Mode digest",
"AI hours saved counter",
"MCP server access",
"Claude Code skill bundle",
"One-command install",
"GitHub OIDC sign-in",
];
const present = features.filter((f) => body.includes(f));
expect(present.length).toBeGreaterThanOrEqual(6);
});
it("FAQ contains all five required questions", async () => {
const res = await app.request("/pricing");
const body = await res.text();
expect(body).toContain("Is it really free? What's the catch?");
expect(body).toContain(
"Do I need to bring my own Anthropic API key on the free tier?"
);
expect(body).toContain("What happens when I exceed my plan's quota?");
expect(body).toContain("Can I migrate from GitHub for free?");
expect(body).toContain("Does the free tier include private repos?");
});
it("CTAs route anonymous users through /register?next=/settings/billing", async () => {
const res = await app.request("/pricing");
const body = await res.text();
// At least one register-funnel CTA must exist.
expect(body).toMatch(/href="\/register(\?next=\/settings\/billing[^"]*)?"/);
// And the page must mention /settings/billing somewhere as the
// destination after sign-up.
expect(body).toContain("/settings/billing");
});
it("self-host column mentions `curl gluecron.com/install`", async () => {
const res = await app.request("/pricing");
const body = await res.text();
expect(body).toContain("curl gluecron.com/install");
});
it("does not require authentication (no redirect)", async () => {
const res = await app.request("/pricing");
expect(res.status).toBe(200);
expect(res.status).not.toBe(302);
expect(res.status).not.toBe(401);
});
it("hero copy contains the L8 tagline", async () => {
const res = await app.request("/pricing");
const body = await res.text();
expect(body).toContain("Free for the AI-curious.");
expect(body).toContain("Pay only when you're ready to scale.");
});
});
|