docs: map existing codebase

- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues
This commit is contained in:
Shirone
2026-01-27 13:48:24 +01:00
parent ef3f8de33b
commit c30cde242a
7 changed files with 1925 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
# Testing Patterns
**Analysis Date:** 2026-01-27
## Test Framework
**Runner:**
- Vitest 4.0.16 (for unit and integration tests)
- Playwright (for E2E tests)
- Config: `apps/server/vitest.config.ts`, `libs/*/vitest.config.ts`, `apps/ui/playwright.config.ts`
**Assertion Library:**
- Vitest built-in expect assertions
- API: `expect().toBe()`, `expect().toEqual()`, `expect().toHaveLength()`, `expect().toHaveProperty()`
**Run Commands:**
```bash
npm run test # E2E tests (Playwright, headless)
npm run test:headed # E2E tests with browser visible
npm run test:packages # All shared package unit tests (vitest)
npm run test:server # Server unit tests (vitest run)
npm run test:server:coverage # Server tests with coverage report
npm run test:all # All tests (packages + server)
npm run test:unit # Vitest run (all projects)
npm run test:unit:watch # Vitest watch mode
```
## Test File Organization
**Location:**
- Co-located with source: `src/module.ts` has `tests/unit/module.test.ts`
- Server tests: `apps/server/tests/` (separate directory)
- Library tests: `libs/*/tests/` (each package)
- E2E tests: `apps/ui/tests/` (Playwright)
**Naming:**
- Pattern: `{moduleName}.test.ts` for unit tests
- Pattern: `{moduleName}.spec.ts` for specification tests
- Glob pattern: `tests/**/*.test.ts`, `tests/**/*.spec.ts`
**Structure:**
```
apps/server/
├── tests/
│ ├── setup.ts # Global test setup
│ ├── unit/
│ │ ├── providers/ # Provider tests
│ │ │ ├── claude-provider.test.ts
│ │ │ ├── codex-provider.test.ts
│ │ │ └── base-provider.test.ts
│ │ └── services/
│ └── utils/
│ └── helpers.ts # Test utilities
└── src/
libs/platform/
├── tests/
│ ├── paths.test.ts
│ ├── security.test.ts
│ ├── subprocess.test.ts
│ └── node-finder.test.ts
└── src/
```
## Test Structure
**Suite Organization:**
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { FeatureLoader } from '@/services/feature-loader.js';
describe('feature-loader.ts', () => {
let featureLoader: FeatureLoader;
beforeEach(() => {
vi.clearAllMocks();
featureLoader = new FeatureLoader();
});
afterEach(async () => {
// Cleanup resources
});
describe('methodName', () => {
it('should do specific thing', () => {
expect(result).toBe(expected);
});
});
});
```
**Patterns:**
- Setup pattern: `beforeEach()` initializes test instance, clears mocks
- Teardown pattern: `afterEach()` cleans up temp directories, removes created files
- Assertion pattern: one logical assertion per test (or multiple closely related)
- Test isolation: each test runs with fresh setup
## Mocking
**Framework:**
- Vitest `vi` module: `vi.mock()`, `vi.mocked()`, `vi.clearAllMocks()`
- Mock patterns: module mocking, function spying, return value mocking
**Patterns:**
Module mocking:
```typescript
vi.mock('@anthropic-ai/claude-agent-sdk');
// In test:
vi.mocked(sdk.query).mockReturnValue(
(async function* () {
yield { type: 'text', text: 'Response 1' };
})()
);
```
Async generator mocking (for streaming APIs):
```typescript
const generator = provider.executeQuery({
prompt: 'Hello',
model: 'claude-opus-4-5-20251101',
cwd: '/test',
});
const results = await collectAsyncGenerator(generator);
```
Partial mocking with spies:
```typescript
const provider = new TestProvider();
const spy = vi.spyOn(provider, 'getName');
spy.mockReturnValue('mocked-name');
```
**What to Mock:**
- External APIs (Claude SDK, GitHub SDK, cloud services)
- File system operations (use temp directories instead when possible)
- Network calls
- Process execution
- Time-dependent operations
**What NOT to Mock:**
- Core business logic (test the actual implementation)
- Type definitions
- Internal module dependencies (test integration with real services)
- Standard library functions (fs, path, etc. - use fixtures instead)
## Fixtures and Factories
**Test Data:**
```typescript
// Test helper for collecting async generator results
async function collectAsyncGenerator<T>(generator: AsyncGenerator<T>): Promise<T[]> {
const results: T[] = [];
for await (const item of generator) {
results.push(item);
}
return results;
}
// Temporary directory fixture
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-'));
projectPath = path.join(tempDir, 'test-project');
await fs.mkdir(projectPath, { recursive: true });
});
afterEach(async () => {
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
```
**Location:**
- Inline in test files for simple fixtures
- `tests/utils/helpers.ts` for shared test utilities
- Factory functions for complex test objects: `createTestProvider()`, `createMockFeature()`
## Coverage
**Requirements (Server):**
- Lines: 60%
- Functions: 75%
- Branches: 55%
- Statements: 60%
- Config: `apps/server/vitest.config.ts` with thresholds
**Excluded from Coverage:**
- Route handlers: tested via integration/E2E tests
- Type re-exports
- Middleware: tested via integration tests
- Prompt templates
- MCP integration: awaits MCP SDK integration tests
- Provider CLI integrations: awaits integration tests
**View Coverage:**
```bash
npm run test:server:coverage # Generate coverage report
# Opens HTML report in: apps/server/coverage/index.html
```
**Coverage Tools:**
- Provider: v8
- Reporters: text, json, html, lcov
- File inclusion: `src/**/*.ts`
- File exclusion: `src/**/*.d.ts`, specific service files in thresholds
## Test Types
**Unit Tests:**
- Scope: Individual functions and methods
- Approach: Test inputs → outputs with mocked dependencies
- Location: `apps/server/tests/unit/`
- Examples:
- Provider executeQuery() with mocked SDK
- Path construction functions with assertions
- Error classification with different error types
- Config validation with various inputs
**Integration Tests:**
- Scope: Multiple modules working together
- Approach: Test actual service calls with real file system or temp directories
- Pattern: Setup data → call method → verify results
- Example: Feature loader reading/writing feature.json files
- Example: Auto-mode service coordinating with multiple services
**E2E Tests:**
- Framework: Playwright
- Scope: Full user workflows from UI
- Location: `apps/ui/tests/`
- Config: `apps/ui/playwright.config.ts`
- Setup:
- Backend server with mock agent enabled
- Frontend Vite dev server
- Sequential execution (workers: 1) to avoid auth conflicts
- Screenshots/traces on failure
- Auth: Global setup authentication in `tests/global-setup.ts`
- Fixtures: `tests/e2e-fixtures/` for test project data
## Common Patterns
**Async Testing:**
```typescript
it('should execute async operation', async () => {
const result = await featureLoader.loadFeature(projectPath, featureId);
expect(result).toBeDefined();
expect(result.id).toBe(featureId);
});
// For streams/generators:
const generator = provider.executeQuery({ prompt, model, cwd });
const results = await collectAsyncGenerator(generator);
expect(results).toHaveLength(2);
```
**Error Testing:**
```typescript
it('should throw error when feature not found', async () => {
await expect(featureLoader.getFeature(projectPath, 'nonexistent')).rejects.toThrow('not found');
});
// Testing error classification:
const errorInfo = classifyError(new Error('ENOENT'));
expect(errorInfo.category).toBe('FileSystem');
```
**Fixture Setup:**
```typescript
it('should create feature with images', async () => {
// Setup: create temp feature directory
const featureDir = path.join(projectPath, '.automaker', 'features', featureId);
await fs.mkdir(featureDir, { recursive: true });
// Act: perform operation
const result = await featureLoader.updateFeature(projectPath, {
id: featureId,
imagePaths: ['/temp/image.png'],
});
// Assert: verify file operations
const migratedPath = path.join(featureDir, 'images', 'image.png');
expect(fs.existsSync(migratedPath)).toBe(true);
});
```
**Mock Reset Pattern:**
```typescript
// In vitest.config.ts:
mockReset: true, // Reset all mocks before each test
restoreMocks: true, // Restore original implementations
clearMocks: true, // Clear mock call history
// In test:
beforeEach(() => {
vi.clearAllMocks();
delete process.env.ANTHROPIC_API_KEY;
});
```
## Test Configuration
**Vitest Config Patterns:**
Server config (`apps/server/vitest.config.ts`):
- Environment: node
- Globals: true (describe/it without imports)
- Setup files: `./tests/setup.ts`
- Alias resolution: resolves `@automaker/*` to source files for mocking
Library config:
- Simpler setup: just environment and globals
- Coverage with high thresholds (90%+ lines)
**Global Setup:**
```typescript
// tests/setup.ts
import { vi, beforeEach } from 'vitest';
process.env.NODE_ENV = 'test';
process.env.DATA_DIR = '/tmp/test-data';
beforeEach(() => {
vi.clearAllMocks();
});
```
## Testing Best Practices
**Isolation:**
- Each test is independent (no state sharing)
- Cleanup temp files in afterEach
- Reset mocks and environment variables in beforeEach
**Clarity:**
- Descriptive test names: "should do X when Y condition"
- One logical assertion per test
- Clear arrange-act-assert structure
**Speed:**
- Mock external services
- Use in-memory temp directories
- Avoid real network calls
- Sequential E2E tests to prevent conflicts
**Maintainability:**
- Use beforeEach/afterEach for common setup
- Extract test helpers to `tests/utils/`
- Keep test data simple and local
- Mock consistently across tests
---
_Testing analysis: 2026-01-27_