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

A test pins importing an origin's issue: one row per origin id, the local sequence pushed past its number #6664

Merged opened this

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

Merged

scout/forge:scout/6654-a-test-pins-importing-an-origin-s-issue → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6654-a-test-pins-importing-an-origin-s-issue was merged into main as 127c4b2.

Review

Sign in to review this change.
1 file changed +130 −0
test/origins-import-issue.test.ts added +130
@@ -0,0 +1,130 @@
1 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2 import { mkdtemp, rm } from "node:fs/promises";
3 import { tmpdir } from "node:os";
4 import { join } from "node:path";
5 import { createApp, type App } from "../src/app.ts";
6 import { createIssue } from "../src/core/issues.ts";
7 import { createRepo } from "../src/core/repos.ts";
8 import { importIssue, type ImportContext } from "../src/origins/import.ts";
9 import type { RemoteIssue } from "../src/origins/types.ts";
10 import { seedAdmin } from "./helpers/admin.ts";
11
12 /** `importIssue` on its own: one row per origin id, numbers kept, sequence dragged up. */
13
14 const remote = (over: Partial<RemoteIssue> = {}): RemoteIssue => ({
15 remoteId: "r-40",
16 number: 40,
17 title: "Remote title",
18 body: "body",
19 author: "octo",
20 state: "open",
21 labels: [],
22 createdAt: "2026-01-01T00:00:00.000Z",
23 updatedAt: "2026-01-02T00:00:00.000Z",
24 closedAt: null,
25 htmlUrl: "https://origin.test/o/r/issues/40",
26 comments: 0,
27 ...over,
28 });
29
30 describe("importIssue", () => {
31 let dir: string;
32 let app: App;
33 let ctx: ImportContext;
34 let authorId: string;
35
36 beforeAll(async () => {
37 dir = await mkdtemp(join(tmpdir(), "forge-import-issue-"));
38 app = createApp({ dataDir: dir, port: 0 });
39 const user = await seedAdmin(app, {
40 username: "alice",
41 email: "[email protected]",
42 password: "alice-login-secret",
43 });
44 if (!user.ok) throw new Error(`no user: ${user.error}`);
45 authorId = user.value.id;
46 const repo = await createRepo(app.db, join(dir, "repos"), {
47 ownerId: authorId,
48 name: "mirror",
49 });
50 if (!repo.ok) throw new Error(`no repo: ${repo.error}`);
51 ctx = { db: app.db, repoId: repo.value.id, actorId: authorId };
52 });
53
54 afterAll(async () => {
55 app.close();
56 await rm(dir, { recursive: true, force: true });
57 });
58
59 const rows = (remoteId: string) =>
60 app.db
61 .query<
62 { id: string; title: string; number: number; kind: string; author_id: string },
63 [string, string]
64 >("SELECT id, title, number, kind, author_id FROM issue WHERE repo_id = ?1 AND origin_remote_id = ?2")
65 .all(ctx.repoId, remoteId);
66
67 test("the same remote issue twice is one row: created, then not; a new title updates it", () => {
68 const first = importIssue(ctx, remote());
69 expect(first.created).toBe(true);
70 expect(first.number).toBe(40);
71 expect(first.kind).toBe("issue");
72
73 const again = importIssue(ctx, remote());
74 expect(again.created).toBe(false);
75 expect(again.issueId).toBe(first.issueId);
76 expect(rows("r-40")).toHaveLength(1);
77
78 const renamed = importIssue(
79 ctx,
80 remote({ title: "Renamed upstream", updatedAt: "2026-01-03T00:00:00.000Z" }),
81 );
82 expect(renamed.created).toBe(false);
83 expect(renamed.changed).toBe(true);
84 const [row] = rows("r-40");

This test depends on the previous test having imported #40. Import inside the test and assert toBe(41) so it fails if the sequence is only nudged to 40 or is not moved at all.

85 expect(row?.title).toBe("Renamed upstream");
86 expect(row?.author_id).toBe(authorId);
87 });
88
89 test("after importing #40 the next native issue is numbered above 40", () => {
90 const native = createIssue(app.db, {
91 repoId: ctx.repoId,
92 authorId,
93 title: "native",
94 });
95 if (!native.ok) throw new Error(native.error);
96 expect(native.value.number).toBeGreaterThan(40);
97 });
98
99 test("a blank remote title reads #<number>", () => {
100 importIssue(ctx, remote({ remoteId: "r-41", number: 41, title: " \n " }));
101 expect(rows("r-41")[0]?.title).toBe("#41");
102 });
103
104 test("a remote with pull set imports as a pull", () => {
105 const got = importIssue(
106 ctx,
107 remote({
108 remoteId: "r-60",
109 number: 60,
110 pull: {
111 baseBranch: "main",
112 headBranch: "feature",
113 headRepo: "o/r",
114 baseOid: "a".repeat(40),
115 headOid: "b".repeat(40),
116 mergedAt: null,
117 draft: false,
118 },
119 }),
120 );
121 expect(got.kind).toBe("pull");
122 expect(rows("r-60")[0]?.kind).toBe("pull");
123 const pull = app.db
124 .query<{ head_branch: string }, [string]>(
125 "SELECT head_branch FROM pull WHERE issue_id = ?1",
126 )
127 .get(got.issueId);
128 expect(pull?.head_branch).toBe("feature");
129 });
130 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs