docs: test logout visibility through real app
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
- Preserve the existing `POST /auth/logout` route, logout coordinator, authentication-state cleanup, query cleanup, and session/transcript isolation behavior.
|
- Preserve the existing `POST /auth/logout` route, logout coordinator, authentication-state cleanup, query cleanup, and session/transcript isolation behavior.
|
||||||
- Do not change backend, Compose, native `tht`, deployment, or packaging files.
|
- Do not change backend, Compose, native `tht`, deployment, or packaging files.
|
||||||
- Do not add dependencies or change the English UI string `Log out`.
|
- Do not add dependencies or change the English UI string `Log out`.
|
||||||
|
- Visibility integration tests must render the real `App` and real `AppShell`; do not assert on
|
||||||
|
props or elements exposed only by the mocked `AppShell` in `AuthGate.test.tsx`.
|
||||||
- Use strict TDD: add the focused regression tests, observe the intended RED, implement the minimum change, then run focused and full GREEN gates.
|
- Use strict TDD: add the focused regression tests, observe the intended RED, implement the minimum change, then run focused and full GREEN gates.
|
||||||
- Preserve and do not stage the pre-existing changes in `.superpowers/sdd/progress.md`, `brain/index.md`, and `brain/codebase/psd-dwh-transport.md`.
|
- Preserve and do not stage the pre-existing changes in `.superpowers/sdd/progress.md`, `brain/index.md`, and `brain/codebase/psd-dwh-transport.md`.
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
### Task 1: Propagate the local-auth capability and conditionally render logout
|
### Task 1: Propagate the local-auth capability and conditionally render logout
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `frontend/src/auth/AuthGate.test.tsx:10-17,63-68`
|
- Modify: `frontend/src/App.test.tsx:1-15`
|
||||||
- Modify: `frontend/src/shell/AppShell.auth.test.tsx:11-28,45-89,90-153`
|
- Modify: `frontend/src/shell/AppShell.auth.test.tsx:11-28,45-89,90-153`
|
||||||
- Modify: `frontend/src/auth/AuthGate.tsx:20-29,79-81`
|
- Modify: `frontend/src/auth/AuthGate.tsx:20-29,79-81`
|
||||||
- Modify: `frontend/src/shell/AppShell.tsx:39-40,715-722`
|
- Modify: `frontend/src/shell/AppShell.tsx:39-40,715-722`
|
||||||
@@ -51,65 +53,68 @@ export function AppShell({ canLogout }: AppShellProps): JSX.Element;
|
|||||||
|
|
||||||
- [ ] **Step 1: Add RED tests for capability propagation and shell visibility**
|
- [ ] **Step 1: Add RED tests for capability propagation and shell visibility**
|
||||||
|
|
||||||
In `frontend/src/auth/AuthGate.test.tsx`, replace the `AppShell` mock with a capability-observing
|
Replace `frontend/src/App.test.tsx` with a real `App` integration test that supplies all shell
|
||||||
mock:
|
requests and varies only the public authentication mode:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
vi.mock("../shell/AppShell", () => ({
|
import { render, screen } from "@testing-library/react";
|
||||||
AppShell: ({ canLogout }: { canLogout: boolean }) => (
|
import { http, HttpResponse } from "msw";
|
||||||
<div data-testid="authenticated-shell" data-can-logout={String(canLogout)}>
|
import { server } from "./test/msw";
|
||||||
Authenticated shell
|
import { App } from "./App";
|
||||||
{canLogout && (
|
|
||||||
<button type="button" onClick={() => { window.dispatchEvent(new Event("test-logout")); }}>
|
|
||||||
Log out
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
Extend the existing local authenticated-shell test so it proves the positive contract:
|
function registerAuthenticatedShell(mode: "local" | "upstream") {
|
||||||
|
|
||||||
```tsx
|
|
||||||
test("renders the authenticated shell from the safe /me DTO", async () => {
|
|
||||||
render(<AuthGate />);
|
|
||||||
|
|
||||||
const shell = await screen.findByTestId("authenticated-shell");
|
|
||||||
expect(shell).toHaveAttribute("data-can-logout", "true");
|
|
||||||
expect(screen.getByRole("button", { name: "Log out" })).toBeInTheDocument();
|
|
||||||
expect(screen.queryByRole("heading", { name: /sign in/i })).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Add an upstream regression beside it:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
test("disables shell logout for trusted upstream authentication", async () => {
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get("/api/auth/config", () => HttpResponse.json({
|
http.get("/api/auth/config", () => HttpResponse.json({
|
||||||
mode: "upstream", localLogin: false, oidcLogin: false,
|
mode,
|
||||||
|
localLogin: mode === "local",
|
||||||
|
oidcLogin: false,
|
||||||
})),
|
})),
|
||||||
http.get("/api/me", () => HttpResponse.json({
|
http.get("/api/me", () => HttpResponse.json({
|
||||||
issuer: "portal",
|
issuer: mode === "local" ? "local" : "portal",
|
||||||
subject: "portal-user",
|
subject: "portal-user",
|
||||||
displayName: "Portal user",
|
displayName: "Portal user",
|
||||||
roles: ["user"],
|
roles: ["user"],
|
||||||
permissions: ["session.use"],
|
permissions: ["session.use"],
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
csrfToken: null,
|
csrfToken: mode === "local" ? "c".repeat(43) : null,
|
||||||
session: null,
|
session: null,
|
||||||
})),
|
})),
|
||||||
|
http.get("/api/settings", () => HttpResponse.json({
|
||||||
|
workspace: "psd", provider: "zai", model: "glm-5.2", thinking: "medium",
|
||||||
|
})),
|
||||||
|
http.get("/api/workspaces", () => HttpResponse.json([])),
|
||||||
|
http.get("/api/models", () => HttpResponse.json({ models: [] })),
|
||||||
|
http.get("/api/sessions", () => HttpResponse.json([])),
|
||||||
|
http.get("/api/workspace-registry/status", () => HttpResponse.json({
|
||||||
|
branch: "main", ahead: 0, behind: 0, degraded: false,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render(<AuthGate />);
|
test("local authentication renders the standalone logout control", async () => {
|
||||||
|
registerAuthenticatedShell("local");
|
||||||
|
|
||||||
const shell = await screen.findByTestId("authenticated-shell");
|
render(<App />);
|
||||||
expect(shell).toHaveAttribute("data-can-logout", "false");
|
|
||||||
|
expect(await screen.findByRole("button", { name: "New session" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Portal user")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: "Log out" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("trusted upstream authentication keeps identity but omits ThothII logout", async () => {
|
||||||
|
registerAuthenticatedShell("upstream");
|
||||||
|
|
||||||
|
render(<App />);
|
||||||
|
|
||||||
|
expect(await screen.findByText("Portal user")).toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: "Log out" })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: "Log out" })).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Keep the existing `AppShell` mock in `frontend/src/auth/AuthGate.test.tsx` unchanged. That suite
|
||||||
|
continues to test gate state transitions; the new `App.test.tsx` cases own the real propagation
|
||||||
|
and rendered-visibility contract.
|
||||||
|
|
||||||
In `frontend/src/shell/AppShell.auth.test.tsx`, add a `canLogout` argument to the existing helper
|
In `frontend/src/shell/AppShell.auth.test.tsx`, add a `canLogout` argument to the existing helper
|
||||||
and make the keyed/direct local-auth renders explicit:
|
and make the keyed/direct local-auth renders explicit:
|
||||||
|
|
||||||
@@ -169,11 +174,11 @@ test("hides logout outside local authentication while preserving the identity",
|
|||||||
Run from `frontend/`:
|
Run from `frontend/`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx vitest run src/auth/AuthGate.test.tsx src/shell/AppShell.auth.test.tsx
|
npx vitest run src/App.test.tsx src/shell/AppShell.auth.test.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: FAIL. The local `AuthGate` test receives no `canLogout` prop, and the real `AppShell`
|
Expected: FAIL. The real `AppShell` still renders `Log out` in both the upstream integration and
|
||||||
still renders `Log out` when the new negative test passes `false`.
|
the new negative shell test.
|
||||||
|
|
||||||
- [ ] **Step 3: Implement the minimal explicit capability flow**
|
- [ ] **Step 3: Implement the minimal explicit capability flow**
|
||||||
|
|
||||||
@@ -272,7 +277,7 @@ Do not alter `signOut()`, `logoutUser()`, `frontend/src/api/auth.ts`, or any bac
|
|||||||
Run from `frontend/`:
|
Run from `frontend/`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx vitest run src/auth/AuthGate.test.tsx src/shell/AppShell.auth.test.tsx
|
npx vitest run src/App.test.tsx src/shell/AppShell.auth.test.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: both test files PASS, including the new local-positive and upstream-negative cases.
|
Expected: both test files PASS, including the new local-positive and upstream-negative cases.
|
||||||
@@ -307,7 +312,7 @@ Run from the repository root:
|
|||||||
```bash
|
```bash
|
||||||
git diff --check
|
git diff --check
|
||||||
git diff -- frontend/src/auth/AuthGate.tsx \
|
git diff -- frontend/src/auth/AuthGate.tsx \
|
||||||
frontend/src/auth/AuthGate.test.tsx \
|
frontend/src/App.test.tsx \
|
||||||
frontend/src/shell/AppShell.tsx \
|
frontend/src/shell/AppShell.tsx \
|
||||||
frontend/src/shell/AppShell.auth.test.tsx \
|
frontend/src/shell/AppShell.auth.test.tsx \
|
||||||
frontend/src/shell/AppShell.new-session.test.tsx \
|
frontend/src/shell/AppShell.new-session.test.tsx \
|
||||||
@@ -324,7 +329,7 @@ Commit only the implementation files:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add frontend/src/auth/AuthGate.tsx \
|
git add frontend/src/auth/AuthGate.tsx \
|
||||||
frontend/src/auth/AuthGate.test.tsx \
|
frontend/src/App.test.tsx \
|
||||||
frontend/src/shell/AppShell.tsx \
|
frontend/src/shell/AppShell.tsx \
|
||||||
frontend/src/shell/AppShell.auth.test.tsx \
|
frontend/src/shell/AppShell.auth.test.tsx \
|
||||||
frontend/src/shell/AppShell.new-session.test.tsx \
|
frontend/src/shell/AppShell.new-session.test.tsx \
|
||||||
|
|||||||
Reference in New Issue
Block a user