mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-06 11:44:08 +00:00
838 lines
23 KiB
TypeScript
838 lines
23 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { compileLighthouseContext, prepareLighthouseContext } from "./compiler";
|
|
import {
|
|
buildComplianceContext,
|
|
buildFilteredProviderContext,
|
|
buildFindingSeveritySummaryContext,
|
|
buildFindingStatusSummaryContext,
|
|
buildProviderGroupContext,
|
|
buildServiceSummaryContext,
|
|
} from "./contributions";
|
|
import { buildLighthousePageContext } from "./pages";
|
|
import { lighthouseContextEnvelopeSchema } from "./schema";
|
|
import { getApiLighthouseContextByteLength } from "./transport";
|
|
|
|
describe("lighthouseContextEnvelopeSchema", () => {
|
|
describe("when validating an inline page context", () => {
|
|
it("should accept a valid version 1 envelope", () => {
|
|
// Given
|
|
const envelope = {
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
filters: { severity: ["critical"] },
|
|
},
|
|
],
|
|
};
|
|
|
|
// When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse(envelope);
|
|
|
|
// Then
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it("should accept every supported contextual item kind", () => {
|
|
// Given
|
|
const envelope = {
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
checkId: "check-1",
|
|
severity: "critical",
|
|
},
|
|
{
|
|
kind: "resource",
|
|
id: "resource-1",
|
|
source: "selection",
|
|
scopeKey: "resources:/resources",
|
|
label: "Selected resource",
|
|
resourceId: "resource-1",
|
|
service: "s3",
|
|
failedFindingsCount: 4,
|
|
},
|
|
{
|
|
kind: "compliance",
|
|
id: "cis-1.5",
|
|
source: "automatic",
|
|
scopeKey: "compliance:/compliance",
|
|
label: "CIS 1.5",
|
|
framework: "cis_1.5_aws",
|
|
score: 82,
|
|
totals: { passed: 82, failed: 18, total: 100 },
|
|
},
|
|
{
|
|
kind: "attack_path",
|
|
id: "query-1",
|
|
source: "automatic",
|
|
scopeKey: "attack-paths:/attack-paths/query-builder",
|
|
label: "Attack path query",
|
|
scanId: "scan-1",
|
|
queryId: "query-1",
|
|
parameters: { region: "eu-west-1", limit: 10 },
|
|
nodeCount: 12,
|
|
edgeCount: 11,
|
|
},
|
|
{
|
|
kind: "scan",
|
|
id: "scans-summary",
|
|
source: "automatic",
|
|
scopeKey: "scans:/scans",
|
|
label: "Visible scans",
|
|
total: 25,
|
|
},
|
|
{
|
|
kind: "provider",
|
|
id: "providers-summary",
|
|
source: "automatic",
|
|
scopeKey: "providers:/providers",
|
|
label: "Visible providers",
|
|
total: 7,
|
|
},
|
|
],
|
|
};
|
|
|
|
// When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse(envelope);
|
|
|
|
// Then
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("when context exceeds transport limits", () => {
|
|
it("should reject filters containing more than 20 values", () => {
|
|
// Given
|
|
const envelope = {
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
filters: {
|
|
severity: Array.from({ length: 21 }, (_, index) => `${index}`),
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
// When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse(envelope);
|
|
|
|
// Then
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("should reject more than twelve context items", () => {
|
|
// Given
|
|
const item = {
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
};
|
|
|
|
// When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: Array.from({ length: 13 }, (_, index) => ({
|
|
...item,
|
|
id: `page-${index}`,
|
|
})),
|
|
});
|
|
|
|
// Then
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("should reject strings longer than 256 characters", () => {
|
|
// Given / When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "x".repeat(257),
|
|
path: "/findings",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Then
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("should reject unknown item kinds", () => {
|
|
// Given / When
|
|
const result = lighthouseContextEnvelopeSchema.safeParse({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "secret",
|
|
id: "credentials",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Credentials",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Then
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("compileLighthouseContext", () => {
|
|
describe("when multiple contributors describe the same entity", () => {
|
|
it("should deduplicate items by kind and id", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const items = [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
severity: "critical",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "Duplicate finding",
|
|
findingId: "finding-1",
|
|
severity: "critical",
|
|
},
|
|
];
|
|
|
|
// When
|
|
const context = compileLighthouseContext(items, scopeKey);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => `${item.kind}:${item.id}`)).toEqual([
|
|
"page:findings",
|
|
"finding:finding-1",
|
|
]);
|
|
});
|
|
|
|
it("should retain the highest-priority duplicate", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const items = [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "focused",
|
|
scopeKey,
|
|
label: "Focused finding",
|
|
findingId: "finding-1",
|
|
},
|
|
];
|
|
|
|
// When
|
|
const context = compileLighthouseContext(items, scopeKey);
|
|
|
|
// Then
|
|
expect(context?.items[1]).toMatchObject({
|
|
id: "finding-1",
|
|
source: "focused",
|
|
label: "Focused finding",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when contributors arrive in render order", () => {
|
|
it("should order page, focused, selection, and summary items deterministically", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const items = [
|
|
{
|
|
kind: "finding",
|
|
id: "findings-summary",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Visible findings",
|
|
findingId: "summary",
|
|
total: 42,
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-focused",
|
|
source: "focused",
|
|
scopeKey,
|
|
label: "Focused finding",
|
|
findingId: "finding-focused",
|
|
},
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
];
|
|
|
|
// When
|
|
const context = compileLighthouseContext(items, scopeKey);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual([
|
|
"findings",
|
|
"finding-focused",
|
|
"finding-1",
|
|
"findings-summary",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("when serialized context exceeds the byte limit", () => {
|
|
it("should drop lowest-priority items until the context fits", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const page = {
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
};
|
|
const selection = {
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
};
|
|
const summaries = Array.from({ length: 6 }, (_, index) => ({
|
|
kind: "finding",
|
|
id: `summary-${index}`,
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: `Summary ${index} ${"x".repeat(240)}`,
|
|
findingId: `summary-${index}`,
|
|
checkId: `check-${index}-${"y".repeat(240)}`,
|
|
providerUid: `provider-${index}-${"z".repeat(237)}`,
|
|
total: index,
|
|
}));
|
|
|
|
// When
|
|
const context = compileLighthouseContext(
|
|
[page, selection, ...summaries],
|
|
scopeKey,
|
|
);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual([
|
|
"findings",
|
|
"finding-1",
|
|
"summary-0",
|
|
"summary-1",
|
|
"summary-2",
|
|
"summary-3",
|
|
]);
|
|
});
|
|
|
|
it("should drop oversized selections while keeping the page", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const page = {
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
};
|
|
const selections = Array.from({ length: 2 }, (_, index) => ({
|
|
kind: "finding",
|
|
id: `finding-${index}`,
|
|
source: "selection",
|
|
scopeKey,
|
|
label: "x".repeat(256),
|
|
findingId: "y".repeat(256),
|
|
checkId: "z".repeat(256),
|
|
severity: "s".repeat(256),
|
|
status: "t".repeat(256),
|
|
providerUid: "p".repeat(256),
|
|
resourceUid: "r".repeat(256),
|
|
region: "g".repeat(256),
|
|
}));
|
|
|
|
// When
|
|
const context = compileLighthouseContext([...selections, page], scopeKey);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual([
|
|
"findings",
|
|
"finding-0",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("when context exceeds the item limit", () => {
|
|
it("should progressively drop only the lowest-priority items", () => {
|
|
// Given
|
|
const scopeKey = "findings:/findings";
|
|
const page = {
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Findings",
|
|
path: "/findings",
|
|
};
|
|
const focused = {
|
|
kind: "finding",
|
|
id: "focused",
|
|
source: "focused",
|
|
scopeKey,
|
|
label: "Focused finding",
|
|
findingId: "focused",
|
|
};
|
|
const selections = Array.from({ length: 2 }, (_, index) => ({
|
|
kind: "finding",
|
|
id: `selection-${index}`,
|
|
source: "selection",
|
|
scopeKey,
|
|
label: `Selected finding ${index}`,
|
|
findingId: `selection-${index}`,
|
|
}));
|
|
const summaries = Array.from({ length: 12 }, (_, index) => ({
|
|
kind: "finding",
|
|
id: `summary-${index}`,
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: `Summary ${index}`,
|
|
findingId: `summary-${index}`,
|
|
total: index,
|
|
}));
|
|
|
|
// When
|
|
const context = compileLighthouseContext(
|
|
[page, ...summaries, ...selections, focused],
|
|
scopeKey,
|
|
);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual([
|
|
"findings",
|
|
"focused",
|
|
"selection-0",
|
|
"selection-1",
|
|
"summary-0",
|
|
"summary-1",
|
|
"summary-2",
|
|
"summary-3",
|
|
"summary-4",
|
|
"summary-5",
|
|
"summary-6",
|
|
"summary-7",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("when the Overview publishes every contributor", () => {
|
|
it("should fit a fully populated Overview within transport limits", () => {
|
|
// Given every real Overview contributor plus the page item
|
|
const candidates = [
|
|
buildLighthousePageContext(
|
|
"/",
|
|
new URLSearchParams(
|
|
"filter[provider_id__in]=b81165a0-4f28-4b5c-9a41-1e2d3c4b5a69&filter[provider_type__in]=aws",
|
|
),
|
|
),
|
|
buildComplianceContext({
|
|
pathname: "/",
|
|
id: "prowler-threat-score",
|
|
framework: "Prowler ThreatScore",
|
|
score: 62.4,
|
|
scoreDelta: -3.21,
|
|
criticalRequirementsCount: 5,
|
|
worstSection: "1.2 Attack Surface",
|
|
worstSectionScore: 38.6,
|
|
passed: 120,
|
|
failed: 40,
|
|
total: 160,
|
|
}),
|
|
buildFindingStatusSummaryContext({
|
|
pathname: "/",
|
|
passed: 320,
|
|
failed: 80,
|
|
newPassed: 12,
|
|
newFailed: 7,
|
|
}),
|
|
buildFindingSeveritySummaryContext({
|
|
pathname: "/",
|
|
severityCounts: {
|
|
critical: 4,
|
|
high: 18,
|
|
medium: 40,
|
|
low: 15,
|
|
informational: 3,
|
|
},
|
|
}),
|
|
buildComplianceContext({
|
|
pathname: "/",
|
|
id: "watchlist-ens_rd2022_aws",
|
|
framework: "ENS RD2022",
|
|
score: 30,
|
|
}),
|
|
buildComplianceContext({
|
|
pathname: "/",
|
|
id: "watchlist-cis_1.5_aws",
|
|
framework: "CIS AWS 1.5",
|
|
score: 45,
|
|
}),
|
|
buildServiceSummaryContext({
|
|
pathname: "/",
|
|
service: "s3",
|
|
failedFindingsCount: 34,
|
|
total: 120,
|
|
}),
|
|
buildFilteredProviderContext({
|
|
pathname: "/",
|
|
id: "b81165a0-4f28-4b5c-9a41-1e2d3c4b5a69",
|
|
uid: "123456789012",
|
|
type: "aws",
|
|
alias: "Production",
|
|
}),
|
|
buildProviderGroupContext({
|
|
pathname: "/",
|
|
id: "3f2a1b0c-9d8e-7f60-5a4b-3c2d1e0f9a8b",
|
|
name: "Production accounts",
|
|
}),
|
|
];
|
|
|
|
// When
|
|
const context = compileLighthouseContext(candidates, "overview:/");
|
|
|
|
// Then every real Overview contributor fits within the budget
|
|
expect(context).toBeDefined();
|
|
expect(context?.items).toHaveLength(candidates.length);
|
|
expect(context?.items[0]?.kind).toBe("page");
|
|
expect(context?.items.some((item) => item.id.startsWith("group-"))).toBe(
|
|
true,
|
|
);
|
|
expect(getApiLighthouseContextByteLength(context!)).toBeLessThanOrEqual(
|
|
4 * 1024,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when contributors belong to another page", () => {
|
|
it("should ignore stale scoped data", () => {
|
|
// Given / When
|
|
const context = compileLighthouseContext(
|
|
[
|
|
{
|
|
kind: "page",
|
|
id: "resources",
|
|
source: "automatic",
|
|
scopeKey: "resources:/resources",
|
|
label: "Resources",
|
|
path: "/resources",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Old finding",
|
|
findingId: "finding-1",
|
|
},
|
|
],
|
|
"resources:/resources",
|
|
);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual(["resources"]);
|
|
});
|
|
});
|
|
|
|
describe("when current context is invalid", () => {
|
|
it("should return no context so sending remains available", () => {
|
|
// Given / When
|
|
const context = compileLighthouseContext(
|
|
[
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Invalid finding without findingId",
|
|
},
|
|
],
|
|
"findings:/findings",
|
|
);
|
|
|
|
// Then
|
|
expect(context).toBeUndefined();
|
|
});
|
|
|
|
it("should drop only the invalid item and keep the valid ones", () => {
|
|
// Given a valid page plus a finding whose optional field was
|
|
// normalized to null (e.g. by a backend or storage layer)
|
|
const context = compileLighthouseContext(
|
|
[
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
checkId: null,
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-2",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Selected finding",
|
|
findingId: "finding-2",
|
|
},
|
|
],
|
|
"findings:/findings",
|
|
);
|
|
|
|
// Then the null-carrying item drops alone
|
|
expect(context?.items.map((item) => item.id)).toEqual([
|
|
"findings",
|
|
"finding-2",
|
|
]);
|
|
});
|
|
|
|
it("should drop items of unknown kinds without voiding the envelope", () => {
|
|
// Given an item kind from a newer (or rolled-back) UI build
|
|
const context = compileLighthouseContext(
|
|
[
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
{
|
|
kind: "future-widget",
|
|
id: "widget-1",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Unknown widget",
|
|
},
|
|
],
|
|
"findings:/findings",
|
|
);
|
|
|
|
// Then
|
|
expect(context?.items.map((item) => item.id)).toEqual(["findings"]);
|
|
});
|
|
});
|
|
|
|
describe("when automatic items compete for the byte budget", () => {
|
|
it("should evict provider labels before posture summaries", () => {
|
|
// Given a page, a ThreatScore summary, and enough oversized provider
|
|
// labels to exceed the byte budget regardless of arrival order
|
|
const scopeKey = "overview:/";
|
|
const page = {
|
|
kind: "page",
|
|
id: "overview",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Overview",
|
|
path: "/",
|
|
};
|
|
const threatScore = {
|
|
kind: "compliance",
|
|
id: "prowler-threat-score",
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: "Prowler ThreatScore",
|
|
framework: "Prowler ThreatScore",
|
|
score: 62.4,
|
|
};
|
|
const providers = Array.from({ length: 10 }, (_, index) => ({
|
|
kind: "provider",
|
|
id: `provider-${index}`,
|
|
source: "automatic",
|
|
scopeKey,
|
|
label: `Provider ${index} ${"x".repeat(240)}`,
|
|
providerUid: `uid-${index}-${"y".repeat(240)}`,
|
|
}));
|
|
|
|
// When the providers mount before the ThreatScore summary
|
|
const context = compileLighthouseContext(
|
|
[page, ...providers, threatScore],
|
|
scopeKey,
|
|
);
|
|
|
|
// Then the summary survives and only provider labels are evicted
|
|
expect(context?.items.map((item) => item.kind)).toContain("compliance");
|
|
expect(
|
|
context?.items.filter((item) => item.kind === "provider").length,
|
|
).toBeLessThan(providers.length);
|
|
expect(context?.items[1]?.id).toBe("prowler-threat-score");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("prepareLighthouseContext", () => {
|
|
it("should keep the valid items when one carries a malformed optional", () => {
|
|
// Given a stored envelope whose finding had checkId normalized to null
|
|
const context = prepareLighthouseContext({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
{
|
|
kind: "finding",
|
|
id: "finding-1",
|
|
source: "selection",
|
|
scopeKey: "findings:/findings",
|
|
label: "Selected finding",
|
|
findingId: "finding-1",
|
|
checkId: null,
|
|
},
|
|
],
|
|
});
|
|
|
|
// Then the malformed item drops alone instead of voiding the send
|
|
expect(context?.items.map((item) => item.id)).toEqual(["findings"]);
|
|
});
|
|
|
|
it("should scope from the first usable item when the leading one is malformed", () => {
|
|
// Given a leading item with no scopeKey at all
|
|
const context = prepareLighthouseContext({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{ kind: "finding", id: "broken" },
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Then the later valid page item still compiles
|
|
expect(context?.items.map((item) => item.id)).toEqual(["findings"]);
|
|
});
|
|
|
|
it("should not let a malformed foreign-scope item decide the scope", () => {
|
|
// Given a malformed leading item whose scopeKey points at another page
|
|
const context = prepareLighthouseContext({
|
|
schemaVersion: 1,
|
|
transport: "inline",
|
|
items: [
|
|
{ kind: "resource", id: "broken", scopeKey: "resources:/resources" },
|
|
{
|
|
kind: "page",
|
|
id: "findings",
|
|
source: "automatic",
|
|
scopeKey: "findings:/findings",
|
|
label: "Findings",
|
|
path: "/findings",
|
|
},
|
|
],
|
|
});
|
|
|
|
// Then the valid page defines the scope and compiles
|
|
expect(context?.items.map((item) => item.scopeKey)).toEqual([
|
|
"findings:/findings",
|
|
]);
|
|
});
|
|
|
|
it("should return no context for values without an item list", () => {
|
|
expect(prepareLighthouseContext(undefined)).toBeUndefined();
|
|
expect(prepareLighthouseContext({ items: "not-a-list" })).toBeUndefined();
|
|
expect(prepareLighthouseContext({ items: [] })).toBeUndefined();
|
|
});
|
|
});
|