Document comment/workflow APIs and fix commentId route handling

swadhinbiswas committed 8 months ago
121094d
1 parent 7a069c9
commit 121094d
3 files changed
+194 -2
▼src/lib/openapi.ts
+171-0
146
146
},
147
147
required: ["reviewers"],
148
148
},
149
+ PullRequestCommentCreateRequest: {
150
+ type: "object",
151
+ properties: {
152
+ body: { type: "string" },
153
+ path: { type: "string" },
154
+ line: { type: "integer" },
155
+ side: { type: "string", enum: ["LEFT", "RIGHT"] },
156
+ startLine: { type: "integer" },
157
+ commitSha: { type: "string" },
158
+ inReplyToId: { type: "string" },
159
+ suggestedChange: { type: "string" },
160
+ },
161
+ required: ["body"],
162
+ },
163
+ PullRequestCommentUpdateRequest: {
164
+ type: "object",
165
+ properties: {
166
+ body: { type: "string" },
167
+ },
168
+ required: ["body"],
169
+ },
170
+ WorkflowStateCreateRequest: {
171
+ type: "object",
172
+ properties: {
173
+ name: { type: "string" },
174
+ description: { type: "string" },
175
+ },
176
+ required: ["name"],
177
+ },
149
178
},
150
179
},
151
180
paths: {
476
505
},
477
506
},
478
507
},
508
+ "/repos/{owner}/{repo}/pulls/{number}/comments": {
509
+ get: {
510
+ tags: ["Pull Requests"],
511
+ summary: "List PR comments",
512
+ security: [{ bearerAuth: [] }],
513
+ parameters: [
514
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
515
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
516
+ { name: "number", in: "path", required: true, schema: { type: "integer" } },
517
+ ],
518
+ responses: {
519
+ 200: { description: "Comments returned" },
520
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
521
+ 404: { description: "Repository or pull request not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
522
+ },
523
+ },
524
+ post: {
525
+ tags: ["Pull Requests"],
526
+ summary: "Create PR comment",
527
+ security: [{ bearerAuth: [] }],
528
+ parameters: [
529
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
530
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
531
+ { name: "number", in: "path", required: true, schema: { type: "integer" } },
532
+ ],
533
+ requestBody: {
534
+ required: true,
535
+ content: {
536
+ "application/json": {
537
+ schema: { $ref: "#/components/schemas/PullRequestCommentCreateRequest" },
538
+ },
539
+ },
540
+ },
541
+ responses: {
542
+ 200: { description: "Comment created" },
543
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
544
+ 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
545
+ 404: { description: "Repository or pull request not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
546
+ },
547
+ },
548
+ patch: {
549
+ tags: ["Pull Requests"],
550
+ summary: "Update PR comment",
551
+ security: [{ bearerAuth: [] }],
552
+ parameters: [
553
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
554
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
555
+ { name: "number", in: "path", required: true, schema: { type: "integer" } },
556
+ { name: "commentId", in: "query", required: true, schema: { type: "string" } },
557
+ ],
558
+ requestBody: {
559
+ required: true,
560
+ content: {
561
+ "application/json": {
562
+ schema: { $ref: "#/components/schemas/PullRequestCommentUpdateRequest" },
563
+ },
564
+ },
565
+ },
566
+ responses: {
567
+ 200: { description: "Comment updated" },
568
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
569
+ 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
570
+ 404: { description: "Comment or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
571
+ },
572
+ },
573
+ delete: {
574
+ tags: ["Pull Requests"],
575
+ summary: "Delete PR comment",
576
+ security: [{ bearerAuth: [] }],
577
+ parameters: [
578
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
579
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
580
+ { name: "number", in: "path", required: true, schema: { type: "integer" } },
581
+ { name: "commentId", in: "query", required: true, schema: { type: "string" } },
582
+ ],
583
+ responses: {
584
+ 200: { description: "Comment deleted" },
585
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
586
+ 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
587
+ 404: { description: "Comment or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
588
+ },
589
+ },
590
+ },
591
+ "/repos/{owner}/{repo}/workflow/states": {
592
+ get: {
593
+ tags: ["Workflow"],
594
+ summary: "List workflow PR states",
595
+ security: [{ bearerAuth: [] }],
596
+ parameters: [
597
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
598
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
599
+ ],
600
+ responses: {
601
+ 200: { description: "State definitions returned" },
602
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
603
+ 404: { description: "Repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
604
+ },
605
+ },
606
+ post: {
607
+ tags: ["Workflow"],
608
+ summary: "Create workflow PR state",
609
+ security: [{ bearerAuth: [] }],
610
+ parameters: [
611
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
612
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
613
+ ],
614
+ requestBody: {
615
+ required: true,
616
+ content: {
617
+ "application/json": {
618
+ schema: { $ref: "#/components/schemas/WorkflowStateCreateRequest" },
619
+ },
620
+ },
621
+ },
622
+ responses: {
623
+ 200: { description: "State created" },
624
+ 400: { description: "Invalid or duplicate state payload", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
625
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
626
+ 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
627
+ 404: { description: "Repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
628
+ },
629
+ },
630
+ },
631
+ "/repos/{owner}/{repo}/workflow/states/{id}": {
632
+ delete: {
633
+ tags: ["Workflow"],
634
+ summary: "Delete workflow PR state",
635
+ security: [{ bearerAuth: [] }],
636
+ parameters: [
637
+ { name: "owner", in: "path", required: true, schema: { type: "string" } },
638
+ { name: "repo", in: "path", required: true, schema: { type: "string" } },
639
+ { name: "id", in: "path", required: true, schema: { type: "string" } },
640
+ ],
641
+ responses: {
642
+ 200: { description: "State deleted" },
643
+ 400: { description: "State is in use", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
644
+ 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
645
+ 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
646
+ 404: { description: "State or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
647
+ },
648
+ },
649
+ },
479
650
"/user/ai-config": {
480
651
get: {
481
652
tags: ["AI Review"],
482
653
▼src/pages/api/repos/[owner]/[repo]/pulls/[number]/comments.ts
+8-2
201
201
const parsed = await parseBody(request, updateCommentSchema);
202
202
if ("error" in parsed) return parsed.error;
203
203
204
- const { owner, repo, number, commentId } = params;
204
+ const { owner, repo, number, commentId: commentIdFromParams } = params;
205
+ const requestUrl = new URL(request.url);
206
+ const commentId = (commentIdFromParams as string | undefined) || requestUrl.searchParams.get("commentId");
207
+ if (!commentId) return badRequest("commentId is required");
205
208
const db = getDatabase() as NodePgDatabase<typeof schema>;
206
209
const repository = await getRepositoryByOwnerAndName(db, owner as string, repo as string);
207
210
if (!repository) return notFound("Repository not found");
262
265
return unauthorized();
263
266
}
264
267
265
- const { owner, repo, number, commentId } = params;
268
+ const { owner, repo, number, commentId: commentIdFromParams } = params;
269
+ const requestUrl = new URL(request.url);
270
+ const commentId = (commentIdFromParams as string | undefined) || requestUrl.searchParams.get("commentId");
271
+ if (!commentId) return badRequest("commentId is required");
266
272
const db = getDatabase() as NodePgDatabase<typeof schema>;
267
273
const repository = await getRepositoryByOwnerAndName(db, owner as string, repo as string);
268
274
if (!repository) return notFound("Repository not found");
269
275
▼tests/integration/pr-comments-path-permissions.test.ts
+15-0
228
228
expect(body?.success).toBe(true);
229
229
expect(mockDb.__mutationState.deleteCalls).toHaveLength(1);
230
230
});
231
+
232
+ it("requires commentId for comment mutation routes", async () => {
233
+ const response = await updateCommentPatch({
234
+ params: { owner: "owner-1", repo: "demo", number: "42" },
235
+ request: new Request("http://localhost/api/repos/owner-1/demo/pulls/42/comments", {
236
+ method: "PATCH",
237
+ headers: { "content-type": "application/json" },
238
+ body: JSON.stringify({ body: "updated" }),
239
+ }),
240
+ } as any);
241
+
242
+ const body = await readJson(response);
243
+ expect(response.status).toBe(400);
244
+ expect(body?.error?.code).toBe("BAD_REQUEST");
245
+ });
231
246
});
232
247