35% front tests

This commit is contained in:
iqubik
2026-03-29 22:17:34 +03:00
parent 61b814482c
commit 425c1d5e8d
29 changed files with 4942 additions and 15 deletions
+110
View File
@@ -0,0 +1,110 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import LoginPage from '../../src/pages/LoginPage'
import { ThemeProvider } from '../../src/ThemeContext'
import { AuthProvider } from '../../src/auth/AuthContext'
const mockNavigate = vi.fn()
const mockLogin = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom')
return {
...(actual as object),
useNavigate: () => mockNavigate,
}
})
vi.mock('../../src/auth/AuthContext', async () => {
const actual = await vi.importActual('../../src/auth/AuthContext')
return {
...(actual as object),
useAuth: () => ({ login: mockLogin }),
}
})
vi.mock('../../src/api', () => ({
default: {
post: vi.fn(),
},
}))
const renderLoginPage = () => {
return render(
<BrowserRouter>
<ThemeProvider>
<AuthProvider>
<LoginPage />
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
)
}
describe('LoginPage', () => {
beforeEach(() => {
mockNavigate.mockClear()
mockLogin.mockClear()
vi.clearAllMocks()
})
it('должен отображать заголовок с версией', () => {
renderLoginPage()
expect(screen.getByText(/Вход в 3DP-MANAGER/)).toBeInTheDocument()
})
it('должен отображать поля ввода логина и пароля', () => {
renderLoginPage()
expect(screen.getByLabelText('Логин')).toBeInTheDocument()
expect(screen.getByLabelText('Пароль')).toBeInTheDocument()
})
it('должен отображать кнопку "Войти"', () => {
renderLoginPage()
expect(screen.getByText('Войти')).toBeInTheDocument()
})
it('должен позволять вводить текст в поля', () => {
renderLoginPage()
const loginField = screen.getByLabelText('Логин')
const passwordField = screen.getByLabelText('Пароль')
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'password123' } })
expect(loginField).toHaveValue('admin')
expect(passwordField).toHaveValue('password123')
})
it('должен показывать ошибку при неверных учетных данных', async () => {
const api = await import('../../src/api')
vi.mocked(api.default.post).mockRejectedValue({ response: { status: 401 } })
renderLoginPage()
fireEvent.change(screen.getByLabelText('Логин'), { target: { value: 'admin' } })
fireEvent.change(screen.getByLabelText('Пароль'), { target: { value: 'wrong' } })
fireEvent.click(screen.getByText('Войти'))
await waitFor(() => {
expect(screen.getByText('Неверный логин или пароль')).toBeInTheDocument()
})
})
it('должен выполнять навигацию на главную при успешном входе', async () => {
const api = await import('../../src/api')
vi.mocked(api.default.post).mockResolvedValue({ data: { access_token: 'fake-token' } })
renderLoginPage()
fireEvent.change(screen.getByLabelText('Логин'), { target: { value: 'admin' } })
fireEvent.change(screen.getByLabelText('Пароль'), { target: { value: 'password' } })
fireEvent.click(screen.getByText('Войти'))
await waitFor(() => {
expect(mockLogin).toHaveBeenCalledWith('fake-token')
expect(mockNavigate).toHaveBeenCalledWith('/')
})
})
})
+44
View File
@@ -0,0 +1,44 @@
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import NotFoundPage from '../../src/pages/NotFoundPage'
const mockNavigate = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom')
return {
...(actual as object),
useNavigate: () => mockNavigate,
}
})
const renderNotFoundPage = () => {
return render(
<BrowserRouter>
<NotFoundPage />
</BrowserRouter>
)
}
describe('NotFoundPage', () => {
it('должен отображать код ошибки 404', () => {
renderNotFoundPage()
expect(screen.getByText('404')).toBeInTheDocument()
})
it('должен отображать сообщение "Страница не найдена"', () => {
renderNotFoundPage()
expect(screen.getByText('Страница не найдена')).toBeInTheDocument()
})
it('должен иметь кнопку "На главную"', () => {
renderNotFoundPage()
expect(screen.getByText('На главную')).toBeInTheDocument()
})
it('должен выполнять навигацию на главную при клике на кнопку', () => {
renderNotFoundPage()
fireEvent.click(screen.getByText('На главную'))
expect(mockNavigate).toHaveBeenCalledWith('/')
})
})
+651
View File
@@ -0,0 +1,651 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import SettingsPage from '../../src/pages/SettingsPage'
import { ThemeProvider } from '../../src/ThemeContext'
import { AuthProvider } from '../../src/auth/AuthContext'
const mockGet = vi.fn()
const mockPost = vi.fn()
const mockPut = vi.fn()
vi.mock('../../src/api', () => ({
default: {
get: (...args: unknown[]) => mockGet(...args),
post: (...args: unknown[]) => mockPost(...args),
put: (...args: unknown[]) => mockPut(...args),
},
}))
// Хелпер для настройки мока get по умолчанию
const setupMockGet = (overrides?: { settings?: Record<string, unknown>, subscriptions?: unknown[] }) => {
mockGet.mockImplementation((url: string) => {
if (url === '/settings') return Promise.resolve({ data: overrides?.settings || {} })
if (url === '/subscriptions') return Promise.resolve({ data: overrides?.subscriptions || [] })
return Promise.resolve({ data: {} })
})
}
const renderSettingsPage = () => {
return render(
<BrowserRouter>
<ThemeProvider>
<AuthProvider>
<SettingsPage />
</AuthProvider>
</ThemeProvider>
</BrowserRouter>
)
}
describe('SettingsPage', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.clearAllMocks()
})
describe('Рендеринг', () => {
it('должен рендериться с заголовком', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Настройки утилиты')).toBeInTheDocument()
})
})
it('должен отображать секцию панели 3x-ui', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Панель 3x-ui')).toBeInTheDocument()
})
})
it('должен отображать секцию генерации инбаундов', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Генерация инбаундов')).toBeInTheDocument()
})
})
it('должен отображать поля ввода для 3x-ui', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByLabelText('URL панели')).toBeInTheDocument()
expect(screen.getByLabelText('Логин 3x-ui')).toBeInTheDocument()
expect(screen.getByLabelText('Пароль 3x-ui')).toBeInTheDocument()
})
})
it('должен отображать поле интервала генерации', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByLabelText('Интервал генерации')).toBeInTheDocument()
})
})
it('должен отображать пресеты интервалов', async () => {
setupMockGet()
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Сутки')).toBeInTheDocument()
expect(screen.getByText('3 дня')).toBeInTheDocument()
expect(screen.getByText('Неделя')).toBeInTheDocument()
})
})
})
describe('Загрузка настроек', () => {
it('должен загружать настройки при монтировании', async () => {
const mockSettings = {
xui_url: 'https://test.com:2053',
xui_login: 'admin',
xui_password: 'password',
rotation_interval: '60',
rotation_status: 'active',
last_rotation_timestamp: '1234567890',
}
setupMockGet({ settings: mockSettings })
renderSettingsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/settings')
})
})
it('должен загружать подписки при монтировании', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: true }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
renderSettingsPage()
await waitFor(() => {
expect(mockGet).toHaveBeenCalledWith('/subscriptions')
})
})
})
describe('Изменение полей', () => {
it('должен позволять изменять URL панели', async () => {
setupMockGet()
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
fireEvent.change(urlField, { target: { value: 'https://new-url.com:2053' } })
expect(urlField).toHaveValue('https://new-url.com:2053')
})
it('должен позволять изменять логин 3x-ui', async () => {
setupMockGet()
renderSettingsPage()
const loginField = await screen.findByLabelText('Логин 3x-ui')
fireEvent.change(loginField, { target: { value: 'newadmin' } })
expect(loginField).toHaveValue('newadmin')
})
it('должен позволять изменять пароль 3x-ui', async () => {
setupMockGet()
renderSettingsPage()
const passwordField = await screen.findByLabelText('Пароль 3x-ui')
fireEvent.change(passwordField, { target: { value: 'newpassword' } })
expect(passwordField).toHaveValue('newpassword')
})
})
describe('Пресеты интервалов', () => {
it('должен отображать пресеты интервалов', async () => {
setupMockGet({ settings: { rotation_interval: '30' } })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Сутки')).toBeInTheDocument()
expect(screen.getByText('3 дня')).toBeInTheDocument()
expect(screen.getByText('Неделя')).toBeInTheDocument()
})
})
})
describe('Сохранение настроек подключения', () => {
it('должен показывать ошибку при пустых полях', async () => {
setupMockGet()
renderSettingsPage()
const saveButton = await screen.findByText('Сохранить подключение')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText(/Заполните все поля/i)).toBeInTheDocument()
})
})
it('должен сохранять настройки при валидных данных', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
const loginField = screen.getByLabelText('Логин 3x-ui')
const passwordField = screen.getByLabelText('Пароль 3x-ui')
fireEvent.change(urlField, { target: { value: 'https://test.com:2053' } })
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'password' } })
const saveButton = screen.getByText('Сохранить подключение')
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/settings', expect.objectContaining({
xui_url: 'https://test.com:2053',
xui_login: 'admin',
xui_password: 'password',
}))
})
})
it('должен показывать сообщение об успехе после сохранения', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
const loginField = screen.getByLabelText('Логин 3x-ui')
const passwordField = screen.getByLabelText('Пароль 3x-ui')
fireEvent.change(urlField, { target: { value: 'https://test.com:2053' } })
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'password' } })
const saveButton = screen.getByText('Сохранить подключение')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Настройки сохранены!')).toBeInTheDocument()
})
})
})
describe('Проверка подключения', () => {
it('должен проверять подключение при клике на кнопку "Проверить"', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: { success: true } })
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
const loginField = screen.getByLabelText('Логин 3x-ui')
const passwordField = screen.getByLabelText('Пароль 3x-ui')
fireEvent.change(urlField, { target: { value: 'https://test.com:2053' } })
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'password' } })
const checkButton = screen.getByText('Проверить')
fireEvent.click(checkButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/settings/check', expect.objectContaining({
xui_url: 'https://test.com:2053',
xui_login: 'admin',
xui_password: 'password',
}))
})
})
it('должен показывать успех при успешной проверке', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: { success: true } })
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
const loginField = screen.getByLabelText('Логин 3x-ui')
const passwordField = screen.getByLabelText('Пароль 3x-ui')
fireEvent.change(urlField, { target: { value: 'https://test.com:2053' } })
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'password' } })
const checkButton = screen.getByText('Проверить')
fireEvent.click(checkButton)
await waitFor(() => {
expect(screen.getByText('Подключение успешно!')).toBeInTheDocument()
})
})
it('должен показывать ошибку при неудачной проверке', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: { success: false } })
renderSettingsPage()
const urlField = await screen.findByLabelText('URL панели')
const loginField = screen.getByLabelText('Логин 3x-ui')
const passwordField = screen.getByLabelText('Пароль 3x-ui')
fireEvent.change(urlField, { target: { value: 'https://test.com:2053' } })
fireEvent.change(loginField, { target: { value: 'admin' } })
fireEvent.change(passwordField, { target: { value: 'wrong' } })
const checkButton = screen.getByText('Проверить')
fireEvent.click(checkButton)
await waitFor(() => {
expect(screen.getByText(/Ошибка/i)).toBeInTheDocument()
})
})
})
describe('Сохранение интервала', () => {
it('должен сохранять интервал при клике на кнопку', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const intervalField = await screen.findByLabelText('Интервал генерации')
fireEvent.change(intervalField, { target: { value: '120' } })
const saveButton = screen.getByText('Применить интервал')
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/settings', {
rotation_interval: '120',
})
})
})
it('должен показывать сообщение об успехе после сохранения интервала', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const intervalField = await screen.findByLabelText('Интервал генерации')
fireEvent.change(intervalField, { target: { value: '120' } })
const saveButton = screen.getByText('Применить интервал')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Интервал генерации применён!')).toBeInTheDocument()
})
})
})
describe('Принудительная ротация', () => {
it('должен показывать диалог подтверждения при клике на "Сгенерировать сейчас"', async () => {
setupMockGet()
renderSettingsPage()
const rotateButton = await screen.findByText('Сгенерировать сейчас')
fireEvent.click(rotateButton)
await waitFor(() => {
expect(screen.getByText(/ВНИМАНИЕ/i)).toBeInTheDocument()
})
})
it('должен выполнять ротацию при подтверждении', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: { success: true, message: 'Ротация выполнена' } })
renderSettingsPage()
const rotateButton = await screen.findByText('Сгенерировать сейчас')
fireEvent.click(rotateButton)
const confirmButton = await screen.findByText('Продолжить')
fireEvent.click(confirmButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/rotation/rotate-all')
})
})
})
describe('Управление авторотацией подписок', () => {
it('должен отображать список подписок с чекбоксами', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: true }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Test Sub')).toBeInTheDocument()
})
})
it('должен переключать авторотацию при клике на чекбокс', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: true }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
mockPut.mockResolvedValue({ data: {} })
renderSettingsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(mockPut).toHaveBeenCalledWith('/subscriptions/bulk-auto-rotation', expect.anything())
})
})
it('должен показывать сообщение при включении авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: false }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
mockPut.mockResolvedValue({ data: {} })
renderSettingsPage()
const checkbox = await screen.findByRole('checkbox')
fireEvent.click(checkbox)
await waitFor(() => {
expect(screen.getByText('Авторотация включена')).toBeInTheDocument()
})
})
it('должен выполнять ручную ротацию при клике на кнопку обновления', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: true }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
mockPost.mockResolvedValue({ data: { message: 'Ротация выполнена' } })
renderSettingsPage()
const refreshButton = await screen.findByRole('button', { name: /Обновить подписку вручную/i })
fireEvent.click(refreshButton)
const confirmButton = await screen.findByText('Продолжить')
fireEvent.click(confirmButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/rotation/rotate-one/1')
})
})
it('должен выполнять массовое включение авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: false }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
mockPut.mockResolvedValue({ data: { message: 'Настройки обновлены' } })
renderSettingsPage()
const enableAllButton = await screen.findByText('Включить для всех')
fireEvent.click(enableAllButton)
await waitFor(() => {
expect(mockPut).toHaveBeenCalledWith('/subscriptions/bulk-auto-rotation', expect.objectContaining({
enabled: true,
}))
})
})
it('должен выполнять массовое выключение авторотации', async () => {
const mockSubs = [
{ id: '1', name: 'Test Sub', uuid: 'abc-123', isAutoRotationEnabled: true }
]
mockGet.mockImplementation((url: string) => {
if (url === '/subscriptions') return Promise.resolve({ data: mockSubs })
return Promise.resolve({ data: {} })
})
mockPut.mockResolvedValue({ data: { message: 'Настройки обновлены' } })
renderSettingsPage()
const disableAllButton = await screen.findByText('Выключить для всех')
fireEvent.click(disableAllButton)
await waitFor(() => {
expect(mockPut).toHaveBeenCalledWith('/subscriptions/bulk-auto-rotation', expect.objectContaining({
enabled: false,
}))
})
})
})
describe('Обновление профиля администратора', () => {
it('должен позволять изменять логин администратора', async () => {
setupMockGet()
renderSettingsPage()
const loginField = await screen.findByLabelText('Логин администратора')
fireEvent.change(loginField, { target: { value: 'newadmin' } })
expect(loginField).toHaveValue('newadmin')
})
it('должен позволять изменять пароль администратора', async () => {
setupMockGet()
renderSettingsPage()
const passwordField = await screen.findByLabelText('Новый пароль')
fireEvent.change(passwordField, { target: { value: 'newpassword' } })
expect(passwordField).toHaveValue('newpassword')
})
it('должен сохранять профиль администратора', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const loginField = await screen.findByLabelText('Логин администратора')
const passwordField = screen.getByLabelText('Новый пароль')
fireEvent.change(loginField, { target: { value: 'newadmin' } })
fireEvent.change(passwordField, { target: { value: 'newpassword' } })
const saveButton = screen.getByText('Обновить профиль')
fireEvent.click(saveButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/auth/update-profile', expect.objectContaining({
login: 'newadmin',
password: 'newpassword',
}))
})
})
it('должен показывать сообщение об успехе после обновления профиля', async () => {
setupMockGet()
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const loginField = await screen.findByLabelText('Логин администратора')
const passwordField = screen.getByLabelText('Новый пароль')
fireEvent.change(loginField, { target: { value: 'newadmin' } })
fireEvent.change(passwordField, { target: { value: 'newpassword' } })
const saveButton = screen.getByText('Обновить профиль')
fireEvent.click(saveButton)
await waitFor(() => {
expect(screen.getByText('Профиль администратора обновлен!')).toBeInTheDocument()
})
})
})
describe('Пауза/возобновление ротации', () => {
it('должен отображать статус "Активен" при active статусе', async () => {
setupMockGet({ settings: { rotation_status: 'active' } })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Активен')).toBeInTheDocument()
})
})
it('должен отображать статус "Остановлен" при stopped статусе', async () => {
setupMockGet({ settings: { rotation_status: 'stopped' } })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Остановлен')).toBeInTheDocument()
})
})
it('должен переключать статус при клике на кнопку паузы', async () => {
setupMockGet({ settings: { rotation_status: 'active' } })
mockPost.mockResolvedValue({ data: {} })
renderSettingsPage()
const pauseButton = await screen.findByRole('button', { name: 'Поставить на паузу' })
fireEvent.click(pauseButton)
await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/settings', expect.objectContaining({
rotation_status: 'stopped',
}))
})
})
})
describe('Отображение дат ротации', () => {
it('должен отображать дату последней ротации', async () => {
setupMockGet({ settings: { last_rotation_timestamp: '1234567890' } })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Последняя генерация')).toBeInTheDocument()
})
})
it('должен отображать дату следующей ротации', async () => {
setupMockGet({ settings: {
rotation_status: 'active',
last_rotation_timestamp: '1234567890',
rotation_interval: '60'
} })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Следующая генерация')).toBeInTheDocument()
})
})
it('должен отображать "Пауза" для следующей ротации при stopped статусе', async () => {
setupMockGet({ settings: { rotation_status: 'stopped' } })
renderSettingsPage()
await waitFor(() => {
expect(screen.getByText('Пауза')).toBeInTheDocument()
})
})
})
})
+209
View File
@@ -0,0 +1,209 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import TunnelsPage from '../../src/pages/TunnelsPage'
import api from '../../src/api'
vi.mock('../../src/api')
describe('TunnelsPage', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(api.get).mockResolvedValue({ data: [] })
vi.mocked(api.post).mockResolvedValue({ data: {} })
vi.mocked(api.delete).mockResolvedValue({ data: {} })
})
afterEach(() => {
vi.clearAllMocks()
})
const renderPage = async () => {
const result = render(
<MemoryRouter>
<TunnelsPage />
</MemoryRouter>
)
// Ждём завершения первоначальной загрузки данных
await waitFor(() => {
expect(screen.getByText('Relay серверы')).toBeInTheDocument()
})
return result
}
it('должен рендериться с заголовком', async () => {
await renderPage()
expect(screen.getByText('Relay серверы')).toBeInTheDocument()
})
it('должен отображать кнопку "Добавить"', async () => {
await renderPage()
expect(screen.getByText('Добавить')).toBeInTheDocument()
})
it('должен открывать диалог создания при клике на "Добавить"', async () => {
await renderPage()
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
expect(screen.getByText('Новый редирект сервер')).toBeInTheDocument()
})
it('должен закрывать диалог создания при клике на отмену', async () => {
await renderPage()
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(screen.getByText('Новый редирект сервер')).toBeInTheDocument()
})
const cancelButton = screen.getByText('Отмена')
fireEvent.click(cancelButton)
await waitFor(() => {
expect(screen.queryByText('Новый редирект сервер')).not.toBeInTheDocument()
})
})
it('должен отображать поля формы в диалоге создания', async () => {
await renderPage()
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(screen.getByLabelText('Название')).toBeInTheDocument()
})
expect(screen.getByLabelText('IP адрес')).toBeInTheDocument()
expect(screen.getByLabelText('SSH Порт')).toBeInTheDocument()
expect(screen.getByLabelText('SSH User')).toBeInTheDocument()
})
it('должен переключать режим аутентификации между паролем и ключом', async () => {
await renderPage()
const addButton = screen.getByText('Добавить')
fireEvent.click(addButton)
await waitFor(() => {
expect(screen.getByLabelText('SSH Пароль')).toBeInTheDocument()
})
const keyRadio = screen.getByLabelText('По SSH ключу')
fireEvent.click(keyRadio)
await waitFor(() => {
expect(screen.getByLabelText(/SSH Private Key/i)).toBeInTheDocument()
})
})
it('должен показывать сообщение "Нет серверов" при пустом списке', async () => {
await renderPage()
expect(screen.getByText('Нет серверов')).toBeInTheDocument()
})
it('должен отображать список туннелей', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Test Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: true }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Test Tunnel')).toBeInTheDocument()
})
expect(screen.getByText('192.168.1.1')).toBeInTheDocument()
})
it('должен отображать статус "Активен" для установленного туннеля', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Active Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: true }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Активен')).toBeInTheDocument()
})
})
it('должен отображать статус "Не настроен" для неустановленного туннеля', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Inactive Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: false }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Не настроен')).toBeInTheDocument()
})
})
it('должен отображать кнопку "Установить" для неустановленного туннеля', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: false }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Установить')).toBeInTheDocument()
})
})
it('должен открывать диалог подтверждения при удалении туннеля', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: true }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Tunnel')).toBeInTheDocument()
})
const deleteButton = screen.getAllByTestId('icon-Delete')[0]
fireEvent.click(deleteButton)
await waitFor(() => {
expect(screen.getByText('Удалить сервер из списка?')).toBeInTheDocument()
})
})
it('должен закрывать диалог подтверждения при клике на "Отмена"', async () => {
vi.mocked(api.get).mockResolvedValue({
data: [
{ id: 1, name: 'Tunnel', ip: '192.168.1.1', sshPort: 22, username: 'root', isInstalled: true }
]
})
await renderPage()
await waitFor(() => {
expect(screen.getByText('Tunnel')).toBeInTheDocument()
})
const deleteButton = screen.getAllByTestId('icon-Delete')[0]
fireEvent.click(deleteButton)
await waitFor(() => {
expect(screen.getByText('Удалить сервер из списка?')).toBeInTheDocument()
})
const cancelButton = screen.getByText('Отмена')
fireEvent.click(cancelButton)
await waitFor(() => {
expect(screen.queryByText('Удалить сервер из списка?')).not.toBeInTheDocument()
})
})
})