feat(web): расписание, каналы уведомлений, история проверок, drift-badge

This commit is contained in:
2026-07-04 14:40:29 +07:00
parent 45259b9720
commit 34422420ca
14 changed files with 937 additions and 3 deletions
+146
View File
@@ -0,0 +1,146 @@
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { MemoryRouter } from "react-router-dom"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ChannelsPage } from "./ChannelsPage"
import { AuthProvider } from "@/auth/AuthContext"
import { api } from "@/api/client"
import { vi, beforeEach, test, expect } from "vitest"
import type { Channel } from "@/api/types"
const PROJECT_ID = "p1"
const channels: Channel[] = [
{ id: "c1", type: "telegram", config: { chat_id: "123456" }, enabled: true },
{ id: "c2", type: "webhook", config: { url: "https://hooks.example.com/x" }, enabled: false },
]
function renderPage() {
const qc = new QueryClient()
return render(
<QueryClientProvider client={qc}>
<AuthProvider>
<MemoryRouter initialEntries={["/channels"]}>
<ChannelsPage />
</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, "listChannels").mockResolvedValue(channels)
})
test("отрисовывает список каналов без секрета", async () => {
renderPage()
expect(await screen.findByText("telegram")).toBeInTheDocument()
expect(screen.getByText("webhook")).toBeInTheDocument()
expect(screen.getByText(/123456/)).toBeInTheDocument()
expect(screen.getByText(/hooks\.example\.com/)).toBeInTheDocument()
expect(document.body.textContent).not.toMatch(/bot_token/i)
expect(screen.queryByDisplayValue(/123456/)).not.toBeInTheDocument()
})
test("создание telegram-канала собирает config.chat_id + secret=bot_token", async () => {
const createSpy = vi.spyOn(api, "createChannel").mockResolvedValue({
id: "c3", type: "telegram", config: { chat_id: "999" }, enabled: true,
})
const user = userEvent.setup()
renderPage()
await screen.findByText("telegram")
await user.click(screen.getByRole("combobox", { name: /тип канала/i }))
await user.click(await screen.findByRole("option", { name: /telegram/i }))
await user.type(screen.getByLabelText(/chat id/i), "999")
await user.type(screen.getByLabelText(/bot token/i), "SECRET_TOKEN")
await user.click(screen.getByRole("button", { name: /добавить канал/i }))
await waitFor(() =>
expect(createSpy).toHaveBeenCalledWith(PROJECT_ID, {
type: "telegram",
config: { chat_id: "999" },
secret: "SECRET_TOKEN",
}),
)
expect(document.body.textContent).not.toMatch(/SECRET_TOKEN/)
})
test("создание webhook-канала собирает config.url без секрета", async () => {
const createSpy = vi.spyOn(api, "createChannel").mockResolvedValue({
id: "c4", type: "webhook", config: { url: "https://hooks.example.com/y" }, enabled: true,
})
const user = userEvent.setup()
renderPage()
await screen.findByText("telegram")
await user.click(screen.getByRole("combobox", { name: /тип канала/i }))
await user.click(await screen.findByRole("option", { name: /webhook/i }))
await user.type(screen.getByLabelText(/url/i), "https://hooks.example.com/y")
await user.click(screen.getByRole("button", { name: /добавить канал/i }))
await waitFor(() =>
expect(createSpy).toHaveBeenCalledWith(PROJECT_ID, {
type: "webhook",
config: { url: "https://hooks.example.com/y" },
secret: "",
}),
)
})
test("удаление канала вызывает api.deleteChannel", async () => {
const deleteSpy = vi.spyOn(api, "deleteChannel").mockResolvedValue(undefined)
vi.spyOn(window, "confirm").mockReturnValue(true)
const user = userEvent.setup()
renderPage()
await screen.findByText("telegram")
await user.click(screen.getByRole("button", { name: /удалить канал telegram/i }))
await waitFor(() => expect(deleteSpy).toHaveBeenCalledWith(PROJECT_ID, "c1"))
})
test("кнопка «Тест» вызывает api.testChannel", async () => {
const testSpy = vi.spyOn(api, "testChannel").mockResolvedValue({ status: "ok" })
const user = userEvent.setup()
renderPage()
await screen.findByText("telegram")
const testButtons = screen.getAllByRole("button", { name: /тест/i })
await user.click(testButtons[0])
await waitFor(() => expect(testSpy).toHaveBeenCalledWith(PROJECT_ID, "c1"))
})
test("ошибка тест-отправки отображается как alert", async () => {
vi.spyOn(api, "testChannel").mockRejectedValue(new Error("Канал не отвечает"))
const user = userEvent.setup()
renderPage()
await screen.findByText("telegram")
const testButtons = screen.getAllByRole("button", { name: /тест/i })
await user.click(testButtons[0])
expect(await screen.findByRole("alert")).toHaveTextContent("Канал не отвечает")
})
test("пустое состояние при отсутствии каналов", async () => {
vi.spyOn(api, "listChannels").mockResolvedValue([])
renderPage()
expect(await screen.findByText(/каналов пока нет/i)).toBeInTheDocument()
})