feat(web): Login/Register страницы, protected routes, logout
- ProtectedRoute: loading -> спиннер, !user -> /login, иначе children - LoginPage/RegisterPage: field+react-hook-form/zod, ошибка через role=alert, редирект на /domains при успехе/уже авторизован - main.tsx: AuthProvider + QueryCache/MutationCache onError -> notifyUnauthorized на UnauthorizedError (сброс сессии из кода вне React-дерева) - AuthContext: logout и notifyUnauthorized чистят react-query кэш (qc.clear()) - Layout: email пользователя + кнопка Выйти - App: /login и /register публичные (авторизованный -> /domains), остальное под ProtectedRoute Починка page-тестов (Accounts/Domains/Templates/DomainDiff/App): AuthProvider + мок api.auth.me, спай-ассерты обновлены под projectId-первым-аргументом сигнатур api.* (T5).
This commit is contained in:
@@ -3,10 +3,12 @@ import userEvent from "@testing-library/user-event"
|
||||
import { MemoryRouter, Routes, Route } from "react-router-dom"
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
import { DomainsPage } from "./DomainsPage"
|
||||
import { AuthProvider } from "@/auth/AuthContext"
|
||||
import { api } from "@/api/client"
|
||||
import { vi, beforeEach, test, expect } from "vitest"
|
||||
import type { Account, Domain, Template } from "@/api/types"
|
||||
|
||||
const PROJECT_ID = "p1"
|
||||
const accounts: Account[] = [
|
||||
{ id: "acc1", provider: "selectel", comment: "Main" },
|
||||
{ id: "acc2", provider: "cloudflare", comment: "Backup" },
|
||||
@@ -24,17 +26,24 @@ function renderPage() {
|
||||
const qc = new QueryClient()
|
||||
return render(
|
||||
<QueryClientProvider client={qc}>
|
||||
<MemoryRouter initialEntries={["/domains"]}>
|
||||
<Routes>
|
||||
<Route path="/domains" element={<DomainsPage />} />
|
||||
<Route path="/domains/:id" element={<div>diff page</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
<AuthProvider>
|
||||
<MemoryRouter initialEntries={["/domains"]}>
|
||||
<Routes>
|
||||
<Route path="/domains" element={<DomainsPage />} />
|
||||
<Route path="/domains/:id" element={<div>diff page</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
vi.spyOn(api.auth, "me").mockResolvedValue({
|
||||
user: { id: "u1", email: "a@b.com" },
|
||||
project: { id: PROJECT_ID, name: "Default" },
|
||||
})
|
||||
vi.spyOn(api, "listDomains").mockResolvedValue(domains)
|
||||
vi.spyOn(api, "listAccounts").mockResolvedValue(accounts)
|
||||
vi.spyOn(api, "listTemplates").mockResolvedValue(templates)
|
||||
@@ -64,7 +73,7 @@ test("кнопка импорта вызывает api.importZones с выбра
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /импортировать зоны/i }))
|
||||
|
||||
await waitFor(() => expect(importSpy).toHaveBeenCalledWith("acc2"))
|
||||
await waitFor(() => expect(importSpy).toHaveBeenCalledWith(PROJECT_ID, "acc2"))
|
||||
})
|
||||
|
||||
test("привязка шаблона в строке домена вызывает api.setDomainTemplate", async () => {
|
||||
@@ -77,7 +86,7 @@ test("привязка шаблона в строке домена вызыва
|
||||
await user.click(screen.getByRole("combobox", { name: /example\.com\./i }))
|
||||
await user.click(await screen.findByRole("option", { name: /^standard$/i }))
|
||||
|
||||
await waitFor(() => expect(setTemplateSpy).toHaveBeenCalledWith("d1", "t1"))
|
||||
await waitFor(() => expect(setTemplateSpy).toHaveBeenCalledWith(PROJECT_ID, "d1", "t1"))
|
||||
})
|
||||
|
||||
test("ошибка привязки шаблона отображается пользователю", async () => {
|
||||
|
||||
Reference in New Issue
Block a user