mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(api): declare the 400 and 415 the single-value profile writes answer
The six /api/auth/me scalar writes (unit preference, glucose unit, automatic document reading, local lab OCR, workout routes, the central Codex switch) read their body through safeJson with a 1 KB cap, so each answers 415 for a body that is not JSON by content type and 400 for one that does not parse, beside the 413 they already declared. Their descriptions said so and the responses did not. All three now come from one block. The modules and disable-coach route tests had been skipping the contract check over a 400 and a 409 the registry already declares; the skips are gone.
This commit is contained in:
1 parent
5ca3994523
commit
579789ec38
5 files changed
+300
-320
No files matched your search
+266
-269
File diff suppressed because it is too large.
Load diff
@@ -1141,6 +1141,29 @@ const profileSummaryResponse = z
|
||||
"The bounded profile view a record viewer gets: allergies, family history and current lifestyle facts. Structurally incapable of becoming a Settings or AI surface.",
|
||||
});
|
||||
|
||||
/**
|
||||
* What the single-value `/api/auth/me/*` writes answer for a body they refuse
|
||||
* to read. Each reads through `safeJson` with a 1 KB cap, so the three
|
||||
* refusals are the cap's, not the route's: a wrong content type is 415, a body
|
||||
* over the cap 413, and one that does not parse 400. All three are answered
|
||||
* before validation, and nothing is written.
|
||||
*/
|
||||
const cappedScalarBodyRefusals = {
|
||||
"400": {
|
||||
description: "The request body is not parseable JSON. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
"415": {
|
||||
description:
|
||||
"The request did not carry `Content-Type: application/json`. Nothing was read and nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
};
|
||||
|
||||
export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
"/api/profile/summary": {
|
||||
get: {
|
||||
@@ -1863,10 +1886,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -1917,10 +1937,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -1971,10 +1988,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -2257,10 +2271,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -2307,10 +2318,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
@@ -2360,10 +2368,7 @@ export const profilePaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"413": {
|
||||
description: "Request body exceeds 1024 bytes. Nothing was written.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...cappedScalarBodyRefusals,
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -174,9 +174,6 @@ describe("PATCH /api/auth/me/disable-coach", () => {
|
||||
route: ROUTE,
|
||||
headers: { "content-type": "application/json" },
|
||||
auth: await makeSession(user),
|
||||
contract: {
|
||||
skip: "the registry declares no 400 for this PATCH, which answers one for a body that is not JSON",
|
||||
},
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.error).toBe("Invalid JSON body");
|
||||
|
||||
@@ -37,11 +37,6 @@ type ModulesResponse = {
|
||||
updatedAt?: string;
|
||||
};
|
||||
|
||||
const NO_400 =
|
||||
"the registry declares no 400 for PATCH /api/auth/me/modules although the handler answers one for a body that is not JSON";
|
||||
const NO_409 =
|
||||
"the registry declares no 409 for PATCH /api/auth/me/modules although the handler answers modules_conflict for a stale base token";
|
||||
|
||||
beforeEach(() => truncateAllTables(prisma));
|
||||
|
||||
async function storedPrefs(userId: string): Promise<unknown> {
|
||||
@@ -63,14 +58,12 @@ async function storedUpdatedAt(userId: string): Promise<Date> {
|
||||
async function patch(
|
||||
auth: Awaited<ReturnType<typeof makeSession>>,
|
||||
body: Record<string, unknown>,
|
||||
contract?: { skip: string },
|
||||
) {
|
||||
return callRoute<ModulesResponse>(PATCH, {
|
||||
method: "PATCH",
|
||||
route: ROUTE,
|
||||
body,
|
||||
auth,
|
||||
contract,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,7 +222,6 @@ describe("PATCH /api/auth/me/modules", () => {
|
||||
route: ROUTE,
|
||||
headers: { "content-type": "application/json" },
|
||||
auth: await makeSession(user),
|
||||
contract: { skip: NO_400 },
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.error).toBe("Invalid JSON body");
|
||||
@@ -281,11 +273,10 @@ describe("PATCH /api/auth/me/modules evicts the cached score", () => {
|
||||
const user = await makeUser();
|
||||
caches.analytics.set(`${user.id}|score`, { score: 71 });
|
||||
|
||||
const res = await patch(
|
||||
await makeSession(user),
|
||||
{ sleep: false, baseUpdatedAt: "2020-01-01T00:00:00.000Z" },
|
||||
{ skip: NO_409 },
|
||||
);
|
||||
const res = await patch(await makeSession(user), {
|
||||
sleep: false,
|
||||
baseUpdatedAt: "2020-01-01T00:00:00.000Z",
|
||||
});
|
||||
expect(res.status).toBe(409);
|
||||
expect(caches.analytics.get(`${user.id}|score`)).toEqual({ score: 71 });
|
||||
});
|
||||
@@ -320,11 +311,7 @@ describe("PATCH /api/auth/me/modules optimistic concurrency", () => {
|
||||
const first = await patch(session, { mood: false, baseUpdatedAt: base });
|
||||
expect(first.status).toBe(200);
|
||||
|
||||
const second = await patch(
|
||||
session,
|
||||
{ sleep: false, baseUpdatedAt: base },
|
||||
{ skip: NO_409 },
|
||||
);
|
||||
const second = await patch(session, { sleep: false, baseUpdatedAt: base });
|
||||
expect(second.status).toBe(409);
|
||||
expect(second.meta?.errorCode).toBe("modules_conflict");
|
||||
expect(await storedPrefs(user.id)).toEqual({ mood: false });
|
||||
|
||||
@@ -171,9 +171,6 @@ describe("PATCH /api/auth/me/unit-preference", () => {
|
||||
route: ROUTE,
|
||||
headers: { "content-type": "application/json" },
|
||||
auth: await makeSession(user),
|
||||
contract: {
|
||||
skip: "the registry declares no 400 for this PATCH although the description names it",
|
||||
},
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
expect(await storedPreference(user.id)).toBe("metric");
|
||||
@@ -199,9 +196,6 @@ describe("PATCH /api/auth/me/unit-preference", () => {
|
||||
body: "unitPreference=imperial",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
auth: await makeSession(user),
|
||||
contract: {
|
||||
skip: "the registry declares no 415 for this PATCH although the description names it",
|
||||
},
|
||||
});
|
||||
expect(res.status).toBe(415);
|
||||
expect(await storedPreference(user.id)).toBe("metric");
|
||||
|
||||
Reference in new issue
Block a user