Skip to content
File

Blob: tests/worker/webhooks/public/gitlab.test.ts

typescript210 lines
1import { describe, expect, it } from "vitest";
2 
3import { readProjectDoRows } from "../../../helpers/runtime";
4import { registerWorkerRuntimeHooks } from "../../../helpers/worker-hooks";
5 
6import {
7 buildGitLabProjectPayload,
8 buildGitLabRepositoryPayload,
9 createOwnedProjectContext,
10 postPublicWebhook,
11 putWebhook,
12} from "../helpers";
13import { AFTER_SHA, BEFORE_SHA } from "../public-helpers";
14 
15describe("webhook public routes", () => {
16 registerWorkerRuntimeHooks();
17 
18 describe("GitLab deliveries", () => {
19 it("accepts verified GitLab pushes with default gitlab.com config", async () => {
20 const context = await createOwnedProjectContext({
21 user: {
22 email: "webhook-public-gitlab@example.com",
23 slug: "webhook-public-gitlab",
24 },
25 project: {
26 projectSlug: "gitlab-public-project",
27 repoUrl: "https://gitlab.com/example/gitlab-public-project",
28 },
29 });
30 
31 await putWebhook(context.sessionId, context.project.id, "gitlab", {
32 enabled: true,
33 config: null,
34 secret: "gitlab-secret",
35 });
36 
37 const body = JSON.stringify({
38 object_kind: "push",
39 event_name: "push",
40 ref: "refs/heads/main",
41 before: BEFORE_SHA,
42 after: AFTER_SHA,
43 checkout_sha: AFTER_SHA,
44 project: buildGitLabProjectPayload(context.project.repoUrl, context.project.defaultBranch),
45 repository: buildGitLabRepositoryPayload(context.project.repoUrl),
46 });
47 
48 const accepted = await postPublicWebhook("gitlab", context.project, body, {
49 "content-type": "application/json; charset=utf-8",
50 "x-gitlab-event": "Push Hook",
51 "x-gitlab-token": "gitlab-secret",
52 "x-gitlab-instance": "https://gitlab.com",
53 "idempotency-key": "gitlab-delivery-accepted",
54 });
55 expect(accepted.status).toBe(202);
56 
57 const rows = await readProjectDoRows(context.project.id);
58 expect(rows.runs).toHaveLength(1);
59 expect(rows.runs[0]?.provider).toBe("gitlab");
60 expect(rows.runs[0]?.deliveryId).toBe("gitlab-delivery-accepted");
61 expect(rows.runs[0]?.branch).toBe("main");
62 expect(rows.runs[0]?.commitSha).toBe(AFTER_SHA);
63 expect(rows.webhookDeliveries).toHaveLength(1);
64 expect(rows.webhookDeliveries[0]?.outcome).toBe("accepted");
65 });
66 
67 it("accepts verified GitLab pushes for self-hosted instances with fallback delivery id headers", async () => {
68 const context = await createOwnedProjectContext({
69 user: {
70 email: "webhook-public-gitlab-self-hosted@example.com",
71 slug: "webhook-public-gitlab-self-hosted",
72 },
73 project: {
74 projectSlug: "gitlab-self-hosted-project",
75 repoUrl: "https://gitlab.example.com/example/gitlab-self-hosted-project",
76 },
77 });
78 
79 await putWebhook(context.sessionId, context.project.id, "gitlab", {
80 enabled: true,
81 config: {
82 instanceUrl: "https://gitlab.example.com",
83 },
84 secret: "gitlab-self-hosted-secret",
85 });
86 
87 const body = JSON.stringify({
88 object_kind: "push",
89 event_name: "push",
90 ref: "refs/heads/main",
91 before: BEFORE_SHA,
92 after: AFTER_SHA,
93 checkout_sha: AFTER_SHA,
94 project: buildGitLabProjectPayload(context.project.repoUrl, context.project.defaultBranch),
95 repository: buildGitLabRepositoryPayload(context.project.repoUrl),
96 });
97 
98 const accepted = await postPublicWebhook("gitlab", context.project, body, {
99 "content-type": "application/json; charset=utf-8",
100 "x-gitlab-event": "Push Hook",
101 "x-gitlab-token": "gitlab-self-hosted-secret",
102 "x-gitlab-event-uuid": "gitlab-self-hosted-delivery",
103 });
104 expect(accepted.status).toBe(202);
105 
106 const rows = await readProjectDoRows(context.project.id);
107 expect(rows.runs).toHaveLength(1);
108 expect(rows.runs[0]?.provider).toBe("gitlab");
109 expect(rows.runs[0]?.deliveryId).toBe("gitlab-self-hosted-delivery");
110 expect(rows.runs[0]?.branch).toBe("main");
111 expect(rows.runs[0]?.commitSha).toBe(AFTER_SHA);
112 expect(rows.webhookDeliveries).toHaveLength(1);
113 expect(rows.webhookDeliveries[0]?.outcome).toBe("accepted");
114 expect(rows.webhookDeliveries[0]?.repoUrl).toBe(context.project.repoUrl);
115 });
116 
117 it("records GitLab system hooks as ignored_event without creating a run", async () => {
118 const context = await createOwnedProjectContext({
119 user: {
120 email: "webhook-public-gitlab-system-hook@example.com",
121 slug: "webhook-public-gitlab-system-hook",
122 },
123 project: {
124 projectSlug: "gitlab-system-hook-project",
125 repoUrl: "https://gitlab.com/example/gitlab-system-hook-project",
126 },
127 });
128 
129 await putWebhook(context.sessionId, context.project.id, "gitlab", {
130 enabled: true,
131 config: null,
132 secret: "gitlab-system-hook-secret",
133 });
134 
135 const body = JSON.stringify({
136 object_kind: "system_hook",
137 event_name: "project_create",
138 project: buildGitLabProjectPayload(context.project.repoUrl, context.project.defaultBranch),
139 repository: buildGitLabRepositoryPayload(context.project.repoUrl),
140 });
141 
142 const response = await postPublicWebhook("gitlab", context.project, body, {
143 "content-type": "application/json; charset=utf-8",
144 "x-gitlab-event": "System Hook",
145 "x-gitlab-token": "gitlab-system-hook-secret",
146 "idempotency-key": "gitlab-system-hook-delivery",
147 });
148 expect(response.status).toBe(200);
149 
150 const rows = await readProjectDoRows(context.project.id);
151 expect(rows.runs).toHaveLength(0);
152 expect(rows.webhookDeliveries).toHaveLength(1);
153 expect(rows.webhookDeliveries[0]?.eventName).toBe("System Hook");
154 expect(rows.webhookDeliveries[0]?.outcome).toBe("ignored_event");
155 });
156 
157 it("records GitLab non-push events as ignored_event", async () => {
158 const context = await createOwnedProjectContext({
159 user: {
160 email: "webhook-public-gitlab-events@example.com",
161 slug: "webhook-public-gitlab-events",
162 },
163 project: {
164 projectSlug: "gitlab-events-project",
165 repoUrl: "https://gitlab.com/example/gitlab-events-project",
166 },
167 });
168 
169 await putWebhook(context.sessionId, context.project.id, "gitlab", {
170 enabled: true,
171 secret: "gitlab-events-secret",
172 });
173 
174 const mergeRequestBody = JSON.stringify({
175 object_kind: "merge_request",
176 event_name: "merge_request",
177 project: buildGitLabProjectPayload(context.project.repoUrl),
178 repository: buildGitLabRepositoryPayload(context.project.repoUrl),
179 });
180 const mergeRequestResponse = await postPublicWebhook("gitlab", context.project, mergeRequestBody, {
181 "content-type": "application/json; charset=utf-8",
182 "x-gitlab-event": "Merge Request Hook",
183 "x-gitlab-token": "gitlab-events-secret",
184 "idempotency-key": "gitlab-merge-request-delivery",
185 });
186 expect(mergeRequestResponse.status).toBe(200);
187 
188 const tagPushBody = JSON.stringify({
189 object_kind: "tag_push",
190 event_name: "tag_push",
191 ref: "refs/tags/v1.0.0",
192 project: buildGitLabProjectPayload(context.project.repoUrl),
193 repository: buildGitLabRepositoryPayload(context.project.repoUrl),
194 });
195 const tagPushResponse = await postPublicWebhook("gitlab", context.project, tagPushBody, {
196 "content-type": "application/json; charset=utf-8",
197 "x-gitlab-event": "Tag Push Hook",
198 "x-gitlab-token": "gitlab-events-secret",
199 "idempotency-key": "gitlab-tag-push-delivery",
200 });
201 expect(tagPushResponse.status).toBe(200);
202 
203 const rows = await readProjectDoRows(context.project.id);
204 expect(rows.runs).toHaveLength(0);
205 expect(rows.webhookDeliveries).toHaveLength(2);
206 expect(rows.webhookDeliveries.every((row) => row.outcome === "ignored_event")).toBe(true);
207 });
208 });
209});