feat(auth): add remembered local login to the frontend
This commit is contained in:
@@ -5,10 +5,22 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { server } from "../test/msw";
|
||||
import { FakeEventSource } from "../test/fakeEventSource";
|
||||
import { AppShell } from "./AppShell";
|
||||
import type { AuthenticatedUser } from "../api/types";
|
||||
import { clearAuthState, setAuthState } from "../auth/authState";
|
||||
import { useSessionStore } from "../store/sessionStore";
|
||||
import { ACTIVITY_PANEL_STORAGE_KEY } from "./useActivityPanelResize";
|
||||
|
||||
function wrap() {
|
||||
const regularUser: AuthenticatedUser = {
|
||||
issuer: "portal", subject: "alice", displayName: "Alice", roles: ["user"] as const,
|
||||
permissions: ["session.use", "pi.manage"] as const, isAdmin: false, csrfToken: null, session: null,
|
||||
};
|
||||
const adminUser: AuthenticatedUser = {
|
||||
...regularUser, subject: "alice-id", roles: ["admin"] as const,
|
||||
permissions: ["session.use", "session.read_all", "pi.manage"] as const, isAdmin: true,
|
||||
};
|
||||
|
||||
function wrap(user: AuthenticatedUser = regularUser) {
|
||||
if (user !== regularUser) setAuthState(user);
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(<QueryClientProvider client={client}><AppShell /></QueryClientProvider>);
|
||||
}
|
||||
@@ -47,6 +59,8 @@ function deferred() {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
clearAuthState();
|
||||
setAuthState(regularUser);
|
||||
localStorage.clear();
|
||||
FakeEventSource.instances = [];
|
||||
ControlledResizeObserver.instances = [];
|
||||
@@ -57,13 +71,18 @@ beforeEach(() => {
|
||||
useSessionStore.getState().resetSession();
|
||||
server.use(
|
||||
http.get("/api/me", () =>
|
||||
HttpResponse.json({ issuer: "portal", subject: "alice", displayName: "Alice", isAdmin: false }),
|
||||
HttpResponse.json({ issuer: "portal", subject: "alice", displayName: "Alice", isAdmin: false, permissions: ["session.use", "pi.manage"] }),
|
||||
),
|
||||
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(LIST)),
|
||||
http.get("/api/sessions/:id", () => HttpResponse.json({ id: "s1", status: "open", phase: 1 })),
|
||||
http.get("/api/sessions/:id/documents", () => HttpResponse.json([
|
||||
{ phase: "—", key: "question", title: "Domanda originale", format: "text", content: "Attiva uno" },
|
||||
])),
|
||||
http.post("/api/sessions/:id/archive", () => new HttpResponse(null, { status: 204 })),
|
||||
http.post("/api/runtime/prewarm", () => new HttpResponse(null, { status: 202 })),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -135,7 +154,7 @@ test("administrators can explicitly switch to all sessions and see owners", asyn
|
||||
]);
|
||||
}),
|
||||
);
|
||||
wrap();
|
||||
wrap(adminUser);
|
||||
await screen.findByRole("button", { name: "All sessions" });
|
||||
expect(screen.getByRole("button", { name: "My sessions" })).toHaveAttribute("aria-pressed", "true");
|
||||
expect(screen.getByRole("button", { name: "All sessions" })).toHaveAttribute("aria-pressed", "false");
|
||||
@@ -162,7 +181,7 @@ test("administrator confirms before deleting a same-named user's session", async
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
}),
|
||||
);
|
||||
wrap();
|
||||
wrap(adminUser);
|
||||
await userEvent.click(await screen.findByRole("button", { name: "All sessions" }));
|
||||
await screen.findByText("Owner: Alice");
|
||||
await userEvent.click(screen.getByRole("checkbox", { name: "Select Attiva uno" }));
|
||||
@@ -188,7 +207,7 @@ test("administrator confirms before archiving a same-named user's session", asyn
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
}),
|
||||
);
|
||||
wrap();
|
||||
wrap(adminUser);
|
||||
await userEvent.click(await screen.findByRole("button", { name: "All sessions" }));
|
||||
await screen.findByText("Owner: Alice");
|
||||
await userEvent.click(screen.getByRole("button", { name: "Session actions" }));
|
||||
@@ -735,6 +754,63 @@ test("starting a new question invalidates a pending Resume intent", async () =>
|
||||
expect(screen.getByText(/type your question/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a delayed Resume from user A cannot activate a session after user B logs in", async () => {
|
||||
const resumeGate = deferred();
|
||||
const resumeStarted = deferred();
|
||||
const resumeSettled = deferred();
|
||||
server.use(
|
||||
http.post("/api/sessions/:id/resume", async () => {
|
||||
resumeStarted.resolve();
|
||||
try {
|
||||
await resumeGate.promise;
|
||||
return resumeResult("s1");
|
||||
} finally {
|
||||
resumeSettled.resolve();
|
||||
}
|
||||
}),
|
||||
http.get("/api/sessions/:id", () => HttpResponse.json({ id: "s1", status: "open", phase: 4 })),
|
||||
);
|
||||
wrap();
|
||||
|
||||
await userEvent.click(await screen.findByText("Attiva uno"));
|
||||
await userEvent.click(await screen.findByRole("button", { name: /resume/i }));
|
||||
await resumeStarted.promise;
|
||||
act(() => setAuthState({ ...regularUser, subject: "bob", displayName: "Bob" }));
|
||||
resumeGate.resolve();
|
||||
await act(async () => { await resumeSettled.promise; });
|
||||
expect(FakeEventSource.instances).toHaveLength(0);
|
||||
expect(useSessionStore.getState().currentPhase).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Hide model activity" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a delayed session deletion from user A cannot refresh or toast into user B", async () => {
|
||||
const deleteGate = deferred();
|
||||
const deleteStarted = deferred();
|
||||
const deleteSettled = deferred();
|
||||
server.use(http.delete("/api/sessions/:id", async () => {
|
||||
deleteStarted.resolve();
|
||||
try {
|
||||
await deleteGate.promise;
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
} finally {
|
||||
deleteSettled.resolve();
|
||||
}
|
||||
}));
|
||||
wrap();
|
||||
|
||||
await screen.findByText("Attiva uno");
|
||||
screen.getByRole("checkbox", { name: "Select Attiva uno" }).focus();
|
||||
await userEvent.keyboard(" ");
|
||||
await userEvent.click(await screen.findByRole("button", { name: "Delete 1 selected sessions" }));
|
||||
await deleteStarted.promise;
|
||||
act(() => setAuthState({ ...regularUser, subject: "bob", displayName: "Bob" }));
|
||||
deleteGate.resolve();
|
||||
await act(async () => { await deleteSettled.promise; });
|
||||
expect(useSessionStore.getState().toasts).toEqual([]);
|
||||
expect(screen.queryByText("Deleted 1 of 1 sessions.")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("session-item-s1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a successful Delete invalidates an earlier pending Resume for the same target", async () => {
|
||||
const resumeGate = deferred();
|
||||
const resumeStarted = deferred();
|
||||
|
||||
Reference in New Issue
Block a user