feat : notify new update income

This commit is contained in:
2026-09-26 23:34:42 +07:00 Unverified
parent e99a5b76b1
commit 4ef1968b04
14 changed files with 499 additions and 33 deletions
+20 -1
View File
@@ -156,7 +156,25 @@ describe("production deployment contract", () => {
expect(notifier).toContain('new EventSource("/api/active")');
expect(notifier).toContain("window.location.reload()");
expect(notifier).toContain('label: "รีโหลด"');
expect(route).toContain('channel.sendNamed("deployment", deploymentId)');
expect(notifier).toContain('source.addEventListener("deployment-status"');
expect(route).toContain('"deployment-status"');
});
it("announces deployment lifecycle results without blocking releases", async () => {
const [workflow, deployment] = await Promise.all([
repositoryFile(".gitea/workflows/ci.yml"),
repositoryFile("k8s/base/deployment.yaml"),
]);
expect(workflow).toContain("Announce incoming deployment");
expect(workflow).toContain("Publish deployment result");
expect(workflow).toContain("if: always()");
expect(workflow).toContain("continue-on-error: true");
expect(workflow).toContain('status\\":\\"deploying');
expect(workflow).toContain("DEPLOYMENT_JOB_STATUS");
expect(workflow).toContain("/api/deployments/status");
expect(deployment).toContain("DEPLOYMENT_WEBHOOK_SECRET");
expect(deployment).toContain("optional: true");
});
});
@@ -174,6 +192,7 @@ describe("environment template contract", () => {
"S3_SECRET_ACCESS_KEY",
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY",
"NEXT_DEPLOYMENT_ID",
"DEPLOYMENT_WEBHOOK_SECRET",
"DISCORD_BOT_TOKEN",
"DISCORD_CHANNEL_ID",
"DISCORD_LOG_CHANNEL_ID",
+14
View File
@@ -6,6 +6,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
vi.mock("server-only", () => ({}));
const { closeRedisEventStreams, createRedisEventResponse } = await import("@/lib/events/redis-stream");
const { getLatestDeploymentStatus, publishDeploymentStatus } = await import("@/lib/deployment/repository");
const { updateVisitorPresence } = await import("@/lib/presence");
const { closeRedisClient, redisEventChannel } = await import("@/lib/redis/client");
const { consumeRateLimit } = await import("@/lib/security/rate-limit");
@@ -102,4 +103,17 @@ describeWithRedis("shared Redis security paths", () => {
second.abort();
await Promise.all([readerA.cancel(), readerB.cancel()]);
});
it("does not let stale deployment results replace a newer announcement", async () => {
const firstId = "a".repeat(40);
const secondId = "b".repeat(40);
expect((await publishDeploymentStatus(firstId, "deploying")).accepted).toBe(true);
expect((await publishDeploymentStatus(secondId, "deploying")).accepted).toBe(true);
expect((await publishDeploymentStatus(firstId, "ready")).accepted).toBe(false);
expect(await getLatestDeploymentStatus()).toMatchObject({
deploymentId: secondId,
status: "deploying",
});
expect((await publishDeploymentStatus(secondId, "failed")).accepted).toBe(true);
});
});