diff --git a/src/gateway/server-runtime-config.test.ts b/src/gateway/server-runtime-config.test.ts index 34cc4632670..205bac8cf3e 100644 --- a/src/gateway/server-runtime-config.test.ts +++ b/src/gateway/server-runtime-config.test.ts @@ -201,39 +201,73 @@ describe("resolveGatewayRuntimeConfig", () => { ); }); - it("rejects non-loopback control UI when allowed origins are missing", async () => { - await expect( - resolveGatewayRuntimeConfig({ - cfg: { - gateway: { - bind: "lan", - auth: TOKEN_AUTH, - }, - }, - port: 18789, - }), - ).rejects.toThrow("non-loopback Control UI requires gateway.controlUi.allowedOrigins"); - }); - - it("allows non-loopback control UI without allowed origins when dangerous fallback is enabled", async () => { - const result = await resolveGatewayRuntimeConfig({ + it.each([ + { + name: "rejects non-loopback control UI when allowed origins are missing", cfg: { gateway: { - bind: "lan", + bind: "lan" as const, + auth: TOKEN_AUTH, + }, + }, + expectedError: "non-loopback Control UI requires gateway.controlUi.allowedOrigins", + }, + { + name: "allows non-loopback control UI without allowed origins when dangerous fallback is enabled", + cfg: { + gateway: { + bind: "lan" as const, auth: TOKEN_AUTH, controlUi: { dangerouslyAllowHostHeaderOriginFallback: true, }, }, }, - port: 18789, - }); - expect(result.bindHost).toBe("0.0.0.0"); + expectedBindHost: "0.0.0.0", + }, + { + name: "allows non-loopback control UI when allowed origins collapse after trimming", + cfg: { + gateway: { + bind: "lan" as const, + auth: TOKEN_AUTH, + controlUi: { + allowedOrigins: [" https://control.example.com "], + }, + }, + }, + expectedBindHost: "0.0.0.0", + }, + ])("$name", async ({ cfg, expectedError, expectedBindHost }) => { + if (expectedError) { + await expect(resolveGatewayRuntimeConfig({ cfg, port: 18789 })).rejects.toThrow( + expectedError, + ); + return; + } + const result = await resolveGatewayRuntimeConfig({ cfg, port: 18789 }); + expect(result.bindHost).toBe(expectedBindHost); }); }); describe("HTTP security headers", () => { - it("resolves strict transport security header from config", async () => { + it.each([ + { + name: "resolves strict transport security headers from config", + strictTransportSecurity: " max-age=31536000; includeSubDomains ", + expected: "max-age=31536000; includeSubDomains", + }, + { + name: "does not set strict transport security when explicitly disabled", + strictTransportSecurity: false, + expected: undefined, + }, + { + name: "does not set strict transport security when the value is blank", + strictTransportSecurity: " ", + expected: undefined, + }, + ])("$name", async ({ strictTransportSecurity, expected }) => { const result = await resolveGatewayRuntimeConfig({ cfg: { gateway: { @@ -241,7 +275,7 @@ describe("resolveGatewayRuntimeConfig", () => { auth: { mode: "none" }, http: { securityHeaders: { - strictTransportSecurity: " max-age=31536000; includeSubDomains ", + strictTransportSecurity, }, }, }, @@ -249,26 +283,7 @@ describe("resolveGatewayRuntimeConfig", () => { port: 18789, }); - expect(result.strictTransportSecurityHeader).toBe("max-age=31536000; includeSubDomains"); - }); - - it("does not set strict transport security when explicitly disabled", async () => { - const result = await resolveGatewayRuntimeConfig({ - cfg: { - gateway: { - bind: "loopback", - auth: { mode: "none" }, - http: { - securityHeaders: { - strictTransportSecurity: false, - }, - }, - }, - }, - port: 18789, - }); - - expect(result.strictTransportSecurityHeader).toBeUndefined(); + expect(result.strictTransportSecurityHeader).toBe(expected); }); }); });