Skip to content
Source
Sign inSign up
Open History to see the last commit

Milestone.test.ts

248 lines8.2 KB

1import * as GitHub from "@alchemy.run/github";
2import { expect } from "@alchemy.run/test";
3import * as Test from "@alchemy.run/test/Alchemy";
4import * as Issues from "@distilled.cloud/github/issues";
5import * as Output from "alchemy/Output";
6import * as Provider from "alchemy/Provider";
7import { destroy } from "alchemy/RemovalPolicy";
8import * as Effect from "effect/Effect";
9import * as Stream from "effect/Stream";
10import { scopedToOwner } from "./scope.ts";
11import { gh } from "./scope.ts";
12​
13const owner = process.env.GITHUB_TEST_OWNER ?? "alchemy-run-test";
14if (!["alchemy-run-test", "alchemy-run-test-2"].includes(owner)) {
15 throw new Error("GITHUB_TEST_OWNER must be alchemy-run-test or alchemy-run-test-2");
17​
18const { test } = Test.make({
19 providers: GitHub.providers({ baseUrl: "github.com" }),
20});
21​
22const repositoryName = (fixture: string) => `alchemy-pr-1566-milestone-${fixture}`;
23​
24// Repositories are retained because the test token does not have delete_repo.
25const repository = (name: string) =>
26 GitHub.Repository("Repo", {
27 owner,
28 name: repositoryName(name),
29 visibility: "public",
30 autoInit: true,
31 });
32​
33const fixture = (name: string, props: Omit<GitHub.MilestoneProps, "owner" | "repository">) =>
34 Effect.gen(function* () {
35 const repo = yield* repository(name);
36 return yield* GitHub.Milestone("Milestone", {
37 ...props,
38 owner,
39 repository: Output.map(repo.fullName, (fullName) => fullName.split("/")[1]!),
40 }).pipe(destroy());
41 });
42​
43const listMilestones = (fixture: string) =>
44 Effect.gen(function* () {
45 return yield* gh(
46 Stream.runCollect(
47 Issues.listMilestones.items({
48 owner,
49 repo: repositoryName(fixture),
50 state: "all",
51 per_page: 100,
52 }),
53 ),
54 );
55 });
56​
57const verifyDeleted = (fixture: string) =>
58 Effect.gen(function* () {
59 expect(yield* listMilestones(fixture)).toEqual([]);
60 });
61​
62test.provider(
63 "create and update milestone",
64 (stack) =>
65 Effect.gen(function* () {
66 yield* stack.destroy();
67 const created = yield* stack.deploy(
68 fixture("update", {
69 title: "v1.0.0",
70 description: "First release",
71 }),
72 );
73 expect(created.title).toBe("v1.0.0");
74 expect(created.state).toBe("open");
75 expect(created.description).toBe("First release");
76 const updated = yield* stack.deploy(
77 fixture("update", {
78 title: "v1.0.0",
79 description: "Updated: Bug fixes and improvements",
80 dueOn: "2027-12-31",
81 }),
82 );
83 expect(updated.milestoneNumber).toBe(created.milestoneNumber);
84 expect(updated.description).toBe("Updated: Bug fixes and improvements");
85 expect(updated.dueOn).toBe("2027-12-31T00:00:00Z");
86 const observed = yield* listMilestones("update");
87 expect(observed).toHaveLength(1);
88 expect(observed[0]?.description).toBe(updated.description);
89 expect(observed[0]?.due_on).toBe(updated.dueOn);
90 yield* stack.deploy(repository("update"));
91 yield* verifyDeleted("update");
92 yield* stack.destroy();
93 }),
94 {
95 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
96 timeout: 120_000,
97 },
98);
99​
100test.provider(
101 "close and reopen milestone",
102 (stack) =>
103 Effect.gen(function* () {
104 yield* stack.destroy();
105 const created = yield* stack.deploy(
106 fixture("state", { title: "Release v2.0", state: "open" }),
107 );
108 expect(created.state).toBe("open");
109 expect(created.closedAt).toBe(null);
110 const closed = yield* stack.deploy(
111 fixture("state", { title: "Release v2.0", state: "closed" }),
112 );
113 expect(closed.milestoneNumber).toBe(created.milestoneNumber);
114 expect(closed.state).toBe("closed");
115 expect(closed.closedAt).not.toBe(null);
116 expect((yield* listMilestones("state"))[0]?.state).toBe("closed");
117 const reopened = yield* stack.deploy(
118 fixture("state", { title: "Release v2.0", state: "open" }),
119 );
120 expect(reopened.milestoneNumber).toBe(created.milestoneNumber);
121 expect(reopened.state).toBe("open");
122 expect(reopened.closedAt).toBe(null);
123 expect((yield* listMilestones("state"))[0]?.state).toBe("open");
124 yield* stack.deploy(repository("state"));
125 yield* verifyDeleted("state");
126 yield* stack.destroy();
127 }),
128 {
129 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
130 timeout: 120_000,
131 },
132);
133​
134test.provider(
135 "replace milestone when title changes",
136 (stack) =>
137 Effect.gen(function* () {
138 yield* stack.destroy();
139 const created = yield* stack.deploy(
140 fixture("replace", {
141 title: "Q1 2026",
142 description: "First quarter goals",
143 }),
144 );
145 expect(created.title).toBe("Q1 2026");
146 const replaced = yield* stack.deploy(
147 fixture("replace", {
148 title: "Q1 2027",
149 description: "Updated quarter goals",
150 }),
151 );
152 expect(replaced.title).toBe("Q1 2027");
153 expect(replaced.milestoneNumber).not.toBe(created.milestoneNumber);
154 const observed = yield* listMilestones("replace");
155 expect(observed.map((milestone) => milestone.number)).toEqual([replaced.milestoneNumber]);
156 yield* stack.deploy(repository("replace"));
157 yield* verifyDeleted("replace");
158 yield* stack.destroy();
159 }),
160 {
161 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
162 timeout: 120_000,
163 },
164);
165​
166test.provider(
167 "list enumeration includes deployed milestone",
168 (stack) =>
169 Effect.gen(function* () {
170 yield* stack.destroy();
171 const created = yield* stack.deploy(
172 fixture("list", {
173 title: "PR 1566 list milestone",
174 description: "For list test",
175 }),
176 );
177 const provider = yield* Provider.findProvider(GitHub.Milestone);
178 const allMilestones = yield* provider.list().pipe(scopedToOwner(owner));
179 const found = allMilestones.find((milestone) => milestone.nodeId === created.nodeId);
180 expect(found).toBeDefined();
181 expect(found?.title).toBe(created.title);
182 expect(found?.htmlUrl).toBe(created.htmlUrl);
183 yield* stack.deploy(repository("list"));
184 yield* verifyDeleted("list");
185 yield* stack.destroy();
186 }),
187 {
188 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
189 timeout: 120_000,
190 },
191);
192​
193test.provider(
194 "milestone with due date",
195 (stack) =>
196 Effect.gen(function* () {
197 yield* stack.destroy();
198 const created = yield* stack.deploy(
199 fixture("duedate", {
200 title: "Sprint 1",
201 description: "Complete authentication",
202 dueOn: "2026-12-31",
203 }),
204 );
205 expect(created.title).toBe("Sprint 1");
206 expect(created.dueOn).toBe("2026-12-31T00:00:00Z");
207 expect((yield* listMilestones("duedate"))[0]?.due_on).toBe(created.dueOn);
208 yield* stack.deploy(repository("duedate"));
209 yield* verifyDeleted("duedate");
210 yield* stack.destroy();
211 }),
212 {
213 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
214 timeout: 120_000,
215 },
216);
217​
218test.provider(
219 "removing optional properties restores defaults",
220 (stack) =>
221 Effect.gen(function* () {
222 yield* stack.destroy();
223 const created = yield* stack.deploy(
224 fixture("defaults", {
225 title: "Defaults",
226 state: "closed",
227 description: "Remove me",
228 dueOn: "2027-12-31",
229 }),
230 );
231 const updated = yield* stack.deploy(fixture("defaults", { title: "Defaults" }));
232 expect(updated.milestoneNumber).toBe(created.milestoneNumber);
233 expect(updated.state).toBe("open");
234 expect(updated.description ?? "").toBe("");
235 expect(updated.dueOn).toBe(null);
236 const observed = (yield* listMilestones("defaults"))[0];
237 expect(observed?.state).toBe("open");
238 expect(observed?.description ?? "").toBe("");
239 expect(observed?.due_on).toBe(null);
240 yield* stack.deploy(repository("defaults"));
241 yield* verifyDeleted("defaults");
242 yield* stack.destroy();
243 }),
244 {
245 tags: ["provider:github", "provider:github:milestone", "provider:github:repository", "live"],
246 timeout: 120_000,
247 },
248);