@mandujs/core 0.54.23 → 0.54.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -606,7 +606,13 @@ export class ManduFilling<TLoaderData = unknown> {
606
606
  if (this.config.handlers.has("GET") && !allowed.includes("HEAD")) {
607
607
  allowed.push("HEAD");
608
608
  }
609
- return ctx.json({ status: "error", message: `Method ${method} not allowed`, allowed }, 405);
609
+ // RFC 7231 §6.5.5: a 405 response MUST include an `Allow` header listing
610
+ // every supported method (not just the body field). ctx.json() cannot
611
+ // attach headers, so build the response directly here.
612
+ return Response.json(
613
+ { status: "error", message: `Method ${method} not allowed`, allowed },
614
+ { status: 405, headers: { Allow: allowed.join(", ") } },
615
+ );
610
616
  }
611
617
  const lifecycleWithDefaults = this.createLifecycleWithDefaults(routeContext);
612
618
  const runHandler = async () => {
@@ -69,4 +69,39 @@ describe("Filling HEAD handling (#319)", () => {
69
69
  expect(body.allowed).toContain("GET");
70
70
  expect(body.allowed).toContain("HEAD");
71
71
  });
72
+
73
+ // Issue #321: 405 responses MUST carry an `Allow` header listing every
74
+ // supported method — for multi-method routes too, not just single-method.
75
+ it("sets the Allow header to a single method route's methods", async () => {
76
+ const filling = ManduFillingFactory.filling().get((ctx) => ctx.json({ ok: true }));
77
+
78
+ const putRes = await filling.handle(
79
+ new Request("http://localhost/api/health", { method: "PUT" }),
80
+ );
81
+
82
+ expect(putRes.status).toBe(405);
83
+ const allow = putRes.headers.get("Allow") ?? "";
84
+ expect(allow.split(", ")).toContain("GET");
85
+ expect(allow.split(", ")).toContain("HEAD");
86
+ });
87
+
88
+ it("sets the Allow header to ALL methods for a multi-method route (#321)", async () => {
89
+ const filling = ManduFillingFactory.filling()
90
+ .get((ctx) => ctx.json({ ok: true }))
91
+ .post((ctx) => ctx.json({ ok: true }))
92
+ .delete((ctx) => ctx.json({ ok: true }));
93
+
94
+ const putRes = await filling.handle(
95
+ new Request("http://localhost/api/pledges", { method: "PUT" }),
96
+ );
97
+
98
+ expect(putRes.status).toBe(405);
99
+ const allow = putRes.headers.get("Allow") ?? "";
100
+ const methods = allow.split(", ");
101
+ expect(methods).toContain("GET");
102
+ expect(methods).toContain("POST");
103
+ expect(methods).toContain("DELETE");
104
+ // HEAD is implied by GET.
105
+ expect(methods).toContain("HEAD");
106
+ });
72
107
  });