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

GET /api/v1/admin/limits answers `{ items, next_cursor: null }` #5164

Merged opened this

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

Merged

scout/forge:scout/5146-get-api-v1-admin-limits-answers-items-ne → main 2 commits fork 3 threads · 3 unresolved

  • Merged. scout/5146-get-api-v1-admin-limits-answers-items-ne was merged into main as 06c766c.

Review

Sign in to review this change.
3 files changed +10 −3
src/contract/openapi.ts +1 −1
@@ -189,7 +189,7 @@ export const ROUTES: readonly Route[] = [
189189 method: "get",
190190 path: "/api/v1/admin/limits",
191191 summary:
192 "Read the forge's limits (a person admin's token, any scope); answers { limits: [{ key, value, isSet, defaultValue, ... }], changes }",
192 "Read the forge's limits (a person admin's token, any scope); answers { items: [{ key, value, isSet, defaultValue, ... }], changes, next_cursor }",
outdated

Once changes is restored, add it here too: { items: [...], changes, next_cursor }.

193193 auth: true,
194194 refusals: { not_found: "the token is not a person who administers the forge" },
195195 },
src/http/api.ts +1 −1
@@ -1701,7 +1701,7 @@ export function apiRoutes(router: Router<Handler>): void {
17011701 const denied = accountAdmin(ctx, "ro");
17021702 if (denied) return denied;
17031703 const listed = timePhase(ctx.request, "limits", () => listLimits(ctx.app, ctx.auth.user));
1704 return timePhase(ctx.request, "render", () => (listed.ok ? json(listed.value) : notFound()));
1704 return timePhase(ctx.request, "render", () => (listed.ok ? json({ items: listed.value.limits, changes: listed.value.changes, next_cursor: null }) : notFound()));
outdated

High (audit regression): changes is dropped, so the API loses the limit-change history. Keep it as a sibling field, the same way the egress grants route does: json({ items: listed.value.limits, changes: listed.value.changes, next_cursor: null }).

17051705 });
17061706
17071707 router.add("PUT", "/api/v1/admin/limits/:key", async (ctx) => {
test/limits.test.ts +8 −1
@@ -178,7 +178,9 @@ describe("limits", () => {
178178 });
179179 const listed = await api(adminToken, "GET", "/admin/limits");
180180 expect(listed.status).toBe(200);
181 expect(((await listed.json()) as { limits: unknown[] }).limits).toHaveLength(LIMIT_KEYS.length);
181 const body = (await listed.json()) as { items: unknown[]; next_cursor: unknown };
182 expect(body.items).toHaveLength(LIMIT_KEYS.length);
183 expect(body.next_cursor).toBeNull();

Assert changes as well: after the PUT earlier in this test, body.changes should be an array that names the key that was set. Without that assertion, the field can disappear again unnoticed.

182184 expect((await api(bobToken, "GET", "/admin/limits")).status).toBe(403);
183185 expect((await api(bobToken, "PUT", "/admin/limits/share.daily_max", { value: "9" })).status).toBe(403);
184186 expect((await api(adminToken, "PUT", "/admin/limits/share.daily_max", { value: "0" })).status).toBe(422);
@@ -187,6 +189,11 @@ describe("limits", () => {
187189 const put = await api(adminToken, "PUT", "/admin/limits/share.daily_max", { value: "9" });
188190 expect(put.status).toBe(200);
189191 expect(limitValue(app.db, "share.daily_max")).toBe(9);
192 const afterPut = (await (await api(adminToken, "GET", "/admin/limits")).json()) as {
193 changes: { key: string }[];
194 };
195 expect(Array.isArray(afterPut.changes)).toBe(true);
196 expect(afterPut.changes[0]?.key).toBe("share.daily_max");
190197 expect((await api(adminToken, "PUT", "/admin/limits/share.daily_max", { value: 11 })).status).toBe(200);
191198 expect(limitValue(app.db, "share.daily_max")).toBe(11);
192199 expect((await api(adminToken, "PUT", "/admin/limits/share.daily_max", { value: null })).status).toBe(200);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs