Files
Charon/frontend/src/components/ImportSitesModal.test.tsx
GitHub Actions 032d475fba chore: remediate 61 Go linting issues and tighten pre-commit config
Complete lint remediation addressing errcheck, gosec, and staticcheck
violations across backend test files. Tighten pre-commit configuration
to prevent future blind spots.

Key Changes:
- Fix 61 Go linting issues (errcheck, gosec G115/G301/G304/G306, bodyclose)
- Add proper error handling for json.Unmarshal, os.Setenv, db.Close(), w.Write()
- Fix gosec G115 integer overflow with strconv.FormatUint
- Add #nosec annotations with justifications for test fixtures
- Fix SecurityService goroutine leaks (add Close() calls)
- Fix CrowdSec tar.gz non-deterministic ordering with sorted keys

Pre-commit Hardening:
- Remove test file exclusion from golangci-lint hook
- Add gosec to .golangci-fast.yml with critical checks (G101, G110, G305)
- Replace broad .golangci.yml exclusions with targeted path-specific rules
- Test files now linted on every commit

Test Fixes:
- Fix emergency route count assertions (1→2 for dual-port setup)
- Fix DNS provider service tests with proper mock setup
- Fix certificate service tests with deterministic behavior

Backend: 27 packages pass, 83.5% coverage
Frontend: 0 lint warnings, 0 TypeScript errors
Pre-commit: All 14 hooks pass (~37s)
2026-02-02 06:17:48 +00:00

100 lines
4.0 KiB
TypeScript

import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import ImportSitesModal from './ImportSitesModal'
import { vi } from 'vitest'
import { CaddyFile } from '../api/import'
// Mock the upload API used by the component
const mockUpload = vi.fn()
vi.mock('../api/import', () => ({
uploadCaddyfilesMulti: (files: CaddyFile[]) => mockUpload(files),
}))
describe('ImportSitesModal', () => {
beforeEach(() => {
mockUpload.mockReset()
})
test('renders modal, add and remove sites, and edits textarea', () => {
const onClose = vi.fn()
render(<ImportSitesModal visible={true} onClose={onClose} />)
// modal container is present
expect(screen.getByTestId('multi-site-modal')).toBeInTheDocument()
// initially one site with filename input and content textarea
const textareas = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')
expect(textareas.length).toBe(1)
// add a site -> two sites
fireEvent.click(screen.getByText('+ Add site'))
const textareasAfterAdd = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')
expect(textareasAfterAdd.length).toBe(2)
// remove the second site (use getAllByText since multiple Remove buttons now exist)
const removeButtons = screen.getAllByText('Remove')
fireEvent.click(removeButtons[removeButtons.length - 1])
const textareasAfterRemove = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')
expect(textareasAfterRemove.length).toBe(1)
// type into textarea
const ta = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')[0]
fireEvent.change(ta, { target: { value: 'example.com { reverse_proxy 127.0.0.1:8080 }' } })
expect((ta as HTMLTextAreaElement).value).toContain('example.com')
})
test('reads multiple files via hidden input and submits successfully', async () => {
const onClose = vi.fn()
const onUploaded = vi.fn()
mockUpload.mockResolvedValueOnce(undefined)
const { container } = render(<ImportSitesModal visible={true} onClose={onClose} onUploaded={onUploaded} />)
// find the hidden file input
const input: HTMLInputElement | null = container.querySelector('input[type="file"]')
expect(input).toBeTruthy()
// create two files (note: jsdom's File.text() returns empty strings, so we'll set content manually)
const f1 = new File(['site1'], 'site1.caddy', { type: 'text/plain' })
const f2 = new File(['site2'], 'site2.caddy', { type: 'text/plain' })
// fire change event with files
fireEvent.change(input!, { target: { files: [f1, f2] } })
// after input, two textareas should appear (one per file)
await waitFor(() => {
const textareas = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')
expect(textareas.length).toBe(2)
})
// Manually fill textareas since jsdom's File.text() doesn't work correctly
const textareas = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')
fireEvent.change(textareas[0], { target: { value: 'site1' } })
fireEvent.change(textareas[1], { target: { value: 'site2' } })
// submit
fireEvent.click(screen.getByText('Parse and Review'))
await waitFor(() => expect(mockUpload).toHaveBeenCalled())
// New API contract: files are passed as {filename, content} objects
expect(mockUpload).toHaveBeenCalledWith([
{ filename: 'site1.caddy', content: 'site1' },
{ filename: 'site2.caddy', content: 'site2' },
])
expect(onUploaded).toHaveBeenCalled()
expect(onClose).toHaveBeenCalled()
})
test('displays error when upload fails', async () => {
const onClose = vi.fn()
mockUpload.mockRejectedValueOnce(new Error('upload-failed'))
render(<ImportSitesModal visible={true} onClose={onClose} />)
// click submit with default empty site
fireEvent.click(screen.getByText('Parse and Review'))
// error message appears
await waitFor(() => expect(screen.getByText(/upload-failed|Upload failed/i)).toBeInTheDocument())
})
})