mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
577aa14acc
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
354 lines
8.6 KiB
TypeScript
354 lines
8.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import type { FindingGroupRow } from "@/types";
|
|
|
|
import {
|
|
canDrillDownFindingGroup,
|
|
getActiveStatusFilter,
|
|
getFilteredFindingGroupDelta,
|
|
getFindingGroupDelta,
|
|
getFindingGroupImpactedCounts,
|
|
isFindingGroupMuted,
|
|
} from "./findings-groups";
|
|
|
|
function makeGroup(overrides?: Partial<FindingGroupRow>): FindingGroupRow {
|
|
return {
|
|
id: "group-1",
|
|
rowType: "group",
|
|
checkId: "check-1",
|
|
checkTitle: "Test finding group",
|
|
severity: "high",
|
|
status: "FAIL",
|
|
muted: false,
|
|
resourcesTotal: 5,
|
|
resourcesFail: 3,
|
|
newCount: 0,
|
|
changedCount: 0,
|
|
mutedCount: 0,
|
|
providers: ["aws"],
|
|
updatedAt: "2026-04-10T00:00:00Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("isFindingGroupMuted", () => {
|
|
it("should prefer the explicit muted flag from the API", () => {
|
|
expect(
|
|
isFindingGroupMuted(
|
|
makeGroup({
|
|
muted: true,
|
|
mutedCount: 0,
|
|
resourcesFail: 3,
|
|
resourcesTotal: 5,
|
|
}),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("should fall back to legacy counters when muted is not available", () => {
|
|
expect(
|
|
isFindingGroupMuted(
|
|
makeGroup({
|
|
muted: undefined,
|
|
mutedCount: 3,
|
|
resourcesFail: 3,
|
|
}),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getFindingGroupDelta", () => {
|
|
it("should return new when the muted breakdown contains new findings", () => {
|
|
expect(
|
|
getFindingGroupDelta(
|
|
makeGroup({
|
|
newCount: 0,
|
|
changedCount: 0,
|
|
newFailMutedCount: 1,
|
|
}),
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("should return changed when only changed breakdown counters are present", () => {
|
|
expect(
|
|
getFindingGroupDelta(
|
|
makeGroup({
|
|
newCount: 0,
|
|
changedCount: 0,
|
|
changedManualMutedCount: 2,
|
|
}),
|
|
),
|
|
).toBe("changed");
|
|
});
|
|
|
|
it("should prioritize new over changed when both breakdowns are present", () => {
|
|
expect(
|
|
getFindingGroupDelta(
|
|
makeGroup({
|
|
newCount: 0,
|
|
changedCount: 0,
|
|
newPassCount: 1,
|
|
changedFailCount: 3,
|
|
}),
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("should fall back to legacy counters when breakdowns are missing", () => {
|
|
expect(
|
|
getFindingGroupDelta(
|
|
makeGroup({
|
|
newCount: 1,
|
|
changedCount: 0,
|
|
}),
|
|
),
|
|
).toBe("new");
|
|
});
|
|
});
|
|
|
|
describe("getActiveStatusFilter", () => {
|
|
it("returns null when no status filter is active", () => {
|
|
expect(getActiveStatusFilter({})).toBeNull();
|
|
});
|
|
|
|
it("returns the single value from filter[status]", () => {
|
|
const result = getActiveStatusFilter({ "filter[status]": "FAIL" });
|
|
expect(result).toEqual(new Set(["FAIL"]));
|
|
});
|
|
|
|
it("returns the parsed set from filter[status__in]", () => {
|
|
const result = getActiveStatusFilter({
|
|
"filter[status__in]": "FAIL,MANUAL",
|
|
});
|
|
expect(result).toEqual(new Set(["FAIL", "MANUAL"]));
|
|
});
|
|
|
|
it("prefers filter[status] over filter[status__in] when both are present", () => {
|
|
const result = getActiveStatusFilter({
|
|
"filter[status]": "PASS",
|
|
"filter[status__in]": "FAIL,MANUAL",
|
|
});
|
|
expect(result).toEqual(new Set(["PASS"]));
|
|
});
|
|
|
|
it("ignores unknown status values and returns null if nothing remains", () => {
|
|
expect(
|
|
getActiveStatusFilter({ "filter[status__in]": "UNKNOWN,FOO" }),
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("getFindingGroupImpactedCounts", () => {
|
|
it("should fall back to pass and fail counts when resources total is zero", () => {
|
|
// Given
|
|
const group = makeGroup({
|
|
resourcesTotal: 0,
|
|
resourcesFail: 0,
|
|
failCount: 3,
|
|
passCount: 2,
|
|
muted: false,
|
|
});
|
|
|
|
// When
|
|
const result = getFindingGroupImpactedCounts(group);
|
|
|
|
// Then
|
|
expect(result).toEqual({ impacted: 3, total: 5 });
|
|
});
|
|
|
|
it("should include manual findings in fallback counts when resources total is zero", () => {
|
|
// Given
|
|
const group = makeGroup({
|
|
resourcesTotal: 0,
|
|
resourcesFail: 0,
|
|
failCount: 3,
|
|
passCount: 2,
|
|
manualCount: 4,
|
|
muted: false,
|
|
});
|
|
|
|
// When
|
|
const result = getFindingGroupImpactedCounts(group);
|
|
|
|
// Then
|
|
expect(result).toEqual({ impacted: 3, total: 9 });
|
|
});
|
|
|
|
it("should include muted pass and fail counts in the denominator when the result is muted", () => {
|
|
// Given
|
|
const group = makeGroup({
|
|
resourcesTotal: 0,
|
|
resourcesFail: 0,
|
|
failCount: 3,
|
|
passCount: 2,
|
|
failMutedCount: 4,
|
|
passMutedCount: 1,
|
|
muted: true,
|
|
});
|
|
|
|
// When
|
|
const result = getFindingGroupImpactedCounts(group);
|
|
|
|
// Then
|
|
expect(result).toEqual({ impacted: 3, total: 10 });
|
|
});
|
|
|
|
it("should keep resource-based counts when resources total is available", () => {
|
|
// Given
|
|
const group = makeGroup({
|
|
resourcesTotal: 6,
|
|
resourcesFail: 4,
|
|
failCount: 2,
|
|
passCount: 1,
|
|
failMutedCount: 5,
|
|
passMutedCount: 3,
|
|
muted: true,
|
|
});
|
|
|
|
// When
|
|
const result = getFindingGroupImpactedCounts(group);
|
|
|
|
// Then
|
|
expect(result).toEqual({ impacted: 4, total: 6 });
|
|
});
|
|
});
|
|
|
|
describe("canDrillDownFindingGroup", () => {
|
|
it("should allow drill-down when resources exist", () => {
|
|
expect(
|
|
canDrillDownFindingGroup(
|
|
makeGroup({
|
|
resourcesTotal: 2,
|
|
failCount: 0,
|
|
}),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("should keep zero-resource fallback groups non-expandable even when fallback counts are present", () => {
|
|
expect(
|
|
canDrillDownFindingGroup(
|
|
makeGroup({
|
|
resourcesTotal: 0,
|
|
failCount: 0,
|
|
passCount: 2,
|
|
manualCount: 1,
|
|
}),
|
|
),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("should keep drill-down disabled for zero-resource groups when the displayed total is zero", () => {
|
|
expect(
|
|
canDrillDownFindingGroup(
|
|
makeGroup({
|
|
resourcesTotal: 0,
|
|
failCount: 0,
|
|
passCount: 0,
|
|
}),
|
|
),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getFilteredFindingGroupDelta", () => {
|
|
it("falls back to the aggregate delta when no status filter is active", () => {
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newPassCount: 2,
|
|
}),
|
|
{},
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("ignores deltas that belong to filtered-out statuses", () => {
|
|
// Filter is FAIL, but the only delta is a new PASS → should be hidden.
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newPassCount: 3,
|
|
}),
|
|
{ "filter[status__in]": "FAIL" },
|
|
),
|
|
).toBe("none");
|
|
});
|
|
|
|
it("surfaces FAIL deltas when the filter is FAIL", () => {
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newFailCount: 1,
|
|
}),
|
|
{ "filter[status]": "FAIL" },
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("counts muted breakdown counters towards the filtered status", () => {
|
|
// A muted new FAIL still belongs to the FAIL bucket — a FAIL filter
|
|
// should still light up the "new" indicator.
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newFailMutedCount: 1,
|
|
}),
|
|
{ "filter[status]": "FAIL" },
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("sums multiple filtered statuses from filter[status__in]", () => {
|
|
// Filter is FAIL+MANUAL, new delta is only in MANUAL → should still show.
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newManualCount: 1,
|
|
}),
|
|
{ "filter[status__in]": "FAIL,MANUAL" },
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("prefers new over changed within the filtered status", () => {
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newFailCount: 1,
|
|
changedFailCount: 2,
|
|
}),
|
|
{ "filter[status]": "FAIL" },
|
|
),
|
|
).toBe("new");
|
|
});
|
|
|
|
it("returns changed when only changed counters match the filtered status", () => {
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newPassCount: 2, // filtered out
|
|
changedFailCount: 1,
|
|
}),
|
|
{ "filter[status]": "FAIL" },
|
|
),
|
|
).toBe("changed");
|
|
});
|
|
|
|
it("falls back to the aggregate delta when breakdowns are missing (legacy API)", () => {
|
|
// No breakdown fields populated but legacy newCount is set. With a FAIL
|
|
// filter active we cannot know which status bucket it belongs to, so we
|
|
// fall back to showing the delta rather than silently hiding it.
|
|
expect(
|
|
getFilteredFindingGroupDelta(
|
|
makeGroup({
|
|
newCount: 1,
|
|
}),
|
|
{ "filter[status]": "FAIL" },
|
|
),
|
|
).toBe("new");
|
|
});
|
|
});
|