11 KiB
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
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:
-
Vulnerability Reports
- Issue description
- Reproduction steps
- Severity assessment
- Recommended fix
-
Edge Case Catalog
- Input combinations to test
- Expected vs actual behavior
- Priority for fixing
-
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
// 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
// src/tests/integration/stores.integration.test.ts
import supertest from 'supertest';
import { createAndLoginUser, cleanupDb } from '@/tests/utils/testHelpers';
describe('Stores API', () => {
let request: ReturnType<typeof supertest>;
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
// 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(<StoreCard store={store} />);
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(<StoreCard store={store} onSelect={handleSelect} />);
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
# 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:
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:
await cacheService.invalidateFlyers();
4. Unique Filenames Required
File upload tests need unique filenames to avoid collisions.
Solution:
const filename = `test-${Date.now()}-${Math.round(Math.random() * 1e9)}.jpg`;
Test Coverage Guidelines
When Writing Tests
-
Unit Tests (required for all new code):
- Pure functions and utilities
- React components
- Custom hooks
- Service methods
- Repository methods
-
Integration Tests (required for API changes):
- New API endpoints
- Authentication flows
- Middleware behavior
-
E2E Tests (for critical paths):
- User registration/login
- Flyer upload workflow
- Admin operations
Test Isolation
- Reset mock IDs in
beforeEach() - Use unique test data (timestamps, UUIDs)
- Clean up after tests with
cleanupDb() - Don't share state between tests
Combining tester and testwriter
A typical workflow for thorough testing:
- Development: Write code with basic tests using
testwriter - Edge Cases: Use
testerto identify edge cases and vulnerabilities - Coverage: Use
testwriterto add tests for identified edge cases - Review: Use
code-reviewerto 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 - Subagent system overview
- CODER-GUIDE.md - Working with the coder subagent
- ../TESTING.md - Testing guide
- ../adr/0010-testing-strategy-and-standards.md - Testing ADR
- ../adr/0040-testing-economics-and-priorities.md - Testing priorities