Files
dns-autoresolver/web/src/pages/DomainDiffPage.test.tsx
T

145 lines
6.4 KiB
TypeScript

import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { MemoryRouter, Routes, Route } from "react-router-dom"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { DomainDiffPage } from "./DomainDiffPage"
import { AuthProvider } from "@/auth/AuthContext"
import { api } from "@/api/client"
import { vi, beforeEach, test, expect } from "vitest"
import type { Domain } from "@/api/types"
const PROJECT_ID = "p1"
const domainWithTemplate: Domain = {
id: "d1",
providerAccountId: "acc1",
zoneName: "example.com.",
zoneId: "z1",
templateId: "t1",
lastCheckStatus: "drift",
}
function renderPage(qc: QueryClient = new QueryClient()) {
return render(
<QueryClientProvider client={qc}>
<AuthProvider>
<MemoryRouter initialEntries={["/domains/d1"]}>
<Routes><Route path="/domains/:id" element={<DomainDiffPage />} /></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, "domainHistory").mockResolvedValue([])
vi.spyOn(api, "listDomains").mockResolvedValue([domainWithTemplate])
})
test("apply sends applyPrunes=false by default, true only after opting in", async () => {
vi.spyOn(api, "checkDomain").mockResolvedValue({
updates: [{ kind: "update", type: "A", name: "a.", desired: ["1"], actual: ["2"], readOnly: false }],
prunes: [{ kind: "delete", type: "A", name: "b.", actual: ["3"], readOnly: false }],
readOnly: [], inSyncCount: 0,
})
const applySpy = vi.spyOn(api, "applyDomain").mockResolvedValue({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
const user = userEvent.setup()
renderPage()
const applyBtn = await screen.findByRole("button", { name: /apply/i })
await user.click(applyBtn)
await waitFor(() => expect(applySpy).toHaveBeenCalled())
expect(applySpy.mock.calls[0]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: false }])
// включить prune и применить снова
const pruneToggle = screen.getByRole("checkbox", { name: /prune|удал/i })
await user.click(pruneToggle)
await user.click(screen.getByRole("button", { name: /apply/i }))
await waitFor(() => expect(applySpy).toHaveBeenCalledTimes(2))
expect(applySpy.mock.calls[1]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: true }])
// домен с шаблоном: записи зоны не нужны для диффа — запрос не должен уходить к провайдеру
expect(zoneRecordsSpy).not.toHaveBeenCalled()
})
test("пока список доменов грузится — показан общий лоадер, а не баннер об отсутствии шаблона", async () => {
let resolveListDomains: (domains: Domain[]) => void
vi.spyOn(api, "listDomains").mockReturnValue(
new Promise((resolve) => {
resolveListDomains = resolve
}),
)
const checkSpy = vi.spyOn(api, "checkDomain").mockResolvedValue({
updates: [], prunes: [], readOnly: [], inSyncCount: 0,
})
const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
renderPage()
expect(await screen.findByText(/загрузка/i)).toBeInTheDocument()
expect(screen.queryByText(/шаблон не привязан/i)).not.toBeInTheDocument()
expect(checkSpy).not.toHaveBeenCalled()
expect(zoneRecordsSpy).not.toHaveBeenCalled()
resolveListDomains!([domainWithTemplate])
expect(await screen.findByRole("button", { name: /apply/i })).toBeInTheDocument()
})
test("домен без шаблона показывает записи зоны и не вызывает check", async () => {
vi.spyOn(api, "listDomains").mockResolvedValue([
{ id: "d1", providerAccountId: "acc1", zoneName: "example.com.", zoneId: "z1", templateId: null },
])
const checkSpy = vi.spyOn(api, "checkDomain")
vi.spyOn(api, "zoneRecords").mockResolvedValue([
{ type: "A", name: "example.com.", ttl: 3600, values: ["1.2.3.4"] },
])
renderPage()
expect(await screen.findByText(/шаблон не привязан/i)).toBeInTheDocument()
expect(screen.getByRole("button", { name: /создать шаблон из этой зоны/i })).toBeInTheDocument()
expect(await screen.findByText("example.com.")).toBeInTheDocument()
expect(screen.getByText("1.2.3.4")).toBeInTheDocument()
expect(screen.queryByText(/вычисляю дифф/i)).not.toBeInTheDocument()
expect(checkSpy).not.toHaveBeenCalled()
})
test("ошибка загрузки списка доменов показывает баннер ошибки и не уходит в ветку без шаблона", async () => {
vi.spyOn(api, "listDomains").mockRejectedValue(new Error("network down"))
const checkSpy = vi.spyOn(api, "checkDomain")
const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
// retry:false — иначе react-query переретраит listDomains с экспоненциальной
// задержкой и findByText не успевает дождаться финального isError.
renderPage(new QueryClient({ defaultOptions: { queries: { retry: false } } }))
expect(await screen.findByText(/не удалось загрузить список доменов/i)).toBeInTheDocument()
expect(screen.getByText("network down")).toBeInTheDocument()
expect(screen.queryByText(/шаблон не привязан/i)).not.toBeInTheDocument()
expect(checkSpy).not.toHaveBeenCalled()
expect(zoneRecordsSpy).not.toHaveBeenCalled()
})
test("создание шаблона из зоны вызывает templateFromZone", async () => {
vi.spyOn(api, "listDomains").mockResolvedValue([
{ id: "d1", providerAccountId: "acc1", zoneName: "example.com.", zoneId: "z1", templateId: null },
])
vi.spyOn(api, "zoneRecords").mockResolvedValue([])
const templateFromZoneSpy = vi.spyOn(api, "templateFromZone").mockResolvedValue({
id: "t9", name: "example.com. snapshot", records: [], version: 1,
})
const user = userEvent.setup()
renderPage()
const createBtn = await screen.findByRole("button", { name: /создать шаблон из этой зоны/i })
await user.click(createBtn)
await waitFor(() => expect(templateFromZoneSpy).toHaveBeenCalledWith(PROJECT_ID, "d1"))
})