Skip to content

Commit 985f322

Browse files
authored
test(console): cover the first-run wizard's CA reconfirm path (#195)
CaStep's already-registered branch (ca-step.tsx) had no test: the wizard's existing tests only exercise a fresh registration, so a regression here (accepting a mismatched fingerprint, or never letting the operator continue) would not have been caught. Covers, through the public component: the already-registered state shows the fingerprint and keeps Confirm disabled until a paste is entered; the right paste enables Confirm and reconfirms without a new registration; a mismatch keeps Confirm disabled and explains why; Register a different CA cancels the reconfirm without registering anything; and a refused reconfirm surfaces inline without advancing. Refs #193 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> Co-authored-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
1 parent d2dbb82 commit 985f322

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
Copyright The CryptOS Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
18+
import { beforeEach, describe, expect, it, vi } from "vitest";
19+
20+
import { CaStep } from "@/components/first-run/ca-step";
21+
import { colonFingerprint } from "@/lib/fingerprint";
22+
import { type RegisterResult, toCARow } from "@/lib/operator-cas";
23+
import { expectNoA11yViolations } from "@/test/axe";
24+
25+
const registerOperatorCABootstrap = vi.fn();
26+
vi.mock("@/lib/bootstrap", () => ({
27+
registerOperatorCABootstrap: (...args: unknown[]) => registerOperatorCABootstrap(...args),
28+
}));
29+
30+
const SHA = "3c".repeat(32);
31+
const OTHER_SHA = "00".repeat(32);
32+
33+
// A session that opens on a CA already registered by an earlier run (or
34+
// someone else, with a leaked token): CaStep must reconfirm it rather than
35+
// trust it silently.
36+
const registered = (confirmed = false): RegisterResult => ({
37+
adminExtfile: "[ op_admin ]",
38+
confirmed,
39+
ocspProbe: undefined,
40+
operatorCa: toCARow({
41+
issuer: "CN=Example Operator CA",
42+
sha256: SHA,
43+
subject: "CN=Example Operator CA",
44+
}),
45+
});
46+
47+
const openReconfirm = async () => {
48+
registerOperatorCABootstrap.mockResolvedValueOnce(registered());
49+
render(<CaStep inProgress onRegistered={vi.fn()} />);
50+
return screen.findByText(/already registered/i);
51+
};
52+
53+
beforeEach(() => {
54+
registerOperatorCABootstrap.mockReset();
55+
});
56+
57+
describe("CaStep reconfirm path", () => {
58+
it("has no axe violations while confirming an already-registered CA", async () => {
59+
registerOperatorCABootstrap.mockResolvedValueOnce(registered());
60+
const { container } = render(<CaStep inProgress onRegistered={vi.fn()} />);
61+
await screen.findByText(/already registered/i);
62+
await expectNoA11yViolations(container);
63+
});
64+
65+
it("shows the already-registered CA's fingerprint, with Confirm disabled until it's pasted", async () => {
66+
await openReconfirm();
67+
expect(screen.getByText("CN=Example Operator CA")).toBeInTheDocument();
68+
expect(screen.getByRole("button", { name: /confirm this ca/i })).toBeDisabled();
69+
});
70+
71+
it("confirming the right fingerprint continues and registers nothing new", async () => {
72+
const onRegistered = vi.fn();
73+
registerOperatorCABootstrap.mockResolvedValueOnce(registered());
74+
registerOperatorCABootstrap.mockResolvedValueOnce(registered(true));
75+
render(<CaStep inProgress onRegistered={onRegistered} />);
76+
await screen.findByText(/already registered/i);
77+
78+
fireEvent.change(screen.getByLabelText(/paste the fingerprint/i), {
79+
target: { value: colonFingerprint(SHA) },
80+
});
81+
expect(screen.getByRole("button", { name: /confirm this ca/i })).toBeEnabled();
82+
fireEvent.click(screen.getByRole("button", { name: /confirm this ca/i }));
83+
84+
await waitFor(() => expect(onRegistered).toHaveBeenCalledTimes(1));
85+
const [result, anchorDer] = onRegistered.mock.calls[0] as [RegisterResult, null];
86+
expect(result.confirmed).toBe(true);
87+
expect(anchorDer).toBeNull();
88+
expect(registerOperatorCABootstrap).toHaveBeenLastCalledWith(
89+
expect.objectContaining({ confirmSha256: SHA }),
90+
);
91+
});
92+
93+
it("a mismatched fingerprint keeps Confirm disabled, says why, and registers nothing", async () => {
94+
const onRegistered = vi.fn();
95+
await openReconfirm();
96+
97+
fireEvent.change(screen.getByLabelText(/paste the fingerprint/i), {
98+
target: { value: colonFingerprint(OTHER_SHA) },
99+
});
100+
101+
expect(screen.getByRole("alert")).toHaveTextContent(/doesn't match/i);
102+
const confirm = screen.getByRole("button", { name: /confirm this ca/i });
103+
expect(confirm).toBeDisabled();
104+
fireEvent.click(confirm);
105+
106+
expect(onRegistered).not.toHaveBeenCalled();
107+
expect(registerOperatorCABootstrap).toHaveBeenCalledTimes(1);
108+
});
109+
110+
it("Register a different CA cancels the reconfirm and registers nothing", async () => {
111+
const onRegistered = vi.fn();
112+
await openReconfirm();
113+
114+
fireEvent.click(screen.getByRole("button", { name: /register a different ca/i }));
115+
116+
expect(screen.getByLabelText(/operator ca certificate/i)).toBeInTheDocument();
117+
expect(screen.queryByText(/already registered/i)).not.toBeInTheDocument();
118+
expect(onRegistered).not.toHaveBeenCalled();
119+
expect(registerOperatorCABootstrap).toHaveBeenCalledTimes(1);
120+
});
121+
122+
it("surfaces a refused reconfirm inline and does not advance", async () => {
123+
const onRegistered = vi.fn();
124+
registerOperatorCABootstrap.mockResolvedValueOnce(registered());
125+
registerOperatorCABootstrap.mockRejectedValueOnce(new Error("confirm refused"));
126+
render(<CaStep inProgress onRegistered={onRegistered} />);
127+
await screen.findByText(/already registered/i);
128+
129+
fireEvent.change(screen.getByLabelText(/paste the fingerprint/i), {
130+
target: { value: colonFingerprint(SHA) },
131+
});
132+
fireEvent.click(screen.getByRole("button", { name: /confirm this ca/i }));
133+
134+
expect(await screen.findByText(/confirm refused/i)).toBeInTheDocument();
135+
expect(onRegistered).not.toHaveBeenCalled();
136+
});
137+
});

0 commit comments

Comments
 (0)