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

The OpenAPI document describes a pull's labels and hold #5405

Merged opened this

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

Merged

scout/forge:scout/5383-the-openapi-document-describes-a-pull-s → main 2 commits fork

  • Merged. scout/5383-the-openapi-document-describes-a-pull-s was merged into main as 9fa3073.

Review

Sign in to review this change.
5 files changed +50 −2
src/contract/openapi/routes/deleteApiV1ReposOwnerNamePullsNumberHold.ts added +10
@@ -0,0 +1,10 @@
1 import { REPO, type Route } from "../shared.ts";
2
3 export const route: Route = {
4 method: "delete",
5 path: `${REPO}/pulls/{number}/hold`,
6 summary:
7 "Let a held pull go; a person who writes the repository releases, never an agent (rw scope); answers `{ held: false, released }`, where `released` is false when nothing held it",
8 auth: true,
9 refusals: { forbidden: "you are an agent, or you cannot write the repository (403)" },
10 };
src/contract/openapi/routes/index.ts +2
@@ -16,6 +16,7 @@ export { route as deleteApiV1ReposOwnerNameHooksId } from "./deleteApiV1ReposOwn
1616 export { route as deleteApiV1ReposOwnerNameIssuesNumberDependenciesBlocker } from "./deleteApiV1ReposOwnerNameIssuesNumberDependenciesBlocker.ts";
1717 export { route as deleteApiV1ReposOwnerNameMilestonesNumberWatch } from "./deleteApiV1ReposOwnerNameMilestonesNumberWatch.ts";
1818 export { route as deleteApiV1ReposOwnerNamePromptsId } from "./deleteApiV1ReposOwnerNamePromptsId.ts";
19 export { route as deleteApiV1ReposOwnerNamePullsNumberHold } from "./deleteApiV1ReposOwnerNamePullsNumberHold.ts";
1920 export { route as deleteApiV1ReposOwnerNamePullsNumberThreadsAnchorResolve } from "./deleteApiV1ReposOwnerNamePullsNumberThreadsAnchorResolve.ts";
2021 export { route as deleteApiV1SecretsScopeName } from "./deleteApiV1SecretsScopeName.ts";
2122 export { route as deleteApiV1Sessions } from "./deleteApiV1Sessions.ts";
@@ -202,6 +203,7 @@ export { route as putApiV1ReposOwnerNameIssuesNumberLabels } from "./putApiV1Rep
202203 export { route as putApiV1ReposOwnerNameIssuesNumberTeams } from "./putApiV1ReposOwnerNameIssuesNumberTeams.ts";
203204 export { route as putApiV1ReposOwnerNameMilestonesNumberWatch } from "./putApiV1ReposOwnerNameMilestonesNumberWatch.ts";
204205 export { route as putApiV1ReposOwnerNamePullsNumberAssignee } from "./putApiV1ReposOwnerNamePullsNumberAssignee.ts";
206 export { route as putApiV1ReposOwnerNamePullsNumberHold } from "./putApiV1ReposOwnerNamePullsNumberHold.ts";
205207 export { route as putApiV1ReposOwnerNamePullsNumberLabels } from "./putApiV1ReposOwnerNamePullsNumberLabels.ts";
206208 export { route as putApiV1ReposOwnerNameUpstream } from "./putApiV1ReposOwnerNameUpstream.ts";
207209 export { route as putApiV1SecretsScopeName } from "./putApiV1SecretsScopeName.ts";
src/contract/openapi/routes/putApiV1ReposOwnerNamePullsNumberHold.ts added +16
@@ -0,0 +1,16 @@
1 import { REPO, type Route } from "../shared.ts";
2
3 export const route: Route = {
4 method: "put",
5 path: `${REPO}/pulls/{number}/hold`,
6 summary:
7 "Hold an open pull with one line that says why, so no rule reviews or lands it until it is released; a person who writes the repository holds, never an agent (rw scope); answers `{ held: true, hold }`",
8 auth: true,
9 body: { why: "string" },
10 refusals: {
11 forbidden: "you are an agent, or you cannot write the repository (403)",
12 not_open: "the pull is closed or merged (409)",
13 empty_why: "`why` is empty (422)",
14 why_too_long: "`why` is longer than 200 characters (422)",
15 },
16 };
test/openapi-coverage.test.ts −2
@@ -11,7 +11,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [
1111 "DELETE /api/v1/boards/{}",
1212 "DELETE /api/v1/orgs/{}/teams/{}/members/{}",
1313 "DELETE /api/v1/repos/{}/{}/labels/{}",
14 "DELETE /api/v1/repos/{}/{}/pulls/{}/hold",
1514 "DELETE /api/v1/user/keys/{}",
1615 "DELETE /api/v1/user/origins/{}",
1716 "DELETE /api/v1/user/pins/{}/{}",
@@ -91,7 +90,6 @@ const NOT_YET_DESCRIBED: readonly string[] = [
9190 "PUT /api/v1/boards/{}",
9291 "PUT /api/v1/front",
9392 "PUT /api/v1/repos/{}/{}/crew/{}",
94 "PUT /api/v1/repos/{}/{}/pulls/{}/hold",
9593 "PUT /api/v1/repos/{}/{}/pulls/{}/labels",
9694 "PUT /api/v1/user/pins/{}/{}",
9795 ];
test/openapi-oa-pull-hold.test.ts added +22
@@ -0,0 +1,22 @@
1 /** The OpenAPI document describes a pull's labels and hold. */
2 import { expect, test } from "bun:test";
3 import { openapi } from "../src/contract/openapi.ts";
4
5 const R = "/api/v1/repos/{owner}/{name}/pulls/{number}";
6
7 test("a pull's labels and hold have rows, with the refusals the handlers answer", () => {
8 const doc = openapi("https://f.test", "1") as any;
9 expect(doc.paths[`${R}/labels`]?.put, "put labels").toBeDefined();
10 const put = doc.paths[`${R}/hold`]?.put;
11 const del = doc.paths[`${R}/hold`]?.delete;
12 expect(put, "put hold").toBeDefined();
13 expect(del, "delete hold").toBeDefined();
14 const text = JSON.stringify(put);
15 for (const [code, status] of [["forbidden", 403], ["not_open", 409], ["empty_why", 422], ["why_too_long", 422]] as const)
16 expect(text, code).toMatch(new RegExp(`\`${code}\`[^}]*\\(${status}\\)`));
17 expect(JSON.stringify(del)).toMatch(/`forbidden`[^}]*\(403\)/);
18 expect(put.summary).toContain("never an agent");
19 expect(del.summary).toContain("never an agent");
20 const schema = put.requestBody.content["application/json"].schema;
21 expect(schema.properties.why.type).toBe("string");
22 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs