From 001b07406de8058d6eb013df0c0441ef5754e5ea Mon Sep 17 00:00:00 2001 From: gunshiz Date: Sat, 29 Aug 2026 06:08:06 +0000 Subject: [PATCH] test(ops): lock production deployment contract --- tests/deployment-contract.test.ts | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 tests/deployment-contract.test.ts diff --git a/tests/deployment-contract.test.ts b/tests/deployment-contract.test.ts new file mode 100644 index 0000000..2e481ee --- /dev/null +++ b/tests/deployment-contract.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from "vitest"; + +async function repositoryFile(path: string): Promise { + return Bun.file(new URL(`../${path}`, import.meta.url)).text(); +} + +describe("production deployment contract", () => { + it("defines a resilient two-replica web workload", async () => { + const deployment = await repositoryFile("k8s/base/deployment.yaml"); + + expect(deployment).toContain("replicas: 2"); + expect(deployment).toContain("maxSurge: 1"); + expect(deployment).toContain("maxUnavailable: 0"); + expect(deployment).toContain("path: /api/health?ready=1"); + expect(deployment).toContain("path: /api/health"); + expect(deployment).toMatch( + /requests:\s+cpu: 500m\s+memory: 512Mi\s+limits:\s+cpu: "1"\s+memory: 1Gi/u, + ); + expect(deployment).toContain("runAsNonRoot: true"); + expect(deployment).toContain("readOnlyRootFilesystem: true"); + expect(deployment).toContain('drop: ["ALL"]'); + }); + + it("exposes only the app through the requested service and TLS host", async () => { + const [service, ingress] = await Promise.all([ + repositoryFile("k8s/base/service.yaml"), + repositoryFile("k8s/base/ingress.yaml"), + ]); + + expect(service).toContain("type: ClusterIP"); + expect(service).toContain("port: 3000"); + expect(service).toContain("targetPort: http"); + expect(ingress).toContain("ingressClassName: traefik"); + expect(ingress).toContain("host: sheet.sudloh.com"); + expect(ingress).toContain("secretName: sheet-sudloh-com-tls"); + }); + + it("keeps availability and scaling bounds explicit", async () => { + const [hpa, pdb] = await Promise.all([ + repositoryFile("k8s/base/hpa.yaml"), + repositoryFile("k8s/base/pdb.yaml"), + ]); + + expect(hpa).toContain("minReplicas: 2"); + expect(hpa).toContain("maxReplicas: 6"); + expect(hpa).toContain("averageUtilization: 70"); + expect(pdb).toContain("minAvailable: 1"); + }); + + it("uses externally supplied secrets and does not provision data stores", async () => { + const manifestPaths = [ + "k8s/base/namespace.yaml", + "k8s/base/configmap.yaml", + "k8s/base/deployment.yaml", + "k8s/base/worker-deployment.yaml", + "k8s/base/service.yaml", + "k8s/base/ingress.yaml", + "k8s/base/hpa.yaml", + "k8s/base/pdb.yaml", + "k8s/base/ci-rbac.yaml", + "k8s/migration/job.yaml", + ]; + const manifests = ( + await Promise.all(manifestPaths.map(repositoryFile)) + ).join("\n---\n"); + + expect(manifests).toContain("name: buzz-sheet-env"); + expect(manifests).not.toMatch(/kind: (Secret|StatefulSet|PersistentVolumeClaim)/u); + expect(manifests).not.toContain("resources: [\"secrets\"]"); + expect(manifests).not.toContain("kind: ClusterRole"); + expect(manifests).not.toContain("kind: ClusterRoleBinding"); + }); + + it("packages non-root Bun application and migration targets", async () => { + const dockerfile = await repositoryFile("Dockerfile"); + + expect(dockerfile).toContain("FROM dependencies AS migration"); + expect(dockerfile).toContain("FROM oven/bun:${BUN_VERSION} AS app"); + expect(dockerfile.match(/^USER bun$/gmu)).toHaveLength(2); + expect(dockerfile).toContain('ENTRYPOINT ["bun", "scripts/migrate.ts"]'); + expect(dockerfile).toContain('CMD ["bun", "server.js"]'); + }); + + it("verifies first, publishes immutable images, and migrates before rollout", async () => { + const workflow = await repositoryFile(".gitea/workflows/ci.yml"); + + expect(workflow).toContain("bun install --frozen-lockfile"); + expect(workflow).toContain("bun run test"); + expect(workflow).toContain("bunx tsc --noEmit"); + expect(workflow).toContain("bun run lint"); + expect(workflow).toContain("kubectl kustomize k8s/"); + expect(workflow).toContain("target: app"); + expect(workflow).toContain("target: migration"); + expect(workflow).toContain( + "registry.neko-piranha.ts.net/astral/buzz-sheet", + ); + expect(workflow).toContain("migrate-${{ gitea.sha }}"); + expect(workflow.indexOf("condition=complete")).toBeLessThan( + workflow.indexOf("set image"), + ); + expect(workflow).not.toMatch(/playwright|chromium/iu); + }); +}); + +describe("environment template contract", () => { + it("documents every externally supplied production secret", async () => { + const environmentExample = await repositoryFile(".env.example"); + const requiredKeys = [ + "DATABASE_URL", + "BETTER_AUTH_URL", + "BETTER_AUTH_SECRET", + "GOOGLE_CLIENT_ID", + "GOOGLE_CLIENT_SECRET", + "ADMIN_EMAIL", + "REDIS_URL", + "S3_ENDPOINT", + "S3_BUCKET", + "S3_ACCESS_KEY_ID", + "S3_SECRET_ACCESS_KEY", + "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY", + "NEXT_DEPLOYMENT_ID", + ]; + + for (const key of requiredKeys) { + expect(environmentExample).toMatch(new RegExp(`^${key}=`, "mu")); + } + expect(environmentExample).toContain("BUZZ_DEMO_MODE=false"); + }); +});