Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(unit): fix issue with base64 encoding & additional coverage #51

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
11 changes: 8 additions & 3 deletions src/__mocks__/atobMock.js
Original file line number Diff line number Diff line change
@@ -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");
});
};

Expand Down
57 changes: 57 additions & 0 deletions src/modules/api/context.test.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<button onClick={() => setBaseUrl("https://new.example.com")}>
Change Base URL
</button>
<p>Base URL: {baseUrl}</p>
</div>
);
};

describe("RekorClientContext", () => {
beforeAll(() => jest.clearAllMocks());

it("provides a RekorClient instance and manages base URL", async () => {
render(
<RekorClientProvider>
<TestConsumerComponent />
</RekorClientProvider>,
);

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();
});
});
20 changes: 11 additions & 9 deletions src/modules/components/DSSE.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
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";
import { DSSEV001Schema } from "rekor";

describe("DSSEViewer Component", () => {
beforeAll(() => {
jest.clearAllMocks();
atobMock();
});

Expand All @@ -32,11 +38,7 @@ describe("DSSEViewer Component", () => {
};

it("renders without crashing", () => {
render(
<RekorClientProvider>
<DSSEViewer dsse={mockDSSE} />
</RekorClientProvider>,
);
render(<DSSEViewer dsse={mockDSSE} />);
expect(screen.getByText("Hash")).toBeInTheDocument();
});

Expand All @@ -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(<DSSEViewer dsse={mockDSSE} />);
expect(screen.getByText("Public Key Certificate")).toBeInTheDocument();
});
Expand Down
55 changes: 38 additions & 17 deletions src/modules/components/Entry.test.tsx
Original file line number Diff line number Diff line change
@@ -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: () => <div>MockedHashedRekordViewer</div>,
}));

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(<Entry entry={mockEntry} />);

expect(screen.getByText("apiVersion")).not.toBeVisible();

// check if UUID link is rendered
expect(screen.getByText("someUuid")).toBeInTheDocument();

Expand All @@ -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();
});
});

Expand Down
84 changes: 66 additions & 18 deletions src/modules/components/Explorer.test.tsx
Original file line number Diff line number Diff line change
@@ -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<NextRouter> => ({
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(
<RekorClientProvider>
<Explorer />
</RekorClientProvider>,
);

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<NextRouter> => mockRouter,
);

it("renders without issues", () => {
render(
<RekorClientProvider>
<Explorer />
</RekorClientProvider>,
);

expect(screen.getByText("Search")).toBeInTheDocument();
expect(mockRouter.push).not.toHaveBeenCalled();
});

it("displays loading indicator when fetching data", async () => {
Expand All @@ -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(<RekorError error={undefined} />);
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(<RekorError error={{}} />);
const alert = screen.getByRole("alert");
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent("Unknown error");
});
});
});
5 changes: 3 additions & 2 deletions src/modules/components/Explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -47,6 +47,7 @@ function Error({ error }: { error: unknown }) {
style={{ margin: "1em auto" }}
title={title}
variant={"danger"}
role={"alert"}
>
{detail}
</Alert>
Expand Down Expand Up @@ -181,7 +182,7 @@ export function Explorer() {
/>

{error ? (
<Error error={error} />
<RekorError error={error} />
) : loading ? (
<LoadingIndicator />
) : (
Expand Down
16 changes: 13 additions & 3 deletions src/modules/components/HashedRekord.test.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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: {},
Expand All @@ -45,7 +52,10 @@ describe("HashedRekordViewer", () => {

render(<HashedRekordViewer hashedRekord={mockedRekordWithCert} />);

// verify that the decoded certificate content is displayed
expect(screen.getByText(/Decoded:/)).toBeInTheDocument();
expect(
screen.getByText(
/'-----BEGIN CERTIFICATE-----Mocked Certificate-----END CERTIFICATE-----'/,
),
).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion src/modules/components/Intoto.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<IntotoViewer intoto={mockIntoto} />);

// verify the hash link is rendered correctly
Expand Down
Loading
Loading