Public: anyone can read this; the crew can change it.

A test pins issue dependencies: a pull can neither wait nor be waited on, and another repository's issue is refused #7136

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7133-a-test-pins-issue-dependencies-a-pull-ca → main 1 commit fork 2 threads · 2 unresolved

  • Merged. scout/7133-a-test-pins-issue-dependencies-a-pull-ca was merged into main as a2cda2e.

Review

Sign in to review this change.
1 file changed +124 −0
test/dependency-refusals.test.ts added +124
@@ -0,0 +1,124 @@
1 /**
2 * What a plan refuses: a pull neither waits nor is waited on, and an issue
3 * of another repository is no part of this one's plan. After each refusal
4 * the map has no new edge.
5 */
6 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7 import { mkdtemp, rm } from "node:fs/promises";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { createApp, type App } from "../src/app.ts";
11 import { openIssue } from "../src/actions/issues.ts";
12 import { createRepo } from "../src/actions/repos.ts";
13 import { addDependency } from "../src/core/dependencies.ts";
14 import { createIssue, type Issue } from "../src/core/issues.ts";
15 import type { Repo } from "../src/core/repos.ts";
16 import { createToken } from "../src/core/tokens.ts";
17 import { createUser } from "../src/core/users.ts";
18 import { serve, type Servers } from "../src/http/server.ts";
19
20 describe("dependency refusals", () => {
21 let dataDir: string;
22 let app: App;
23 let servers: Servers;
24 let token: string;
25 let repo: Repo;
26 let issue: Issue;
27 let pull: Issue;
28 let foreign: Issue;
29 let foreignRepo: Repo;
30
31 const headers = () => ({
32 authorization: `Bearer ${token}`,
33 "content-type": "application/json",
34 });
35 const link = (n: number, blockedBy: number) =>
36 fetch(`${servers.url}/api/v1/repos/alice/engine/issues/${n}/dependencies`, {
37 method: "POST",
38 headers: headers(),
39 body: JSON.stringify({ blockedBy }),
40 });
41 const edges = async () =>
42 (
43 (await (
44 await fetch(`${servers.url}/api/v1/repos/alice/engine/map`, {
45 headers: headers(),
46 })
47 ).json()) as { edges: unknown[] }
48 ).edges;
49
50 beforeAll(async () => {
51 dataDir = await mkdtemp(join(tmpdir(), "forge-deprefuse-"));
52 app = createApp({ dataDir, port: 0 });
53 servers = await serve(app);
54 const made = await createUser(app.db, {
55 origin: "seed",
56 trusted: true,
57 username: "alice",
58 email: "[email protected]",
59 password: "a-good-passphrase",
60 adminIfFirst: true,
61 });
62 if (!made.ok) throw new Error(made.error);
63 const alice = made.value;
64 token = createToken(app.db, alice.id, "test", "rw").token;
65 const r = await createRepo(app, alice, { name: "engine", visibility: "private" });
66 const o = await createRepo(app, alice, { name: "other", visibility: "private" });
67 if (!r.ok || !o.ok) throw new Error("repo");
68 repo = r.value;
69 foreignRepo = o.value;
70 const i = openIssue(app, alice, repo, { title: "An issue" });
71 if (!i.ok) throw new Error(String(i.error));
72 issue = i.value;
73 const p = createIssue(app.db, {
74 repoId: repo.id,
75 authorId: alice.id,
76 title: "A pull",
77 kind: "pull",
78 });
79 if (!p.ok) throw new Error(p.error);
80 pull = p.value;
81 // Several issues in the other repository, so its numbers outrun this one's.
82 let last: Issue | undefined;
83 for (const title of ["a", "b", "c"]) {
84 const f = openIssue(app, alice, foreignRepo, { title });
85 if (!f.ok) throw new Error(String(f.error));
86 last = f.value;
87 }
88 foreign = last!;
89 });
90
91 afterAll(async () => {
92 await app.close?.();
93 await rm(dataDir, { recursive: true, force: true });
94 });
95
96 test("an issue waiting on a pull is not_an_issue", async () => {
97 const res = await link(issue.number, pull.number);
98 expect(res.status).toBe(422);
99 expect(((await res.json()) as { error: string }).error).toBe("not_an_issue");
100 expect(await edges()).toEqual([]);

This 404 comes from routing, not from the dependency rules. Add a note, or an issue, so it is tightened when the route serves pulls.

101 });
102
103 test("a pull waiting on an issue is not_an_issue", async () => {
104 // The issues route does not serve a pull as the one that waits: 404.
105 const res = await link(pull.number, issue.number);
106 expect(res.status).toBe(404);
107 // The core names the refusal.
108 const core = addDependency(app.db, pull, issue, undefined);
109 expect(core.ok).toBe(false);
110 if (!core.ok) expect(core.error).toBe("not_an_issue");
111 expect(await edges()).toEqual([]);
112 });
113
114 test("another repository's issue is refused, in the core as cross_repo", async () => {
115 const core = addDependency(app.db, issue, foreign, undefined);

Assert foreign.number > pull.number before the call so a 422 here can only mean an unknown number in this repo.

116 expect(core.ok).toBe(false);
117 if (!core.ok) expect(core.error).toBe("cross_repo");
118 // The API resolves the blocker inside the repository, so its number is
119 // unknown here and the answer is 422 either way.
120 const res = await link(issue.number, foreign.number);
121 expect(res.status).toBe(422);
122 expect(await edges()).toEqual([]);
123 });
124 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs