mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
fix(api,ops): transactional adopt, admin gate without a flash, and a digest path to deploys
The self-context adopt runs read-modify-write inside one transaction, the admin shell renders nothing until the role is confirmed, and the json-body helper merges Headers instances correctly. The compose image reference accepts a pinned digest through an env var, and a promote script resolves the published digest, verifies its signature and hands the operator the exact pinning call — the documented path away from a moving latest tag.
This commit is contained in:
1 parent
599b7e8c99
commit
f13aa684f9
12 files changed
+876
-79
No files matched your search
@@ -192,11 +192,13 @@ describe("verb helpers", () => {
|
||||
|
||||
await helper("/api/foo", { a: 1 });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith("/api/foo", {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ a: 1 }),
|
||||
});
|
||||
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe("/api/foo");
|
||||
expect(init.method).toBe(method);
|
||||
expect(init.body).toBe(JSON.stringify({ a: 1 }));
|
||||
expect(new Headers(init.headers).get("Content-Type")).toBe(
|
||||
"application/json",
|
||||
);
|
||||
});
|
||||
|
||||
it("omits body and Content-Type when no body is given", async () => {
|
||||
@@ -212,10 +214,44 @@ describe("verb helpers", () => {
|
||||
|
||||
await apiPost("/api/foo", { a: 1 }, { headers: { "X-Req": "1" } });
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith("/api/foo", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Req": "1" },
|
||||
body: JSON.stringify({ a: 1 }),
|
||||
});
|
||||
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
const headers = new Headers(init.headers);
|
||||
expect(headers.get("Content-Type")).toBe("application/json");
|
||||
expect(headers.get("X-Req")).toBe("1");
|
||||
expect(init.body).toBe(JSON.stringify({ a: 1 }));
|
||||
});
|
||||
|
||||
it("keeps caller headers passed as a Headers instance (no spread loss)", async () => {
|
||||
// A `Headers` instance has no enumerable own properties, so the
|
||||
// previous `{ ...init.headers }` merge silently dropped every
|
||||
// caller header for this `HeadersInit` shape.
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse({ data: null, error: null }));
|
||||
|
||||
await apiPost(
|
||||
"/api/foo",
|
||||
{ a: 1 },
|
||||
{ headers: new Headers({ "X-Req": "1", "Idempotency-Key": "k1" }) },
|
||||
);
|
||||
|
||||
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
const headers = new Headers(init.headers);
|
||||
expect(headers.get("X-Req")).toBe("1");
|
||||
expect(headers.get("Idempotency-Key")).toBe("k1");
|
||||
expect(headers.get("Content-Type")).toBe("application/json");
|
||||
});
|
||||
|
||||
it("respects a caller-supplied Content-Type override", async () => {
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse({ data: null, error: null }));
|
||||
|
||||
await apiPost(
|
||||
"/api/foo",
|
||||
{ a: 1 },
|
||||
{ headers: new Headers({ "Content-Type": "application/json-patch+json" }) },
|
||||
);
|
||||
|
||||
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(new Headers(init.headers).get("Content-Type")).toBe(
|
||||
"application/json-patch+json",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -141,7 +141,15 @@ function withJsonBody(
|
||||
): RequestInit {
|
||||
const merged: RequestInit = { ...init, method };
|
||||
if (body !== undefined) {
|
||||
merged.headers = { "Content-Type": "application/json", ...init?.headers };
|
||||
// `HeadersInit` is a union — a plain object spreads fine, but a
|
||||
// `Headers` instance (or entries array) has no enumerable own
|
||||
// properties, so `{ ...init.headers }` silently dropped every
|
||||
// caller header. `new Headers(...)` normalises all three shapes.
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
merged.headers = headers;
|
||||
merged.body = JSON.stringify(body);
|
||||
}
|
||||
return merged;
|
||||
|
||||
Reference in new issue
Block a user