Fix Stage 0 bootstrap and startup regression
Task 0.1: Create database parent directory before connecting - main.py _lifespan now calls Path(database_path).parent.mkdir(parents=True, exist_ok=True) before aiosqlite.connect() so the app starts cleanly on a fresh Docker volume with a nested database path. Task 0.2: SetupRedirectMiddleware redirects when db is None - Guard now reads: if db is None or not is_setup_complete(db) A missing database (startup still in progress) is treated as setup not complete instead of silently allowing all API routes through. Task 0.3: SetupGuard redirects to /setup on API failure - .catch() handler now sets status to 'pending' instead of 'done'. A crashed backend cannot serve protected routes; conservative fallback is to redirect to /setup. Task 0.4: SetupPage shows spinner while checking setup status - Added 'checking' boolean state; full-screen Spinner is rendered until getSetupStatus() resolves, preventing form flash before redirect. - Added console.warn in catch block; cleanup return added to useEffect. Also: remove unused type: ignore[call-arg] from config.py. Tests: 18 backend tests pass; 117 frontend tests pass.
This commit is contained in:
77
frontend/src/components/__tests__/SetupGuard.test.tsx
Normal file
77
frontend/src/components/__tests__/SetupGuard.test.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { MemoryRouter, Routes, Route } from "react-router-dom";
|
||||
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
|
||||
import { SetupGuard } from "../SetupGuard";
|
||||
|
||||
// Mock the setup API module so tests never hit a real network.
|
||||
vi.mock("../../api/setup", () => ({
|
||||
getSetupStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
import { getSetupStatus } from "../../api/setup";
|
||||
|
||||
const mockedGetSetupStatus = vi.mocked(getSetupStatus);
|
||||
|
||||
function renderGuard() {
|
||||
return render(
|
||||
<FluentProvider theme={webLightTheme}>
|
||||
<MemoryRouter initialEntries={["/dashboard"]}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<SetupGuard>
|
||||
<div data-testid="protected-content">Protected</div>
|
||||
</SetupGuard>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/setup"
|
||||
element={<div data-testid="setup-page">Setup Page</div>}
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</FluentProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("SetupGuard", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("shows a spinner while the setup status is loading", () => {
|
||||
// getSetupStatus resolves eventually — spinner should show immediately.
|
||||
mockedGetSetupStatus.mockReturnValue(new Promise(() => {}));
|
||||
renderGuard();
|
||||
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders children when setup is complete", async () => {
|
||||
mockedGetSetupStatus.mockResolvedValue({ completed: true });
|
||||
renderGuard();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("protected-content")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("redirects to /setup when setup is not complete", async () => {
|
||||
mockedGetSetupStatus.mockResolvedValue({ completed: false });
|
||||
renderGuard();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("setup-page")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId("protected-content")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("redirects to /setup when the API call fails", async () => {
|
||||
// Task 0.3: a failed check must redirect to /setup, not allow through.
|
||||
mockedGetSetupStatus.mockRejectedValue(new Error("Network error"));
|
||||
renderGuard();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("setup-page")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId("protected-content")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user