Files
SnapOtter/tests/unit/api/license-validation.test.ts
T
SnapOtterandGitHub d8cf979d4b fix: resolve 18 QA-discovered bugs across tools, previews, and the AI pipeline (#242)
Exhaustive QA sweep of all 157 tools. Fixes: CSP blob media, csv-excel ExcelJS interop, ocr-pdf segfault, chart-maker upload, non-PDF doc preview, RAW decode, merge-tool multi-file path, html-to-image chromium, ogv/wma/amr/ac3 preview fallbacks, meme/gif/stabilize codecs, nav+home a11y. Plus orphan-format and test-debt cleanup, the AI bundle build script, and a reusable Playwright QA harness under tests/qa/.
2026-06-15 22:26:24 +08:00

274 lines
9.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import {
ENTERPRISE_FEATURES,
PLAN_FEATURES,
validateLicense,
} from "../../../packages/enterprise/src/license.js";
describe("validateLicense", () => {
it("returns null for undefined input", () => {
expect(validateLicense(undefined as unknown as string)).toBeNull();
});
it("returns null for empty string", () => {
expect(validateLicense("")).toBeNull();
});
it("returns null for input without a dot separator", () => {
expect(validateLicense("nodothere")).toBeNull();
});
it("returns null when dot is at position 0", () => {
expect(validateLicense(".signature")).toBeNull();
});
it("returns null for invalid base64url payload", () => {
expect(validateLicense("!!!invalid-base64.AAAA")).toBeNull();
});
it("returns null for valid base64url that is not JSON", () => {
const notJson = Buffer.from("not json at all").toString("base64url");
const fakeSig = Buffer.from("fakesig").toString("base64url");
expect(validateLicense(`${notJson}.${fakeSig}`)).toBeNull();
});
it("returns null for valid JSON payload with wrong signature", () => {
const payload = Buffer.from(
JSON.stringify({
org: "test",
plan: "team",
features: ["s3_storage"],
seats: 5,
expiresAt: new Date(Date.now() + 86400000).toISOString(),
issuedAt: new Date().toISOString(),
}),
).toString("base64url");
const wrongSig = Buffer.from("this-is-not-a-valid-signature").toString("base64url");
expect(validateLicense(`${payload}.${wrongSig}`)).toBeNull();
});
it("returns null for payload with multiple dots", () => {
const payload = Buffer.from(JSON.stringify({ org: "test" })).toString("base64url");
const sig = Buffer.from("sig").toString("base64url");
expect(validateLicense(`${payload}.${sig}.extra`)).toBeNull();
});
});
describe("initEnterprise", () => {
beforeEach(() => {
vi.resetModules();
});
it("returns valid:false and license:null when called with undefined", async () => {
const { initEnterprise } = await import("../../../packages/enterprise/src/index.js");
const result = initEnterprise(undefined);
expect(result).toEqual({ valid: false, license: null });
});
it("returns valid:false and license:null when called with empty string", async () => {
const { initEnterprise } = await import("../../../packages/enterprise/src/index.js");
const result = initEnterprise("");
expect(result).toEqual({ valid: false, license: null });
});
it("returns valid:false and license:null when called with an invalid key", async () => {
const { initEnterprise } = await import("../../../packages/enterprise/src/index.js");
const result = initEnterprise("bad-key-no-dot");
expect(result).toEqual({ valid: false, license: null });
});
it("returns valid:false for a key with wrong signature", async () => {
const { initEnterprise } = await import("../../../packages/enterprise/src/index.js");
const payload = Buffer.from(
JSON.stringify({
org: "test",
plan: "team",
features: [],
seats: 1,
expiresAt: new Date(Date.now() + 86400000).toISOString(),
issuedAt: new Date().toISOString(),
}),
).toString("base64url");
const result = initEnterprise(`${payload}.badsig`);
expect(result).toEqual({ valid: false, license: null });
});
it("sets activeLicense to null for invalid keys", async () => {
const { initEnterprise, getActiveLicense } = await import(
"../../../packages/enterprise/src/index.js"
);
initEnterprise("garbage.data");
expect(getActiveLicense()).toBeNull();
});
});
describe("isFeatureEnabled (via mock)", () => {
beforeEach(() => {
vi.resetModules();
});
it("returns false when no license is active", async () => {
const { mockNoEnterprise } = await import("../../helpers/enterprise-mock.js");
mockNoEnterprise();
const { isFeatureEnabled } = await import("@snapotter/enterprise");
expect(isFeatureEnabled("saml_sso")).toBe(false);
expect(isFeatureEnabled("s3_storage")).toBe(false);
});
it("returns true for features in the active license", async () => {
const { mockEnterpriseFeatures } = await import("../../helpers/enterprise-mock.js");
mockEnterpriseFeatures(["saml_sso", "s3_storage", "mfa"]);
const { isFeatureEnabled } = await import("@snapotter/enterprise");
expect(isFeatureEnabled("saml_sso")).toBe(true);
expect(isFeatureEnabled("s3_storage")).toBe(true);
expect(isFeatureEnabled("mfa")).toBe(true);
});
it("returns false for features NOT in the active license", async () => {
const { mockEnterpriseFeatures } = await import("../../helpers/enterprise-mock.js");
mockEnterpriseFeatures(["saml_sso"]);
const { isFeatureEnabled } = await import("@snapotter/enterprise");
expect(isFeatureEnabled("scim")).toBe(false);
expect(isFeatureEnabled("mfa")).toBe(false);
expect(isFeatureEnabled("webhooks")).toBe(false);
});
it("returns correct results when switching from active to no license", async () => {
const { mockEnterpriseFeatures } = await import("../../helpers/enterprise-mock.js");
mockEnterpriseFeatures(["audit_export"]);
const mod1 = await import("@snapotter/enterprise");
expect(mod1.isFeatureEnabled("audit_export")).toBe(true);
vi.resetModules();
const { mockNoEnterprise } = await import("../../helpers/enterprise-mock.js");
mockNoEnterprise();
const mod2 = await import("@snapotter/enterprise");
expect(mod2.isFeatureEnabled("audit_export")).toBe(false);
});
});
describe("isFeatureEnabled (direct, no mock)", () => {
beforeEach(() => {
vi.resetModules();
});
it("returns false for all features when initEnterprise was not called", async () => {
const { isFeatureEnabled } = await import("../../../packages/enterprise/src/index.js");
for (const feature of ENTERPRISE_FEATURES) {
expect(isFeatureEnabled(feature)).toBe(false);
}
});
it("returns false for all features after initEnterprise with undefined", async () => {
const { initEnterprise, isFeatureEnabled } = await import(
"../../../packages/enterprise/src/index.js"
);
initEnterprise(undefined);
for (const feature of ENTERPRISE_FEATURES) {
expect(isFeatureEnabled(feature)).toBe(false);
}
});
it("returns false for all features after initEnterprise with invalid key", async () => {
const { initEnterprise, isFeatureEnabled } = await import(
"../../../packages/enterprise/src/index.js"
);
initEnterprise("invalid.key");
for (const feature of ENTERPRISE_FEATURES) {
expect(isFeatureEnabled(feature)).toBe(false);
}
});
});
describe("PLAN_FEATURES", () => {
it("team plan has exactly 8 features", () => {
expect(PLAN_FEATURES.team).toHaveLength(8);
});
it("team plan contains the expected features", () => {
const expected = [
"saml_sso",
"s3_storage",
"multi_tenancy",
"audit_export",
"siem_forwarding",
"sso_enforcement",
"upgrade_management",
"admin_alerts",
];
for (const feature of expected) {
expect(PLAN_FEATURES.team).toContain(feature);
}
});
it("team plan does not include compliance-only features", () => {
expect(PLAN_FEATURES.team).not.toContain("scim");
expect(PLAN_FEATURES.team).not.toContain("webhooks");
expect(PLAN_FEATURES.team).not.toContain("mfa");
expect(PLAN_FEATURES.team).not.toContain("per_tool_permissions");
expect(PLAN_FEATURES.team).not.toContain("tamper_resistant_audit");
expect(PLAN_FEATURES.team).not.toContain("legal_hold");
expect(PLAN_FEATURES.team).not.toContain("gdpr_lifecycle");
expect(PLAN_FEATURES.team).not.toContain("team_retention_overrides");
expect(PLAN_FEATURES.team).not.toContain("ip_allowlist");
expect(PLAN_FEATURES.team).not.toContain("config_export_import");
});
it("enterprise plan has all 19 features", () => {
expect(PLAN_FEATURES.enterprise).toHaveLength(19);
});
it("enterprise plan is a superset of team plan", () => {
for (const feature of PLAN_FEATURES.team) {
expect(PLAN_FEATURES.enterprise).toContain(feature);
}
});
it("enterprise plan references the same array as ENTERPRISE_FEATURES", () => {
expect(PLAN_FEATURES.enterprise).toBe(ENTERPRISE_FEATURES);
});
});
describe("ENTERPRISE_FEATURES", () => {
it("contains exactly 19 features", () => {
expect(ENTERPRISE_FEATURES).toHaveLength(19);
});
it("has no duplicate entries", () => {
const unique = new Set(ENTERPRISE_FEATURES);
expect(unique.size).toBe(ENTERPRISE_FEATURES.length);
});
it("contains all expected feature strings", () => {
const expected = [
"saml_sso",
"s3_storage",
"scim",
"multi_tenancy",
"webhooks",
"audit_export",
"mfa",
"per_tool_permissions",
"siem_forwarding",
"tamper_resistant_audit",
"legal_hold",
"gdpr_lifecycle",
"team_retention_overrides",
"sso_enforcement",
"ip_allowlist",
"config_export_import",
"upgrade_management",
"admin_alerts",
"distributed_tracing",
];
expect([...ENTERPRISE_FEATURES].sort()).toEqual([...expected].sort());
});
it("every entry is a non-empty string", () => {
for (const feature of ENTERPRISE_FEATURES) {
expect(typeof feature).toBe("string");
expect(feature.length).toBeGreaterThan(0);
}
});
});