fix: enforce rollout gates across backend entry points

This commit is contained in:
jb
2026-09-19 10:01:23 -04:00
parent 92272d4e9a
commit 99436225f6
16 changed files with 115 additions and 23 deletions
+26
View File
@@ -5,6 +5,7 @@ import schema from "../src/convex/schema";
import type { Asset } from "../src/lib/domain/model";
process.env.CONVEX_BRIDGE_SECRET = "test-bridge-secret";
process.env.APP_CAPABILITY_ALLOWLIST = "asset_search,asset_valuation,asset_sync,assistant_dispatch,session_token,identity,subscriptions";
const modules = {
"src/convex/functions.ts": () => import("../src/convex/functions"),
@@ -47,6 +48,7 @@ async function login(t: ReturnType<typeof convexTest>, input: Partial<any> = {})
describe("Convex persistence", () => {
beforeEach(() => {
process.env.CONVEX_BRIDGE_SECRET = "test-bridge-secret";
process.env.APP_CAPABILITY_ALLOWLIST = "asset_search,asset_valuation,asset_sync,assistant_dispatch,session_token,identity,subscriptions";
});
test("rejects unauthenticated bridge calls and keeps tenants separate", async () => {
@@ -445,4 +447,28 @@ describe("Convex persistence", () => {
await t.mutation(mutation("bridgeDeleteSession"), { ...bridge, sessionHash: "session-hash" });
await expect(browser.query(statusQuery("characters"), {})).resolves.toBeNull();
});
test("backend rollout gate blocks status subscriptions and sync mutations", async () => {
const previous = process.env.APP_CAPABILITY_ALLOWLIST;
process.env.APP_CAPABILITY_ALLOWLIST = "";
try {
process.env.APP_AUTH_ISSUER = "https://auth.example.test";
const t = convexTest({ schema, modules });
const loggedIn = await login(t, { sessionHash: "gated-session" });
const browser = t.withIdentity({
subject: loggedIn.userId,
issuer: "https://auth.example.test",
sid: loggedIn.sessionId,
} as any);
await expect(browser.query(statusQuery("characters"), {})).resolves.toBeNull();
await expect(t.mutation(mutation("bridgeBeginSync"), {
...bridge,
userId: loggedIn.userId,
characterId: 100,
})).rejects.toThrow("asset_sync");
} finally {
if (previous === undefined) delete process.env.APP_CAPABILITY_ALLOWLIST;
else process.env.APP_CAPABILITY_ALLOWLIST = previous;
}
});
});
+2 -1
View File
@@ -13,11 +13,12 @@ describe('deployment rollout gate', () => {
expect(baseline).toEqual(BASELINE_RELEASE_CAPABILITIES);
expect(capabilityEnabled('asset_search', baseline)).toBe(true);
expect(capabilityEnabled('wallet_balance', baseline)).toBe(false);
expect(capabilityEnabled('subscriptions', baseline)).toBe(false);
expect(capabilityEnabled('subscriptions', baseline)).toBe(true);
});
test('supports explicit narrowing and rejects unknown or empty names', () => {
expect(parseCapabilityAllowlist('asset_search, asset_search,session_token')).toEqual(['asset_search', 'session_token']);
expect(parseCapabilityAllowlist('subscriptions')).toEqual(['subscriptions']);
expect(parseCapabilityAllowlist('')).toEqual([]);
expect(() => parseCapabilityAllowlist('asset_search,typo')).toThrow('Unknown deployment capability');
expect(() => parseCapabilityAllowlist('asset_search,')).toThrow('Unknown deployment capability');