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

The OpenAPI document describes watching a milestone: PUT and DELETE …/milestones/{number}/watch #5421

Merged opened this

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

Merged

scout/forge:scout/5315-the-openapi-document-describes-watching → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/5315-the-openapi-document-describes-watching was merged into main as c9a4c4f.

Review

Sign in to review this change.
4 files changed +40 −2
src/contract/openapi.ts +22
@@ -1156,6 +1156,28 @@ export const ROUTES: readonly Route[] = [
11561156 body: { labels: "array" },
11571157 refusals: LABEL_REFUSALS,
11581158 },
1159 {
1160 method: "put",
1161 path: `${REPO}/milestones/{number}/watch`,
1162 summary: "Watch a milestone: the caller's own watch, so a write token and nothing more (a person's; an agent is refused); answers { watching }",
1163 auth: true,
1164 refusals: {
1165 unauthorized: "401: no signed-in account",

Nit: a token without write scope is also refused by requireScope; consider listing that refusal here.

1166 forbidden: "403: only a person watches a milestone",
1167 not_found: "404: no such repository, or it is not readable, or the number is no milestone",
1168 },
1169 },
1170 {
1171 method: "delete",
1172 path: `${REPO}/milestones/{number}/watch`,
1173 summary: "Stop watching a milestone: the caller's own watch, so a write token and nothing more (a person's; an agent is refused); answers { watching }",
1174 auth: true,
1175 refusals: {
1176 unauthorized: "401: no signed-in account",
1177 forbidden: "403: only a person watches a milestone",
1178 not_found: "404: no such repository, or it is not readable, or the number is no milestone",
1179 },
1180 },
11591181 {
11601182 method: "post",
11611183 path: `${REPO}/issues/{number}/private`,
test/openapi-coverage.test.ts −2
@@ -13,7 +13,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [
1313 "DELETE /api/v1/repos/{}/{}",
1414 "DELETE /api/v1/repos/{}/{}/branches/{}",
1515 "DELETE /api/v1/repos/{}/{}/labels/{}",
16 "DELETE /api/v1/repos/{}/{}/milestones/{}/watch",
1716 "DELETE /api/v1/repos/{}/{}/pulls/{}/hold",
1817 "DELETE /api/v1/repos/{}/{}/pulls/{}/threads/{}/resolve",
1918 "DELETE /api/v1/user/keys/{}",
@@ -129,7 +128,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [
129128 "PUT /api/v1/repos/{}/{}/issues/{}/assignee",
130129 "PUT /api/v1/repos/{}/{}/issues/{}/labels",
131130 "PUT /api/v1/repos/{}/{}/issues/{}/teams",
132 "PUT /api/v1/repos/{}/{}/milestones/{}/watch",
133131 "PUT /api/v1/repos/{}/{}/pulls/{}/assignee",
134132 "PUT /api/v1/repos/{}/{}/pulls/{}/hold",
135133 "PUT /api/v1/repos/{}/{}/pulls/{}/labels",
test/openapi-milestone-watch.test.ts added +16
@@ -0,0 +1,16 @@
1 /** The OpenAPI document describes watching a milestone: PUT and DELETE …/milestones/{number}/watch. */
2 import { expect, test } from "bun:test";
3 import { openapi } from "../src/contract/openapi.ts";
4
5 const PATH = "/api/v1/repos/{owner}/{name}/milestones/{number}/watch";
6
7 test("PUT and DELETE watch each have a row with the refusals the handler answers", () => {
8 const doc = openapi("https://f.test", "1") as any;
9 for (const m of ["put", "delete"]) {
10 const op = doc.paths[PATH]?.[m];
11 expect(op, m).toBeDefined();
12 const text = JSON.stringify(op);
13 for (const code of ["unauthorized", "forbidden", "not_found"]) expect(text, `${m} ${code}`).toContain(`\`${code}\``);
14 expect(op.summary).toContain("{ watching }");
15 }
16 });
test/openapi.test.ts +2
@@ -31,6 +31,8 @@ test("every described route is registered in the router", () => {
3131 .map((f) => readFileSync(f, "utf8"))
3232 .join("\n")
3333 // A route registered in a loop over issues and pulls is two routes.
34 // So is the one over PUT and DELETE of a milestone's watch.
35 .replace(/\bmethod,\s*("[^"]*\/milestones\/:number\/watch")/g, '"PUT", $1\n"DELETE", $1')
3436 .replace(/"(PUT)",\s*`([^`]*)\$\{kind\}([^`]*)`/g, (_, m, a, b) => `"${m}", "${a}issues${b}"\n"${m}", "${a}pulls${b}"`);
3537 const registered = new Set(
3638 [...src.matchAll(/"(GET|POST|PATCH|DELETE|PUT)",\s*"([^"]+)"/g)].map((m) => `${m[1]} ${m[2]}`),

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs