Files
Sankofa/docs/guides/TEST_EXAMPLES.md
defiQUG fe0365757a Update documentation structure and enhance .gitignore
- Added generated index files and report directories to .gitignore to prevent unnecessary tracking of transient files.
- Updated README links to reflect new documentation paths for better navigation.
- Improved documentation organization by ensuring all links point to the correct locations, enhancing user experience and accessibility.
2025-12-12 21:18:55 -08:00

315 lines
7.2 KiB
Markdown

# Test Examples and Patterns
This document provides examples and patterns for writing tests in the Sankofa Phoenix project.
## Unit Tests
### Testing Service Functions
```typescript
// api/src/services/auth.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { login } from './auth'
import { getDb } from '../db'
import { AppErrors } from '../lib/errors'
// Mock dependencies
vi.mock('../db')
vi.mock('../lib/errors')
describe('auth service', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should authenticate valid user', async () => {
const mockDb = {
query: vi.fn().mockResolvedValue({
rows: [{
id: '1',
email: 'user@example.com',
name: 'Test User',
password_hash: '$2a$10$hashed',
role: 'USER',
created_at: new Date(),
updated_at: new Date(),
}]
})
}
vi.mocked(getDb).mockReturnValue(mockDb as any)
// Mock bcrypt.compare to return true
vi.mock('bcryptjs', () => ({
compare: vi.fn().mockResolvedValue(true)
}))
const result = await login('user@example.com', 'password123')
expect(result).toHaveProperty('token')
expect(result.user.email).toBe('user@example.com')
})
it('should throw error for invalid credentials', async () => {
const mockDb = {
query: vi.fn().mockResolvedValue({
rows: []
})
}
vi.mocked(getDb).mockReturnValue(mockDb as any)
await expect(login('invalid@example.com', 'wrong')).rejects.toThrow()
})
})
```
### Testing GraphQL Resolvers
```typescript
// api/src/schema/resolvers.test.ts
import { describe, it, expect, vi } from 'vitest'
import { resolvers } from './resolvers'
import * as resourceService from '../services/resource'
vi.mock('../services/resource')
describe('GraphQL resolvers', () => {
it('should return resources', async () => {
const mockContext = {
user: { id: '1', email: 'test@example.com', role: 'USER' },
db: {} as any,
tenantContext: null
}
const mockResources = [
{ id: '1', name: 'Resource 1', type: 'VM', status: 'RUNNING' }
]
vi.mocked(resourceService.getResources).mockResolvedValue(mockResources as any)
const result = await resolvers.Query.resources({}, {}, mockContext)
expect(result).toEqual(mockResources)
expect(resourceService.getResources).toHaveBeenCalledWith(mockContext, undefined)
})
})
```
### Testing Adapters
```typescript
// api/src/adapters/proxmox/adapter.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ProxmoxAdapter } from './adapter'
// Mock fetch
global.fetch = vi.fn()
describe('ProxmoxAdapter', () => {
let adapter: ProxmoxAdapter
beforeEach(() => {
adapter = new ProxmoxAdapter({
apiUrl: 'https://proxmox.example.com:8006',
apiToken: 'test-token'
})
vi.clearAllMocks()
})
it('should discover resources', async () => {
vi.mocked(fetch)
.mockResolvedValueOnce({
ok: true,
json: async () => ({
data: [{ node: 'node1' }]
})
} as Response)
.mockResolvedValueOnce({
ok: true,
json: async () => ({
data: [
{ vmid: 100, name: 'vm-100', status: 'running' }
]
})
} as Response)
const resources = await adapter.discoverResources()
expect(resources).toHaveLength(1)
expect(resources[0].name).toBe('vm-100')
})
it('should handle API errors', async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 401,
statusText: 'Unauthorized',
text: async () => 'Authentication failed'
} as Response)
await expect(adapter.discoverResources()).rejects.toThrow()
})
})
```
## Integration Tests
### Testing Database Operations
```typescript
// api/src/services/resource.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { getDb } from '../db'
import { createResource, getResource } from './resource'
describe('resource service integration', () => {
let db: any
let context: any
beforeAll(async () => {
db = getDb()
context = {
user: { id: 'test-user', role: 'ADMIN' },
db,
tenantContext: null
}
})
afterAll(async () => {
// Cleanup test data
await db.query('DELETE FROM resources WHERE name LIKE $1', ['test-%'])
await db.end()
})
it('should create and retrieve resource', async () => {
const input = {
name: 'test-vm',
type: 'VM',
siteId: 'test-site'
}
const created = await createResource(context, input)
expect(created.name).toBe('test-vm')
const retrieved = await getResource(context, created.id)
expect(retrieved.id).toBe(created.id)
expect(retrieved.name).toBe('test-vm')
})
})
```
## E2E Tests
### Testing API Endpoints
```typescript
// e2e/api.test.ts
import { describe, it, expect, beforeAll } from 'vitest'
import { request } from './helpers'
describe('API E2E tests', () => {
let authToken: string
beforeAll(async () => {
// Login to get token
const response = await request('/graphql', {
method: 'POST',
body: JSON.stringify({
query: `
mutation {
login(email: "test@example.com", password: "test123") {
token
}
}
`
})
})
const data = await response.json()
authToken = data.data.login.token
})
it('should get resources', async () => {
const response = await request('/graphql', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: JSON.stringify({
query: `
query {
resources {
id
name
type
}
}
`
})
})
const data = await response.json()
expect(data.data.resources).toBeInstanceOf(Array)
})
})
```
## React Component Tests
```typescript
// portal/src/components/Dashboard.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { Dashboard } from './Dashboard'
vi.mock('../lib/crossplane-client', () => ({
createCrossplaneClient: () => ({
getVMs: vi.fn().mockResolvedValue([
{ id: '1', name: 'vm-1', status: 'running' }
])
})
}))
describe('Dashboard', () => {
it('should render VM list', async () => {
render(<Dashboard />)
await waitFor(() => {
expect(screen.getByText('vm-1')).toBeInTheDocument()
})
})
})
```
## Best Practices
1. **Use descriptive test names**: Describe what is being tested
2. **Arrange-Act-Assert pattern**: Structure tests clearly
3. **Mock external dependencies**: Don't rely on real external services
4. **Test error cases**: Verify error handling
5. **Clean up test data**: Remove data created during tests
6. **Use fixtures**: Create reusable test data
7. **Test edge cases**: Include boundary conditions
8. **Keep tests isolated**: Tests should not depend on each other
## Running Tests
```bash
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Run specific test file
pnpm test path/to/test/file.test.ts
```
---
**Last Updated**: 2025-01-09