Skip to content

Commit 8f23e31

Browse files
authored
test(unit): add tests (#49)
2 parents d19298e + 0bdadba commit 8f23e31

12 files changed

+313
-53
lines changed

.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"plugin:react-hooks/recommended",
66
"prettier"
77
],
8+
"ignorePatterns": ["**/*.stories.tsx"],
89
"plugins": ["react-hooks"],
910
"overrides": [
1011
// Only use Testing Library lint rules in test files

src/__mocks__/atobMock.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const atobMock = () => {
2+
window.atob = jest.fn().mockImplementation(str => {
3+
const decoded = Buffer.from(str, "base64").toString("utf-8");
4+
console.log(`Decoding: ${str}, Result: ${decoded}`);
5+
6+
return decoded;
7+
});
8+
};
9+
10+
export default atobMock;

src/__mocks__/decodex509Mock.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Mock for decodex509 function
2-
jest.mock("../modules/x509/decode", () => ({
3-
decodex509: jest.fn().mockReturnValue({
4-
publicKey: "Mocked Public Key",
5-
subject: "Mocked Subject",
6-
}),
7-
}));
1+
const decodex509Mock = jest.fn().mockReturnValue({
2+
publicKey:
3+
"-----BEGIN CERTIFICATE-----Mocked Certificate-----END CERTIFICATE-----",
4+
subject: "Mocked Subject",
5+
});
6+
7+
export default decodex509Mock;

src/__mocks__/prismMock.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jest.mock("react-syntax-highlighter/dist/cjs/styles/prism", () => ({}));

src/modules/api/rekor_api.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { renderHook } from "@testing-library/react";
2+
import { useRekorSearch } from "./rekor_api";
3+
import { useRekorClient } from "./context";
4+
5+
jest.mock("./context", () => ({
6+
useRekorClient: jest.fn(),
7+
}));
8+
9+
Object.defineProperty(global.self, "crypto", {
10+
value: {
11+
subtle: {
12+
digest: jest.fn().mockImplementation(async () => {
13+
const hashBuffer = new ArrayBuffer(32);
14+
const hashArray = new Uint8Array(hashBuffer);
15+
hashArray.fill(0);
16+
return hashBuffer;
17+
}),
18+
},
19+
},
20+
});
21+
22+
describe("useRekorSearch", () => {
23+
it("searches by logIndex", async () => {
24+
const mockGetLogEntryByIndex = jest.fn().mockResolvedValue(0);
25+
26+
(useRekorClient as jest.Mock).mockReturnValue({
27+
entries: { getLogEntryByIndex: mockGetLogEntryByIndex },
28+
});
29+
30+
const { result } = renderHook(() => useRekorSearch());
31+
32+
await result.current({ attribute: "logIndex", query: 123 });
33+
34+
expect(mockGetLogEntryByIndex).toHaveBeenCalledWith({ logIndex: 123 });
35+
});
36+
});

src/modules/components/DSSE.test.tsx

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
11
jest.mock("next/router");
2+
// @ts-ignore
3+
import atobMock from "../../__mocks__/atobMock";
24

35
import { RekorClientProvider } from "../api/context";
46
import { render, screen } from "@testing-library/react";
57
import "@testing-library/jest-dom";
68
import { DSSEViewer } from "./DSSE";
79
import { DSSEV001Schema } from "rekor";
810

9-
const mockDSSE: DSSEV001Schema = {
10-
payloadHash: {
11-
algorithm: "sha256",
12-
value: "exampleHashValue",
13-
},
14-
signatures: [
15-
{
16-
signature: "exampleSignature",
17-
verifier:
18-
"-----BEGIN CERTIFICATE-----\nexamplePublicKey\n-----END CERTIFICATE-----",
19-
},
20-
],
21-
};
11+
describe("DSSEViewer Component", () => {
12+
beforeAll(() => {
13+
atobMock();
14+
});
2215

23-
beforeAll(() => {
24-
window.atob = jest
25-
.fn()
26-
.mockImplementation(str => Buffer.from(str, "base64").toString("utf-8"));
27-
});
16+
afterAll(() => {
17+
jest.restoreAllMocks();
18+
});
19+
20+
const mockDSSE: DSSEV001Schema = {
21+
payloadHash: {
22+
algorithm: "sha256",
23+
value: "exampleHashValue",
24+
},
25+
signatures: [
26+
{
27+
signature: "exampleSignature",
28+
verifier:
29+
"-----BEGIN CERTIFICATE-----\nexamplePublicKey\n-----END CERTIFICATE-----",
30+
},
31+
],
32+
};
2833

29-
describe("DSSEViewer Component", () => {
3034
it("renders without crashing", () => {
3135
render(
3236
<RekorClientProvider>

src/modules/components/Entry.test.tsx

+41-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,51 @@
1-
import { render } from "@testing-library/react";
2-
import { RekorClientProvider } from "../api/context";
3-
import { EntryCard } from "./Entry";
1+
jest.mock("react-syntax-highlighter/dist/cjs/styles/prism", () => ({}));
2+
jest.mock("../utils/date", () => ({
3+
toRelativeDateString: jest.fn().mockReturnValue("Some Date"),
4+
}));
5+
6+
import { fireEvent, render, screen } from "@testing-library/react";
7+
import { Entry, EntryCard } from "./Entry";
8+
9+
const mockEntry = {
10+
someUuid: {
11+
body: Buffer.from(
12+
JSON.stringify({ kind: "hashedrekord", apiVersion: "v1", spec: {} }),
13+
).toString("base64"),
14+
attestation: { data: Buffer.from("{}").toString("base64") },
15+
logID: "123",
16+
logIndex: 123,
17+
integratedTime: 1618886400,
18+
publicKey: "mockedPublicKey",
19+
},
20+
};
421

522
describe("Entry", () => {
6-
it("renders", () => {
7-
//
23+
it.skip("renders and toggles the accordion content", () => {
24+
render(<Entry entry={mockEntry} />);
25+
26+
// check if UUID link is rendered
27+
expect(screen.getByText("someUuid")).toBeInTheDocument();
28+
29+
// simulate clicking the accordion toggle
30+
const toggleButton = screen.getByText("Raw Body");
31+
fireEvent.click(toggleButton);
32+
33+
// now the accordion content should be visible
34+
expect(
35+
screen.getByText("Your expected content after decoding and dumping"),
36+
).toBeInTheDocument();
837
});
938
});
1039

1140
describe("EntryCard", () => {
12-
it("renders", () => {
41+
it("renders the title and content", () => {
1342
render(
14-
<RekorClientProvider>
15-
<EntryCard
16-
content={<></>}
17-
title={<></>}
18-
/>
19-
</RekorClientProvider>,
43+
<EntryCard
44+
title="Test Title"
45+
content="Test Content"
46+
/>,
2047
);
48+
expect(screen.getByText("Test Title")).toBeInTheDocument();
49+
expect(screen.getByText("Test Content")).toBeInTheDocument();
2150
});
2251
});
+33-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,46 @@
11
jest.mock("next/router");
22

3-
import { render } from "@testing-library/react";
3+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
44
import { RekorClientProvider } from "../api/context";
55
import { Explorer } from "./Explorer";
66

77
describe("Explorer", () => {
8-
it("renders", () => {
8+
jest.mock("../api/rekor_api", () => ({
9+
useRekorSearch: jest.fn(() =>
10+
jest.fn().mockImplementation(() => {
11+
return Promise.resolve({ entries: [], totalCount: 0 });
12+
}),
13+
),
14+
}));
15+
16+
it("renders without issues", () => {
917
render(
1018
<RekorClientProvider>
1119
<Explorer />
1220
</RekorClientProvider>,
1321
);
22+
23+
expect(screen.getByText("Search")).toBeInTheDocument();
24+
});
25+
26+
it("displays loading indicator when fetching data", async () => {
27+
render(
28+
<RekorClientProvider>
29+
<Explorer />
30+
</RekorClientProvider>,
31+
);
32+
33+
const button = screen.getByText("Search");
34+
fireEvent.click(button);
35+
36+
await waitFor(() => expect(screen.queryByRole("status")).toBeNull());
37+
38+
expect(
39+
screen
40+
.findByLabelText("Showing" || "No matching entries found")
41+
.then(res => {
42+
expect(res).toBeInTheDocument();
43+
}),
44+
);
1445
});
1546
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
jest.mock("next/router");
2+
jest.mock("react-syntax-highlighter/dist/cjs/styles/prism");
3+
4+
import { HashedRekordViewer } from "./HashedRekord";
5+
import { render, screen } from "@testing-library/react";
6+
import { HashedRekorV001Schema } from "rekor";
7+
8+
describe("HashedRekordViewer", () => {
9+
it("renders the component with a public key", () => {
10+
const mockedRekord: HashedRekorV001Schema = {
11+
data: {
12+
hash: {
13+
algorithm: "sha256",
14+
value: "mockedHashValue",
15+
},
16+
},
17+
signature: {
18+
content: "mockedSignatureContent",
19+
publicKey: {
20+
content: window.btoa("mockedPublicKeyContent"), // base64 encode
21+
},
22+
},
23+
};
24+
25+
render(<HashedRekordViewer hashedRekord={mockedRekord} />);
26+
27+
expect(screen.getByText("Hash")).toBeInTheDocument();
28+
expect(screen.getByText("sha256:mockedHashValue")).toBeInTheDocument();
29+
expect(screen.getByText("mockedSignatureContent")).toBeInTheDocument();
30+
expect(screen.getByText("mockedPublicKeyContent")).toBeInTheDocument();
31+
});
32+
33+
it.skip("renders the component with a public key certificate", () => {
34+
const mockedRekordWithCert = {
35+
// simulate a certificate
36+
data: {},
37+
signature: {
38+
publicKey: {
39+
content: window.btoa(
40+
"-----BEGIN CERTIFICATE-----certContent-----END CERTIFICATE-----",
41+
), // base64 encode
42+
},
43+
},
44+
};
45+
46+
render(<HashedRekordViewer hashedRekord={mockedRekordWithCert} />);
47+
48+
// verify that the decoded certificate content is displayed
49+
expect(screen.getByText(/Decoded:/)).toBeInTheDocument();
50+
});
51+
});
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @ts-nocheck
2+
import atobMock from "../../__mocks__/atobMock";
3+
import decodex509Mock from "../../__mocks__/decodex509Mock";
4+
5+
import { render, screen } from "@testing-library/react";
6+
import { IntotoViewer } from "./Intoto";
7+
import { IntotoV002Schema } from "rekor";
8+
9+
const pemCertificate = `-----BEGIN CERTIFICATE-----\n${Buffer.from("Mocked Public Key").toString("base64")}\n-----END CERTIFICATE-----`;
10+
11+
jest.mock("../x509/decode", () => ({
12+
decodex509: decodex509Mock,
13+
}));
14+
15+
describe("IntotoViewer", () => {
16+
beforeAll(() => {
17+
atobMock();
18+
});
19+
20+
afterAll(() => {
21+
jest.restoreAllMocks();
22+
});
23+
24+
const mockIntoto: IntotoV002Schema = {
25+
content: {
26+
envelope: {
27+
payloadType: "application/vnd.in-toto+json",
28+
signatures: [
29+
{
30+
publicKey: pemCertificate,
31+
sig: Buffer.from("signature content", "utf-8").toString("base64"),
32+
},
33+
],
34+
},
35+
payloadHash: {
36+
algorithm: "sha256",
37+
value: "hashValue",
38+
},
39+
},
40+
};
41+
42+
it.skip("renders the component with payload hash, signature, and certificate", () => {
43+
render(<IntotoViewer intoto={mockIntoto} />);
44+
45+
// verify the hash link is rendered correctly
46+
expect(screen.getByText("Hash")).toBeInTheDocument();
47+
expect(screen.getByText("sha256:hashValue")).toBeInTheDocument();
48+
49+
// verify the signature is rendered & decoded
50+
expect(screen.getByText("signature content")).toBeInTheDocument();
51+
expect(screen.getByText(/BEGIN CERTIFICATE/)).toBeInTheDocument();
52+
});
53+
});
+18-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import { SearchForm } from "./SearchForm";
2-
import { render } from "@testing-library/react";
2+
import { render, screen, waitFor } from "@testing-library/react";
33
import { RekorClientProvider } from "../api/context";
4+
import userEvent from "@testing-library/user-event";
45

56
describe("SearchForm", () => {
6-
it("renders", () => {
7+
it("submits the correct form data", async () => {
8+
const mockOnSubmit = jest.fn();
79
render(
810
<RekorClientProvider>
911
<SearchForm
12+
onSubmit={mockOnSubmit}
1013
isLoading={false}
11-
onSubmit={() => {}}
1214
/>
1315
</RekorClientProvider>,
1416
);
17+
18+
// assume "email" is the default selected attribute; otherwise, select it first
19+
await userEvent.type(
20+
screen.getByLabelText(/Email input field/i),
21+
"test@example.com",
22+
);
23+
24+
// submit the form
25+
await userEvent.click(screen.getByText(/Search/i));
26+
27+
await waitFor(() => {
28+
expect(mockOnSubmit).toHaveBeenCalled();
29+
});
1530
});
1631
});

0 commit comments

Comments
 (0)