diff --git a/.eslintrc.json b/.eslintrc.json
index 212b26a..ee2bd75 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -14,7 +14,10 @@
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)"
],
- "extends": ["plugin:testing-library/react"]
+ "extends": ["plugin:testing-library/react"],
+ "rules": {
+ "testing-library/no-debugging-utils": "off"
+ }
}
],
"rules": {
diff --git a/src/__mocks__/atobMock.js b/src/__mocks__/atobMock.js
index 8a2a691..45245e8 100644
--- a/src/__mocks__/atobMock.js
+++ b/src/__mocks__/atobMock.js
@@ -1,9 +1,14 @@
const atobMock = () => {
window.atob = jest.fn().mockImplementation(str => {
- const decoded = Buffer.from(str, "base64").toString("utf-8");
- console.log(`Decoding: ${str}, Result: ${decoded}`);
+ const base64Pattern =
+ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
- return decoded;
+ if (!base64Pattern.test(str)) {
+ // return if string is not base64 encoded
+ return str;
+ }
+
+ return Buffer.from(str, "base64").toString("utf-8");
});
};
diff --git a/src/modules/api/context.test.tsx b/src/modules/api/context.test.tsx
new file mode 100644
index 0000000..22b47c1
--- /dev/null
+++ b/src/modules/api/context.test.tsx
@@ -0,0 +1,57 @@
+const originalEnv = process.env;
+
+beforeEach(() => {
+ jest.resetModules();
+ process.env = {
+ ...originalEnv,
+ NEXT_PUBLIC_REKOR_DEFAULT_DOMAIN: "https://example.com",
+ };
+});
+
+afterEach(() => {
+ process.env = originalEnv;
+});
+
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import {
+ RekorClientProvider,
+ useRekorClient,
+ useRekorBaseUrl,
+} from "./context";
+
+const TestConsumerComponent = () => {
+ useRekorClient();
+ const [baseUrl, setBaseUrl] = useRekorBaseUrl();
+
+ return (
+
+
+
Base URL: {baseUrl}
+
+ );
+};
+
+describe("RekorClientContext", () => {
+ beforeAll(() => jest.clearAllMocks());
+
+ it("provides a RekorClient instance and manages base URL", async () => {
+ render(
+
+
+ ,
+ );
+
+ expect(
+ screen.getByText(/Base URL: https:\/\/example.com/),
+ ).toBeInTheDocument();
+
+ await userEvent.click(screen.getByText(/Change Base URL/));
+
+ expect(
+ screen.getByText(/Base URL: https:\/\/new.example.com/),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/modules/components/DSSE.test.tsx b/src/modules/components/DSSE.test.tsx
index 5c7de81..17ac677 100644
--- a/src/modules/components/DSSE.test.tsx
+++ b/src/modules/components/DSSE.test.tsx
@@ -1,8 +1,13 @@
-jest.mock("next/router");
-// @ts-ignore
+// @ts-nocheck
import atobMock from "../../__mocks__/atobMock";
+import decodex509Mock from "../../__mocks__/decodex509Mock";
+
+jest.mock("next/router");
+
+jest.mock("../x509/decode", () => ({
+ decodex509: decodex509Mock,
+}));
-import { RekorClientProvider } from "../api/context";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { DSSEViewer } from "./DSSE";
@@ -10,6 +15,7 @@ import { DSSEV001Schema } from "rekor";
describe("DSSEViewer Component", () => {
beforeAll(() => {
+ jest.clearAllMocks();
atobMock();
});
@@ -32,11 +38,7 @@ describe("DSSEViewer Component", () => {
};
it("renders without crashing", () => {
- render(
-
-
- ,
- );
+ render();
expect(screen.getByText("Hash")).toBeInTheDocument();
});
@@ -56,7 +58,7 @@ describe("DSSEViewer Component", () => {
).toBeInTheDocument();
});
- it.skip("displays the public key certificate title and content correctly", () => {
+ it("displays the public key certificate title and content correctly", () => {
render();
expect(screen.getByText("Public Key Certificate")).toBeInTheDocument();
});
diff --git a/src/modules/components/Entry.test.tsx b/src/modules/components/Entry.test.tsx
index 25053d7..d510a7e 100644
--- a/src/modules/components/Entry.test.tsx
+++ b/src/modules/components/Entry.test.tsx
@@ -1,28 +1,51 @@
+// @ts-nocheck
jest.mock("react-syntax-highlighter/dist/cjs/styles/prism", () => ({}));
jest.mock("../utils/date", () => ({
toRelativeDateString: jest.fn().mockReturnValue("Some Date"),
}));
+jest.mock("./HashedRekord", () => ({
+ HashedRekordViewer: () => MockedHashedRekordViewer
,
+}));
+
+import atobMock from "../../__mocks__/atobMock";
import { fireEvent, render, screen } from "@testing-library/react";
import { Entry, EntryCard } from "./Entry";
-const mockEntry = {
- someUuid: {
- body: Buffer.from(
- JSON.stringify({ kind: "hashedrekord", apiVersion: "v1", spec: {} }),
- ).toString("base64"),
- attestation: { data: Buffer.from("{}").toString("base64") },
- logID: "123",
- logIndex: 123,
- integratedTime: 1618886400,
- publicKey: "mockedPublicKey",
- },
-};
-
describe("Entry", () => {
- it.skip("renders and toggles the accordion content", () => {
+ beforeAll(() => {
+ atobMock();
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ const mockEntry = {
+ someUuid: {
+ body: Buffer.from(
+ JSON.stringify({ kind: "hashedrekord", apiVersion: "v1", spec: {} }),
+ ).toString("base64"),
+ attestation: { data: Buffer.from("{}").toString("base64") },
+ logID: "123",
+ logIndex: 123,
+ integratedTime: 1618886400,
+ publicKey: "mockedPublicKey",
+ signature: {
+ publicKey: {
+ content: window.btoa(
+ "-----BEGIN CERTIFICATE-----certContent-----END CERTIFICATE-----",
+ ), // base64 encode
+ },
+ },
+ },
+ };
+
+ it("renders and toggles the accordion content", () => {
render();
+ expect(screen.getByText("apiVersion")).not.toBeVisible();
+
// check if UUID link is rendered
expect(screen.getByText("someUuid")).toBeInTheDocument();
@@ -31,9 +54,7 @@ describe("Entry", () => {
fireEvent.click(toggleButton);
// now the accordion content should be visible
- expect(
- screen.getByText("Your expected content after decoding and dumping"),
- ).toBeInTheDocument();
+ expect(screen.getByText("apiVersion")).toBeVisible();
});
});
diff --git a/src/modules/components/Explorer.test.tsx b/src/modules/components/Explorer.test.tsx
index 8a6f994..bf338a9 100644
--- a/src/modules/components/Explorer.test.tsx
+++ b/src/modules/components/Explorer.test.tsx
@@ -1,26 +1,58 @@
-jest.mock("next/router");
+import { NextRouter, useRouter } from "next/router";
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+jest.mock("next/router", () => ({
+ useRouter: jest.fn(),
+}));
+
+beforeEach(() => {
+ jest.resetAllMocks();
+
+ (useRouter as jest.Mock).mockImplementation(
+ (): Partial => ({
+ query: {},
+ pathname: "/",
+ asPath: "/",
+ }),
+ );
+});
+
+import { render, screen, waitFor } from "@testing-library/react";
import { RekorClientProvider } from "../api/context";
-import { Explorer } from "./Explorer";
+import { Explorer, RekorError } from "./Explorer";
+import userEvent from "@testing-library/user-event";
describe("Explorer", () => {
- jest.mock("../api/rekor_api", () => ({
- useRekorSearch: jest.fn(() =>
- jest.fn().mockImplementation(() => {
- return Promise.resolve({ entries: [], totalCount: 0 });
- }),
- ),
- }));
+ it("should render search form and display search button", () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByLabelText("Attribute")).toBeInTheDocument();
+ expect(screen.getByLabelText("Email")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Search" })).toBeInTheDocument();
+ });
+
+ it("should handle invalid logIndex query parameter", () => {
+ const mockRouter = {
+ query: {
+ logIndex: "invalid",
+ },
+ push: jest.fn(),
+ };
+
+ (useRouter as jest.Mock).mockImplementation(
+ (): Partial => mockRouter,
+ );
- it("renders without issues", () => {
render(
,
);
- expect(screen.getByText("Search")).toBeInTheDocument();
+ expect(mockRouter.push).not.toHaveBeenCalled();
});
it("displays loading indicator when fetching data", async () => {
@@ -31,16 +63,32 @@ describe("Explorer", () => {
);
const button = screen.getByText("Search");
- fireEvent.click(button);
+ await userEvent.click(button);
await waitFor(() => expect(screen.queryByRole("status")).toBeNull());
expect(
- screen
- .findByLabelText("Showing" || "No matching entries found")
- .then(res => {
- expect(res).toBeInTheDocument();
- }),
+ screen.findByLabelText("Showing").then(res => {
+ screen.debug();
+ console.log(res);
+ expect(res).toBeInTheDocument();
+ }),
);
});
+
+ describe("RekorError", () => {
+ it("should render an Alert component if the error parameter is undefined", () => {
+ render();
+ const alert = screen.getByRole("alert");
+ expect(alert).toBeInTheDocument();
+ expect(alert).toHaveTextContent("Unknown error");
+ });
+
+ it("should render an Alert component if error parameter is an empty object", () => {
+ render();
+ const alert = screen.getByRole("alert");
+ expect(alert).toBeInTheDocument();
+ expect(alert).toHaveTextContent("Unknown error");
+ });
+ });
});
diff --git a/src/modules/components/Explorer.tsx b/src/modules/components/Explorer.tsx
index 5495301..ddb5c07 100644
--- a/src/modules/components/Explorer.tsx
+++ b/src/modules/components/Explorer.tsx
@@ -26,7 +26,7 @@ function isRekorError(error: unknown): error is RekorError {
return !!error && typeof error === "object";
}
-function Error({ error }: { error: unknown }) {
+export function RekorError({ error }: { error: unknown }) {
let title = "Unknown error";
let detail: string | undefined;
@@ -47,6 +47,7 @@ function Error({ error }: { error: unknown }) {
style={{ margin: "1em auto" }}
title={title}
variant={"danger"}
+ role={"alert"}
>
{detail}
@@ -181,7 +182,7 @@ export function Explorer() {
/>
{error ? (
-
+
) : loading ? (
) : (
diff --git a/src/modules/components/HashedRekord.test.tsx b/src/modules/components/HashedRekord.test.tsx
index 44f2fca..2f124d6 100644
--- a/src/modules/components/HashedRekord.test.tsx
+++ b/src/modules/components/HashedRekord.test.tsx
@@ -1,6 +1,13 @@
+// @ts-nocheck
jest.mock("next/router");
jest.mock("react-syntax-highlighter/dist/cjs/styles/prism");
+import decodex509Mock from "../../__mocks__/decodex509Mock";
+
+jest.mock("../x509/decode", () => ({
+ decodex509: decodex509Mock,
+}));
+
import { HashedRekordViewer } from "./HashedRekord";
import { render, screen } from "@testing-library/react";
import { HashedRekorV001Schema } from "rekor";
@@ -30,7 +37,7 @@ describe("HashedRekordViewer", () => {
expect(screen.getByText("mockedPublicKeyContent")).toBeInTheDocument();
});
- it.skip("renders the component with a public key certificate", () => {
+ it("renders the component with a public key certificate", () => {
const mockedRekordWithCert = {
// simulate a certificate
data: {},
@@ -45,7 +52,10 @@ describe("HashedRekordViewer", () => {
render();
- // verify that the decoded certificate content is displayed
- expect(screen.getByText(/Decoded:/)).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ /'-----BEGIN CERTIFICATE-----Mocked Certificate-----END CERTIFICATE-----'/,
+ ),
+ ).toBeInTheDocument();
});
});
diff --git a/src/modules/components/Intoto.test.tsx b/src/modules/components/Intoto.test.tsx
index 1350989..2dd60e7 100644
--- a/src/modules/components/Intoto.test.tsx
+++ b/src/modules/components/Intoto.test.tsx
@@ -39,7 +39,7 @@ describe("IntotoViewer", () => {
},
};
- it.skip("renders the component with payload hash, signature, and certificate", () => {
+ it("renders the component with payload hash, signature, and certificate", () => {
render();
// verify the hash link is rendered correctly
diff --git a/src/modules/x509/decode.test.ts b/src/modules/x509/decode.test.ts
new file mode 100644
index 0000000..47d934f
--- /dev/null
+++ b/src/modules/x509/decode.test.ts
@@ -0,0 +1,49 @@
+jest.mock("./constants", () => ({
+ digitalSignature: "digitalSignature",
+ nonRepudiation: "nonRepudiation",
+ keyEncipherment: "keyEncipherment",
+ dataEncipherment: "dataEncipherment",
+}));
+
+jest.mock("@peculiar/x509", () => ({
+ X509Certificate: jest.fn().mockImplementation(() => ({
+ extensions: [],
+ serialNumber: "123",
+ issuer: { organization: ["Test Org"] },
+ notBefore: new Date("2020-01-01"),
+ notAfter: new Date("2025-01-01"),
+ publicKey: { algorithm: "rsaEncryption" },
+ subjectName: "CN=Test",
+ })),
+}));
+
+jest.mock("../utils/date", () => ({
+ toRelativeDateString: jest.fn(date => date.toISOString()),
+}));
+
+import { decodex509 } from "./decode";
+
+describe("decodex509", () => {
+ it("decodes a raw certificate string", () => {
+ const rawCertificate =
+ "-----BEGIN CERTIFICATE-----Mocked Certificate-----END CERTIFICATE-----";
+
+ const decodedCert = decodex509(rawCertificate);
+
+ expect(decodedCert).toMatchObject({
+ data: {
+ "Serial Number": expect.any(String),
+ },
+ Signature: {
+ Issuer: expect.any(Object),
+ Validity: {
+ "Not Before": expect.any(String),
+ "Not After": expect.any(String),
+ },
+ Algorithm: expect.any(String),
+ Subject: expect.any(String),
+ },
+ "X509v3 extensions": expect.any(Object),
+ });
+ });
+});