Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

billing.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

billing.test.tsBlame140 lines · 1 contributor
8f50ed0Claude1/**
2 * Block F4 — Billing + quotas tests.
3 *
4 * Pure FALLBACK_PLANS + formatPrice tests + route auth smoke. Helpers that
5 * touch the DB (`getUserQuota`, `setUserPlan`, `bumpUsage`) are only exercised
6 * via type/shape checks — the real integration happens on the live server.
7 */
8
9import { describe, it, expect } from "bun:test";
10import app from "../app";
11import {
12 FALLBACK_PLANS,
13 DEFAULT_PLAN_SLUG,
14 formatPrice,
15 listPlans,
16 getPlan,
17 checkQuota,
18} from "../lib/billing";
19
20describe("billing — FALLBACK_PLANS", () => {
21 it("contains free/pro/team/enterprise", () => {
22 expect(FALLBACK_PLANS).toHaveProperty("free");
23 expect(FALLBACK_PLANS).toHaveProperty("pro");
24 expect(FALLBACK_PLANS).toHaveProperty("team");
25 expect(FALLBACK_PLANS).toHaveProperty("enterprise");
26 });
27
28 it("free plan is $0 with no private repos", () => {
29 expect(FALLBACK_PLANS.free.priceCents).toBe(0);
30 expect(FALLBACK_PLANS.free.privateRepos).toBe(false);
31 });
32
33 it("paid plans unlock private repos", () => {
34 expect(FALLBACK_PLANS.pro.privateRepos).toBe(true);
35 expect(FALLBACK_PLANS.team.privateRepos).toBe(true);
36 expect(FALLBACK_PLANS.enterprise.privateRepos).toBe(true);
37 });
38
39 it("limits scale up across tiers", () => {
40 expect(FALLBACK_PLANS.pro.repoLimit).toBeGreaterThan(
41 FALLBACK_PLANS.free.repoLimit
42 );
43 expect(FALLBACK_PLANS.team.repoLimit).toBeGreaterThan(
44 FALLBACK_PLANS.pro.repoLimit
45 );
46 expect(FALLBACK_PLANS.enterprise.repoLimit).toBeGreaterThan(
47 FALLBACK_PLANS.team.repoLimit
48 );
49 });
50
51 it("DEFAULT_PLAN_SLUG is 'free'", () => {
52 expect(DEFAULT_PLAN_SLUG).toBe("free");
53 });
54});
55
56describe("billing — formatPrice", () => {
57 it("returns 'Free' for 0 cents", () => {
58 expect(formatPrice(0)).toBe("Free");
59 });
60
61 it("formats non-zero prices as $N.NN/mo", () => {
62 expect(formatPrice(900)).toBe("$9.00/mo");
63 expect(formatPrice(2900)).toBe("$29.00/mo");
64 expect(formatPrice(150)).toBe("$1.50/mo");
65 });
66});
67
68describe("billing — listPlans / getPlan", () => {
69 it("listPlans returns at least the 4 fallback plans", async () => {
70 const plans = await listPlans();
71 expect(plans.length).toBeGreaterThanOrEqual(4);
72 });
73
74 it("getPlan('free') returns a plan object", async () => {
75 const plan = await getPlan("free");
76 expect(plan.slug).toBe("free");
77 expect(plan.priceCents).toBe(0);
78 });
79
80 it("getPlan for unknown slug falls back to free", async () => {
81 const plan = await getPlan("no-such-plan");
82 expect(plan.slug).toBe("free");
83 });
84});
85
86describe("billing — checkQuota", () => {
87 it("fails-open on unknown user id", async () => {
88 const ok = await checkQuota(
89 "00000000-0000-0000-0000-000000000000",
90 "aiTokensUsedThisMonth",
91 100
92 );
93 expect(typeof ok).toBe("boolean");
94 });
95});
96
97describe("billing — route smoke", () => {
98 it("GET /settings/billing without auth → 302 /login", async () => {
99 const res = await app.request("/settings/billing");
100 expect(res.status).toBe(302);
101 expect(res.headers.get("location") || "").toContain("/login");
102 });
103
104 it("GET /admin/billing without auth → 302 /login", async () => {
105 const res = await app.request("/admin/billing");
106 expect(res.status).toBe(302);
107 expect(res.headers.get("location") || "").toContain("/login");
108 });
109
110 it("POST /admin/billing/:id/plan without auth → 302 /login", async () => {
111 const res = await app.request(
112 "/admin/billing/00000000-0000-0000-0000-000000000000/plan",
113 {
114 method: "POST",
115 body: new URLSearchParams({ slug: "pro" }),
116 headers: { "content-type": "application/x-www-form-urlencoded" },
117 }
118 );
119 expect(res.status).toBe(302);
120 expect(res.headers.get("location") || "").toContain("/login");
121 });
122});
123
124describe("billing — lib exports", () => {
125 it("exports the full surface", async () => {
126 const mod = await import("../lib/billing");
127 expect(typeof mod.listPlans).toBe("function");
128 expect(typeof mod.getPlan).toBe("function");
129 expect(typeof mod.getUserQuota).toBe("function");
130 expect(typeof mod.setUserPlan).toBe("function");
131 expect(typeof mod.bumpUsage).toBe("function");
132 expect(typeof mod.checkQuota).toBe("function");
133 expect(typeof mod.repoCountForUser).toBe("function");
134 expect(typeof mod.wouldExceedRepoLimit).toBe("function");
135 expect(typeof mod.resetIfCycleExpired).toBe("function");
136 expect(typeof mod.formatPrice).toBe("function");
137 expect(mod.FALLBACK_PLANS).toBeDefined();
138 expect(mod.DEFAULT_PLAN_SLUG).toBe("free");
139 });
140});