mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-12 06:24:14 +00:00
241 lines
7.4 KiB
TypeScript
241 lines
7.4 KiB
TypeScript
import { isValidElement } from "react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
// `cmmc.tsx` re-exports `toAccordionItems` which builds JSX referencing
|
|
// client-side accordion components. Those components transitively import
|
|
// server-only code (next-auth → next/server) and would crash vitest at load
|
|
// time. Mocking the JSX deps lets us load the module and exercise the real
|
|
// `mapComplianceData` and `toAccordionItems` functions.
|
|
vi.mock(
|
|
"@/components/compliance/compliance-accordion/client-accordion-content",
|
|
() => ({
|
|
ClientAccordionContent: () => null,
|
|
}),
|
|
);
|
|
vi.mock(
|
|
"@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title",
|
|
() => ({
|
|
ComplianceAccordionRequirementTitle: () => null,
|
|
}),
|
|
);
|
|
vi.mock(
|
|
"@/components/compliance/compliance-accordion/compliance-accordion-title",
|
|
() => ({
|
|
ComplianceAccordionTitle: () => null,
|
|
}),
|
|
);
|
|
|
|
import {
|
|
AttributesData,
|
|
AttributesItemData,
|
|
CMMCAttributesMetadata,
|
|
CMMCLevel,
|
|
REQUIREMENT_STATUS,
|
|
RequirementItemData,
|
|
RequirementsData,
|
|
RequirementStatus,
|
|
} from "@/types/compliance";
|
|
|
|
import { mapComplianceData, toAccordionItems } from "./cmmc";
|
|
|
|
const FRAMEWORK = "CMMC";
|
|
|
|
const baseMetadata = (
|
|
overrides: Partial<CMMCAttributesMetadata> = {},
|
|
): CMMCAttributesMetadata => ({
|
|
Domain: "Access Control",
|
|
Level: "Level 1" as CMMCLevel,
|
|
SourceRequirement: "48 CFR 52.204-21(b)(1)(i)",
|
|
...overrides,
|
|
});
|
|
|
|
const buildAttribute = (
|
|
id: string,
|
|
metadata: CMMCAttributesMetadata,
|
|
{ name = "" }: { name?: string } = {},
|
|
): AttributesItemData => ({
|
|
type: "compliance-requirements-attributes",
|
|
id,
|
|
attributes: {
|
|
framework_description: "CMMC 2.0",
|
|
name,
|
|
framework: FRAMEWORK,
|
|
version: "2.0",
|
|
description: "Requirement clause text.",
|
|
attributes: {
|
|
metadata: [metadata],
|
|
check_ids: ["check_one"],
|
|
},
|
|
},
|
|
});
|
|
|
|
const buildRequirement = (
|
|
id: string,
|
|
status: RequirementStatus = REQUIREMENT_STATUS.PASS,
|
|
): RequirementItemData => ({
|
|
type: "compliance-requirements-details",
|
|
id,
|
|
attributes: {
|
|
framework: FRAMEWORK,
|
|
version: "2.0",
|
|
description: "Canonical CMMC requirement text.",
|
|
status,
|
|
},
|
|
});
|
|
|
|
const buildInputs = (
|
|
pairs: Array<{
|
|
attribute: AttributesItemData;
|
|
requirement: RequirementItemData;
|
|
}>,
|
|
): { attributesData: AttributesData; requirementsData: RequirementsData } => ({
|
|
attributesData: { data: pairs.map((p) => p.attribute) },
|
|
requirementsData: { data: pairs.map((p) => p.requirement) },
|
|
});
|
|
|
|
describe("mapComplianceData (CMMC 2.0)", () => {
|
|
it("returns an empty list when there are no attributes", () => {
|
|
const { attributesData, requirementsData } = buildInputs([]);
|
|
expect(mapComplianceData(attributesData, requirementsData)).toEqual([]);
|
|
});
|
|
|
|
it("groups requirements by Domain", () => {
|
|
const attrA = buildAttribute(
|
|
"AC.L1-b.1.i",
|
|
baseMetadata({ Domain: "Access Control" }),
|
|
);
|
|
const attrB = buildAttribute(
|
|
"AC.L2-3.1.3",
|
|
baseMetadata({ Domain: "Access Control" }),
|
|
);
|
|
|
|
const { attributesData, requirementsData } = buildInputs([
|
|
{ attribute: attrA, requirement: buildRequirement("AC.L1-b.1.i") },
|
|
{ attribute: attrB, requirement: buildRequirement("AC.L2-3.1.3") },
|
|
]);
|
|
|
|
const [framework] = mapComplianceData(attributesData, requirementsData);
|
|
|
|
expect(framework.name).toBe(FRAMEWORK);
|
|
expect(framework.categories).toHaveLength(1);
|
|
expect(framework.categories[0].name).toBe("Access Control");
|
|
expect(framework.categories[0].controls[0].requirements).toHaveLength(2);
|
|
});
|
|
|
|
it("orders domains by the canonical NIST 800-171 family order", () => {
|
|
const attrSI = buildAttribute(
|
|
"SI.L1-b.1.xiv",
|
|
baseMetadata({ Domain: "System and Information Integrity" }),
|
|
);
|
|
const attrAC = buildAttribute(
|
|
"AC.L1-b.1.i",
|
|
baseMetadata({ Domain: "Access Control" }),
|
|
);
|
|
|
|
const { attributesData, requirementsData } = buildInputs([
|
|
{ attribute: attrSI, requirement: buildRequirement("SI.L1-b.1.xiv") },
|
|
{ attribute: attrAC, requirement: buildRequirement("AC.L1-b.1.i") },
|
|
]);
|
|
|
|
const [framework] = mapComplianceData(attributesData, requirementsData);
|
|
|
|
expect(framework.categories.map((c) => c.name)).toEqual([
|
|
"Access Control",
|
|
"System and Information Integrity",
|
|
]);
|
|
});
|
|
|
|
it("propagates Domain, Level and SourceRequirement onto the requirement", () => {
|
|
const attribute = buildAttribute(
|
|
"AC.L1-b.1.i",
|
|
baseMetadata({
|
|
Domain: "Access Control",
|
|
Level: "Level 1" as CMMCLevel,
|
|
SourceRequirement: "48 CFR 52.204-21(b)(1)(i)",
|
|
}),
|
|
{ name: "Limit information system access" },
|
|
);
|
|
|
|
const { attributesData, requirementsData } = buildInputs([
|
|
{ attribute, requirement: buildRequirement("AC.L1-b.1.i") },
|
|
]);
|
|
|
|
const [framework] = mapComplianceData(attributesData, requirementsData);
|
|
const requirementOut = framework.categories[0].controls[0].requirements[0];
|
|
|
|
expect(requirementOut.name).toBe(
|
|
"AC.L1-b.1.i - Limit information system access",
|
|
);
|
|
expect(requirementOut.domain).toBe("Access Control");
|
|
expect(requirementOut.level).toBe("Level 1");
|
|
expect(requirementOut.source_requirement).toBe("48 CFR 52.204-21(b)(1)(i)");
|
|
});
|
|
|
|
it("derives counters from RequirementStatus", () => {
|
|
const STATUS_COUNTER = {
|
|
PASS: "pass",
|
|
FAIL: "fail",
|
|
MANUAL: "manual",
|
|
} as const;
|
|
type StatusCounter = (typeof STATUS_COUNTER)[keyof typeof STATUS_COUNTER];
|
|
|
|
const cases: Array<{
|
|
status: RequirementStatus;
|
|
expected: StatusCounter;
|
|
}> = [
|
|
{ status: REQUIREMENT_STATUS.PASS, expected: STATUS_COUNTER.PASS },
|
|
{ status: REQUIREMENT_STATUS.FAIL, expected: STATUS_COUNTER.FAIL },
|
|
{ status: REQUIREMENT_STATUS.MANUAL, expected: STATUS_COUNTER.MANUAL },
|
|
];
|
|
|
|
for (const { status, expected } of cases) {
|
|
const attribute = buildAttribute(`AC-${status}`, baseMetadata());
|
|
const { attributesData, requirementsData } = buildInputs([
|
|
{ attribute, requirement: buildRequirement(`AC-${status}`, status) },
|
|
]);
|
|
|
|
const [framework] = mapComplianceData(attributesData, requirementsData);
|
|
expect(framework[expected]).toBe(1);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("toAccordionItems (CMMC 2.0)", () => {
|
|
it("produces one accordion item per domain with its requirement leaves", () => {
|
|
const attrAC = buildAttribute(
|
|
"AC.L1-b.1.i",
|
|
baseMetadata({ Domain: "Access Control" }),
|
|
);
|
|
const attrIA = buildAttribute(
|
|
"IA.L1-b.1.v",
|
|
baseMetadata({ Domain: "Identification and Authentication" }),
|
|
);
|
|
|
|
const frameworks = mapComplianceData(
|
|
{ data: [attrAC, attrIA] },
|
|
{
|
|
data: [
|
|
buildRequirement("AC.L1-b.1.i"),
|
|
buildRequirement("IA.L1-b.1.v"),
|
|
],
|
|
},
|
|
);
|
|
|
|
const items = toAccordionItems(frameworks, "scan-1");
|
|
|
|
expect(items).toHaveLength(2);
|
|
expect(items[0].key).toBe(`${FRAMEWORK}-Access Control`);
|
|
expect(isValidElement(items[0].title)).toBe(true);
|
|
expect(items[0].items).toHaveLength(1);
|
|
// Requirement keys are stable (derived from the requirement id), not
|
|
// positional indexes — reordering must not remap expanded state.
|
|
expect(items[0].items?.[0]?.key).toBe(
|
|
`${FRAMEWORK}-Access Control-AC.L1-b.1.i`,
|
|
);
|
|
});
|
|
|
|
it("returns an empty list when given no frameworks", () => {
|
|
expect(toAccordionItems([], "scan-1")).toEqual([]);
|
|
});
|
|
});
|