mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-23 00:41:40 +00:00
test(ui): add vitest coverage for the 3 new OAuth login components
CONTRIBUTING.md requires corresponding tests for new UI components. Adds: - OAuthDeviceLoginButton.test.tsx (6 cases) — Sign-in button disabled state, startCall invocation, user-code + verification-URL rendering, onSuccess callback on poll success, error-state rendering on poll failure, and cancel flow. - ChatGPTLoginButton.test.tsx (1 case) — renders with the ChatGPT label. - CopilotLoginButton.test.tsx (1 case) — renders with the GitHub Copilot label. Real timers throughout; polling tests wait up to 10s for the 3s-interval first poll to fire. 20/20 local tests pass across my new + adjacent existing UI test files; ``npm run build`` succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d663094202
commit
c3d29a1ded
3 changed files with 222 additions and 0 deletions
|
|
@ -0,0 +1,26 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import ChatGPTLoginButton from "./ChatGPTLoginButton";
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => ({ accessToken: "test-token" }),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/networking", async () => {
|
||||
const actual = await vi.importActual("@/components/networking");
|
||||
return {
|
||||
...actual,
|
||||
chatgptOauthStartCall: vi.fn(),
|
||||
chatgptOauthStatusCall: vi.fn(),
|
||||
chatgptOauthCancelCall: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("ChatGPTLoginButton", () => {
|
||||
it("renders with the ChatGPT provider label", () => {
|
||||
render(<ChatGPTLoginButton credentialName="c" onSuccess={vi.fn()} />);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Sign in with ChatGPT/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import CopilotLoginButton from "./CopilotLoginButton";
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => ({ accessToken: "test-token" }),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/networking", async () => {
|
||||
const actual = await vi.importActual("@/components/networking");
|
||||
return {
|
||||
...actual,
|
||||
copilotOauthStartCall: vi.fn(),
|
||||
copilotOauthStatusCall: vi.fn(),
|
||||
copilotOauthCancelCall: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("CopilotLoginButton", () => {
|
||||
it("renders with the GitHub Copilot provider label", () => {
|
||||
render(<CopilotLoginButton credentialName="c" onSuccess={vi.fn()} />);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Sign in with GitHub Copilot/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import OAuthDeviceLoginButton from "./OAuthDeviceLoginButton";
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => ({ accessToken: "test-token" }),
|
||||
}));
|
||||
|
||||
describe("OAuthDeviceLoginButton", () => {
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
const baseProps = {
|
||||
providerLabel: "ChatGPT",
|
||||
startCall: vi.fn(),
|
||||
statusCall: vi.fn(),
|
||||
cancelCall: vi.fn(),
|
||||
onSuccess: vi.fn(),
|
||||
};
|
||||
|
||||
it("disables Sign-in when no credential name is provided", () => {
|
||||
render(<OAuthDeviceLoginButton {...baseProps} onSuccess={vi.fn()} />);
|
||||
const button = screen.getByRole("button", { name: /Sign in with ChatGPT/i });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables Sign-in once a credential name is provided", () => {
|
||||
render(
|
||||
<OAuthDeviceLoginButton
|
||||
{...baseProps}
|
||||
credentialName="my-creds"
|
||||
onSuccess={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Sign in with ChatGPT/i }),
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
it("on Sign-in click, shows the user code and verification URL returned by startCall", async () => {
|
||||
const startCall = vi.fn().mockResolvedValue({
|
||||
session_id: "sess-1",
|
||||
user_code: "ABCD-1234",
|
||||
verification_url: "https://auth.example/code",
|
||||
interval: 5,
|
||||
});
|
||||
const statusCall = vi.fn().mockResolvedValue({ status: "pending" });
|
||||
|
||||
render(
|
||||
<OAuthDeviceLoginButton
|
||||
{...baseProps}
|
||||
credentialName="my-creds"
|
||||
startCall={startCall}
|
||||
statusCall={statusCall}
|
||||
cancelCall={vi.fn()}
|
||||
onSuccess={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /Sign in with ChatGPT/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("ABCD-1234")).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByRole("link", { name: "https://auth.example/code" }),
|
||||
).toHaveAttribute("href", "https://auth.example/code");
|
||||
expect(startCall).toHaveBeenCalledWith("test-token", "my-creds");
|
||||
});
|
||||
|
||||
it("calls onSuccess when polling reports success", async () => {
|
||||
const onSuccess = vi.fn();
|
||||
const startCall = vi.fn().mockResolvedValue({
|
||||
session_id: "sess-1",
|
||||
user_code: "CODE",
|
||||
verification_url: "https://auth.example/code",
|
||||
interval: 5,
|
||||
});
|
||||
const statusCall = vi.fn().mockResolvedValue({ status: "success" });
|
||||
|
||||
render(
|
||||
<OAuthDeviceLoginButton
|
||||
{...baseProps}
|
||||
credentialName="my-creds"
|
||||
startCall={startCall}
|
||||
statusCall={statusCall}
|
||||
cancelCall={vi.fn()}
|
||||
onSuccess={onSuccess}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /Sign in with ChatGPT/i }));
|
||||
|
||||
// Poll interval is 3s; allow up to 10s of real time for the first poll
|
||||
// to fire and resolve with status=success.
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(onSuccess).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}, 15000);
|
||||
|
||||
it("surfaces error status from polling", async () => {
|
||||
const startCall = vi.fn().mockResolvedValue({
|
||||
session_id: "sess-1",
|
||||
user_code: "CODE",
|
||||
verification_url: "https://auth.example/code",
|
||||
interval: 5,
|
||||
});
|
||||
const statusCall = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ status: "error", message: "upstream failure" });
|
||||
|
||||
render(
|
||||
<OAuthDeviceLoginButton
|
||||
{...baseProps}
|
||||
credentialName="my-creds"
|
||||
startCall={startCall}
|
||||
statusCall={statusCall}
|
||||
cancelCall={vi.fn()}
|
||||
onSuccess={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /Sign in with ChatGPT/i }));
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText("upstream failure")).toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
}, 15000);
|
||||
|
||||
it("on Cancel click, calls cancelCall and returns to idle state", async () => {
|
||||
const cancelCall = vi.fn().mockResolvedValue(undefined);
|
||||
const startCall = vi.fn().mockResolvedValue({
|
||||
session_id: "sess-1",
|
||||
user_code: "CODE",
|
||||
verification_url: "https://auth.example/code",
|
||||
interval: 5,
|
||||
});
|
||||
const statusCall = vi.fn().mockResolvedValue({ status: "pending" });
|
||||
|
||||
render(
|
||||
<OAuthDeviceLoginButton
|
||||
{...baseProps}
|
||||
credentialName="my-creds"
|
||||
startCall={startCall}
|
||||
statusCall={statusCall}
|
||||
cancelCall={cancelCall}
|
||||
onSuccess={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /Sign in with ChatGPT/i }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("CODE")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /Cancel/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(cancelCall).toHaveBeenCalledWith("test-token", "sess-1");
|
||||
});
|
||||
// Back to idle — Sign-in button visible again
|
||||
expect(
|
||||
screen.getByRole("button", { name: /Sign in with ChatGPT/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue