@clef-sh/ui 0.1.13-beta.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/client/assets/index-CVpAmirt.js +26 -0
- package/dist/client/favicon-96x96.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/favicon.svg +16 -0
- package/dist/client/index.html +50 -0
- package/dist/client-lib/api.d.ts +3 -0
- package/dist/client-lib/api.d.ts.map +1 -0
- package/dist/client-lib/components/Button.d.ts +10 -0
- package/dist/client-lib/components/Button.d.ts.map +1 -0
- package/dist/client-lib/components/CopyButton.d.ts +6 -0
- package/dist/client-lib/components/CopyButton.d.ts.map +1 -0
- package/dist/client-lib/components/EnvBadge.d.ts +7 -0
- package/dist/client-lib/components/EnvBadge.d.ts.map +1 -0
- package/dist/client-lib/components/MatrixGrid.d.ts +13 -0
- package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -0
- package/dist/client-lib/components/Sidebar.d.ts +16 -0
- package/dist/client-lib/components/Sidebar.d.ts.map +1 -0
- package/dist/client-lib/components/StatusDot.d.ts +6 -0
- package/dist/client-lib/components/StatusDot.d.ts.map +1 -0
- package/dist/client-lib/components/TopBar.d.ts +9 -0
- package/dist/client-lib/components/TopBar.d.ts.map +1 -0
- package/dist/client-lib/index.d.ts +12 -0
- package/dist/client-lib/index.d.ts.map +1 -0
- package/dist/client-lib/theme.d.ts +42 -0
- package/dist/client-lib/theme.d.ts.map +1 -0
- package/dist/server/api.d.ts +11 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +1020 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +231 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +74 -0
- package/src/client/App.tsx +205 -0
- package/src/client/api.test.tsx +94 -0
- package/src/client/api.ts +30 -0
- package/src/client/components/Button.tsx +52 -0
- package/src/client/components/CopyButton.test.tsx +43 -0
- package/src/client/components/CopyButton.tsx +36 -0
- package/src/client/components/EnvBadge.tsx +32 -0
- package/src/client/components/MatrixGrid.tsx +265 -0
- package/src/client/components/Sidebar.tsx +337 -0
- package/src/client/components/StatusDot.tsx +30 -0
- package/src/client/components/TopBar.tsx +50 -0
- package/src/client/index.html +50 -0
- package/src/client/index.ts +18 -0
- package/src/client/main.tsx +15 -0
- package/src/client/public/favicon-96x96.png +0 -0
- package/src/client/public/favicon.ico +0 -0
- package/src/client/public/favicon.svg +16 -0
- package/src/client/screens/BackendScreen.test.tsx +611 -0
- package/src/client/screens/BackendScreen.tsx +836 -0
- package/src/client/screens/DiffView.test.tsx +130 -0
- package/src/client/screens/DiffView.tsx +547 -0
- package/src/client/screens/GitLogView.test.tsx +113 -0
- package/src/client/screens/GitLogView.tsx +192 -0
- package/src/client/screens/ImportScreen.tsx +710 -0
- package/src/client/screens/LintView.test.tsx +143 -0
- package/src/client/screens/LintView.tsx +589 -0
- package/src/client/screens/MatrixView.test.tsx +138 -0
- package/src/client/screens/MatrixView.tsx +143 -0
- package/src/client/screens/NamespaceEditor.test.tsx +694 -0
- package/src/client/screens/NamespaceEditor.tsx +1122 -0
- package/src/client/screens/RecipientsScreen.tsx +696 -0
- package/src/client/screens/ScanScreen.test.tsx +323 -0
- package/src/client/screens/ScanScreen.tsx +523 -0
- package/src/client/screens/ServiceIdentitiesScreen.tsx +1398 -0
- package/src/client/theme.ts +48 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
|
|
3
|
+
import { ScanScreen } from "./ScanScreen";
|
|
4
|
+
|
|
5
|
+
// Mock apiFetch
|
|
6
|
+
jest.mock("../api", () => ({
|
|
7
|
+
apiFetch: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
|
+
const { apiFetch } = require("../api") as { apiFetch: jest.Mock };
|
|
12
|
+
|
|
13
|
+
function mockStatusEmpty() {
|
|
14
|
+
apiFetch.mockImplementation((url: string) => {
|
|
15
|
+
if (url === "/api/scan/status") {
|
|
16
|
+
return Promise.resolve({
|
|
17
|
+
ok: true,
|
|
18
|
+
json: () => Promise.resolve({ lastRun: null, lastRunAt: null }),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return Promise.reject(new Error("unexpected"));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mockScanResult(result: object) {
|
|
26
|
+
apiFetch.mockImplementation((url: string) => {
|
|
27
|
+
if (url === "/api/scan/status") {
|
|
28
|
+
return Promise.resolve({
|
|
29
|
+
ok: true,
|
|
30
|
+
json: () => Promise.resolve({ lastRun: null, lastRunAt: null }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (url === "/api/scan") {
|
|
34
|
+
return Promise.resolve({
|
|
35
|
+
ok: true,
|
|
36
|
+
json: () => Promise.resolve(result),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return Promise.reject(new Error("unexpected"));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mockStatusWithResult(result: object) {
|
|
44
|
+
apiFetch.mockImplementation((url: string) => {
|
|
45
|
+
if (url === "/api/scan/status") {
|
|
46
|
+
return Promise.resolve({
|
|
47
|
+
ok: true,
|
|
48
|
+
json: () => Promise.resolve({ lastRun: result, lastRunAt: new Date().toISOString() }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return Promise.reject(new Error("unexpected"));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("ScanScreen — idle state", () => {
|
|
60
|
+
it("renders scan button in idle state", async () => {
|
|
61
|
+
mockStatusEmpty();
|
|
62
|
+
await act(async () => {
|
|
63
|
+
render(<ScanScreen />);
|
|
64
|
+
});
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(screen.getByTestId("scan-idle")).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("shows severity radio buttons", async () => {
|
|
71
|
+
mockStatusEmpty();
|
|
72
|
+
await act(async () => {
|
|
73
|
+
render(<ScanScreen />);
|
|
74
|
+
});
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(screen.getByTestId("severity-all")).toBeTruthy();
|
|
77
|
+
expect(screen.getByTestId("severity-high")).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("ScanScreen — scan triggered", () => {
|
|
83
|
+
it("calls POST /api/scan when scan button is clicked", async () => {
|
|
84
|
+
mockScanResult({
|
|
85
|
+
matches: [],
|
|
86
|
+
unencryptedMatrixFiles: [],
|
|
87
|
+
filesScanned: 10,
|
|
88
|
+
filesSkipped: 2,
|
|
89
|
+
durationMs: 100,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await act(async () => {
|
|
93
|
+
render(<ScanScreen />);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
97
|
+
|
|
98
|
+
await act(async () => {
|
|
99
|
+
fireEvent.click(screen.getByTestId("scan-button"));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
const scanCall = apiFetch.mock.calls.find((c: any[]) => c[0] === "/api/scan");
|
|
105
|
+
expect(scanCall).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("shows clean result after scan with no issues", async () => {
|
|
110
|
+
mockScanResult({
|
|
111
|
+
matches: [],
|
|
112
|
+
unencryptedMatrixFiles: [],
|
|
113
|
+
filesScanned: 10,
|
|
114
|
+
filesSkipped: 0,
|
|
115
|
+
durationMs: 200,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await act(async () => {
|
|
119
|
+
render(<ScanScreen />);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
123
|
+
|
|
124
|
+
await act(async () => {
|
|
125
|
+
fireEvent.click(screen.getByTestId("scan-button"));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(screen.getByTestId("scan-clean")).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("shows issues when scan returns matches", async () => {
|
|
134
|
+
mockScanResult({
|
|
135
|
+
matches: [
|
|
136
|
+
{
|
|
137
|
+
file: "src/config.ts",
|
|
138
|
+
line: 5,
|
|
139
|
+
column: 1,
|
|
140
|
+
matchType: "pattern",
|
|
141
|
+
patternName: "AWS access key",
|
|
142
|
+
preview: "AKIA\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
unencryptedMatrixFiles: [],
|
|
146
|
+
filesScanned: 10,
|
|
147
|
+
filesSkipped: 0,
|
|
148
|
+
durationMs: 300,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await act(async () => {
|
|
152
|
+
render(<ScanScreen />);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
156
|
+
|
|
157
|
+
await act(async () => {
|
|
158
|
+
fireEvent.click(screen.getByTestId("scan-button"));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await waitFor(() => {
|
|
162
|
+
expect(screen.queryAllByTestId("match-preview").length).toBeGreaterThan(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("ScanScreen — dismiss", () => {
|
|
168
|
+
it("hides match after dismiss and shows dismissed count", async () => {
|
|
169
|
+
mockScanResult({
|
|
170
|
+
matches: [
|
|
171
|
+
{
|
|
172
|
+
file: "src/config.ts",
|
|
173
|
+
line: 5,
|
|
174
|
+
column: 1,
|
|
175
|
+
matchType: "pattern",
|
|
176
|
+
patternName: "AWS access key",
|
|
177
|
+
preview: "AKIA\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
unencryptedMatrixFiles: [],
|
|
181
|
+
filesScanned: 5,
|
|
182
|
+
filesSkipped: 0,
|
|
183
|
+
durationMs: 80,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await act(async () => {
|
|
187
|
+
render(<ScanScreen />);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
191
|
+
|
|
192
|
+
await act(async () => {
|
|
193
|
+
fireEvent.click(screen.getByTestId("scan-button"));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(screen.queryAllByTestId("dismiss-button").length).toBeGreaterThan(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await act(async () => {
|
|
201
|
+
fireEvent.click(screen.getAllByTestId("dismiss-button")[0]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await waitFor(() => {
|
|
205
|
+
expect(screen.getByText(/dismissed/)).toBeTruthy();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("ScanScreen — filter", () => {
|
|
211
|
+
it("filters to pattern matches when pattern filter selected", async () => {
|
|
212
|
+
mockScanResult({
|
|
213
|
+
matches: [
|
|
214
|
+
{
|
|
215
|
+
file: "src/a.ts",
|
|
216
|
+
line: 1,
|
|
217
|
+
column: 1,
|
|
218
|
+
matchType: "pattern",
|
|
219
|
+
patternName: "AWS access key",
|
|
220
|
+
preview: "AKIA\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
file: "src/b.ts",
|
|
224
|
+
line: 2,
|
|
225
|
+
column: 1,
|
|
226
|
+
matchType: "entropy",
|
|
227
|
+
entropy: 5.1,
|
|
228
|
+
preview: "DB_PASS=\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
unencryptedMatrixFiles: [],
|
|
232
|
+
filesScanned: 10,
|
|
233
|
+
filesSkipped: 0,
|
|
234
|
+
durationMs: 100,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await act(async () => {
|
|
238
|
+
render(<ScanScreen />);
|
|
239
|
+
});
|
|
240
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
241
|
+
|
|
242
|
+
await act(async () => {
|
|
243
|
+
fireEvent.click(screen.getByTestId("scan-button"));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await waitFor(() => {
|
|
247
|
+
expect(screen.queryAllByTestId("match-preview").length).toBe(2);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await act(async () => {
|
|
251
|
+
fireEvent.click(screen.getByTestId("filter-pattern"));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
expect(screen.queryAllByTestId("match-preview").length).toBe(1);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("hides entropy matches in high severity mode", async () => {
|
|
260
|
+
mockStatusEmpty();
|
|
261
|
+
|
|
262
|
+
await act(async () => {
|
|
263
|
+
render(<ScanScreen />);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
await waitFor(() => screen.getByTestId("scan-idle"));
|
|
267
|
+
|
|
268
|
+
await act(async () => {
|
|
269
|
+
fireEvent.click(screen.getByTestId("severity-high"));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Verify high severity is selected
|
|
273
|
+
const radioHigh = screen.getByTestId("severity-high") as HTMLInputElement;
|
|
274
|
+
expect(radioHigh.checked).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("ScanScreen — restore from session", () => {
|
|
279
|
+
it("restores last scan result on mount via GET /api/scan/status", async () => {
|
|
280
|
+
mockStatusWithResult({
|
|
281
|
+
matches: [],
|
|
282
|
+
unencryptedMatrixFiles: [],
|
|
283
|
+
filesScanned: 15,
|
|
284
|
+
filesSkipped: 3,
|
|
285
|
+
durationMs: 500,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await act(async () => {
|
|
289
|
+
render(<ScanScreen />);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(screen.getByTestId("scan-clean")).toBeTruthy();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("shows issues state when restored result has issues", async () => {
|
|
298
|
+
mockStatusWithResult({
|
|
299
|
+
matches: [
|
|
300
|
+
{
|
|
301
|
+
file: "src/secret.ts",
|
|
302
|
+
line: 3,
|
|
303
|
+
column: 1,
|
|
304
|
+
matchType: "pattern",
|
|
305
|
+
patternName: "Stripe live key",
|
|
306
|
+
preview: "sk_l\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
unencryptedMatrixFiles: [],
|
|
310
|
+
filesScanned: 8,
|
|
311
|
+
filesSkipped: 0,
|
|
312
|
+
durationMs: 400,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await act(async () => {
|
|
316
|
+
render(<ScanScreen />);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await waitFor(() => {
|
|
320
|
+
expect(screen.queryAllByTestId("match-preview").length).toBeGreaterThan(0);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
});
|