Files
SnapOtter/tests/integration/platform/saml-license-gate.test.ts
T
SnapOtterandGitHub 301e6eb01a test: coverage campaign and mutation testing across five packages (#628)
Coverage 83.6 to 87.36% lines, 81.63 to 84.14% branches. Mutation testing across five packages: image-engine 85, media-engine 92, doc-engine 87, shared+enterprise 86, apps/api security and jobs slice. Runs all five lanes weekly. Fixes the silently-broken mutation CI (babel pin), a redact-pdf envelope-shape test bug, an untested enterprise license valid-signature path, and an audit test that only exercised a hand-copied reproduction. Test and config only, no product code changes beyond the babel pin and one test-only oidc export. Full suite: 16,712 pass, 0 fail.
2026-07-24 17:36:57 +08:00

75 lines
2.7 KiB
TypeScript

/**
* SAML license-gate coverage.
*
* saml-auth.test.ts licenses the saml_sso feature so the routes register. This
* companion drives the *other* branch: SAML_ENABLED is on, but the enterprise
* saml_sso feature is NOT licensed, so registerSaml() logs a warning and
* returns early WITHOUT registering any /api/auth/saml/* route. Isolated in its
* own file because the enterprise gate is mocked to deny (mockNoEnterprise via
* vi.doMock), the inverse of the sibling suite.
*/
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
vi.resetModules();
const { mockNoEnterprise } = await import("../../helpers/enterprise-mock.js");
mockNoEnterprise();
const { env } = await import("../../../apps/api/src/config.js");
const { buildTestApp } = await import("../test-server.js");
import type { TestApp } from "../test-server.js";
let testApp: TestApp;
// SAML env is validated by loadEnv()'s superRefine only at load time; the
// module graph is already loaded here, so mutating the cached env after the
// fact turns SAML on for registerSaml() without tripping that validation.
const saved: Record<string, unknown> = {};
const SAML_ENV = {
SAML_ENABLED: true,
EXTERNAL_URL: "http://localhost:9999",
SAML_IDP_SSO_URL: "http://localhost:0/sso",
SAML_IDP_CERTIFICATE: "MIIC-test-certificate",
};
beforeAll(async () => {
for (const [k, v] of Object.entries(SAML_ENV)) {
saved[k] = (env as Record<string, unknown>)[k];
(env as Record<string, unknown>)[k] = v;
}
testApp = await buildTestApp();
}, 30_000);
afterAll(async () => {
for (const [k, v] of Object.entries(saved)) {
(env as Record<string, unknown>)[k] = v;
}
await testApp.cleanup();
}, 10_000);
describe("SAML routes when saml_sso is not licensed", () => {
it("does not register the metadata route (early return, feature unlicensed)", async () => {
// The route only exists when registerSaml() gets past the license check.
// With the feature denied it returns early, so Fastify has no handler and
// falls through to a plain 404 (the path is public, so the auth middleware
// does not turn it into a 401 first).
const res = await testApp.app.inject({ method: "GET", url: "/api/auth/saml/metadata" });
expect(res.statusCode).toBe(404);
});
it("does not register the login route", async () => {
const res = await testApp.app.inject({ method: "GET", url: "/api/auth/saml/login" });
expect(res.statusCode).toBe(404);
});
it("does not register the ACS callback route", async () => {
const res = await testApp.app.inject({
method: "POST",
url: "/api/auth/saml/callback",
headers: { "content-type": "application/x-www-form-urlencoded" },
payload: "SAMLResponse=stub",
});
expect(res.statusCode).toBe(404);
});
});