@esaio/esa-mcp-server 0.1.0
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/.claude/settings.local.json +23 -0
- package/.dockerignore +36 -0
- package/.envrc +2 -0
- package/.github/dependabot.yml +18 -0
- package/.github/workflows/docker-publish.yml +120 -0
- package/.github/workflows/main.yml +41 -0
- package/.node-version +1 -0
- package/CLAUDE.md +94 -0
- package/Dockerfile +34 -0
- package/LICENSE +7 -0
- package/README.en.md +139 -0
- package/README.md +139 -0
- package/bin/index.js +30 -0
- package/biome.json +57 -0
- package/package.json +48 -0
- package/src/__tests__/fixtures/mock-comment.ts +90 -0
- package/src/__tests__/fixtures/mock-post.ts +79 -0
- package/src/__tests__/index.test.ts +209 -0
- package/src/api_client/__tests__/index.test.ts +149 -0
- package/src/api_client/__tests__/middleware.test.ts +119 -0
- package/src/api_client/__tests__/with-context.test.ts +98 -0
- package/src/api_client/index.ts +29 -0
- package/src/api_client/middleware.ts +21 -0
- package/src/api_client/with-context.ts +26 -0
- package/src/config/__tests__/index.test.ts +65 -0
- package/src/config/index.ts +20 -0
- package/src/context/mcp-context.ts +1 -0
- package/src/context/stdio-context.ts +6 -0
- package/src/errors/missing-team-name-error.ts +8 -0
- package/src/formatters/__tests__/mcp-response.test.ts +106 -0
- package/src/formatters/mcp-response.ts +95 -0
- package/src/generated/api-types.ts +2691 -0
- package/src/i18n/__tests__/index.test.ts +53 -0
- package/src/i18n/index.ts +39 -0
- package/src/index.ts +47 -0
- package/src/locales/en.json +13 -0
- package/src/locales/ja.json +13 -0
- package/src/prompts/__tests__/index.test.ts +47 -0
- package/src/prompts/__tests__/summarize-post.test.ts +291 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/summarize-post.ts +94 -0
- package/src/resources/__tests__/index.test.ts +49 -0
- package/src/resources/__tests__/recent-posts-list.test.ts +91 -0
- package/src/resources/__tests__/recent-posts.test.ts +270 -0
- package/src/resources/index.ts +33 -0
- package/src/resources/recent-posts-list.ts +22 -0
- package/src/resources/recent-posts.ts +45 -0
- package/src/schemas/team-name-schema.ts +19 -0
- package/src/tools/__tests__/categories.test.ts +226 -0
- package/src/tools/__tests__/comments.test.ts +970 -0
- package/src/tools/__tests__/helps.test.ts +222 -0
- package/src/tools/__tests__/index.test.ts +47 -0
- package/src/tools/__tests__/post-actions.test.ts +445 -0
- package/src/tools/__tests__/posts.test.ts +917 -0
- package/src/tools/__tests__/search.test.ts +339 -0
- package/src/tools/__tests__/teams.test.ts +615 -0
- package/src/tools/categories.ts +93 -0
- package/src/tools/comments.ts +258 -0
- package/src/tools/helps.ts +50 -0
- package/src/tools/index.ts +324 -0
- package/src/tools/post-actions.ts +132 -0
- package/src/tools/posts.ts +179 -0
- package/src/tools/search.ts +98 -0
- package/src/tools/teams.ts +157 -0
- package/src/transformers/__tests__/category-transformer.test.ts +161 -0
- package/src/transformers/__tests__/comment-transformer.test.ts +129 -0
- package/src/transformers/__tests__/post-name-normalizer.test.ts +53 -0
- package/src/transformers/__tests__/post-transformer.test.ts +70 -0
- package/src/transformers/__tests__/query-normalizer.test.ts +98 -0
- package/src/transformers/__tests__/team-name-normalizer.test.ts +21 -0
- package/src/transformers/category-transformer.ts +36 -0
- package/src/transformers/comment-transformer.ts +34 -0
- package/src/transformers/post-name-normalizer.ts +30 -0
- package/src/transformers/post-transformer.ts +38 -0
- package/src/transformers/query-normalizer.ts +36 -0
- package/src/transformers/team-name-normalizer.ts +7 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +30 -0
- package/tsdown.config.ts +13 -0
- package/vitest.config.ts +24 -0
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createExpectedTransformed,
|
|
4
|
+
createMockPost,
|
|
5
|
+
createNullBodyPost,
|
|
6
|
+
createWipPost,
|
|
7
|
+
} from "../../__tests__/fixtures/mock-post.js";
|
|
8
|
+
import type { createEsaClient } from "../../api_client/index.js";
|
|
9
|
+
import { createPost, getPost, updatePost } from "../posts.js";
|
|
10
|
+
|
|
11
|
+
describe("getPost", () => {
|
|
12
|
+
const mockClient = {
|
|
13
|
+
GET: vi.fn(),
|
|
14
|
+
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
15
|
+
GET: ReturnType<typeof vi.fn>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should return a post successfully", async () => {
|
|
23
|
+
const mockPost = createMockPost();
|
|
24
|
+
|
|
25
|
+
mockClient.GET.mockResolvedValue({
|
|
26
|
+
data: mockPost,
|
|
27
|
+
error: undefined,
|
|
28
|
+
response: {
|
|
29
|
+
ok: true,
|
|
30
|
+
status: 200,
|
|
31
|
+
} as Response,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = await getPost(mockClient, {
|
|
35
|
+
teamName: "test-team",
|
|
36
|
+
postNumber: 123,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
40
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
41
|
+
{
|
|
42
|
+
params: {
|
|
43
|
+
path: { team_name: "test-team", post_number: 123 },
|
|
44
|
+
query: { include: undefined },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const expectedResponse = createExpectedTransformed(mockPost);
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify(expectedResponse, null, 2),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should return a post with comments when include parameter is specified", async () => {
|
|
62
|
+
const mockPost = createWipPost({
|
|
63
|
+
body_md: "Post content",
|
|
64
|
+
body_html: "<p>Post content</p>",
|
|
65
|
+
comments_count: 2,
|
|
66
|
+
stargazers_count: 3,
|
|
67
|
+
watchers_count: 5,
|
|
68
|
+
comments: [
|
|
69
|
+
{
|
|
70
|
+
id: 1,
|
|
71
|
+
post_number: 123,
|
|
72
|
+
body_md: "First comment",
|
|
73
|
+
body_html: "<p>First comment</p>",
|
|
74
|
+
created_at: "2024-01-03T10:00:00+09:00",
|
|
75
|
+
updated_at: "2024-01-03T10:00:00+09:00",
|
|
76
|
+
created_by: {
|
|
77
|
+
name: "commenter1",
|
|
78
|
+
screen_name: "commenter1",
|
|
79
|
+
icon: "https://example.com/icon4.png",
|
|
80
|
+
myself: false,
|
|
81
|
+
},
|
|
82
|
+
url: "",
|
|
83
|
+
stargazers_count: 0,
|
|
84
|
+
star: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 2,
|
|
88
|
+
post_number: 123,
|
|
89
|
+
body_md: "Second comment",
|
|
90
|
+
body_html: "<p>Second comment</p>",
|
|
91
|
+
created_at: "2024-01-03T11:00:00+09:00",
|
|
92
|
+
updated_at: "2024-01-03T11:00:00+09:00",
|
|
93
|
+
created_by: {
|
|
94
|
+
name: "commenter2",
|
|
95
|
+
screen_name: "commenter2",
|
|
96
|
+
icon: "https://example.com/icon5.png",
|
|
97
|
+
myself: false,
|
|
98
|
+
},
|
|
99
|
+
url: "",
|
|
100
|
+
stargazers_count: 0,
|
|
101
|
+
star: false,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
mockClient.GET.mockResolvedValue({
|
|
107
|
+
data: mockPost,
|
|
108
|
+
error: undefined,
|
|
109
|
+
response: {
|
|
110
|
+
ok: true,
|
|
111
|
+
status: 200,
|
|
112
|
+
} as Response,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await getPost(mockClient, {
|
|
116
|
+
teamName: "test-team",
|
|
117
|
+
postNumber: 456,
|
|
118
|
+
include: "comments",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(mockClient.GET).toHaveBeenCalledWith(
|
|
122
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
123
|
+
{
|
|
124
|
+
params: {
|
|
125
|
+
path: { team_name: "test-team", post_number: 456 },
|
|
126
|
+
query: { include: "comments" },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
132
|
+
expect(parsedResult.wip).toBe("WIP");
|
|
133
|
+
expect(parsedResult.kind).toBe("flow");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should handle API errors", async () => {
|
|
137
|
+
const mockError = { error: "not_found", message: "Post not found" };
|
|
138
|
+
|
|
139
|
+
mockClient.GET.mockResolvedValue({
|
|
140
|
+
data: undefined,
|
|
141
|
+
error: mockError,
|
|
142
|
+
response: {
|
|
143
|
+
ok: false,
|
|
144
|
+
status: 404,
|
|
145
|
+
} as Response,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = await getPost(mockClient, {
|
|
149
|
+
teamName: "test-team",
|
|
150
|
+
postNumber: 999,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual({
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: `Error: ${JSON.stringify(mockError, null, 2)}`,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should handle network errors", async () => {
|
|
164
|
+
const networkError = new Error("Network connection failed");
|
|
165
|
+
|
|
166
|
+
mockClient.GET.mockRejectedValue(networkError);
|
|
167
|
+
|
|
168
|
+
const result = await getPost(mockClient, {
|
|
169
|
+
teamName: "test-team",
|
|
170
|
+
postNumber: 123,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result).toEqual({
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: "Error: Network connection failed",
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle non-Error exceptions", async () => {
|
|
184
|
+
mockClient.GET.mockRejectedValue("Unexpected error");
|
|
185
|
+
|
|
186
|
+
const result = await getPost(mockClient, {
|
|
187
|
+
teamName: "test-team",
|
|
188
|
+
postNumber: 123,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(result).toEqual({
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: "Error: Unexpected error",
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should handle post with undefined body_md", async () => {
|
|
202
|
+
const mockPost = createNullBodyPost({
|
|
203
|
+
number: 789,
|
|
204
|
+
name: "empty-post.md",
|
|
205
|
+
full_name: "dev/empty-post.md",
|
|
206
|
+
url: "https://test-team.esa.example.com/posts/789",
|
|
207
|
+
message: "Empty post",
|
|
208
|
+
created_at: "2024-01-05T00:00:00+09:00",
|
|
209
|
+
updated_at: "2024-01-05T00:00:00+09:00",
|
|
210
|
+
comments_count: 0,
|
|
211
|
+
tasks_count: 0,
|
|
212
|
+
done_tasks_count: 0,
|
|
213
|
+
stargazers_count: 0,
|
|
214
|
+
watchers_count: 0,
|
|
215
|
+
star: false,
|
|
216
|
+
watch: false,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
mockClient.GET.mockResolvedValue({
|
|
220
|
+
data: mockPost,
|
|
221
|
+
error: undefined,
|
|
222
|
+
response: {
|
|
223
|
+
ok: true,
|
|
224
|
+
status: 200,
|
|
225
|
+
} as Response,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const result = await getPost(mockClient, {
|
|
229
|
+
teamName: "test-team",
|
|
230
|
+
postNumber: 789,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
234
|
+
expect(parsedResult.body_md).toBe(undefined);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should throw MissingTeamNameError when teamName is empty", async () => {
|
|
238
|
+
const result = await getPost(mockClient, {
|
|
239
|
+
teamName: "",
|
|
240
|
+
postNumber: 123,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(result).toEqual({
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: "Error: Missing required parameter 'teamName'. Use esa_get_teams to list available teams, then retry with teamName specified.",
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(mockClient.GET).not.toHaveBeenCalled();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("createPost", () => {
|
|
257
|
+
const mockClient = {
|
|
258
|
+
POST: vi.fn(),
|
|
259
|
+
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
260
|
+
POST: ReturnType<typeof vi.fn>;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
vi.clearAllMocks();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should create a post successfully", async () => {
|
|
268
|
+
const mockPost = createMockPost();
|
|
269
|
+
|
|
270
|
+
mockClient.POST.mockResolvedValue({
|
|
271
|
+
data: mockPost,
|
|
272
|
+
error: undefined,
|
|
273
|
+
response: {
|
|
274
|
+
ok: true,
|
|
275
|
+
status: 201,
|
|
276
|
+
} as Response,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const result = await createPost(mockClient, {
|
|
280
|
+
teamName: "test-team",
|
|
281
|
+
name: "Test Post",
|
|
282
|
+
bodyMd: "# Test Content\n\nThis is a test post.",
|
|
283
|
+
tags: ["test", "sample"],
|
|
284
|
+
category: "dev/docs",
|
|
285
|
+
wip: false,
|
|
286
|
+
message: "Initial creation",
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
290
|
+
"/v1/teams/{team_name}/posts",
|
|
291
|
+
{
|
|
292
|
+
params: {
|
|
293
|
+
path: { team_name: "test-team" },
|
|
294
|
+
},
|
|
295
|
+
body: {
|
|
296
|
+
post: {
|
|
297
|
+
name: "Test Post",
|
|
298
|
+
body_md: "# Test Content\n\nThis is a test post.",
|
|
299
|
+
tags: ["test", "sample"],
|
|
300
|
+
category: "dev/docs",
|
|
301
|
+
wip: false,
|
|
302
|
+
message: "Initial creation",
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const expectedResponse = createExpectedTransformed(mockPost);
|
|
309
|
+
|
|
310
|
+
expect(result).toEqual({
|
|
311
|
+
content: [
|
|
312
|
+
{
|
|
313
|
+
type: "text",
|
|
314
|
+
text: JSON.stringify(expectedResponse, null, 2),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should split name into category and name when name contains slash", async () => {
|
|
321
|
+
const mockPost = createMockPost();
|
|
322
|
+
|
|
323
|
+
mockClient.POST.mockResolvedValue({
|
|
324
|
+
data: mockPost,
|
|
325
|
+
error: undefined,
|
|
326
|
+
response: {
|
|
327
|
+
ok: true,
|
|
328
|
+
status: 201,
|
|
329
|
+
} as Response,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await createPost(mockClient, {
|
|
333
|
+
teamName: "test-team",
|
|
334
|
+
name: "docs/api/v2/authentication.md",
|
|
335
|
+
bodyMd: "# Authentication Guide",
|
|
336
|
+
wip: false,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
340
|
+
"/v1/teams/{team_name}/posts",
|
|
341
|
+
{
|
|
342
|
+
params: {
|
|
343
|
+
path: { team_name: "test-team" },
|
|
344
|
+
},
|
|
345
|
+
body: {
|
|
346
|
+
post: {
|
|
347
|
+
name: "authentication.md",
|
|
348
|
+
body_md: "# Authentication Guide",
|
|
349
|
+
tags: undefined,
|
|
350
|
+
category: "docs/api/v2",
|
|
351
|
+
wip: false,
|
|
352
|
+
message: undefined,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should not split name when category is already specified", async () => {
|
|
360
|
+
const mockPost = createMockPost();
|
|
361
|
+
|
|
362
|
+
mockClient.POST.mockResolvedValue({
|
|
363
|
+
data: mockPost,
|
|
364
|
+
error: undefined,
|
|
365
|
+
response: {
|
|
366
|
+
ok: true,
|
|
367
|
+
status: 201,
|
|
368
|
+
} as Response,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await createPost(mockClient, {
|
|
372
|
+
teamName: "test-team",
|
|
373
|
+
name: "File/Path/Name.md",
|
|
374
|
+
category: "specified/category",
|
|
375
|
+
wip: false,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
379
|
+
"/v1/teams/{team_name}/posts",
|
|
380
|
+
{
|
|
381
|
+
params: {
|
|
382
|
+
path: { team_name: "test-team" },
|
|
383
|
+
},
|
|
384
|
+
body: {
|
|
385
|
+
post: {
|
|
386
|
+
name: "File/Path/Name.md",
|
|
387
|
+
body_md: undefined,
|
|
388
|
+
tags: undefined,
|
|
389
|
+
category: "specified/category",
|
|
390
|
+
wip: false,
|
|
391
|
+
message: undefined,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should create a WIP post with minimal parameters", async () => {
|
|
399
|
+
const mockPost = createWipPost();
|
|
400
|
+
|
|
401
|
+
mockClient.POST.mockResolvedValue({
|
|
402
|
+
data: mockPost,
|
|
403
|
+
error: undefined,
|
|
404
|
+
response: {
|
|
405
|
+
ok: true,
|
|
406
|
+
status: 201,
|
|
407
|
+
} as Response,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const result = await createPost(mockClient, {
|
|
411
|
+
teamName: "test-team",
|
|
412
|
+
name: "WIP Post",
|
|
413
|
+
wip: true,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
417
|
+
"/v1/teams/{team_name}/posts",
|
|
418
|
+
{
|
|
419
|
+
params: {
|
|
420
|
+
path: { team_name: "test-team" },
|
|
421
|
+
},
|
|
422
|
+
body: {
|
|
423
|
+
post: {
|
|
424
|
+
name: "WIP Post",
|
|
425
|
+
body_md: undefined,
|
|
426
|
+
tags: undefined,
|
|
427
|
+
category: undefined,
|
|
428
|
+
wip: true,
|
|
429
|
+
message: undefined,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
436
|
+
expect(parsedResult.wip).toBe("WIP");
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should handle API errors when creating a post", async () => {
|
|
440
|
+
const mockError = { error: "bad_request", message: "Invalid post data" };
|
|
441
|
+
|
|
442
|
+
mockClient.POST.mockResolvedValue({
|
|
443
|
+
data: undefined,
|
|
444
|
+
error: mockError,
|
|
445
|
+
response: {
|
|
446
|
+
ok: false,
|
|
447
|
+
status: 400,
|
|
448
|
+
} as Response,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const result = await createPost(mockClient, {
|
|
452
|
+
teamName: "test-team",
|
|
453
|
+
name: "",
|
|
454
|
+
wip: true,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
expect(result).toEqual({
|
|
458
|
+
content: [
|
|
459
|
+
{
|
|
460
|
+
type: "text",
|
|
461
|
+
text: `Error: ${JSON.stringify(mockError, null, 2)}`,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should handle network errors when creating a post", async () => {
|
|
468
|
+
const networkError = new Error("Network connection failed");
|
|
469
|
+
|
|
470
|
+
mockClient.POST.mockRejectedValue(networkError);
|
|
471
|
+
|
|
472
|
+
const result = await createPost(mockClient, {
|
|
473
|
+
teamName: "test-team",
|
|
474
|
+
name: "Test Post",
|
|
475
|
+
wip: true,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(result).toEqual({
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: "Error: Network connection failed",
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("should handle non-Error exceptions when creating a post", async () => {
|
|
489
|
+
mockClient.POST.mockRejectedValue("Unexpected error");
|
|
490
|
+
|
|
491
|
+
const result = await createPost(mockClient, {
|
|
492
|
+
teamName: "test-team",
|
|
493
|
+
name: "Test Post",
|
|
494
|
+
wip: true,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(result).toEqual({
|
|
498
|
+
content: [
|
|
499
|
+
{
|
|
500
|
+
type: "text",
|
|
501
|
+
text: "Error: Unexpected error",
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("should create a post without body", async () => {
|
|
508
|
+
const mockPost = createNullBodyPost({
|
|
509
|
+
number: 999,
|
|
510
|
+
name: "empty-body.md",
|
|
511
|
+
full_name: "dev/empty-body.md",
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
mockClient.POST.mockResolvedValue({
|
|
515
|
+
data: mockPost,
|
|
516
|
+
error: undefined,
|
|
517
|
+
response: {
|
|
518
|
+
ok: true,
|
|
519
|
+
status: 201,
|
|
520
|
+
} as Response,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const result = await createPost(mockClient, {
|
|
524
|
+
teamName: "test-team",
|
|
525
|
+
name: "Empty Body Post",
|
|
526
|
+
category: "dev",
|
|
527
|
+
wip: false,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
expect(mockClient.POST).toHaveBeenCalledWith(
|
|
531
|
+
"/v1/teams/{team_name}/posts",
|
|
532
|
+
{
|
|
533
|
+
params: {
|
|
534
|
+
path: { team_name: "test-team" },
|
|
535
|
+
},
|
|
536
|
+
body: {
|
|
537
|
+
post: {
|
|
538
|
+
name: "Empty Body Post",
|
|
539
|
+
body_md: undefined,
|
|
540
|
+
tags: undefined,
|
|
541
|
+
category: "dev",
|
|
542
|
+
wip: false,
|
|
543
|
+
message: undefined,
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
550
|
+
expect(parsedResult.body_md).toBe(undefined);
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe("updatePost", () => {
|
|
555
|
+
const mockClient = {
|
|
556
|
+
PATCH: vi.fn(),
|
|
557
|
+
} as unknown as ReturnType<typeof createEsaClient> & {
|
|
558
|
+
PATCH: ReturnType<typeof vi.fn>;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
beforeEach(() => {
|
|
562
|
+
vi.clearAllMocks();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("should update a post successfully", async () => {
|
|
566
|
+
const mockPost = createMockPost();
|
|
567
|
+
|
|
568
|
+
mockClient.PATCH.mockResolvedValue({
|
|
569
|
+
data: mockPost,
|
|
570
|
+
error: undefined,
|
|
571
|
+
response: {
|
|
572
|
+
ok: true,
|
|
573
|
+
status: 200,
|
|
574
|
+
} as Response,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const result = await updatePost(mockClient, {
|
|
578
|
+
teamName: "test-team",
|
|
579
|
+
postNumber: 123,
|
|
580
|
+
name: "Updated Post Title",
|
|
581
|
+
bodyMd: "# Updated Content\n\nThis is updated content.",
|
|
582
|
+
tags: ["updated", "test"],
|
|
583
|
+
category: "dev/updated",
|
|
584
|
+
wip: false,
|
|
585
|
+
message: "Updated post content",
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
589
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
590
|
+
{
|
|
591
|
+
params: {
|
|
592
|
+
path: { team_name: "test-team", post_number: 123 },
|
|
593
|
+
},
|
|
594
|
+
body: {
|
|
595
|
+
post: {
|
|
596
|
+
name: "Updated Post Title",
|
|
597
|
+
body_md: "# Updated Content\n\nThis is updated content.",
|
|
598
|
+
tags: ["updated", "test"],
|
|
599
|
+
category: "dev/updated",
|
|
600
|
+
wip: false,
|
|
601
|
+
message: "Updated post content",
|
|
602
|
+
original_revision: undefined,
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
const expectedResponse = createExpectedTransformed(mockPost);
|
|
609
|
+
|
|
610
|
+
expect(result).toEqual({
|
|
611
|
+
content: [
|
|
612
|
+
{
|
|
613
|
+
type: "text",
|
|
614
|
+
text: JSON.stringify(expectedResponse, null, 2),
|
|
615
|
+
},
|
|
616
|
+
],
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it("should split name into category and name when name contains slash", async () => {
|
|
621
|
+
const mockPost = createMockPost();
|
|
622
|
+
|
|
623
|
+
mockClient.PATCH.mockResolvedValue({
|
|
624
|
+
data: mockPost,
|
|
625
|
+
error: undefined,
|
|
626
|
+
response: {
|
|
627
|
+
ok: true,
|
|
628
|
+
status: 200,
|
|
629
|
+
} as Response,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
await updatePost(mockClient, {
|
|
633
|
+
teamName: "test-team",
|
|
634
|
+
postNumber: 123,
|
|
635
|
+
name: "dev/guidelines/coding-standards.md",
|
|
636
|
+
message: "Updated title structure",
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
640
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
641
|
+
{
|
|
642
|
+
params: {
|
|
643
|
+
path: { team_name: "test-team", post_number: 123 },
|
|
644
|
+
},
|
|
645
|
+
body: {
|
|
646
|
+
post: {
|
|
647
|
+
name: "coding-standards.md",
|
|
648
|
+
body_md: undefined,
|
|
649
|
+
tags: undefined,
|
|
650
|
+
category: "dev/guidelines",
|
|
651
|
+
wip: undefined,
|
|
652
|
+
message: "Updated title structure",
|
|
653
|
+
original_revision: undefined,
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("should not split name when category is already specified", async () => {
|
|
661
|
+
const mockPost = createMockPost();
|
|
662
|
+
|
|
663
|
+
mockClient.PATCH.mockResolvedValue({
|
|
664
|
+
data: mockPost,
|
|
665
|
+
error: undefined,
|
|
666
|
+
response: {
|
|
667
|
+
ok: true,
|
|
668
|
+
status: 200,
|
|
669
|
+
} as Response,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
await updatePost(mockClient, {
|
|
673
|
+
teamName: "test-team",
|
|
674
|
+
postNumber: 123,
|
|
675
|
+
name: "File/Path/Name.md",
|
|
676
|
+
category: "specified/category",
|
|
677
|
+
message: "Update message",
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
681
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
682
|
+
{
|
|
683
|
+
params: {
|
|
684
|
+
path: { team_name: "test-team", post_number: 123 },
|
|
685
|
+
},
|
|
686
|
+
body: {
|
|
687
|
+
post: {
|
|
688
|
+
name: "File/Path/Name.md",
|
|
689
|
+
body_md: undefined,
|
|
690
|
+
tags: undefined,
|
|
691
|
+
category: "specified/category",
|
|
692
|
+
wip: undefined,
|
|
693
|
+
message: "Update message",
|
|
694
|
+
original_revision: undefined,
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it("should update a post with original revision for conflict check", async () => {
|
|
702
|
+
const mockPost = createMockPost();
|
|
703
|
+
|
|
704
|
+
mockClient.PATCH.mockResolvedValue({
|
|
705
|
+
data: mockPost,
|
|
706
|
+
error: undefined,
|
|
707
|
+
response: {
|
|
708
|
+
ok: true,
|
|
709
|
+
status: 200,
|
|
710
|
+
} as Response,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const result = await updatePost(mockClient, {
|
|
714
|
+
teamName: "test-team",
|
|
715
|
+
postNumber: 123,
|
|
716
|
+
name: "Updated Title",
|
|
717
|
+
originalRevision: {
|
|
718
|
+
bodyMd: "Original content",
|
|
719
|
+
number: 5,
|
|
720
|
+
user: "test-user",
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
725
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
726
|
+
{
|
|
727
|
+
params: {
|
|
728
|
+
path: { team_name: "test-team", post_number: 123 },
|
|
729
|
+
},
|
|
730
|
+
body: {
|
|
731
|
+
post: {
|
|
732
|
+
name: "Updated Title",
|
|
733
|
+
body_md: undefined,
|
|
734
|
+
tags: undefined,
|
|
735
|
+
category: undefined,
|
|
736
|
+
wip: undefined,
|
|
737
|
+
message: undefined,
|
|
738
|
+
original_revision: {
|
|
739
|
+
body_md: "Original content",
|
|
740
|
+
number: 5,
|
|
741
|
+
user: "test-user",
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const expectedResponse = createExpectedTransformed(mockPost);
|
|
749
|
+
|
|
750
|
+
expect(result).toEqual({
|
|
751
|
+
content: [
|
|
752
|
+
{
|
|
753
|
+
type: "text",
|
|
754
|
+
text: JSON.stringify(expectedResponse, null, 2),
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it("should update only WIP status", async () => {
|
|
761
|
+
const mockPost = createWipPost();
|
|
762
|
+
|
|
763
|
+
mockClient.PATCH.mockResolvedValue({
|
|
764
|
+
data: mockPost,
|
|
765
|
+
error: undefined,
|
|
766
|
+
response: {
|
|
767
|
+
ok: true,
|
|
768
|
+
status: 200,
|
|
769
|
+
} as Response,
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const result = await updatePost(mockClient, {
|
|
773
|
+
teamName: "test-team",
|
|
774
|
+
postNumber: 456,
|
|
775
|
+
wip: true,
|
|
776
|
+
message: "Marking as WIP",
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
780
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
781
|
+
{
|
|
782
|
+
params: {
|
|
783
|
+
path: { team_name: "test-team", post_number: 456 },
|
|
784
|
+
},
|
|
785
|
+
body: {
|
|
786
|
+
post: {
|
|
787
|
+
name: undefined,
|
|
788
|
+
body_md: undefined,
|
|
789
|
+
tags: undefined,
|
|
790
|
+
category: undefined,
|
|
791
|
+
wip: true,
|
|
792
|
+
message: "Marking as WIP",
|
|
793
|
+
original_revision: undefined,
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
},
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
800
|
+
expect(parsedResult.wip).toBe("WIP");
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("should handle API errors when updating a post", async () => {
|
|
804
|
+
const mockError = { error: "conflict", message: "Post has been modified" };
|
|
805
|
+
|
|
806
|
+
mockClient.PATCH.mockResolvedValue({
|
|
807
|
+
data: undefined,
|
|
808
|
+
error: mockError,
|
|
809
|
+
response: {
|
|
810
|
+
ok: false,
|
|
811
|
+
status: 409,
|
|
812
|
+
} as Response,
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
const result = await updatePost(mockClient, {
|
|
816
|
+
teamName: "test-team",
|
|
817
|
+
postNumber: 123,
|
|
818
|
+
name: "Updated Title",
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
expect(result).toEqual({
|
|
822
|
+
content: [
|
|
823
|
+
{
|
|
824
|
+
type: "text",
|
|
825
|
+
text: `Error: ${JSON.stringify(mockError, null, 2)}`,
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it("should handle network errors when updating a post", async () => {
|
|
832
|
+
const networkError = new Error("Network connection failed");
|
|
833
|
+
|
|
834
|
+
mockClient.PATCH.mockRejectedValue(networkError);
|
|
835
|
+
|
|
836
|
+
const result = await updatePost(mockClient, {
|
|
837
|
+
teamName: "test-team",
|
|
838
|
+
postNumber: 123,
|
|
839
|
+
bodyMd: "Updated content",
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
expect(result).toEqual({
|
|
843
|
+
content: [
|
|
844
|
+
{
|
|
845
|
+
type: "text",
|
|
846
|
+
text: "Error: Network connection failed",
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it("should handle non-Error exceptions when updating a post", async () => {
|
|
853
|
+
mockClient.PATCH.mockRejectedValue("Unexpected error");
|
|
854
|
+
|
|
855
|
+
const result = await updatePost(mockClient, {
|
|
856
|
+
teamName: "test-team",
|
|
857
|
+
postNumber: 123,
|
|
858
|
+
tags: ["new-tag"],
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
expect(result).toEqual({
|
|
862
|
+
content: [
|
|
863
|
+
{
|
|
864
|
+
type: "text",
|
|
865
|
+
text: "Error: Unexpected error",
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it("should update a post with empty body", async () => {
|
|
872
|
+
const mockPost = createNullBodyPost({
|
|
873
|
+
number: 789,
|
|
874
|
+
name: "updated-empty.md",
|
|
875
|
+
full_name: "dev/updated-empty.md",
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
mockClient.PATCH.mockResolvedValue({
|
|
879
|
+
data: mockPost,
|
|
880
|
+
error: undefined,
|
|
881
|
+
response: {
|
|
882
|
+
ok: true,
|
|
883
|
+
status: 200,
|
|
884
|
+
} as Response,
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
const result = await updatePost(mockClient, {
|
|
888
|
+
teamName: "test-team",
|
|
889
|
+
postNumber: 789,
|
|
890
|
+
bodyMd: "",
|
|
891
|
+
category: "dev",
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
expect(mockClient.PATCH).toHaveBeenCalledWith(
|
|
895
|
+
"/v1/teams/{team_name}/posts/{post_number}",
|
|
896
|
+
{
|
|
897
|
+
params: {
|
|
898
|
+
path: { team_name: "test-team", post_number: 789 },
|
|
899
|
+
},
|
|
900
|
+
body: {
|
|
901
|
+
post: {
|
|
902
|
+
name: undefined,
|
|
903
|
+
body_md: "",
|
|
904
|
+
tags: undefined,
|
|
905
|
+
category: "dev",
|
|
906
|
+
wip: undefined,
|
|
907
|
+
message: undefined,
|
|
908
|
+
original_revision: undefined,
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
const parsedResult = JSON.parse(result.content[0].text as string);
|
|
915
|
+
expect(parsedResult.body_md).toBe(undefined);
|
|
916
|
+
});
|
|
917
|
+
});
|