@aaronshaf/plane 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/check-coverage.ts +2 -2
- package/src/api.ts +67 -59
- package/src/app.ts +78 -0
- package/src/bin.ts +6 -71
- package/src/commands/cycles.ts +104 -85
- package/src/commands/init.ts +57 -55
- package/src/commands/intake.ts +82 -65
- package/src/commands/issue.ts +445 -314
- package/src/commands/issues.ts +51 -43
- package/src/commands/labels.ts +52 -43
- package/src/commands/members.ts +25 -19
- package/src/commands/modules.ts +136 -99
- package/src/commands/pages.ts +58 -49
- package/src/commands/projects.ts +28 -22
- package/src/commands/states.ts +31 -25
- package/src/config.ts +152 -154
- package/src/format.ts +15 -8
- package/src/output.ts +28 -28
- package/src/resolve.ts +88 -53
- package/tests/api.test.ts +178 -155
- package/tests/cycles-extended.test.ts +205 -162
- package/tests/format.test.ts +72 -54
- package/tests/helpers/mock-api.ts +16 -14
- package/tests/intake.test.ts +173 -139
- package/tests/issue-activity.test.ts +191 -158
- package/tests/issue-commands.test.ts +749 -304
- package/tests/issue-comments-worklogs.test.ts +337 -265
- package/tests/issue-links.test.ts +229 -193
- package/tests/modules.test.ts +283 -239
- package/tests/new-schemas.test.ts +203 -183
- package/tests/new-schemas2.test.ts +195 -183
- package/tests/output.test.ts +66 -64
- package/tests/pages.test.ts +122 -108
- package/tests/resolve.test.ts +186 -156
- package/tests/schemas.test.ts +215 -177
package/tests/pages.test.ts
CHANGED
|
@@ -1,124 +1,138 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "bun:test";
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
import { http, HttpResponse } from "msw";
|
|
12
|
+
import { setupServer } from "msw/node";
|
|
13
|
+
import { _clearProjectCache } from "@/resolve";
|
|
6
14
|
|
|
7
|
-
const BASE = "http://pages-test.local"
|
|
8
|
-
const WS = "testws"
|
|
15
|
+
const BASE = "http://pages-test.local";
|
|
16
|
+
const WS = "testws";
|
|
9
17
|
|
|
10
|
-
const PROJECTS = [
|
|
18
|
+
const PROJECTS = [
|
|
19
|
+
{ id: "proj-acme", identifier: "ACME", name: "Acme Project" },
|
|
20
|
+
];
|
|
11
21
|
const PAGES = [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]
|
|
22
|
+
{
|
|
23
|
+
id: "pg1",
|
|
24
|
+
name: "Architecture Overview",
|
|
25
|
+
description_html: "<p>Our architecture...</p>",
|
|
26
|
+
created_at: "2025-01-10T10:00:00Z",
|
|
27
|
+
updated_at: "2025-01-15T10:00:00Z",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "pg2",
|
|
31
|
+
name: "Migration Guide",
|
|
32
|
+
description_html: null,
|
|
33
|
+
created_at: "2025-01-05T10:00:00Z",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
26
36
|
|
|
27
37
|
const server = setupServer(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
|
|
39
|
+
HttpResponse.json({ results: PROJECTS }),
|
|
40
|
+
),
|
|
41
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/pages/`, () =>
|
|
42
|
+
HttpResponse.json({ results: PAGES }),
|
|
43
|
+
),
|
|
44
|
+
http.get(
|
|
45
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/pages/pg1/`,
|
|
46
|
+
() => HttpResponse.json(PAGES[0]),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
38
49
|
|
|
39
|
-
beforeAll(() => server.listen({ onUnhandledRequest: "error" }))
|
|
40
|
-
afterAll(() => server.close())
|
|
50
|
+
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
51
|
+
afterAll(() => server.close());
|
|
41
52
|
|
|
42
53
|
beforeEach(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
54
|
+
_clearProjectCache();
|
|
55
|
+
process.env["PLANE_HOST"] = BASE;
|
|
56
|
+
process.env["PLANE_WORKSPACE"] = WS;
|
|
57
|
+
process.env["PLANE_API_TOKEN"] = "test-token";
|
|
58
|
+
});
|
|
48
59
|
|
|
49
60
|
afterEach(() => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
61
|
+
server.resetHandlers();
|
|
62
|
+
delete process.env["PLANE_HOST"];
|
|
63
|
+
delete process.env["PLANE_WORKSPACE"];
|
|
64
|
+
delete process.env["PLANE_API_TOKEN"];
|
|
65
|
+
});
|
|
55
66
|
|
|
56
67
|
describe("pagesList", () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
it("lists pages with updated date and name", async () => {
|
|
69
|
+
const { pagesList } = await import("@/commands/pages");
|
|
70
|
+
const logs: string[] = [];
|
|
71
|
+
const orig = console.log;
|
|
72
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
73
|
+
try {
|
|
74
|
+
await Effect.runPromise((pagesList as any).handler({ project: "ACME" }));
|
|
75
|
+
} finally {
|
|
76
|
+
console.log = orig;
|
|
77
|
+
}
|
|
78
|
+
const output = logs.join("\n");
|
|
79
|
+
expect(output).toContain("pg1");
|
|
80
|
+
expect(output).toContain("2025-01-15");
|
|
81
|
+
expect(output).toContain("Architecture Overview");
|
|
82
|
+
expect(output).toContain("pg2");
|
|
83
|
+
expect(output).toContain("Migration Guide");
|
|
84
|
+
});
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
it("falls back to created_at when no updated_at", async () => {
|
|
87
|
+
const { pagesList } = await import("@/commands/pages");
|
|
88
|
+
const logs: string[] = [];
|
|
89
|
+
const orig = console.log;
|
|
90
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
91
|
+
try {
|
|
92
|
+
await Effect.runPromise((pagesList as any).handler({ project: "ACME" }));
|
|
93
|
+
} finally {
|
|
94
|
+
console.log = orig;
|
|
95
|
+
}
|
|
96
|
+
// pg2 has no updated_at, should use created_at
|
|
97
|
+
expect(logs.join("\n")).toContain("2025-01-05");
|
|
98
|
+
});
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
})
|
|
100
|
+
it("shows 'No pages' when empty", async () => {
|
|
101
|
+
server.use(
|
|
102
|
+
http.get(
|
|
103
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/pages/`,
|
|
104
|
+
() => HttpResponse.json({ results: [] }),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
const { pagesList } = await import("@/commands/pages");
|
|
108
|
+
const logs: string[] = [];
|
|
109
|
+
const orig = console.log;
|
|
110
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
111
|
+
try {
|
|
112
|
+
await Effect.runPromise((pagesList as any).handler({ project: "ACME" }));
|
|
113
|
+
} finally {
|
|
114
|
+
console.log = orig;
|
|
115
|
+
}
|
|
116
|
+
expect(logs.join("\n")).toBe("No pages");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
107
119
|
|
|
108
120
|
describe("pagesGet", () => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
it("prints full JSON for a page", async () => {
|
|
122
|
+
const { pagesGet } = await import("@/commands/pages");
|
|
123
|
+
const logs: string[] = [];
|
|
124
|
+
const orig = console.log;
|
|
125
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
126
|
+
try {
|
|
127
|
+
await Effect.runPromise(
|
|
128
|
+
(pagesGet as any).handler({ project: "ACME", pageId: "pg1" }),
|
|
129
|
+
);
|
|
130
|
+
} finally {
|
|
131
|
+
console.log = orig;
|
|
132
|
+
}
|
|
133
|
+
const parsed = JSON.parse(logs.join("\n"));
|
|
134
|
+
expect(parsed.id).toBe("pg1");
|
|
135
|
+
expect(parsed.name).toBe("Architecture Overview");
|
|
136
|
+
expect(parsed.description_html).toContain("architecture");
|
|
137
|
+
});
|
|
138
|
+
});
|
package/tests/resolve.test.ts
CHANGED
|
@@ -1,178 +1,208 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
import { http, HttpResponse } from "msw"
|
|
4
|
-
import { setupServer } from "msw/node"
|
|
5
1
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "bun:test";
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
import { http, HttpResponse } from "msw";
|
|
12
|
+
import { setupServer } from "msw/node";
|
|
13
|
+
import {
|
|
14
|
+
resolveProject,
|
|
15
|
+
parseIssueRef,
|
|
16
|
+
findIssueBySeq,
|
|
17
|
+
getStateId,
|
|
18
|
+
_clearProjectCache,
|
|
19
|
+
} from "@/resolve";
|
|
12
20
|
|
|
13
|
-
const BASE = "http://test.local"
|
|
14
|
-
const WS = "testws"
|
|
21
|
+
const BASE = "http://test.local";
|
|
22
|
+
const WS = "testws";
|
|
15
23
|
|
|
16
24
|
const PROJECTS = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
25
|
+
{ id: "proj-acme", identifier: "ACME", name: "Acme Project" },
|
|
26
|
+
{ id: "proj-web", identifier: "WEB", name: "Web Project" },
|
|
27
|
+
];
|
|
20
28
|
|
|
21
29
|
const ISSUES = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
{
|
|
31
|
+
id: "i1",
|
|
32
|
+
sequence_id: 29,
|
|
33
|
+
name: "Migrate Button",
|
|
34
|
+
priority: "high",
|
|
35
|
+
state: "s1",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "i2",
|
|
39
|
+
sequence_id: 30,
|
|
40
|
+
name: "Migrate TextInput",
|
|
41
|
+
priority: "medium",
|
|
42
|
+
state: "s2",
|
|
43
|
+
},
|
|
44
|
+
];
|
|
25
45
|
|
|
26
46
|
const STATES = [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
]
|
|
47
|
+
{ id: "s-backlog", name: "Backlog", group: "backlog" },
|
|
48
|
+
{ id: "s-todo", name: "Todo", group: "unstarted" },
|
|
49
|
+
{ id: "s-progress", name: "In Progress", group: "started" },
|
|
50
|
+
{ id: "s-done", name: "Done", group: "completed" },
|
|
51
|
+
];
|
|
32
52
|
|
|
33
53
|
const server = setupServer(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
beforeAll(() => server.listen({ onUnhandledRequest: "error" }))
|
|
46
|
-
afterAll(() => server.close())
|
|
54
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
|
|
55
|
+
HttpResponse.json({ results: PROJECTS }),
|
|
56
|
+
),
|
|
57
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/`, () =>
|
|
58
|
+
HttpResponse.json({ results: ISSUES }),
|
|
59
|
+
),
|
|
60
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/states/`, () =>
|
|
61
|
+
HttpResponse.json({ results: STATES }),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
66
|
+
afterAll(() => server.close());
|
|
47
67
|
|
|
48
68
|
beforeEach(() => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
69
|
+
_clearProjectCache();
|
|
70
|
+
process.env["PLANE_HOST"] = BASE;
|
|
71
|
+
process.env["PLANE_WORKSPACE"] = WS;
|
|
72
|
+
process.env["PLANE_API_TOKEN"] = "test-token";
|
|
73
|
+
});
|
|
54
74
|
|
|
55
75
|
afterEach(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
76
|
+
server.resetHandlers();
|
|
77
|
+
delete process.env["PLANE_HOST"];
|
|
78
|
+
delete process.env["PLANE_WORKSPACE"];
|
|
79
|
+
delete process.env["PLANE_API_TOKEN"];
|
|
80
|
+
});
|
|
61
81
|
|
|
62
82
|
describe("resolveProject", () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
83
|
+
it("resolves a known project identifier", async () => {
|
|
84
|
+
const result = await Effect.runPromise(resolveProject("ACME"));
|
|
85
|
+
expect(result.key).toBe("ACME");
|
|
86
|
+
expect(result.id).toBe("proj-acme");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("is case-insensitive", async () => {
|
|
90
|
+
const result = await Effect.runPromise(resolveProject("acme"));
|
|
91
|
+
expect(result.key).toBe("ACME");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("resolves WEB too", async () => {
|
|
95
|
+
const result = await Effect.runPromise(resolveProject("WEB"));
|
|
96
|
+
expect(result.id).toBe("proj-web");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("fails for unknown project", async () => {
|
|
100
|
+
const result = await Effect.runPromise(
|
|
101
|
+
Effect.either(resolveProject("NOPE")),
|
|
102
|
+
);
|
|
103
|
+
expect(result._tag).toBe("Left");
|
|
104
|
+
if (result._tag === "Left") {
|
|
105
|
+
expect(result.left.message).toContain("Unknown project");
|
|
106
|
+
expect(result.left.message).toContain("NOPE");
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("uses the cache on second call", async () => {
|
|
111
|
+
await Effect.runPromise(resolveProject("ACME"));
|
|
112
|
+
// Override with empty — cache hit should prevent re-fetching
|
|
113
|
+
server.use(
|
|
114
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
|
|
115
|
+
HttpResponse.json({ results: [] }),
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
const result = await Effect.runPromise(resolveProject("WEB"));
|
|
119
|
+
expect(result.id).toBe("proj-web"); // still from cache
|
|
120
|
+
});
|
|
121
|
+
});
|
|
100
122
|
|
|
101
123
|
describe("parseIssueRef", () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
it("parses a valid ref", async () => {
|
|
125
|
+
const result = await Effect.runPromise(parseIssueRef("ACME-29"));
|
|
126
|
+
expect(result.projKey).toBe("ACME");
|
|
127
|
+
expect(result.seq).toBe(29);
|
|
128
|
+
expect(result.projectId).toBe("proj-acme");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("is case-insensitive", async () => {
|
|
132
|
+
const result = await Effect.runPromise(parseIssueRef("acme-29"));
|
|
133
|
+
expect(result.projKey).toBe("ACME");
|
|
134
|
+
expect(result.seq).toBe(29);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("fails on missing dash", async () => {
|
|
138
|
+
const result = await Effect.runPromise(
|
|
139
|
+
Effect.either(parseIssueRef("ACME29")),
|
|
140
|
+
);
|
|
141
|
+
expect(result._tag).toBe("Left");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("fails on non-numeric sequence", async () => {
|
|
145
|
+
const result = await Effect.runPromise(
|
|
146
|
+
Effect.either(parseIssueRef("ACME-abc")),
|
|
147
|
+
);
|
|
148
|
+
expect(result._tag).toBe("Left");
|
|
149
|
+
if (result._tag === "Left") {
|
|
150
|
+
expect(result.left.message).toContain("Invalid issue ref");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("fails on empty string", async () => {
|
|
155
|
+
const result = await Effect.runPromise(Effect.either(parseIssueRef("")));
|
|
156
|
+
expect(result._tag).toBe("Left");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
133
159
|
|
|
134
160
|
describe("findIssueBySeq", () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
161
|
+
it("finds issue by sequence_id", async () => {
|
|
162
|
+
const issue = await Effect.runPromise(findIssueBySeq("proj-acme", 29));
|
|
163
|
+
expect(issue.id).toBe("i1");
|
|
164
|
+
expect(issue.name).toBe("Migrate Button");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("finds second issue", async () => {
|
|
168
|
+
const issue = await Effect.runPromise(findIssueBySeq("proj-acme", 30));
|
|
169
|
+
expect(issue.sequence_id).toBe(30);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("fails when issue not found", async () => {
|
|
173
|
+
const result = await Effect.runPromise(
|
|
174
|
+
Effect.either(findIssueBySeq("proj-acme", 999)),
|
|
175
|
+
);
|
|
176
|
+
expect(result._tag).toBe("Left");
|
|
177
|
+
if (result._tag === "Left") {
|
|
178
|
+
expect(result.left.message).toContain("#999 not found");
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
154
182
|
|
|
155
183
|
describe("getStateId", () => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
184
|
+
it("finds state by group name", async () => {
|
|
185
|
+
const id = await Effect.runPromise(getStateId("proj-acme", "completed"));
|
|
186
|
+
expect(id).toBe("s-done");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("finds state by exact name (case-insensitive)", async () => {
|
|
190
|
+
const id = await Effect.runPromise(getStateId("proj-acme", "in progress"));
|
|
191
|
+
expect(id).toBe("s-progress");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("finds backlog state", async () => {
|
|
195
|
+
const id = await Effect.runPromise(getStateId("proj-acme", "backlog"));
|
|
196
|
+
expect(id).toBe("s-backlog");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("fails for unknown state", async () => {
|
|
200
|
+
const result = await Effect.runPromise(
|
|
201
|
+
Effect.either(getStateId("proj-acme", "nope")),
|
|
202
|
+
);
|
|
203
|
+
expect(result._tag).toBe("Left");
|
|
204
|
+
if (result._tag === "Left") {
|
|
205
|
+
expect(result.left.message).toContain("State not found");
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|