When controlUiBasePath is set, classifyControlUiRequest returned method-not-allowed (405) for all non-GET/HEAD requests under basePath, blocking plugin webhook handlers (BlueBubbles, Mattermost, etc.) from receiving POST requests. This is a 2026.3.1 regression. Return not-control-ui instead, matching the empty-basePath behavior, so requests fall through to plugin HTTP handlers. Remove the now-dead method-not-allowed type variant, handler branch, and utility function. Closes #31983 Closes #32275 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { classifyControlUiRequest } from "./control-ui-routing.js";
|
|
|
|
describe("classifyControlUiRequest", () => {
|
|
it("falls through non-read root requests for plugin webhooks", () => {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "",
|
|
pathname: "/bluebubbles-webhook",
|
|
search: "",
|
|
method: "POST",
|
|
});
|
|
expect(classified).toEqual({ kind: "not-control-ui" });
|
|
});
|
|
|
|
it("returns not-found for legacy /ui routes when root-mounted", () => {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "",
|
|
pathname: "/ui/settings",
|
|
search: "",
|
|
method: "GET",
|
|
});
|
|
expect(classified).toEqual({ kind: "not-found" });
|
|
});
|
|
|
|
it("falls through basePath non-read methods for plugin webhooks", () => {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "/openclaw",
|
|
pathname: "/openclaw",
|
|
search: "",
|
|
method: "POST",
|
|
});
|
|
expect(classified).toEqual({ kind: "not-control-ui" });
|
|
});
|
|
|
|
it("falls through PUT/DELETE/PATCH/OPTIONS under basePath for plugin handlers", () => {
|
|
for (const method of ["PUT", "DELETE", "PATCH", "OPTIONS"]) {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "/openclaw",
|
|
pathname: "/openclaw/webhook",
|
|
search: "",
|
|
method,
|
|
});
|
|
expect(classified, `${method} should fall through`).toEqual({ kind: "not-control-ui" });
|
|
}
|
|
});
|
|
|
|
it("returns redirect for basePath entrypoint GET", () => {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "/openclaw",
|
|
pathname: "/openclaw",
|
|
search: "?foo=1",
|
|
method: "GET",
|
|
});
|
|
expect(classified).toEqual({ kind: "redirect", location: "/openclaw/?foo=1" });
|
|
});
|
|
|
|
it("classifies basePath subroutes as control ui", () => {
|
|
const classified = classifyControlUiRequest({
|
|
basePath: "/openclaw",
|
|
pathname: "/openclaw/chat",
|
|
search: "",
|
|
method: "HEAD",
|
|
});
|
|
expect(classified).toEqual({ kind: "serve" });
|
|
});
|
|
});
|