# 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() 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