Skip to content
This repository was archived by the owner on Jul 8, 2025. It is now read-only.

Commit 44ab1ac

Browse files
committed
test: add Dashboard tests
1 parent 9e1c7bf commit 44ab1ac

File tree

6 files changed

+2749
-24
lines changed

6 files changed

+2749
-24
lines changed

src/components/Dashboard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const wrapObjectOutput = (input: string | MaliciousPkgType | null) => {
6565
const isObject = /\{"/.test(input);
6666
if (isObject) {
6767
return (
68-
<pre className="max-h-40 overflow-y-auto whitespace-pre-wrap bg-secondary p-2">
68+
<pre className="max-h-40 overflow-y-auto whitespace-pre-wrap rounded-lg bg-secondary p-2">
6969
<code>{input}</code>
7070
</pre>
7171
);
@@ -159,7 +159,7 @@ export function Dashboard() {
159159
<div className="flex mb-2 mx-2 justify-between w-[calc(100vw-20rem)]">
160160
<div className="flex gap-2 items-center">
161161
<h2 className="font-bold font-lg">All Alerts</h2>
162-
<Badge>{filteredAlerts.length}</Badge>
162+
<Badge data-testid="alerts-count">{filteredAlerts.length}</Badge>
163163
</div>
164164

165165
<div className="flex items-center gap-8">
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { render } from "@/lib/test-utils";
2+
import { screen, waitFor, within } from "@testing-library/react";
3+
import { describe, expect, it, vi } from "vitest";
4+
import { Dashboard } from "../Dashboard";
5+
import React from "react";
6+
import { server } from "@/mocks/msw/node";
7+
import { HttpResponse, http } from "msw";
8+
import userEvent from "@testing-library/user-event";
9+
10+
vi.mock("recharts", async (importOriginal) => {
11+
const originalModule = (await importOriginal()) as Record<string, unknown>;
12+
return {
13+
...originalModule,
14+
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => {
15+
return <div data-testid="mock-responsive-container">{children}</div>;
16+
},
17+
};
18+
});
19+
20+
function mockAlertsWithMaliciousPkg() {
21+
server.use(
22+
http.get("*/dashboard/alerts", () => {
23+
return HttpResponse.json([
24+
{
25+
conversation: {
26+
question_answers: [
27+
{
28+
question: {
29+
message:
30+
"\n\n```tsx src/App.tsx (23-29)\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = \"REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>\";\n const breadcrumb = useBreadcrumb();\n\n\n```\nPlease explain the cause of this error and how to solve it: 'test' is declared but its value is never read.",
31+
timestamp: "2025-01-02T13:42:48.617799Z",
32+
message_id: "c8a6afd1-98d7-43c4-97ff-7b183b8f6a5f",
33+
},
34+
answer: {
35+
message:
36+
'The error message "\'test\' is declared but its value is never read" indicates that the variable `test` is defined in your code, but it is never used anywhere. This can lead to confusion for other developers reading the code and might indicate that the intended functionality has not been implemented.\n\n### Cause of the Error\n\n1. **Unused Variable**: The variable `test` is declared but not used in the rest of the code within the `App` component. This can be a result of incomplete implementation or a placeholder that was meant to be used later but was forgotten.\n\n### How to Solve It\n\nYou have a few options to resolve this issue:\n\n1. **Remove the Unused Variable**:\n - If the variable is not needed, you can safely remove it to clean up the code and eliminate the warning.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n2. **Use the Variable**:\n - If the variable is intended to be used, ensure that it is utilized somewhere in the component. For example, you could log it for debugging purposes or use it in some part of the application logic.\n\n ```tsx\n const { prompts, loading, fetchPrompts } = usePromptsStore();\n useSse();\n\n const test = "REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>";\n const breadcrumb = useBreadcrumb();\n\n useEffect(() => {\n console.log(test); // Example usage\n fetchPrompts();\n }, [fetchPrompts]);\n ```\n\n3. **Suppress the Warning (Not Recommended)**:\n - If you are sure that the variable is intentionally unused and do not want to remove it, you can suppress the warning. However, this is generally not recommended as it can lead to oversight of real issues.\n\n ```tsx\n // @ts-ignore\n const test = "REDACTED<$lCzMsqxUWElmZqd/qX4llR3pqsrBJVxkt6SB6WXXKKhYG37ZnscxPawjfZmgPVoSpcSR975hOIE2s/6iVr2JETpVdwdaG4hAYlzJ4tteJg==>";\n const breadcrumb = useBreadcrumb();\n ```\n\n### Best Practices\n\n- **Keep Code Clean**: Regularly clean up unused variables to maintain a clean and understandable codebase.\n- **Use Linters**: Utilize tools like ESLint to automatically detect and flag unused variables, helping you maintain code quality.\n\nBy addressing this issue, you can improve the maintainability and readability of your code.',
37+
timestamp: "2025-01-02T13:42:49.361714Z",
38+
message_id: "a542408b-d0bc-4f6f-8b71-64c93c1e388a",
39+
},
40+
},
41+
],
42+
provider: "vllm",
43+
type: "chat",
44+
chat_id: "chatcmpl-7d87679de7ed41639eb91d8ebbaa6f72",
45+
conversation_timestamp: "2025-01-02T13:42:48.617799Z",
46+
},
47+
alert_id: "c8a6afd1-98d7-43c4-97ff-7b183b8f6a5f",
48+
code_snippet: null,
49+
trigger_string:
50+
'GitHub - Access Token:\n useSse();\n\n const test = "ghp_token";\n const breadcrumb = useBreadcrumb();\n\n',
51+
trigger_type: "codegate-secrets",
52+
trigger_category: "critical",
53+
timestamp: "2025-01-02T13:42:48.617799Z",
54+
},
55+
{
56+
conversation: {
57+
question_answers: [
58+
{
59+
question: {
60+
message:
61+
'\n\n```py testing_file.py (1-15)\nimport invokehttp\nimport fastapi\nfrom fastapi import FastAPI, Request, Response, HTTPException\nimport numpy\n\n\[email protected]("/providers", methods=["GET"])\ndef get_providers():\n return jsonify([provider.to_dict() for provider in registry.providers])\n\nGITHUB_TOKEN="ghp_apiToke"\n\nif __name__ == "__main__":\n app.run()\n\n```\nare there malicious?',
62+
timestamp: "2024-12-09T17:50:04.418017Z",
63+
message_id: "fa53ba0d-4fb0-4770-a563-88e504af621f",
64+
},
65+
answer: {
66+
message:
67+
'Warning: CodeGate detected one or more potentially malicious or archived packages.\n- Pkg 1: [trustypkg.dev/pypi/invokehttp](https://www.trustypkg.dev/pypi/invokehttp)\n\nYes, there is a security concern in your code. The `invokehttp` package is known to be malicious and should not be used in your project. Here\'s how to make your code more secure:\n\n1. Remove the malicious package and replace it with safe alternatives:\n - Instead of `invokehttp`, you can use well-maintained packages like `requests` or `httpx` for HTTP requests\n \nHere\'s the corrected version:\n\n```python testing_file.py\nimport requests # or httpx\nimport fastapi\nfrom fastapi import FastAPI, Request, Response, HTTPException\nimport numpy\n\n\[email protected]("/providers", methods=["GET"])\ndef get_providers():\n return jsonify([provider.to_dict() for provider in registry.providers])\n\nGITHUB_TOKEN="REDACTED<$SoFD847FAe6yVl6NWtk/p5+X8sQW5eU2NO0gHwbcCH4JtuEeS3Mi1IZ0BTM3oU4028c7+Vn9kkCvHQQvp8YnevjxIbZftv0xtK1iH7BgRqrAFTyh1A==>"\n\nif __name__ == "__main__":\n app.run()\n```\n\nAdditional security recommendations:\n1. Make sure to manage your dependencies using a requirements.txt or Pipfile\n2. Regularly update your dependencies to get security patches\n3. Consider using a virtual environment for your project\n4. Use package security scanning tools like `safety` or `bandit` to check for known vulnerabilities in your dependencies',
68+
timestamp: "2024-12-09T17:50:20.846946Z",
69+
message_id: "91b84560-2d36-4aab-807a-c6f4612dd5fd",
70+
},
71+
},
72+
],
73+
provider: "anthropic",
74+
type: "chat",
75+
chat_id: "chatcmpl-0f3c7eb8-4306-452b-a415-b2de8867e734",
76+
conversation_timestamp: "2024-12-09T17:50:04.418017Z",
77+
},
78+
alert_id: "fa53ba0d-4fb0-4770-a563-88e504af621f",
79+
code_snippet: null,
80+
trigger_string: {
81+
name: "invokehttp",
82+
type: "pypi",
83+
status: "malicious",
84+
description: "Python HTTP for Humans.",
85+
},
86+
trigger_type: "codegate-context-retriever",
87+
trigger_category: "critical",
88+
timestamp: "2024-12-09T17:50:04.418017Z",
89+
},
90+
]);
91+
}),
92+
);
93+
}
94+
95+
describe("Dashboard", () => {
96+
it("should render charts and table", async () => {
97+
render(<Dashboard />);
98+
expect(screen.getByText(/security issues detected/i)).toBeVisible();
99+
expect(screen.getByText(/malicious packages by type/i)).toBeVisible();
100+
expect(screen.getByText(/alerts by date/i)).toBeVisible();
101+
102+
expect(
103+
screen.getByRole("heading", {
104+
name: /all alerts/i,
105+
}),
106+
).toBeVisible();
107+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("0");
108+
expect(
109+
(await screen.findAllByTestId(/mock-responsive-container/i)).length,
110+
).toEqual(1);
111+
await waitFor(() => expect(screen.getByText("Jan 02")).toBeVisible());
112+
await waitFor(() => expect(screen.getByText("Jan 03")).toBeVisible());
113+
await waitFor(() => expect(screen.getByText("Jan 07")).toBeVisible());
114+
115+
expect(screen.getByTestId("codegate-secrets-count")).toHaveTextContent(
116+
"13",
117+
);
118+
119+
expect(
120+
within(screen.getByTestId("malicious-piechart")).getByText("N/A"),
121+
).toBeVisible();
122+
123+
expect(
124+
screen.getByRole("columnheader", {
125+
name: /trigger type/i,
126+
}),
127+
).toBeVisible();
128+
expect(
129+
screen.getByRole("columnheader", {
130+
name: /trigger token/i,
131+
}),
132+
).toBeVisible();
133+
134+
expect(
135+
screen.getByRole("columnheader", {
136+
name: /file/i,
137+
}),
138+
).toBeVisible();
139+
expect(
140+
screen.getByRole("columnheader", {
141+
name: /code/i,
142+
}),
143+
).toBeVisible();
144+
expect(
145+
screen.getByRole("columnheader", {
146+
name: /timestamp/i,
147+
}),
148+
).toBeVisible();
149+
150+
expect(
151+
screen.getByRole("switch", {
152+
name: /malicious packages/i,
153+
}),
154+
).toBeVisible();
155+
expect(screen.getByRole("searchbox")).toBeVisible();
156+
157+
const row = screen.getAllByRole("row")[1];
158+
159+
expect(within(row).getByText(/ghp_token/i)).toBeVisible();
160+
expect(within(row).getByText(/codegate-secrets/i)).toBeVisible();
161+
expect(within(row).getAllByText(/n\/a/i).length).toEqual(2);
162+
expect(within(row).getByText(/2025\/01\/07/i)).toBeVisible();
163+
expect(within(row).getByText(/18:02 pm/i)).toBeVisible();
164+
165+
// check trigger_string null
166+
expect(
167+
within(screen.getAllByRole("row")[2]).getAllByText(/n\/a/i).length,
168+
).toEqual(3);
169+
});
170+
171+
it("should render malicious pkg", async () => {
172+
mockAlertsWithMaliciousPkg();
173+
render(<Dashboard />);
174+
175+
expect(
176+
(await screen.findAllByTestId(/mock-responsive-container/i)).length,
177+
).toEqual(2);
178+
179+
expect(
180+
within(screen.getByTestId("malicious-piechart")).getByText(/1/i),
181+
).toBeVisible();
182+
183+
expect(
184+
within(screen.getByTestId("security-issues-barchart")).getByText(
185+
/codegate-context-retriever/i,
186+
),
187+
).toBeVisible();
188+
189+
expect(screen.getByText(/package:/i)).toBeVisible();
190+
expect(
191+
screen.getByRole("link", {
192+
name: /pypi\/invokehttp/i,
193+
}),
194+
).toHaveAttribute(
195+
"href",
196+
"https://www.insight.stacklok.com/report/pypi/invokehttp",
197+
);
198+
expect(
199+
screen.getByText(/malicious python http for humans\./i),
200+
).toBeVisible();
201+
});
202+
203+
it("should filter by malicious pkg", async () => {
204+
mockAlertsWithMaliciousPkg();
205+
render(<Dashboard />);
206+
207+
expect(
208+
(await screen.findAllByTestId(/mock-responsive-container/i)).length,
209+
).toEqual(2);
210+
211+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("2");
212+
expect(
213+
screen.getByRole("cell", {
214+
name: /codegate-secrets/i,
215+
}),
216+
).toBeVisible();
217+
218+
userEvent.click(
219+
screen.getByRole("switch", {
220+
name: /malicious packages/i,
221+
}),
222+
);
223+
224+
await waitFor(() =>
225+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("1"),
226+
);
227+
228+
expect(screen.getByText(/package:/i)).toBeVisible();
229+
expect(
230+
screen.getByRole("link", {
231+
name: /pypi\/invokehttp/i,
232+
}),
233+
).toBeVisible();
234+
expect(
235+
screen.getByText(/malicious python http for humans\./i),
236+
).toBeVisible();
237+
238+
userEvent.click(
239+
screen.getByRole("switch", {
240+
name: /malicious packages/i,
241+
}),
242+
);
243+
await waitFor(() =>
244+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("2"),
245+
);
246+
});
247+
248+
it("should search by secrets alert", async () => {
249+
mockAlertsWithMaliciousPkg();
250+
render(<Dashboard />);
251+
252+
expect(
253+
(await screen.findAllByTestId(/mock-responsive-container/i)).length,
254+
).toEqual(2);
255+
256+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("2");
257+
expect(
258+
screen.getByRole("cell", {
259+
name: /codegate-secrets/i,
260+
}),
261+
).toBeVisible();
262+
expect(
263+
screen.getByRole("cell", {
264+
name: /codegate-context-retriever/i,
265+
}),
266+
).toBeVisible();
267+
268+
userEvent.type(screen.getByRole("searchbox"), "codegate-secrets");
269+
270+
await waitFor(() =>
271+
expect(screen.getByTestId(/alerts-count/i)).toHaveTextContent("1"),
272+
);
273+
const row = screen.getAllByRole("row")[1];
274+
expect(within(row).getByText(/ghp_token/i)).toBeVisible();
275+
expect(within(row).getByText(/codegate-secrets/i)).toBeVisible();
276+
});
277+
});

0 commit comments

Comments
 (0)