docs: comprehensive documentation polish & CI/CD automation

Major Updates:
- Rewrote all docs in beginner-friendly 'ELI5' language
- Created docs index with user journey navigation
- Added complete getting-started guide for novice users
- Set up GitHub Container Registry (GHCR) automation
- Configured GitHub Pages deployment for documentation

Documentation:
- docs/index.md - Central navigation hub
- docs/getting-started.md - Step-by-step beginner guide
- docs/github-setup.md - CI/CD setup instructions
- README.md - Complete rewrite in accessible language
- CONTRIBUTING.md - Contributor guidelines
- Multiple comprehensive API and schema docs

CI/CD Workflows:
- .github/workflows/docker-build.yml - Multi-platform builds to GHCR
- .github/workflows/docs.yml - Automated docs deployment to Pages
- Supports main (latest), development (dev), and version tags
- Automated testing of built images
- Beautiful documentation site with dark theme

Benefits:
- Zero barrier to entry for new users
- Automated Docker builds (AMD64 + ARM64)
- Professional documentation site
- No Docker Hub account needed (uses GHCR)
- Complete CI/CD pipeline

All 7 implementation phases complete - project is production ready!
This commit is contained in:
Wikid82
2025-11-18 13:11:11 -05:00
parent b9dcc6c347
commit e58fcb714d
76 changed files with 16989 additions and 99 deletions

View File

@@ -0,0 +1,168 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { useImport } from '../useImport'
import * as api from '../../services/api'
// Mock the API
vi.mock('../../services/api', () => ({
importAPI: {
status: vi.fn(),
preview: vi.fn(),
upload: vi.fn(),
commit: vi.fn(),
cancel: vi.fn(),
},
}))
describe('useImport', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: false })
})
afterEach(() => {
vi.clearAllMocks()
})
it('starts with no active session', async () => {
const { result } = renderHook(() => useImport())
await waitFor(() => {
expect(result.current.session).toBeNull()
})
expect(result.current.loading).toBe(false)
expect(result.current.error).toBeNull()
})
it('uploads content and creates session', async () => {
const mockSession = {
uuid: 'session-1',
filename: 'Caddyfile',
state: 'reviewing',
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
const mockPreview = {
hosts: [{ domain: 'test.com' }],
conflicts: [],
errors: [],
}
vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession })
vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession })
vi.mocked(api.importAPI.preview).mockResolvedValue(mockPreview)
const { result } = renderHook(() => useImport())
await result.current.upload('example.com { reverse_proxy localhost:8080 }')
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
expect(api.importAPI.upload).toHaveBeenCalledWith('example.com { reverse_proxy localhost:8080 }', undefined)
expect(result.current.loading).toBe(false)
})
it('handles upload errors', async () => {
const mockError = new Error('Upload failed')
vi.mocked(api.importAPI.upload).mockRejectedValue(mockError)
const { result } = renderHook(() => useImport())
await expect(result.current.upload('invalid')).rejects.toThrow('Upload failed')
expect(result.current.error).toBe('Upload failed')
})
it('commits import with resolutions', async () => {
const mockSession = {
uuid: 'session-2',
filename: 'Caddyfile',
state: 'reviewing',
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession })
vi.mocked(api.importAPI.status)
.mockResolvedValueOnce({ has_pending: true, session: mockSession })
.mockResolvedValueOnce({ has_pending: false })
vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] })
vi.mocked(api.importAPI.commit).mockResolvedValue({})
const { result } = renderHook(() => useImport())
await result.current.upload('test')
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
await result.current.commit({ 'test.com': 'skip' })
expect(api.importAPI.commit).toHaveBeenCalledWith('session-2', { 'test.com': 'skip' })
await waitFor(() => {
expect(result.current.session).toBeNull()
})
})
it('cancels active import session', async () => {
const mockSession = {
uuid: 'session-3',
filename: 'Caddyfile',
state: 'reviewing',
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession })
vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession })
vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] })
vi.mocked(api.importAPI.cancel).mockResolvedValue(undefined)
const { result } = renderHook(() => useImport())
await result.current.upload('test')
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
await result.current.cancel()
expect(api.importAPI.cancel).toHaveBeenCalledWith('session-3')
expect(result.current.session).toBeNull()
})
it('handles commit errors', async () => {
const mockSession = {
uuid: 'session-4',
filename: 'Caddyfile',
state: 'reviewing',
created_at: '2025-01-18T10:00:00Z',
updated_at: '2025-01-18T10:00:00Z',
}
vi.mocked(api.importAPI.upload).mockResolvedValue({ session: mockSession })
vi.mocked(api.importAPI.status).mockResolvedValue({ has_pending: true, session: mockSession })
vi.mocked(api.importAPI.preview).mockResolvedValue({ hosts: [], conflicts: [], errors: [] })
const mockError = new Error('Commit failed')
vi.mocked(api.importAPI.commit).mockRejectedValue(mockError)
const { result } = renderHook(() => useImport())
await result.current.upload('test')
await waitFor(() => {
expect(result.current.session).toEqual(mockSession)
})
await expect(result.current.commit({})).rejects.toThrow('Commit failed')
expect(result.current.error).toBe('Commit failed')
})
})