1import * as Output from "@/Output.ts";
2import * as Provider from "@/Provider.ts";
3import { GitHubCredentials } from "@/GitHub/Credentials.ts";
4import { Octokit } from "@/GitHub/Octokit.ts";
5import * as GitHub from "@/GitHub/index.ts";
6import * as Test from "@/Test/Alchemy.ts";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9
10const { test } = Test.make({
11 providers: GitHub.providers({ baseUrl: "github.com" }),
12});
13
14const owner = process.env.GITHUB_TEST_OWNER ?? "alchemy-run-test";
15if (!["alchemy-run-test", "alchemy-run-test-2"].includes(owner)) {
16 throw new Error(
17 "GITHUB_TEST_OWNER must be alchemy-run-test or alchemy-run-test-2",
18 );
19}
20
21const repoNameOf = (repo: GitHub.Repository) =>
22 Output.map(repo.fullName, (name) => name.split("/")[1]!);
23
24test.provider(
25 "create and update label",
26 (stack) =>
27 Effect.gen(function* () {
28 yield* stack.destroy();
29 const testId = "label-test";
30 const repoName = `test-label-${testId}`;
31
32 // Create a test repository with label
33 const deploy1 = () =>
34 stack.deploy(
35 Effect.gen(function* () {
36 const repo = yield* GitHub.Repository(testId, {
37 owner,
38 name: repoName,
39 autoInit: true,
40 visibility: "public",
41 });
42
43 const label = yield* GitHub.Label("bug", {
44 owner,
45 repository: repoNameOf(repo),
46 name: "bug",
47 color: "d73a4a",
48 description: "Something isn't working",
49 });
50
51 return { repo, label };
52 }),
53 );
54
55 const result1 = yield* deploy1();
56 expect(result1.label.name).toBe("bug");
57 expect(result1.label.color).toBe("d73a4a");
58 expect(result1.label.description).toBe("Something isn't working");
59
60 // Update the label
61 const deploy2 = () =>
62 stack.deploy(
63 Effect.gen(function* () {
64 const repo = yield* GitHub.Repository(testId, {
65 owner,
66 name: repoName,
67 autoInit: true,
68 visibility: "public",
69 });
70
71 const label = yield* GitHub.Label("bug", {
72 owner,
73 repository: repoNameOf(repo),
74 name: "bug",
75 color: "ff0000",
76 description: "Updated: Critical bug",
77 });
78
79 return { repo, label };
80 }),
81 );
82
83 const result2 = yield* deploy2();
84 expect(result2.label.name).toBe("bug");
85 expect(result2.label.color).toBe("ff0000");
86 expect(result2.label.description).toBe("Updated: Critical bug");
87 expect(result2.label.labelId).toBe(result1.label.labelId);
88
89 const client = yield* Octokit;
90 const observed = yield* Effect.tryPromise(() =>
91 client.rest.issues.getLabel({ owner, repo: repoName, name: "bug" }),
92 );
93 expect(observed.data.id).toBe(result1.label.labelId);
94 expect(observed.data.color).toBe("ff0000");
95 expect(observed.data.description).toBe("Updated: Critical bug");
96
97 yield* stack.destroy();
98 const remaining = yield* Effect.tryPromise(() =>
99 client.paginate(client.rest.issues.listLabelsForRepo, {
100 owner,
101 repo: repoName,
102 }),
103 );
104 expect(remaining.some((label) => label.name === "bug")).toBe(false);
105 }),
106 {
107 tags: [
108 "provider:github",
109 "provider:github:label",
110 "provider:github:repository",
111 "live",
112 ],
113 timeout: 120_000,
114 },
115);
116
117test.provider(
118 "create multiple labels",
119 (stack) =>
120 Effect.gen(function* () {
121 yield* stack.destroy();
122 const testId = "label-multi";
123 const repoName = `test-label-${testId}`;
124
125 const deploy = () =>
126 stack.deploy(
127 Effect.gen(function* () {
128 const repo = yield* GitHub.Repository(testId, {
129 owner,
130 name: repoName,
131 autoInit: true,
132 visibility: "public",
133 });
134
135 const labels = {
136 bug: yield* GitHub.Label("bug", {
137 owner,
138 repository: repoNameOf(repo),
139 name: "bug",
140 color: "d73a4a",
141 description: "Something isn't working",
142 }),
143 feature: yield* GitHub.Label("feature", {
144 owner,
145 repository: repoNameOf(repo),
146 name: "feature",
147 color: "a2eeef",
148 description: "New feature or request",
149 }),
150 documentation: yield* GitHub.Label("docs", {
151 owner,
152 repository: repoNameOf(repo),
153 name: "documentation",
154 color: "0075ca",
155 description: "Improvements to documentation",
156 }),
157 };
158
159 return { repo, labels };
160 }),
161 );
162
163 const result = yield* deploy();
164 expect(result.labels.bug.name).toBe("bug");
165 expect(result.labels.feature.name).toBe("feature");
166 expect(result.labels.documentation.name).toBe("documentation");
167
168 yield* stack.destroy();
169 }),
170 {
171 tags: [
172 "provider:github",
173 "provider:github:label",
174 "provider:github:repository",
175 "live",
176 ],
177 timeout: 120_000,
178 },
179);
180
181test.provider(
182 "replace label when name changes",
183 (stack) =>
184 Effect.gen(function* () {
185 yield* stack.destroy();
186 const testId = "label-replace";
187 const repoName = `test-label-${testId}`;
188
189 // Create label with original name
190 const deploy1 = () =>
191 stack.deploy(
192 Effect.gen(function* () {
193 const repo = yield* GitHub.Repository(testId, {
194 owner,
195 name: repoName,
196 autoInit: true,
197 visibility: "public",
198 });
199
200 const label = yield* GitHub.Label("work", {
201 owner,
202 repository: repoNameOf(repo),
203 name: "wip",
204 color: "fbca04",
205 });
206
207 return { repo, label };
208 }),
209 );
210
211 const result1 = yield* deploy1();
212 expect(result1.label.name).toBe("wip");
213 const originalId = result1.label.labelId;
214
215 // Change name (should replace)
216 const deploy2 = () =>
217 stack.deploy(
218 Effect.gen(function* () {
219 const repo = yield* GitHub.Repository(testId, {
220 owner,
221 name: repoName,
222 autoInit: true,
223 visibility: "public",
224 });
225
226 const label = yield* GitHub.Label("work", {
227 owner,
228 repository: repoNameOf(repo),
229 name: "in-progress",
230 color: "fbca04",
231 });
232
233 return { repo, label };
234 }),
235 );
236
237 const result2 = yield* deploy2();
238 expect(result2.label.name).toBe("in-progress");
239 expect(result2.label.labelId).not.toBe(originalId);
240
241 yield* stack.destroy();
242 }),
243 {
244 tags: [
245 "provider:github",
246 "provider:github:label",
247 "provider:github:repository",
248 "live",
249 ],
250 timeout: 120_000,
251 },
252);
253
254test.provider(
255 "list enumeration includes deployed label",
256 (stack) =>
257 Effect.gen(function* () {
258 yield* stack.destroy();
259 const testId = "label-list";
260 const repoName = `test-label-${testId}`;
261
262 const deploy = () =>
263 stack.deploy(
264 Effect.gen(function* () {
265 const repo = yield* GitHub.Repository(testId, {
266 owner,
267 name: repoName,
268 autoInit: true,
269 visibility: "public",
270 });
271
272 const label = yield* GitHub.Label("test", {
273 owner,
274 repository: repoNameOf(repo),
275 name: `test-${testId}`,
276 color: "ededed",
277 description: "For list test",
278 });
279
280 return { repo, label };
281 }),
282 );
283
284 const result = yield* deploy();
285
286 // List all labels and verify ours is included
287 const credentials = yield* yield* GitHubCredentials;
288 const client = credentials.octokit();
289 client.hook.before("request", (options) => {
290 const url = new URL(options.url, "https://api.github.com");
291 if (url.pathname === "/user/repos") {
292 url.pathname = `/orgs/${owner}/repos`;
293 options.url = url.toString();
294 }
295 });
296 const provider = yield* Provider.findProvider(GitHub.Label);
297 const allLabels = yield* provider
298 .list()
299 .pipe(
300 Effect.provideService(
301 GitHubCredentials,
302 Effect.succeed({ ...credentials, octokit: () => client }),
303 ),
304 );
305 const found = allLabels.find((l) => l.labelId === result.label.labelId);
306
307 expect(found).toBeDefined();
308 expect(found?.name).toBe(result.label.name);
309
310 yield* stack.destroy();
311 }),
312 {
313 tags: [
314 "provider:github",
315 "provider:github:label",
316 "provider:github:repository",
317 "live",
318 ],
319 timeout: 120_000,
320 },
321);
322
323test.provider(
324 "label with default color",
325 (stack) =>
326 Effect.gen(function* () {
327 yield* stack.destroy();
328 const testId = "label-default";
329 const repoName = `test-label-${testId}`;
330
331 const deploy = () =>
332 stack.deploy(
333 Effect.gen(function* () {
334 const repo = yield* GitHub.Repository(testId, {
335 owner,
336 name: repoName,
337 autoInit: true,
338 visibility: "public",
339 });
340
341 const label = yield* GitHub.Label("custom", {
342 owner,
343 repository: repoNameOf(repo),
344 name: "custom-label",
345 // color omitted, should use default
346 description: "Uses default color",
347 });
348
349 return { repo, label };
350 }),
351 );
352
353 const result = yield* deploy();
354 expect(result.label.name).toBe("custom-label");
355 expect(result.label.color).toBeDefined();
356
357 yield* stack.destroy();
358 }),
359 {
360 tags: [
361 "provider:github",
362 "provider:github:label",
363 "provider:github:repository",
364 "live",
365 ],
366 timeout: 120_000,
367 },
368);