feat(gatekeeper): add PR merge automation service

This commit is contained in:
2026-03-10 21:35:11 -05:00
parent 3289677056
commit 6e2b9a307e
27 changed files with 1089 additions and 64 deletions
@@ -4,6 +4,7 @@ import { ConfigService } from "@nestjs/config";
import { Test, type TestingModule } from "@nestjs/testing";
import type { Request } from "express";
import { describe, beforeEach, expect, it, vi } from "vitest";
import { GatekeeperService } from "../gatekeeper/gatekeeper.service";
import { QueueNotificationsService } from "./queue-notifications.service";
import {
type WoodpeckerWebhookPayload,
@@ -20,6 +21,9 @@ describe("WoodpeckerWebhookController", () => {
const mockService = {
notifyAgentCiResult: vi.fn(),
};
const mockGatekeeperService = {
handleCiEvent: vi.fn(),
};
const mockConfigService = {
get: vi.fn(),
@@ -39,6 +43,7 @@ describe("WoodpeckerWebhookController", () => {
controllers: [WoodpeckerWebhookController],
providers: [
{ provide: QueueNotificationsService, useValue: mockService },
{ provide: GatekeeperService, useValue: mockGatekeeperService },
{ provide: ConfigService, useValue: mockConfigService },
],
}).compile();
@@ -52,6 +57,8 @@ describe("WoodpeckerWebhookController", () => {
status: "success",
buildUrl: "https://ci.example/build/123",
repo: "mosaic/stack",
prNumber: 42,
headSha: "abcdef1234567890",
};
const signature = signPayload(payload, "test-secret");
mockService.notifyAgentCiResult.mockResolvedValue({ notified: 2 });
@@ -65,6 +72,12 @@ describe("WoodpeckerWebhookController", () => {
).resolves.toEqual({ ok: true, notified: 2 });
expect(mockService.notifyAgentCiResult).toHaveBeenCalledWith(payload);
expect(mockGatekeeperService.handleCiEvent).toHaveBeenCalledWith(
"mosaic/stack",
42,
"abcdef1234567890",
"success"
);
});
it("returns ok without notifying when the signature is invalid", async () => {
@@ -129,4 +142,22 @@ describe("WoodpeckerWebhookController", () => {
)
).resolves.toEqual({ ok: true, notified: 0 });
});
it("does not call Gatekeeper when the PR metadata is missing", async () => {
const payload: WoodpeckerWebhookPayload = {
branch: "feat/ms24-ci-webhook",
status: "success",
buildUrl: "https://ci.example/build/555",
repo: "mosaic/stack",
};
mockService.notifyAgentCiResult.mockResolvedValue({ notified: 1 });
await controller.handleWebhook(
{ rawBody: Buffer.from(JSON.stringify(payload)) } as Request,
payload,
signPayload(payload, "test-secret")
);
expect(mockGatekeeperService.handleCiEvent).not.toHaveBeenCalled();
});
});