Commit6ae664d
test: assert guards are CALLED, not merely mentioned
test: assert guards are CALLED, not merely mentioned
Fixing the weakness found while fault-injecting the AI quota work:
`expect(src).toContain("someGuard")` is satisfied by `void someGuard;`,
which mentions the guard while calling nothing. Every source-level
assertion written during this audit used that form, so each of the guards
shipped this session could have been removed and its regression test
would still have passed.
Tightened to require the call form across six files — canReadPackage,
canReadRepoPackages, isRepoMember, canScimManageAccount,
recordedSubscriptionScope, bumpUsage, invalidateQuotaCache,
resetIfCycleExpired, parseReceivePackRefs, gunzipBounded — plus
from(orgMembers) for the membership lookup.
Left bare deliberately, where bare is correct: `gunzipSync` is an ABSENCE
assertion and the loose form is the strong one there; `budget_exceeded`
is a string literal; `aiTokensUsedThisMonth` is a column name; and
`scimProvisionedByOrgId` is a property, already pinned by an adjacent
assertion on the assignment form and an occurrence count.
Proven, not assumed. Replacing the npm registry's read gate with `void
canReadPackage;` now fails package-access; before this commit it passed,
meaning the private-package leak could have been reintroduced silently.
Same for stripe-subscription-scope with the scope argument neutered.
Source restored after each injection — the diff is tests only.
Suite: 3504 pass, 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>6 files changed+31−316ae664d6e50e18382c3a7851b30c39cba42473f5
6 changed files+31−31
Modifiedsrc/__tests__/ai-quota-metering.test.ts+6−6View fileUnifiedSplit
@@ -137,7 +137,7 @@ describe("recordAiCost meters the quota", () => {
137137 });
138138
139139 it("bumps the column the enforcement path actually reads", () => {
140 expect(src).toContain("bumpUsage");
140 expect(src).toContain("bumpUsage(");
141141 expect(src).toContain("aiTokensUsedThisMonth");
142142 });
143143
@@ -145,9 +145,9 @@ describe("recordAiCost meters the quota", () => {
145145 // Otherwise a burst of calls keeps reusing a stale allow until the cache
146146 // expires and sails past the cap.
147147 const meter = slice(src, "async function meterAiQuota", "export interface");
148 expect(meter).toContain("invalidateQuotaCache");
149 expect(meter.indexOf("bumpUsage")).toBeLessThan(
150 meter.indexOf("invalidateQuotaCache")
148 expect(meter).toContain("invalidateQuotaCache(");
149 expect(meter.indexOf("bumpUsage(")).toBeLessThan(
150 meter.indexOf("invalidateQuotaCache(")
151151 );
152152 });
153153});
@@ -161,8 +161,8 @@ describe("the quota gate rolls the monthly cycle", () => {
161161 "export async function checkAiQuotaCached",
162162 "export function invalidateQuotaCache"
163163 );
164 expect(body).toContain("resetIfCycleExpired");
165 expect(body.indexOf("resetIfCycleExpired")).toBeLessThan(
164 expect(body).toContain("resetIfCycleExpired(");
165 expect(body.indexOf("resetIfCycleExpired(")).toBeLessThan(
166166 body.indexOf("getUserQuota(userId)")
167167 );
168168 });
Modifiedsrc/__tests__/bounded-gunzip.test.ts+4−4View fileUnifiedSplit
@@ -146,7 +146,7 @@ describe("git-receive-pack authorizes before reading the body", () => {
146146
147147 it("checks write access before any decompression", () => {
148148 const gateAt = handler.indexOf(`satisfiesAccess(access, "write")`);
149 const gunzipAt = handler.indexOf("gunzipBounded");
149 const gunzipAt = handler.indexOf("gunzipBounded(");
150150 expect(gunzipAt).toBeGreaterThan(-1);
151151 expect(gateAt).toBeLessThan(gunzipAt);
152152 });
@@ -158,8 +158,8 @@ describe("git-receive-pack authorizes before reading the body", () => {
158158 it("still parses refs, so policy and post-receive keep working", () => {
159159 // The reorder moved the body read; it must not have dropped ref parsing,
160160 // or pushes would silently skip the policy gate and the hook.
161 expect(handler).toContain("parseReceivePackRefs");
162 const gunzipAt = handler.indexOf("gunzipBounded");
163 expect(handler.indexOf("parseReceivePackRefs")).toBeGreaterThan(gunzipAt);
161 expect(handler).toContain("parseReceivePackRefs(");
162 const gunzipAt = handler.indexOf("gunzipBounded(");
163 expect(handler.indexOf("parseReceivePackRefs(")).toBeGreaterThan(gunzipAt);
164164 });
165165});
Modifiedsrc/__tests__/org-team-roster.test.ts+1−1View fileUnifiedSplit
@@ -95,7 +95,7 @@ describe("the team route enforces org membership", () => {
9595 })();
9696
9797 it("looks the viewer up in orgMembers", () => {
98 expect(handler).toContain("orgMembers");
98 expect(handler).toContain("from(orgMembers)");
9999 expect(handler).toContain("eq(orgMembers.userId, user.id)");
100100 });
101101
Modifiedsrc/__tests__/package-access.test.ts+8−8View fileUnifiedSplit
@@ -150,7 +150,7 @@ describe("canReadPackage resolves the owning repository live", () => {
150150 });
151151});
152152
153describe("isRepoMember", () => {
153describe("isRepoMember(", () => {
154154 it("is false for anonymous even on a public repo", async () => {
155155 // Membership is not the same as readability — a public reader is not a
156156 // member, which is what decides visibility of private packages.
@@ -198,10 +198,10 @@ describe("registry read routes invoke the access gate", () => {
198198 it("GET /npm/ checks canReadPackage before serving", async () => {
199199 const src = sourceWithoutLineComments("routes/packages-api.ts");
200200 const body = handlerBody(src, `api.get("/npm/*"`, `api.put("/npm/*"`);
201 expect(body).toContain("canReadPackage");
201 expect(body).toContain("canReadPackage(");
202202 // The tarball branch lives inside this handler, so gating the handler
203203 // gates the tarball too — assert the gate precedes the tarball read.
204 const gateAt = body.indexOf("canReadPackage");
204 const gateAt = body.indexOf("canReadPackage(");
205205 const tarballAt = body.indexOf("match.tarball");
206206 expect(tarballAt).toBeGreaterThan(gateAt);
207207 });
@@ -213,15 +213,15 @@ describe("registry read routes invoke the access gate", () => {
213213 `api.get("/api/packages/:owner/:repo"`,
214214 `api.get("/api/packages/:owner/:repo/:pkgName`
215215 );
216 expect(listBody).toContain("canReadRepoPackages");
217 expect(listBody).toContain("isRepoMember");
216 expect(listBody).toContain("canReadRepoPackages(");
217 expect(listBody).toContain("isRepoMember(");
218218
219219 const detailBody = handlerBody(
220220 src,
221221 `api.get("/api/packages/:owner/:repo/:pkgName`,
222222 `api.get("/npm/*"`
223223 );
224 expect(detailBody).toContain("canReadRepoPackages");
224 expect(detailBody).toContain("canReadRepoPackages(");
225225 });
226226
227227 it("the package UI routes filter private packages for non-members", async () => {
@@ -231,7 +231,7 @@ describe("registry read routes invoke the access gate", () => {
231231 `ui.get("/:owner/:repo/packages"`,
232232 `ui.get("/:owner/:repo/packages/:pkgName`
233233 );
234 expect(listBody).toContain("isRepoMember");
234 expect(listBody).toContain("isRepoMember(");
235235 // The rendered rows must come from the filtered list, not the raw query.
236236 expect(listBody).toContain("visiblePkgs.map");
237237 expect(listBody).not.toContain("rows = pkgs.map");
@@ -241,6 +241,6 @@ describe("registry read routes invoke the access gate", () => {
241241 `ui.get("/:owner/:repo/packages/:pkgName`,
242242 "export default"
243243 );
244 expect(detailBody).toContain("isRepoMember");
244 expect(detailBody).toContain("isRepoMember(");
245245 });
246246});
Modifiedsrc/__tests__/scim-account-scope.test.ts+7−7View fileUnifiedSplit
@@ -134,8 +134,8 @@ describe("every SCIM verb gates account-level writes", () => {
134134 `scim.patch("/scim/v2/:orgId/Users/:userId"`
135135 );
136136 expect(body).toContain(DEPROVISION_MARKER);
137 expect(body).toContain("canScimManageAccount");
138 expect(body.indexOf("canScimManageAccount")).toBeLessThan(
137 expect(body).toContain("canScimManageAccount(");
138 expect(body.indexOf("canScimManageAccount(")).toBeLessThan(
139139 body.indexOf(DEPROVISION_MARKER)
140140 );
141141 });
@@ -147,8 +147,8 @@ describe("every SCIM verb gates account-level writes", () => {
147147 `scim.delete("/scim/v2/:orgId/Users/:userId"`
148148 );
149149 expect(body).toContain(DEPROVISION_MARKER);
150 expect(body).toContain("canScimManageAccount");
151 expect(body.indexOf("canScimManageAccount")).toBeLessThan(
150 expect(body).toContain("canScimManageAccount(");
151 expect(body.indexOf("canScimManageAccount(")).toBeLessThan(
152152 body.indexOf(DEPROVISION_MARKER)
153153 );
154154 });
@@ -160,14 +160,14 @@ describe("every SCIM verb gates account-level writes", () => {
160160 `scim.get("/scim/v2/:orgId/ServiceProviderConfig"`
161161 );
162162 expect(body).toContain(DEPROVISION_MARKER);
163 expect(body).toContain("canScimManageAccount");
164 expect(body.indexOf("canScimManageAccount")).toBeLessThan(
163 expect(body).toContain("canScimManageAccount(");
164 expect(body.indexOf("canScimManageAccount(")).toBeLessThan(
165165 body.indexOf(DEPROVISION_MARKER)
166166 );
167167 // Membership removal stays unconditional — that IS the deprovision.
168168 expect(body).toContain("delete(orgMembers)");
169169 expect(body.indexOf("delete(orgMembers)")).toBeLessThan(
170 body.indexOf("canScimManageAccount")
170 body.indexOf("canScimManageAccount(")
171171 );
172172 });
173173
Modifiedsrc/__tests__/stripe-subscription-scope.test.ts+5−5View fileUnifiedSplit
@@ -66,7 +66,7 @@ const text = (ls: Leaf[]) =>
6666 .map((l) => l.value)
6767 .join(" ");
6868
69describe("recordedSubscriptionScope", () => {
69describe("recordedSubscriptionScope(", () => {
7070 it("returns no constraint when the event names no subscription", () => {
7171 // A one-off invoice has no subscription; the customer match is all
7272 // there is to go on, so the caller must not be over-constrained.
@@ -136,8 +136,8 @@ describe("stripe-webhook applies the scope everywhere it mutates a plan", () =>
136136 `case "invoice.payment_failed"`
137137 );
138138 expect(body).toContain('planSlug: "free"');
139 expect(body).toContain("recordedSubscriptionScope");
140 expect(body.indexOf("recordedSubscriptionScope")).toBeGreaterThan(
139 expect(body).toContain("recordedSubscriptionScope(");
140 expect(body.indexOf("recordedSubscriptionScope(")).toBeGreaterThan(
141141 body.indexOf("stripeCustomerId, customerId")
142142 );
143143 });
@@ -145,13 +145,13 @@ describe("stripe-webhook applies the scope everywhere it mutates a plan", () =>
145145 it("invoice.payment_failed scopes the past_due flag", () => {
146146 const body = slice(`case "invoice.payment_failed"`, "default:");
147147 expect(body).toContain('stripeSubscriptionStatus: "past_due"');
148 expect(body).toContain("recordedSubscriptionScope");
148 expect(body).toContain("recordedSubscriptionScope(");
149149 });
150150
151151 it("reconcileSubscription scopes its non-active write", () => {
152152 const body = slice("async function reconcileSubscription", "if (!slug)");
153153 expect(body).toContain("stripeSubscriptionId: sub.id");
154 expect(body).toContain("recordedSubscriptionScope");
154 expect(body).toContain("recordedSubscriptionScope(");
155155 });
156156
157157 it("leaves exactly one unscoped update — the active-upgrade path", () => {
158158