Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web-report/src-e2e/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ describe('App test', () => {

await waitFor(() => {
expect(screen.getByTestId('faults-component-total-faults')).toContainHTML(`${total_faults}`);
expect(screen.getByTestId('faults-component-fault-counts')).toContainHTML(faultCounts.size.toString());
expect(screen.getByTestId('faults-component-fault-counts')).toContainHTML(faultCounts.length.toString());
});
});
});
81 changes: 6 additions & 75 deletions web-report/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,12 @@
import './App.css'
import {Dashboard} from "@/components/Dashboard.tsx";
import {useEffect, useState} from "react";
import {WebFuzzingReport} from "@/types/GeneratedTypes.tsx";
import {LoadingScreen} from "@/components/LoadingScreen.tsx";
import {fetchFileContent} from "@/lib/utils";
import {ITestFiles} from "@/types/General.tsx";
import {AppProvider} from "@/AppProvider.tsx";
import {AppContent} from "@/AppContent.tsx";

function App() {

const [data, setData] = useState<WebFuzzingReport | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [testFiles, setTestFiles] = useState<ITestFiles[]>([]);

useEffect(() => {
const fetchData = async () => {
try {
const jsonData = await fetchFileContent('./report.json') as WebFuzzingReport;
setData(jsonData);
} catch (error: Error | unknown) {
if (error instanceof Error) {
setError("Could not load the report file. Please check if the file exists and is accessible in /public folder.");
} else {
console.error(error);
}
} finally {
setLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
if(data?.test_file_paths){
data.test_file_paths.map(file => {
fetchFileContent(file).then((content) => {
if (typeof content === "string") {
setTestFiles(prev => [...prev, {
name: file,
code: content
}]);
} else {
setError("Could not load the test file. Please check if the file exists and is accessible.");
}
}).catch((error) => {
console.error(error);
setError("Could not load the test file. Please check if the file exists and is accessible.");
})
})
}
}, [data]);

if (error){
return (
<main className="min-h-screen p-4 bg-gray-100">
<div className="flex items-center justify-center min-h-screen">
<div className="bg-white p-4 rounded shadow-md">
<h1 className="text-xl font-bold">Error</h1>
<p>{error}</p>
</div>
</div>
</main>
)
}

if (loading) {
return (
<main className="min-h-screen p-4 bg-gray-100">
<LoadingScreen/>
</main>
)
}
return (
<main className="min-h-screen p-4 bg-gray-100">
{data && <Dashboard data={data} test_files={testFiles} />}
</main>
return(
<AppProvider>
<AppContent/>
</AppProvider>
)
}

Expand Down
34 changes: 34 additions & 0 deletions web-report/src/AppContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useAppContext} from "@/AppProvider.tsx";
import {LoadingScreen} from "@/components/LoadingScreen.tsx";
import {Dashboard} from "@/components/Dashboard.tsx";

export const AppContent: React.FC = () => {
const {data, loading, error} = useAppContext();

if (error){
return (
<main className="min-h-screen p-4 bg-gray-100">
<div className="flex items-center justify-center min-h-screen">
<div className="bg-white p-4 rounded shadow-md">
<h1 className="text-xl font-bold">Error</h1>
<p>{error}</p>
</div>
</div>
</main>
)
}

if (loading) {
return (
<main className="min-h-screen p-4 bg-gray-100">
<LoadingScreen/>
</main>
)
}

return (
<main className="min-h-screen p-4 bg-gray-100">
{data && <Dashboard/>}
</main>
)
}
153 changes: 153 additions & 0 deletions web-report/src/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {createContext, useContext, useState, ReactNode, useEffect} from 'react';
import {WebFuzzingCommonsReport} from "@/types/GeneratedTypes.tsx";
import {ITestFiles} from "@/types/General.tsx";
import {fetchFileContent, ITransformedReport, transformWebFuzzingReport} from "@/lib/utils.tsx";

type AppContextType = {
data: WebFuzzingCommonsReport | null;
loading: boolean;
error: string | null;
testFiles: ITestFiles[];
transformedReport: ITransformedReport[];
filterEndpoints: (activeFilters: Record<number, string>) => ITransformedReport[];
filteredEndpoints: ITransformedReport[];
};

const AppContext = createContext<AppContextType | undefined>(undefined);

type AppProviderProps = {
children: ReactNode;
};

export const AppProvider = ({ children }: AppProviderProps) => {

const [data, setData] = useState<WebFuzzingCommonsReport | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [testFiles, setTestFiles] = useState<ITestFiles[]>([]);
const transformedReport = transformWebFuzzingReport(data);

useEffect(() => {
const fetchData = async () => {
try {
const jsonData = await fetchFileContent('./report.json') as WebFuzzingCommonsReport;
setData(jsonData);
} catch (error: Error | unknown) {
if (error instanceof Error) {
setError("Could not load the report file. Please check if the file exists and is accessible in /public folder.");
} else {
console.error(error);
}
} finally {
setLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
if(data?.test_file_paths){
data.test_file_paths.map(file => {
fetchFileContent(file).then((content) => {
if (typeof content === "string") {
setTestFiles(prev => [...prev, {
name: file,
code: content
}]);
} else {
setError("Could not load the test file. Please check if the file exists and is accessible.");
}
}).catch((error) => {
console.error(error);
setError("Could not load the test file. Please check if the file exists and is accessible.");
})
})
}
}, [data]);

const [filteredEndpoints, setFilteredEndpoints] = useState(transformedReport);

useEffect(() => {
// Transform the report data into a format suitable for filtering
if (data) {
const transformed = transformWebFuzzingReport(data);
setFilteredEndpoints(transformed);
}
}, [data]);

const filterEndpoints = (activeFilters: Record<number, string>) => {
// Filter the endpoints based on the active filters
const filtered = transformedReport.filter(endpoint => {
// If no filters are active, show all endpoints
if (Object.keys(activeFilters).length === 0) {
return true;
}

// Check if any status code or fault code is marked as "removed"
const hasRemovedStatusCode = endpoint.http_status_codes.some(code =>
activeFilters[code.code] === "removed"
);
const hasRemovedFaultCode = endpoint.faults.some(code =>
activeFilters[-code.code] === "removed"
);

// Check if any status code or fault code is marked as "active"
const hasActiveStatusCode = endpoint.http_status_codes.some(code =>
activeFilters[code.code] === "active"
);
const hasActiveFaultCode = endpoint.faults.some(code =>
activeFilters[-code.code] === "active"
);

const hasActiveFilter = activeFilters && Object.values(activeFilters).some((value) => value === "active");
const hasRemovedFilter = activeFilters && Object.values(activeFilters).some((value) => value === "removed");

if (!hasActiveFilter && !hasRemovedFilter) {
// If no filters are active, show all endpoints
return true;
}

if (hasActiveFilter) {
if (hasRemovedFilter) {
// If there are both active and removed filters, check if the endpoint matches any of them
if(hasRemovedFaultCode || hasRemovedStatusCode) {
return false;
}

return !!(hasActiveStatusCode || hasActiveFaultCode);

} else {
// If there are only active filters, check if the endpoint matches any of them
return !!(hasActiveStatusCode || hasActiveFaultCode);

}
} else if (hasRemovedFilter) {
// If there are only removed filters, check if the endpoint matches any of them
return !(hasRemovedStatusCode || hasRemovedFaultCode);

} else {
// If there are no active or removed filters, show all endpoints
return true;
}
});
setFilteredEndpoints(filtered)
return filtered;
}

const value: AppContextType = { data, loading, error, testFiles, transformedReport, filterEndpoints, filteredEndpoints };

return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
};

export const useAppContext = (): AppContextType => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext must be used within AppProvider');
}
return context;
};
8 changes: 4 additions & 4 deletions web-report/src/assets/info.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"number_of_http_calls": "Total number of HTTP calls in the generated test suites.",
"code_number_identifiers": "Code number identifiers for detected fault types",
"identifier_name": "Identifier name for the fault type.",
"test_files_located": "{number_of_test_cases} test cases are located in {file_name}",
"http_endpoint_codes": "{number_of_endpoints} endpoints have {code} HTTP code out of {total_endpoints} endpoints.",
"number_of_faults_per_code": "Number of faults detected for each code.",
"distribution_of_endpoints_per_code": "Distribution of endpoints per code (Affected/Total Endpoints).",
"distribution_tooltip": "{operation_count} {endpoint_text} {code} error code out of {totalEndpointNumber} endpoints.",
"generated_test_files": "Number of generated test files.",
"generated_test_cases": "Number of generated test cases.",
"total_faults": "Total number of faults detected in the API.",
"distinct_fault_types": "Total number of distinct fault types detected in the API.",
"creation_date": "Date when the report was generated.",
"tool_name_version": "Name and version of the tool that generated the report.",
"schema_version": "Version of the schema used for the report.",
"status_2xx": "Coverage of the 2xx status codes",
"status_3xx": "Coverage of the 3xx status codes",
"status_4xx": "Coverage of the 4xx status codes",
"status_5xx": "Coverage of the 5xx status codes",
"fault_codes": [
{
"short_definition": "HTTP_STATUS_500",
Expand Down
26 changes: 13 additions & 13 deletions web-report/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,16 @@ import {Endpoints} from "@/pages/Endpoints.tsx";
import {TestResults} from "@/pages/TestResults.tsx";

import {ScrollArea, ScrollBar} from "@/components/ui/scroll-area.tsx";
import {WebFuzzingReport} from "@/types/GeneratedTypes.tsx";
import {ITestFiles} from "@/types/General.tsx";
import {useAppContext} from "@/AppProvider.tsx";


export interface ITestTabs {
value: string;
}

export interface IDashboard {
data: WebFuzzingReport;
test_files: Array<ITestFiles>;
}
export const Dashboard: React.FC = () => {
const {data} = useAppContext();

export const Dashboard: React.FC<IDashboard> = ({data, test_files}) => {
const [activeTab, setActiveTab] = useState("overview")

const [testTabs, setTestTabs] = useState<Array<ITestTabs>>([]);
Expand All @@ -46,6 +42,14 @@ export const Dashboard: React.FC<IDashboard> = ({data, test_files}) => {
}
}

if(!data) {
return (
<div className="flex items-center justify-center h-screen">
<p className="text-lg font-semibold">Loading...</p>
</div>
);
}

const numberOfTestCaseOfFiles = data.test_file_paths.map((test_file) => {
return {
"file_name": test_file,
Expand Down Expand Up @@ -114,17 +118,13 @@ export const Dashboard: React.FC<IDashboard> = ({data, test_files}) => {
</TabsContent>

<TabsContent value="endpoints">
<Endpoints addTestTab={addTestTab} data={data}/>
<Endpoints addTestTab={addTestTab}/>
</TabsContent>

{
testTabs.map((test, index) => (
<TabsContent value={`${test.value}`} key={index}>
<TestResults test_case_name={test.value}
test_cases={data.test_cases}
found_faults={data.faults.found_faults}
problem_details={data.problem_details}
test_files={test_files}/>
<TestResults test_case_name={test.value} />
</TabsContent>
))
}
Expand Down
Loading