# Tester and Testwriter Subagent Guide This guide covers two related but distinct subagents for testing in the Flyer Crawler project: - **tester**: Adversarial testing to find edge cases, race conditions, and vulnerabilities - **testwriter**: Creating comprehensive test suites for features and fixes ## Quick Reference | Aspect | tester | testwriter | | ---------------- | -------------------------------------------- | ------------------------------------------ | | **Primary Use** | Find bugs, security issues, edge cases | Create test suites, improve coverage | | **Key Files** | N/A (analysis-focused) | `*.test.ts`, `src/tests/utils/` | | **Key ADRs** | ADR-010 (Testing), ADR-040 (Test Economics) | ADR-010 (Testing), ADR-045 (Test Fixtures) | | **Test Command** | `podman exec -it flyer-crawler-dev npm test` | Same | | **Test Stack** | Vitest, Supertest, Testing Library | Same | | **Delegate To** | `testwriter` (write tests for findings) | `coder` (fix failing tests) | ## Understanding the Difference | Aspect | tester | testwriter | | --------------- | ------------------------------- | ------------------------------- | | **Purpose** | Find bugs and weaknesses | Create test coverage | | **Approach** | Adversarial, exploratory | Systematic, comprehensive | | **Output** | Bug reports, security findings | Test files, test utilities | | **When to Use** | Before release, security review | During development, refactoring | ## The tester Subagent ### When to Use Use the **tester** subagent when you need to: - Find edge cases that might cause failures - Identify race conditions in async code - Test security vulnerabilities - Stress test APIs or database queries - Validate error handling paths - Find memory leaks or performance issues ### What the tester Knows The tester subagent understands: - Common vulnerability patterns (SQL injection, XSS, CSRF) - Race condition scenarios in Node.js - Edge cases in data validation - Authentication and authorization bypasses - BullMQ queue edge cases - Database transaction isolation issues ### Example Requests **Finding edge cases:** ``` "Use the tester subagent to find edge cases in the flyer upload endpoint. Consider file types, sizes, concurrent uploads, and invalid data scenarios." ``` **Security testing:** ``` "Use the tester subagent to review the authentication flow for security vulnerabilities, including JWT handling, session management, and OAuth integration." ``` **Race condition analysis:** ``` "Use the tester subagent to identify potential race conditions in the shopping list sharing feature where multiple users might modify the same list simultaneously." ``` ### Sample Output from tester The tester subagent typically produces: 1. **Vulnerability Reports** - Issue description - Reproduction steps - Severity assessment - Recommended fix 2. **Edge Case Catalog** - Input combinations to test - Expected vs actual behavior - Priority for fixing 3. **Test Scenarios** - Detailed test cases for the testwriter - Setup and teardown requirements - Assertions to verify ## The testwriter Subagent ### When to Use Use the **testwriter** subagent when you need to: - Write unit tests for new features - Add integration tests for API endpoints - Create end-to-end test scenarios - Improve test coverage for existing code - Write regression tests for bug fixes - Create test utilities and factories ### What the testwriter Knows The testwriter subagent understands: - Project testing stack (Vitest, Testing Library, Supertest) - Mock factory patterns (`src/tests/utils/mockFactories.ts`) - Test helper utilities (`src/tests/utils/testHelpers.ts`) - Database cleanup patterns - Integration test setup with globalSetup - Known testing issues documented in CLAUDE.md ### Testing Framework Stack | Tool | Version | Purpose | | ------------------------- | ------- | ----------------- | | Vitest | 4.0.15 | Test runner | | @testing-library/react | 16.3.0 | Component testing | | @testing-library/jest-dom | 6.9.1 | DOM assertions | | supertest | 7.1.4 | API testing | | msw | 2.12.3 | Network mocking | ### Test File Organization ``` src/ ├── components/ │ └── *.test.tsx # Component tests (colocated) ├── hooks/ │ └── *.test.ts # Hook tests (colocated) ├── services/ │ └── *.test.ts # Service tests (colocated) ├── routes/ │ └── *.test.ts # Route handler tests (colocated) └── tests/ ├── integration/ # Integration tests └── e2e/ # End-to-end tests ``` ### Example Requests **Unit tests for a new feature:** ``` "Use the testwriter subagent to create comprehensive unit tests for the new StoreSearchService in src/services/storeSearchService.ts. Include edge cases for empty results, partial matches, and pagination." ``` **Integration tests for API:** ``` "Use the testwriter subagent to add integration tests for the POST /api/flyers endpoint, covering successful uploads, validation errors, authentication requirements, and file size limits." ``` **Regression test for bug fix:** ``` "Use the testwriter subagent to create a regression test that verifies the fix for issue #123 where duplicate flyer items were created when uploading certain PDFs." ``` ### Test Patterns the testwriter Uses #### Unit Test Pattern ```typescript // src/services/storeSearchService.test.ts import { describe, it, expect, beforeEach, vi } from 'vitest'; import { createMockStore, resetMockIds } from '@/tests/utils/mockFactories'; describe('StoreSearchService', () => { beforeEach(() => { resetMockIds(); // Ensure deterministic IDs vi.clearAllMocks(); }); describe('searchByName', () => { it('returns matching stores when query matches', async () => { const mockStore = createMockStore({ name: 'Test Mart' }); // ... test implementation }); it('returns empty array when no matches found', async () => { // ... test implementation }); it('handles special characters in search query', async () => { // ... test implementation }); }); }); ``` #### Integration Test Pattern ```typescript // src/tests/integration/stores.integration.test.ts import supertest from 'supertest'; import { createAndLoginUser, cleanupDb } from '@/tests/utils/testHelpers'; describe('Stores API', () => { let request: ReturnType; let authToken: string; let testUserId: string; beforeAll(async () => { const app = (await import('../../../server')).default; request = supertest(app); const { token, userId } = await createAndLoginUser(request); authToken = token; testUserId = userId; }); afterAll(async () => { await cleanupDb({ users: [testUserId] }); }); describe('GET /api/stores', () => { it('returns list of stores', async () => { const response = await request.get('/api/stores').set('Authorization', `Bearer ${authToken}`); expect(response.status).toBe(200); expect(response.body.data.stores).toBeInstanceOf(Array); }); }); }); ``` #### Component Test Pattern ```typescript // src/components/StoreCard.test.tsx import { describe, it, expect, vi } from 'vitest'; import { renderWithProviders, screen } from '@/tests/utils/renderWithProviders'; import { createMockStore } from '@/tests/utils/mockFactories'; import { StoreCard } from './StoreCard'; describe('StoreCard', () => { it('renders store name and location count', () => { const store = createMockStore({ name: 'Test Store', location_count: 5 }); renderWithProviders(); expect(screen.getByText('Test Store')).toBeInTheDocument(); expect(screen.getByText('5 locations')).toBeInTheDocument(); }); it('calls onSelect when clicked', async () => { const store = createMockStore(); const handleSelect = vi.fn(); renderWithProviders(); await userEvent.click(screen.getByText(store.name)); expect(handleSelect).toHaveBeenCalledWith(store); }); }); ``` ## Test Execution Environment ### Critical Requirement > **ALL tests MUST be executed inside the dev container (Linux environment)** Tests that pass on Windows but fail on Linux are considered **broken tests**. ### Running Tests ```bash # From Windows host - run in container podman exec -it flyer-crawler-dev npm run test:unit podman exec -it flyer-crawler-dev npm run test:integration # Inside dev container npm run test:unit npm run test:integration # Run specific test file npm test -- --run src/services/storeService.test.ts ``` ### Test Commands Reference | Command | Description | | -------------------------- | ------------------------------------- | | `npm test` | All unit tests | | `npm run test:unit` | Unit tests only | | `npm run test:integration` | Integration tests (requires DB/Redis) | | `npm run test:coverage` | Tests with coverage report | ## Known Testing Issues The testwriter subagent is aware of these documented issues: ### 1. Vitest globalSetup Context Isolation Vitest's `globalSetup` runs in a separate Node.js context. Mocks and spies do NOT share instances with test files. **Impact**: BullMQ worker service mocks don't work in integration tests. **Solution**: Use `.todo()` for affected tests or create test-only API endpoints. ### 2. Cleanup Queue Timing The cleanup worker may process jobs before tests can verify them. **Solution**: ```typescript const { cleanupQueue } = await import('../../services/queues.server'); await cleanupQueue.drain(); await cleanupQueue.pause(); // ... run test ... await cleanupQueue.resume(); ``` ### 3. Cache Stale After Direct SQL Direct database inserts bypass cache invalidation. **Solution**: ```typescript await cacheService.invalidateFlyers(); ``` ### 4. Unique Filenames Required File upload tests need unique filenames to avoid collisions. **Solution**: ```typescript const filename = `test-${Date.now()}-${Math.round(Math.random() * 1e9)}.jpg`; ``` ## Test Coverage Guidelines ### When Writing Tests 1. **Unit Tests** (required for all new code): - Pure functions and utilities - React components - Custom hooks - Service methods - Repository methods 2. **Integration Tests** (required for API changes): - New API endpoints - Authentication flows - Middleware behavior 3. **E2E Tests** (for critical paths): - User registration/login - Flyer upload workflow - Admin operations ### Test Isolation 1. Reset mock IDs in `beforeEach()` 2. Use unique test data (timestamps, UUIDs) 3. Clean up after tests with `cleanupDb()` 4. Don't share state between tests ## Combining tester and testwriter A typical workflow for thorough testing: 1. **Development**: Write code with basic tests using `testwriter` 2. **Edge Cases**: Use `tester` to identify edge cases and vulnerabilities 3. **Coverage**: Use `testwriter` to add tests for identified edge cases 4. **Review**: Use `code-reviewer` to verify test quality ### Example Combined Workflow ``` 1. "Use testwriter to create initial tests for the new discount calculation feature" 2. "Use tester to find edge cases in the discount calculation - consider rounding errors, negative values, percentage limits, and currency precision" 3. "Use testwriter to add tests for the edge cases identified: - Rounding to 2 decimal places - Negative discount values - Discounts over 100% - Very small amounts (under $0.01)" ``` ## Related Documentation - [OVERVIEW.md](./OVERVIEW.md) - Subagent system overview - [CODER-GUIDE.md](./CODER-GUIDE.md) - Working with the coder subagent - [SECURITY-DEBUG-GUIDE.md](./SECURITY-DEBUG-GUIDE.md) - Security testing and code review - [../development/TESTING.md](../development/TESTING.md) - Testing guide - [../adr/0010-testing-strategy-and-standards.md](../adr/0010-testing-strategy-and-standards.md) - Testing ADR - [../adr/0040-testing-economics-and-priorities.md](../adr/0040-testing-economics-and-priorities.md) - Testing priorities