feat : 6 astra improve it
This commit is contained in:
@@ -70,6 +70,7 @@ describe("production deployment contract", () => {
|
||||
"k8s/base/hpa.yaml",
|
||||
"k8s/base/pdb.yaml",
|
||||
"k8s/base/ci-rbac.yaml",
|
||||
"k8s/base/network-policy.yaml",
|
||||
"k8s/migration/job.yaml",
|
||||
];
|
||||
const manifests = (
|
||||
@@ -81,15 +82,19 @@ describe("production deployment contract", () => {
|
||||
expect(manifests).not.toContain("resources: [\"secrets\"]");
|
||||
expect(manifests).not.toContain("kind: ClusterRole");
|
||||
expect(manifests).not.toContain("kind: ClusterRoleBinding");
|
||||
expect(manifests).toContain("kind: NetworkPolicy");
|
||||
expect(manifests).toContain("key: DATABASE_URL");
|
||||
expect(manifests).not.toContain("secretRef:");
|
||||
});
|
||||
|
||||
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).toContain("FROM ${BUN_IMAGE} AS migration");
|
||||
expect(dockerfile).toContain("FROM ${BUN_IMAGE} AS app");
|
||||
expect(dockerfile).toMatch(/ARG BUN_IMAGE=oven\/bun:1\.3\.14-alpine@sha256:[a-f0-9]{64}/u);
|
||||
expect(dockerfile.match(/^USER 1000:1000$/gmu)).toHaveLength(2);
|
||||
expect(dockerfile).toContain('ENTRYPOINT ["bun", "scripts/migrate.ts"]');
|
||||
expect(dockerfile).toContain('ENTRYPOINT ["bun", "migrate.js"]');
|
||||
expect(dockerfile).toContain('CMD ["bun", "server.js"]');
|
||||
expect(dockerfile).toContain("ARG BASE_URL");
|
||||
expect(dockerfile).toContain("ENV BASE_URL=${BASE_URL}");
|
||||
@@ -111,6 +116,9 @@ describe("production deployment contract", () => {
|
||||
expect(workflow).toContain("kubectl kustomize k8s/");
|
||||
expect(workflow).toContain("needs: verify");
|
||||
expect(workflow).not.toContain("pull_request:");
|
||||
expect(workflow).toContain("security:audit");
|
||||
expect(workflow).toMatch(/gitleaks@sha256:[a-f0-9]{64}/u);
|
||||
expect(workflow).not.toContain("insecure-skip-tls-verify=true");
|
||||
expect(workflow).toContain("--target app");
|
||||
expect(workflow).toContain('--build-arg BASE_URL="$BASE_URL"');
|
||||
expect(workflow).toContain(
|
||||
@@ -123,13 +131,13 @@ describe("production deployment contract", () => {
|
||||
expect(workflow).toContain(
|
||||
"registry.neko-piranha.ts.net/astral/buzz-sheet",
|
||||
);
|
||||
expect(workflow).toContain("migrate-${{ gitea.sha }}");
|
||||
expect(workflow).toContain("migrate-$REVISION");
|
||||
expect(workflow).toContain("deployment/buzz-sheet-discord-worker");
|
||||
const deleteMigrationJob = workflow.indexOf(
|
||||
'delete job buzz-sheet-migrate --ignore-not-found',
|
||||
);
|
||||
const createMigrationJob = workflow.indexOf(
|
||||
'create --validate=false -f "$migration_manifest"',
|
||||
'create -f "$migration_manifest"',
|
||||
);
|
||||
expect(deleteMigrationJob).toBeGreaterThan(-1);
|
||||
expect(deleteMigrationJob).toBeLessThan(createMigrationJob);
|
||||
@@ -176,7 +184,7 @@ describe("environment template contract", () => {
|
||||
for (const key of requiredKeys) {
|
||||
expect(environmentExample).toMatch(new RegExp(`^${key}=`, "mu"));
|
||||
}
|
||||
expect(environmentExample).toContain("BUZZ_DEMO_MODE=false");
|
||||
expect(environmentExample).not.toContain("BUZZ_DEMO_MODE");
|
||||
expect(environmentExample).toContain(
|
||||
"NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID=ca-pub-9687404323559597",
|
||||
);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("server-only", () => ({}));
|
||||
|
||||
import { closeDb, getDb } from "@/db";
|
||||
import { artifactProfiles, catalogCharacters, guideSections, guides, media } from "@/db/schema";
|
||||
import { getPublicGuide, getPublicGuideSummary } from "@/lib/guides/queries";
|
||||
|
||||
const databaseUrl = process.env.DATABASE_INTEGRATION_URL;
|
||||
const describeWithDatabase = databaseUrl ? describe : describe.skip;
|
||||
|
||||
describeWithDatabase("public guide section queries", () => {
|
||||
const id = randomUUID();
|
||||
const key = `test-${id}`;
|
||||
const slug = key;
|
||||
const originalDatabaseUrl = process.env.DATABASE_URL;
|
||||
const originalPublicUrl = process.env.S3_PUBLIC_URL;
|
||||
|
||||
beforeAll(async () => {
|
||||
process.env.DATABASE_URL = databaseUrl;
|
||||
process.env.S3_PUBLIC_URL = "https://media.example.test";
|
||||
const db = getDb();
|
||||
await db.insert(catalogCharacters).values({ key, name: "Test Character", imageKey: "test.png" });
|
||||
await db.insert(media).values({ id, objectKey: `test/${id}.png`, fileName: "test.png", mimeType: "image/png", byteSize: 1, status: "ready" });
|
||||
await db.insert(guides).values({ id, characterKey: key, slug, name: "Test Guide", overview: "Overview", coverMediaId: id, isPublic: true });
|
||||
await db.insert(guideSections).values([
|
||||
{ guideId: id, kind: "weapon", slug: "weapon", title: "Weapon", sortOrder: 1 },
|
||||
{ guideId: id, kind: "artifact", slug: "artifact", title: "Artifact", sortOrder: 2 },
|
||||
]);
|
||||
await db.insert(artifactProfiles).values({ guideId: id, sands: ["ATK%"] });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const db = getDb();
|
||||
await db.delete(guides).where(eq(guides.id, id));
|
||||
await db.delete(media).where(eq(media.id, id));
|
||||
await db.delete(catalogCharacters).where(eq(catalogCharacters.key, key));
|
||||
await closeDb();
|
||||
if (originalDatabaseUrl === undefined) delete process.env.DATABASE_URL;
|
||||
else process.env.DATABASE_URL = originalDatabaseUrl;
|
||||
if (originalPublicUrl === undefined) delete process.env.S3_PUBLIC_URL;
|
||||
else process.env.S3_PUBLIC_URL = originalPublicUrl;
|
||||
});
|
||||
|
||||
it("probes populated sections without including empty navigation entries", async () => {
|
||||
const summary = await getPublicGuideSummary(slug);
|
||||
expect(summary?.sections.map((section) => section.slug)).toEqual(["artifact"]);
|
||||
});
|
||||
|
||||
it("fetches only the selected section's content", async () => {
|
||||
const overview = await getPublicGuide(slug, "overview");
|
||||
expect(overview?.artifactProfile).toBeNull();
|
||||
expect(overview?.availableSections?.map((section) => section.slug)).toEqual(["artifact"]);
|
||||
const artifact = await getPublicGuide(slug, "artifact");
|
||||
expect(artifact?.artifactProfile?.sands).toEqual(["ATK%"]);
|
||||
expect(await getPublicGuide(slug, "weapon")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import Redis from "ioredis";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("server-only", () => ({}));
|
||||
|
||||
import { closeRedisEventStreams, createRedisEventResponse } from "@/lib/events/redis-stream";
|
||||
import { updateVisitorPresence } from "@/lib/presence";
|
||||
import { closeRedisClient, redisEventChannel } from "@/lib/redis/client";
|
||||
import { consumeRateLimit } from "@/lib/security/rate-limit";
|
||||
|
||||
const integrationUrl = process.env.REDIS_INTEGRATION_URL;
|
||||
const describeWithRedis = integrationUrl ? describe : describe.skip;
|
||||
|
||||
describeWithRedis("shared Redis security paths", () => {
|
||||
const runId = randomUUID();
|
||||
const original = {
|
||||
url: process.env.REDIS_URL,
|
||||
event: process.env.REDIS_EVENT_PREFIX,
|
||||
presence: process.env.REDIS_PRESENCE_PREFIX,
|
||||
security: process.env.REDIS_SECURITY_PREFIX,
|
||||
};
|
||||
let publisher: Redis;
|
||||
|
||||
beforeAll(async () => {
|
||||
process.env.REDIS_URL = integrationUrl;
|
||||
process.env.REDIS_EVENT_PREFIX = `buzz:test:${runId}:events`;
|
||||
process.env.REDIS_PRESENCE_PREFIX = `buzz:test:${runId}:presence`;
|
||||
process.env.REDIS_SECURITY_PREFIX = `buzz:test:${runId}:security`;
|
||||
publisher = new Redis(integrationUrl!);
|
||||
await publisher.ping();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
closeRedisEventStreams();
|
||||
await closeRedisClient();
|
||||
if (publisher) {
|
||||
const keys = await publisher.keys(`buzz:test:${runId}:*`);
|
||||
if (keys.length) await publisher.del(...keys);
|
||||
await publisher.quit();
|
||||
}
|
||||
for (const [key, value] of Object.entries(original)) {
|
||||
const envName = { url: "REDIS_URL", event: "REDIS_EVENT_PREFIX", presence: "REDIS_PRESENCE_PREFIX", security: "REDIS_SECURITY_PREFIX" }[key]!;
|
||||
if (value === undefined) delete process.env[envName];
|
||||
else process.env[envName] = value;
|
||||
}
|
||||
});
|
||||
|
||||
it("counts requests atomically across callers", async () => {
|
||||
const results = await Promise.all(Array.from({ length: 8 }, () => consumeRateLimit(`test:${runId}`, { window: 60, max: 5 })));
|
||||
expect(results.filter((result) => result.allowed)).toHaveLength(5);
|
||||
expect(results.filter((result) => !result.allowed)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("tracks visitor counts without a separate Redis round trip per visitor", async () => {
|
||||
const first = await updateVisitorPresence({ tabId: `${runId}:1`, guideId: "guide-a", active: true });
|
||||
expect(first.global).toBe(1);
|
||||
expect(first.guide).toBe(1);
|
||||
await updateVisitorPresence({ tabId: `${runId}:2`, guideId: "guide-a", active: true });
|
||||
const count = await publisher.zcard(`${process.env.REDIS_PRESENCE_PREFIX}:visitors`);
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it("fans one subscribed topic out to two SSE clients", async () => {
|
||||
const topic = `page:${runId}`;
|
||||
const first = new AbortController();
|
||||
const second = new AbortController();
|
||||
const [responseA, responseB] = await Promise.all([
|
||||
createRedisEventResponse(topic, first.signal),
|
||||
createRedisEventResponse(topic, second.signal),
|
||||
]);
|
||||
const readerA = responseA.body!.getReader();
|
||||
const readerB = responseB.body!.getReader();
|
||||
await Promise.all([readerA.read(), readerB.read()]);
|
||||
const payload = JSON.stringify({ type: "page.updated", id: runId, version: 1 });
|
||||
await publisher.publish(redisEventChannel(topic), payload);
|
||||
const [a, b] = await Promise.all([readerA.read(), readerB.read()]);
|
||||
const decoder = new TextDecoder();
|
||||
expect(decoder.decode(a.value)).toContain(payload);
|
||||
expect(decoder.decode(b.value)).toContain(payload);
|
||||
first.abort();
|
||||
second.abort();
|
||||
await Promise.all([readerA.cancel(), readerB.cancel()]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user