Skip to content

Commit 3fe172a

Browse files
committed
not declared faults
1 parent 32f289c commit 3fe172a

5 files changed

Lines changed: 56 additions & 11 deletions

File tree

web-report/src/assets/info.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"numberOfFaultsPerCode": "Number of faults detected for each code.",
1111
"distributionOfEndpointsPerCode": "Faulty endpoints per code (Affected/Total Endpoints).",
1212
"distributionTooltip": "{operationCount} {endpointText} {code} error code out of {totalEndpoints} endpoints.",
13+
"undeclaredOperationsTooltip": "In addition, {undeclaredCount} operation(s) with this fault are not declared in the API schema, and so are not counted in the endpoint totals.",
14+
"notInSchema": "Faults were detected on this operation, but it is not declared in the API schema. It is not counted in the endpoint statistics.",
1315
"generatedTestFiles": "Number of generated test files.",
1416
"generatedTestCases": "Number of generated test cases.",
1517
"totalFaults": "Total number of faults detected in the API.",

web-report/src/components/EndpointAccordion.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {Badge} from "@/components/ui/badge.tsx";
33
import React, {useState} from "react";
44
import {TestCases} from "@/components/TestCases.tsx";
55
import {getColor, getHoverColor} from "@/lib/utils";
6+
import info from "@/assets/info.json";
67

78
interface IStatusType {
89
code: number | string;
@@ -12,6 +13,7 @@ interface IStatusType {
1213
export interface IEndpointAccordionProps {
1314
endpoint: string;
1415
value: string;
16+
declared?: boolean;
1517
statusCodes: IStatusType[];
1618
faults: IStatusType[];
1719
addTestTab: (value: string, event: React.MouseEvent<HTMLElement>) => void;
@@ -20,6 +22,7 @@ export interface IEndpointAccordionProps {
2022
export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
2123
endpoint,
2224
value,
25+
declared = true,
2326
statusCodes,
2427
faults,
2528
addTestTab
@@ -73,9 +76,14 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
7376
const faultColors = ["bg-red-300", "bg-red-500", "bg-red-700"];
7477
return (
7578
<AccordionItem value={value} className="border-2 border-black mb-4 overflow-hidden" data-testid={endpoint}>
76-
<AccordionTrigger className="bg-blue-100 px-3 sm:px-4 py-3 text-sm sm:text-lg font-bold hover:no-underline hover:bg-blue-200">
79+
<AccordionTrigger className={`px-3 sm:px-4 py-3 text-sm sm:text-lg font-bold hover:no-underline ${declared ? "bg-blue-100 hover:bg-blue-200" : "bg-amber-100 hover:bg-amber-200"}`}>
7780
<div className="flex-1 font-mono break-all text-left">{endpoint}</div>
7881
<div className="flex flex-wrap justify-end gap-1 sm:gap-2 mr-2 sm:mr-4">
82+
{!declared && (
83+
<Badge className="bg-amber-500 font-mono text-xs" title={info.notInSchema}>
84+
NOT IN SCHEMA
85+
</Badge>
86+
)}
7987
{sortedStatusCodes.map((code, idx) => (
8088
<Badge key={`_${idx}`} className={`${getColor(code.code, true, false)} font-mono text-xs`}>
8189
{code.code == -1 ? "NO-RESPONSE" : `H${code.code}`}

web-report/src/components/FaultsComponent.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import faults from "../../../src/main/resources/wfc/faults/fault_categories.json
1111

1212
export const FaultsComponent: React.FC<Faults> = ({totalNumber, foundFaults}) => {
1313
const {data} = useAppContext();
14-
const totalEndpointNumber = data?.problemDetails.rest?.endpointIds.length;
14+
const endpointIds = data?.problemDetails.rest?.endpointIds;
15+
const totalEndpointNumber = endpointIds?.length;
1516

16-
const faultCounts = getFaultCounts(foundFaults);
17+
const faultCounts = getFaultCounts(foundFaults, endpointIds);
1718
const [isModalOpen, setIsModalOpen] = useState(false)
1819
const [currentStatus, setCurrentStatus] = useState(-1);
1920

@@ -80,8 +81,14 @@ export const FaultsComponent: React.FC<Faults> = ({totalNumber, foundFaults}) =>
8081
endpointText: fault.operationCount > 1 ? "endpoints have" : "endpoint has",
8182
code: fault.code,
8283
totalEndpoints:totalEndpointNumber ? totalEndpointNumber : 0
83-
})}>
84-
<div>{fault.operationCount}/{totalEndpointNumber}</div>
84+
}) + (fault.undeclaredOperationCount > 0
85+
? " " + getText(info.undeclaredOperationsTooltip, {undeclaredCount: fault.undeclaredOperationCount})
86+
: "")}>
87+
<div>
88+
{fault.operationCount}/{totalEndpointNumber}
89+
{fault.undeclaredOperationCount > 0 &&
90+
<span className="text-amber-600 font-bold"> +{fault.undeclaredOperationCount}</span>}
91+
</div>
8592
</ReportTooltip>
8693
<div className="col-span-2 text-center font-bold">{fault.count}</div>
8794
</div>

web-report/src/lib/utils.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export const calculateAllStatusCounts = (coveredHttpStatus: CoveredEndpoint[], e
199199
return allStatusCounts;
200200
}
201201

202-
export const getFaultCounts = (foundFaults: FoundFault[]) => {
202+
export const getFaultCounts = (foundFaults: FoundFault[], endpointIds?: string[]) => {
203203
const faultCounts = new Map();
204204
// A fault defines a unique operationId, code, and context. To define a unique fault, we can use a combination of these three properties.
205205

@@ -222,11 +222,17 @@ export const getFaultCounts = (foundFaults: FoundFault[]) => {
222222

223223
return Array.from(uniqueCodes).map(code => {
224224
const faultsWithCode = uniqueFaults.filter(fault => fault.code === code);
225-
const uniqueOperationCounts = new Set(faultsWithCode.map(fault => fault.operationId)).size;
225+
const uniqueOperations = new Set(faultsWithCode.map(fault => fault.operationId));
226+
// Operations not declared in the schema must not skew the endpoint ratio,
227+
// so they are reported separately instead of being counted in operationCount
228+
const undeclaredOperationCount = endpointIds
229+
? [...uniqueOperations].filter(operation => !endpointIds.includes(operation)).length
230+
: 0;
226231
return {
227232
code: code,
228233
count: faultsWithCode.length,
229-
operationCount: uniqueOperationCounts,
234+
operationCount: uniqueOperations.size - undeclaredOperationCount,
235+
undeclaredOperationCount: undeclaredOperationCount,
230236
}
231237
}).sort((a, b) => a.code - b.code);
232238
}
@@ -273,6 +279,9 @@ export const getLanguage = (fileName: string) => {
273279

274280
export interface ITransformedReport {
275281
endpoint: string;
282+
// false when the operation is not declared in the API schema (endpointIds),
283+
// but faults were still detected on it (e.g. an undocumented OPTIONS handler)
284+
declared: boolean;
276285
faults: {
277286
code: number;
278287
testCases: string[];
@@ -294,6 +303,7 @@ export const transformWebFuzzingReport = (original: WebFuzzingCommonsReport | nu
294303
original.problemDetails.rest?.endpointIds.forEach(endpoint => {
295304
endpointMap.set(endpoint, {
296305
endpoint,
306+
declared: true,
297307
httpStatusCodes: [],
298308
faults: []
299309
});
@@ -305,7 +315,12 @@ export const transformWebFuzzingReport = (original: WebFuzzingCommonsReport | nu
305315
}
306316

307317
if (!endpointMap.has(fault.operationId)) {
308-
console.log(`Endpoint ${fault.operationId} not found in endpointIds`);
318+
endpointMap.set(fault.operationId, {
319+
endpoint: fault.operationId,
320+
declared: false,
321+
httpStatusCodes: [],
322+
faults: []
323+
});
309324
}
310325

311326
const endpointData = endpointMap.get(fault.operationId);

web-report/src/pages/Endpoints.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,33 @@ export const Endpoints: React.FC<IProps> = ({addTestTab, openEndpoint, setOpenEn
1414

1515
const {transformedReport, filteredEndpoints, statusFilters, setStatusFilters} = useAppContext();
1616

17+
const declaredEndpoints = transformedReport.filter(endpoint => endpoint.declared);
18+
const filteredDeclaredEndpoints = filteredEndpoints.filter(endpoint => endpoint.declared);
19+
const undeclaredEndpoints = transformedReport.filter(endpoint => !endpoint.declared);
20+
const filteredUndeclaredEndpoints = filteredEndpoints.filter(endpoint => !endpoint.declared);
21+
1722
return (
1823
<div className="border-2 border-black p-3 sm:p-6 rounded-none">
1924
<StatusCodeFilters data={transformedReport} filters={statusFilters} onFiltersChange={setStatusFilters}/>
2025
<div className="flex items-center mb-2">
2126
<h3 className="text-sm font-medium text-gray-700 mr-3"># Endpoints:</h3>
2227
<div className="flex flex-wrap gap-2 font-bold font-mono">
23-
<p className="text-black-400">{filteredEndpoints.length}</p> / <p className="text-red-400">{transformedReport.length}</p>
28+
<p className="text-black-400">{filteredDeclaredEndpoints.length}</p> / <p className="text-red-400">{declaredEndpoints.length}</p>
2429
</div>
2530
</div>
31+
{undeclaredEndpoints.length > 0 &&
32+
<div className="flex items-center mb-2">
33+
<h3 className="text-sm font-medium text-gray-700 mr-3"># Faulty operations not in schema:</h3>
34+
<div className="flex flex-wrap gap-2 font-bold font-mono">
35+
<p className="text-black-400">{filteredUndeclaredEndpoints.length}</p> / <p className="text-amber-600">{undeclaredEndpoints.length}</p>
36+
</div>
37+
</div>
38+
}
2639
<Accordion type="single" collapsible value={openEndpoint} onValueChange={setOpenEndpoint} className="w-full">
2740
{
2841
filteredEndpoints.map((item, index) => (
2942
<EndpointAccordion data-testid="endpoint" key={index} value={item.endpoint}
30-
endpoint={item.endpoint}
43+
endpoint={item.endpoint} declared={item.declared}
3144
statusCodes={item.httpStatusCodes} faults={item.faults}
3245
addTestTab={addTestTab}/>
3346
))

0 commit comments

Comments
 (0)