fix: fail closed backend capabilities and isolate gate smoke
This commit is contained in:
@@ -471,4 +471,23 @@ describe("Convex persistence", () => {
|
||||
else process.env.APP_CAPABILITY_ALLOWLIST = previous;
|
||||
}
|
||||
});
|
||||
|
||||
test("backend rollout gate fails closed when propagation is missing or malformed", async () => {
|
||||
const previous = process.env.APP_CAPABILITY_ALLOWLIST;
|
||||
try {
|
||||
for (const value of [undefined, "not-a-capability"]) {
|
||||
if (value === undefined) delete process.env.APP_CAPABILITY_ALLOWLIST;
|
||||
else process.env.APP_CAPABILITY_ALLOWLIST = value;
|
||||
process.env.APP_AUTH_ISSUER = "https://auth.example.test";
|
||||
const t = convexTest({ schema, modules });
|
||||
const loggedIn = await login(t, { sessionHash: `missing-gate-${value ?? 'unset'}` });
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
|
||||
import {
|
||||
BASELINE_RELEASE_CAPABILITIES,
|
||||
capabilityEnabled,
|
||||
parseBackendCapabilityAllowlist,
|
||||
parseCapabilityAllowlist,
|
||||
releaseIdIsImmutable,
|
||||
} from '../src/lib/server/release-gate';
|
||||
@@ -23,6 +24,8 @@ describe('deployment rollout gate', () => {
|
||||
expect(parseCapabilityAllowlist('')).toEqual([]);
|
||||
expect(() => parseCapabilityAllowlist('asset_search,typo')).toThrow('Unknown deployment capability');
|
||||
expect(() => parseCapabilityAllowlist('asset_search,')).toThrow('Unknown deployment capability');
|
||||
expect(parseBackendCapabilityAllowlist('asset_sync')).toEqual(['asset_sync']);
|
||||
expect(() => parseBackendCapabilityAllowlist(undefined)).toThrow('required in the backend deployment');
|
||||
});
|
||||
|
||||
test('requires a non-placeholder production release identifier', () => {
|
||||
|
||||
@@ -5,14 +5,17 @@ const workflow = readFileSync('.gitea/workflows/ci.yml', 'utf8');
|
||||
const compose = readFileSync('compose.yaml', 'utf8');
|
||||
const dockerfile = readFileSync('Dockerfile', 'utf8');
|
||||
const dockerignore = readFileSync('.dockerignore', 'utf8');
|
||||
const convexConfig = readFileSync('scripts/configure-convex.ts', 'utf8');
|
||||
|
||||
test('Gitea workflow separates PR source checks from trusted container builds', () => {
|
||||
test('Gitea workflow keeps source checks separate and holds unverified container CI', () => {
|
||||
expect(workflow).toContain('pull_request:');
|
||||
expect(workflow).toContain("if: ${{ gitea.event_name == 'push' }}");
|
||||
expect(workflow).toContain('if: ${{ false }}');
|
||||
expect(workflow).toContain('GITHUB_RUN_ID is required');
|
||||
expect(workflow).toContain('RELEASE_ENV=staging');
|
||||
expect(workflow).toContain('bun run test:release-gates');
|
||||
expect(workflow).toContain('docker compose config --quiet');
|
||||
expect(workflow).toContain('docker build --build-arg APP_RELEASE_ID=fixture-ci');
|
||||
expect(workflow).toContain('GITHUB_RUN_ATTEMPT');
|
||||
expect(workflow).toContain('--network none');
|
||||
});
|
||||
|
||||
@@ -27,4 +30,5 @@ test('Compose and Docker defaults are isolated and digest-pinned', () => {
|
||||
for (const pattern of ['*.pem', '*.key', '*.p12', '*.pfx', 'credentials*', 'secrets*', '.npmrc']) {
|
||||
expect(dockerignore).toContain(pattern);
|
||||
}
|
||||
expect(convexConfig).toContain("'APP_CAPABILITY_ALLOWLIST'");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user