Public: anyone can read this; the crew can change it.
GET /api/v1/admin/capacity answers `{ items, next_cursor: null }` #5161
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5145-get-api-v1-admin-capacity-answers-items → main 2 commits fork 3 threads · 3 unresolved
4 files
changed +14
−5
src/contract/openapi.ts +1 −1
| @@ -197,7 +197,7 @@ export const ROUTES: readonly Route[] = [ | ||
| 197 | 197 | method: "get", |
| 198 | 198 | path: "/api/v1/admin/capacity", |
| 199 | 199 | summary: |
| 200 | "Read the forge's capacity settings (a person admin's token, any scope); answers { settings: [{ key, label, help, value, source, default, env, changed? }], changes }", | |
| 200 | "Read the forge's capacity settings (a person admin's token, any scope); answers { items: [{ key, label, help, value, source, default, env, changed? }], changes, next_cursor }", | |
| 201 | 201 | auth: true, |
| 202 | 202 | refusals: { not_found: "the token is not a person who administers the forge" }, |
| 203 | 203 | }, |
src/http/api.ts +1 −1
| @@ -1675,7 +1675,7 @@ export function apiRoutes(router: Router<Handler>): void { | ||
| 1675 | 1675 | const denied = accountAdmin(ctx, "ro"); |
| 1676 | 1676 | if (denied) return denied; |
| 1677 | 1677 | const listed = timePhase(ctx.request, "capacity", () => listCapacity(ctx.app, ctx.auth.user)); |
| 1678 | return timePhase(ctx.request, "render", () => (listed.ok ? json(listed.value) : notFound())); | |
| 1678 | return timePhase(ctx.request, "render", () => (listed.ok ? json({ items: listed.value.settings, changes: listed.value.changes, next_cursor: null }) : notFound())); | |
outdated
|
||
| 1679 | 1679 | }); |
| 1680 | 1680 | |
| 1681 | 1681 | router.add("PUT", "/api/v1/admin/capacity/:key", async (ctx) => { |
test/capacity.test.ts +7 −1
| @@ -198,7 +198,13 @@ describe("capacity: only a forge admin reads or changes it", () => { | ||
| 198 | 198 | }); |
| 199 | 199 | |
| 200 | 200 | test("an admin with a read-only token may read, not change", async () => { |
| 201 | expect((await api(aliceRo, "GET", "/admin/capacity")).status).toBe(200); | |
| 201 | const read = await api(aliceRo, "GET", "/admin/capacity"); | |
| 202 | expect(read.status).toBe(200); | |
| 203 | const body = (await read.json()) as { items: unknown[]; changes: unknown[]; next_cursor: null }; | |
outdated
Assert the list shape: |
||
| 204 | expect(Array.isArray(body.items)).toBe(true); | |
| 205 | expect(Array.isArray(body.changes)).toBe(true); | |
| 206 | expect(body.next_cursor).toBeNull(); | |
| 207 | expect("settings" in body).toBe(false); | |
| 202 | 208 | expect( |
| 203 | 209 | ( |
| 204 | 210 | await api(aliceRo, "PUT", "/admin/capacity/checks.concurrency", { |
test/guard-new-accounts.test.ts +5 −2
| @@ -188,9 +188,12 @@ describe("guard_new_accounts", () => { | ||
| 188 | 188 | headers: { authorization: `Bearer ${token}` }, |
| 189 | 189 | }).then( |
| 190 | 190 | (r) => |
| 191 | r.json() as Promise<{ settings: { key: string; value: string }[] }>, | |
| 191 | r.json() as Promise<{ | |
| 192 | items: { key: string; value: string }[]; | |
| 193 | next_cursor: null; | |
| 194 | }>, | |
| 192 | 195 | ); |
| 193 | const keys = (await get()).settings.map((s) => s.key); | |
| 196 | const keys = (await get()).items.map((s) => s.key); | |
| 194 | 197 | expect(keys).toContain("guard_new_accounts"); |
| 195 | 198 | |
| 196 | 199 | const put = (value: string) => |
new above
offline
Once the handler is flattened:
answers { items: [{ key, label, help, value, source, default, env, changed? }], changes, next_cursor }.