Instruction file imported from emirluffy/qualityleap (
.cursor/rules/testing-strategy.mdc). Copyright stays with the author.
QualityLeap - Testing Strategy
Testing Philosophy
- Aim for 80%+ code coverage for business-critical logic
- Test behavior, not implementation details
- Write tests before fixing bugs (regression prevention)
- Automate testing in CI/CD pipeline
Test Types
1. Unit Tests
What to Test
- XP calculation logic
- Badge criteria evaluation
- Reward redemption validation
- Currency transaction integrity
- Level progression formulas
- Gating logic
Framework
- Jest for JavaScript/TypeScript
- React Testing Library for React components
Example: XP Calculation
// xp-calculator.test.ts
describe('XPCalculator', () => {
it('should award 100 XP for CSAT score of 5/5', () => {
const xp = calculateXP({
type: 'CSAT_RECEIVED',
score: 5
});
expect(xp).toBe(100);
});
it('should award 50 XP for First Call Resolution', () => {
const xp = calculateXP({
type: 'FCR_ACHIEVED'
});
expect(xp).toBe(50);
});
it('should not award AHT bonus if QA score below threshold', () => {
const xp = calculateXP({
type: 'AHT_BONUS',
aht: 180,
qaScore: 80 // Below 85 threshold
});
expect(xp).toBe(0);
});
});
Example: Component Test
// BadgeCard.test.tsx
import { render, screen } from '@testing-library/react';
import { BadgeCard } from './BadgeCard';
describe('BadgeCard', () => {
it('should render earned badge with checkmark', () => {
render(<BadgeCard badge={mockBadge} earned={true} />);
expect(screen.getByRole('img')).toHaveAttribute('src', mockBadge.imageUrl);
expect(screen.getByTestId('earned-checkmark')).toBeInTheDocument();
});
it('should show progress for progressive badges', () => {
render(<BadgeCard badge={mockBadge} progress={3/5} />);
expect(screen.getByText('3/5 days')).toBeInTheDocument();
});
it('should display locked badge with grayscale filter', () => {
render(<BadgeCard badge={mockBadge} earned={false} />);
expect(screen.getByRole('img')).toHaveClass('grayscale');
});
});
2. Integration Tests
What to Test
- API endpoints with test database
- Database transactions (XP award + level up + transaction record)
- WebSocket event flow
- External system integrations (with mocks)
- Event processing pipeline
Framework
- Supertest for HTTP API testing
- Test Database (separate PostgreSQL instance)
Example: API Test
// rewards.integration.test.ts
describe('POST /api/rewards/:id/redeem', () => {
beforeEach(async () => {
await setupTestDatabase();
await createTestUser({ id: 'user123', currency_balance: 500 });
await createTestReward({ id: 'reward456', cost: 200 });
});
it('should redeem reward and deduct currency', async () => {
const response = await request(app)
.post('/api/rewards/reward456/redeem')
.set('Authorization', `Bearer ${testToken}`)
.expect(200);
expect(response.body.success).toBe(true);
// Verify currency deducted
const user = await User.findById('user123');
expect(user.currency_balance).toBe(300);
// Verify transaction recorded
const transaction = await Transaction.findOne({
where: { user_id: 'user123', transaction_type: 'CURRENCY_SPENT' }
});
expect(transaction).toBeDefined();
expect(transaction.amount).toBe(-200);
});
it('should reject redemption with insufficient balance', async () => {
await User.update({ currency_balance: 100 }, { where: { id: 'user123' }});
const response = await request(app)
.post('/api/rewards/reward456/redeem')
.set('Authorization', `Bearer ${testToken}`)
.expect(400);
expect(response.body.error.code).toBe('INSUFFICIENT_BALANCE');
});
it('should reject redemption if reward out of stock', async () => {
await Reward.update({ stock_quantity: 0 }, { where: { id: 'reward456' }});
const response = await request(app)
.post('/api/rewards/reward456/redeem')
.set('Authorization', `Bearer ${testToken}`)
.expect(400);
expect(response.body.error.code).toBe('OUT_OF_STOCK');
});
});
3. E2E Tests (Critical User Flows)
What to Test
- Complete user journeys
- Cross-feature interactions
- Real-time updates (WebSocket)
Framework
- Playwright or Cypress
Critical Flows to Test
- New User Onboarding: Login → View profile → See initial quests
- XP Earning Flow: Action occurs → XP notification → Level up
- Badge Unlock Flow: Criteria met → Badge earned notification → Badge in profile
- Reward Redemption: Browse catalog → Redeem → Approval → Confirmation
- Leaderboard Update: Action occurs → Leaderboard position changes in real-time
- Challenge Participation: Accept challenge → Complete tasks → Win reward
Example: E2E Test
// xp-earning.e2e.test.ts
test('should earn XP and level up after high CSAT', async ({ page }) => {
// Login
await page.goto('/login');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
// Navigate to profile
await page.click('[data-testid="profile-link"]');
const initialXP = await page.textContent('[data-testid="current-xp"]');
// Simulate external event (via API or webhook)
await simulateEvent({
type: 'CSAT_RECEIVED',
userId: 'test-user-id',
score: 5
});
// Verify XP notification appears
await expect(page.locator('[data-testid="xp-notification"]'))
.toContainText('+100 XP');
// Verify XP updated on profile
await expect(page.locator('[data-testid="current-xp"]'))
.not.toHaveText(initialXP);
});
4. Performance Tests
What to Test
- API response times under load
- WebSocket connection handling (5000+ concurrent users)
- Leaderboard query performance
- Database query optimization
Framework
- Apache JMeter or k6
Example: Load Test
// load-test.js (k6)
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100, // 100 virtual users
duration: '5m', // 5 minutes
thresholds: {
http_req_duration: ['p(95)<200'], // 95% of requests < 200ms
},
};
export default function () {
const response = http.get('http://api.qualityleap.com/api/leaderboards/team/total_xp');
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
sleep(1);
}
5. Security Tests
What to Test
- Authentication bypass attempts
- Authorization checks (role-based access)
- SQL injection vulnerabilities
- XSS attack prevention
- CSRF protection
- Rate limiting
- Data exposure in API responses
Tools
- OWASP ZAP for automated security scanning
- Manual penetration testing
Example: Security Test
// security.test.ts
describe('Authorization', () => {
it('should prevent regular user from accessing manager endpoints', async () => {
const regularUserToken = generateToken({ role: 'agent' });
const response = await request(app)
.post('/api/challenges')
.set('Authorization', `Bearer ${regularUserToken}`)
.send({ name: 'Test Challenge' })
.expect(403);
expect(response.body.error.code).toBe('INSUFFICIENT_PERMISSIONS');
});
it('should prevent viewing other users\' transaction history', async () => {
const response = await request(app)
.get('/api/users/other-user-id/transactions')
.set('Authorization', `Bearer ${testToken}`)
.expect(403);
});
});
Test Data Management
Test Fixtures
- Create reusable test data fixtures
- Use factories for complex objects
// factories/user.factory.ts
export function createTestUser(overrides = {}) {
return {
id: faker.datatype.uuid(),
email: faker.internet.email(),
first_name: faker.name.firstName(),
last_name: faker.name.lastName(),
current_level: 1,
current_xp: 0,
currency_balance: 0,
...overrides
};
}
Database Seeding
- Seed test database with realistic data
- Include edge cases (max level, zero balance, etc.)
Mocking External Systems
Mock Integrations
// mocks/qa-system.mock.ts
export class MockQASystem {
async getQAScore(callId: string) {
return {
callId,
score: 92,
lineItems: [
{ category: 'empathy', score: 95 },
{ category: 'procedure', score: 90 }
]
};
}
}
Use Mocks in Tests
jest.mock('../integrations/qa-system', () => ({
QASystem: MockQASystem
}));
CI/CD Integration
GitHub Actions Example
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: qualityleap_test
POSTGRES_PASSWORD: testpass
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:testpass@localhost:5432/qualityleap_test
REDIS_URL: redis://localhost:6379
- name: Upload coverage
uses: codecov/codecov-action@v3
Test Coverage Goals
Minimum Coverage Targets
- Unit tests: 80%+ for services and utilities
- Integration tests: All critical API endpoints
- E2E tests: Top 10 user flows
- Security tests: All authenticated endpoints
Coverage Reports
- Generate coverage reports in CI/CD
- Block PRs with coverage below threshold
- Track coverage trends over time
Testing Best Practices
- Arrange-Act-Assert: Structure all tests clearly
- One Assertion Per Test: Keep tests focused
- Descriptive Test Names: Use "should..." format
- Independent Tests: No test depends on another
- Fast Execution: Keep unit tests under 1s each
- Realistic Data: Use factories, avoid hardcoded IDs
- Clean Up: Reset database state between tests
- Mock External Calls: Don't hit real APIs in tests
- Test Edge Cases: Zero, negative, max values
- Fail Fast: Run unit tests before integration tests