Agent skill
QAS Agent (Quality Assurance & Security)
Creates comprehensive test suites including unit, integration, E2E, and security tests
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/qas-agent-quality-assurance-security-oehm-smith-linux-env
SKILL.md
QAS Agent (Quality Assurance & Security)
Overview
The QAS Agent creates and runs comprehensive tests to validate that implementation meets all acceptance criteria. This skill ensures code works correctly under all conditions.
When to Use This Skill
- Implementation is complete
- Acceptance criteria defined
- Security audit complete
- Before creating PR
- When test coverage needed
Critical Rules
- Use TDD with Superpowers - Integrate with existing TDD skill
- Test real behavior - Never test mocks (that's testing the test)
- Cover edge cases - Happy path is not enough
- Security tests mandatory - Test attack scenarios
- E2E tests use real APIs - No mocks in E2E tests
Test Types
1. Unit Tests
Test individual functions in isolation
2. Integration Tests
Test component interactions
3. E2E (End-to-End) Tests
Test complete user workflows
4. Security Tests
Test authorization, authentication, attack scenarios
Process
Step 1: Read All Feature Documentation
CRITICAL: Read all feature documentation from files.
Files to read:
docs/features/[feature-slug]/01-bsa-analysis.md (acceptance criteria)
docs/features/[feature-slug]/02-architecture-design.md (system design)
docs/features/[feature-slug]/03-migration.sql (database schema)
docs/features/[feature-slug]/04-security-audit.md (security test scenarios)
docs/features/[feature-slug]/05-documentation-summary.md (API docs)
What to extract:
- Acceptance criteria (from BSA) - each becomes a test
- Security vulnerabilities to test (from Security Audit)
- API endpoints (from Documentation)
- Edge cases (from all sources)
Step 2: Create Test Plan
From extracted information, create comprehensive test plan.
Step 3: Create Test Coverage Matrix
Test Coverage Matrix:
| Acceptance Criterion | Unit Test | Integration Test | E2E Test | Security Test |
|---|---|---|---|---|
| User can create export | ✅ | ✅ | ✅ | - |
| Rate limit enforced | - | ✅ | ✅ | ✅ |
| Download link expires | ✅ | ✅ | - | ✅ |
| RLS prevents cross-access | - | ✅ | - | ✅ |
Step 4: Write Unit Tests
Template:
typescript
describe('ExportService', () => {
describe('createExport', () => {
it('should create export with valid parameters', async () => {
// Arrange
const userId = 'user-123';
const format = 'json';
// Act
const result = await exportService.createExport(userId, format);
// Assert
expect(result).toMatchObject({
user_id: userId,
format: format,
status: 'pending',
});
expect(result.id).toBeDefined();
expect(result.expires_at).toBeInstanceOf(Date);
});
it('should reject invalid format', async () => {
await expect(
exportService.createExport('user-123', 'xml')
).rejects.toThrow('Invalid format: must be json or csv');
});
it('should enforce rate limit', async () => {
const userId = 'user-123';
// Create first export
await exportService.createExport(userId, 'json');
// Attempt second export within 24 hours
await expect(
exportService.createExport(userId, 'json')
).rejects.toThrow('Rate limit exceeded: 1 export per 24 hours');
});
it('should calculate expiration date correctly', async () => {
const result = await exportService.createExport('user-123', 'json');
const expectedExpiration = new Date();
expectedExpiration.setDate(expectedExpiration.getDate() + 7);
expect(result.expires_at.getTime()).toBeCloseTo(
expectedExpiration.getTime(),
-4 // Within 10 seconds
);
});
});
});
Step 5: Write Integration Tests
typescript
describe('Export API Integration', () => {
let testUser: User;
let authToken: string;
beforeEach(async () => {
testUser = await createTestUser();
authToken = await generateAuthToken(testUser.id);
});
afterEach(async () => {
await cleanupTestUser(testUser.id);
});
it('should create export and send email notification', async () => {
// Create export
const response = await request(app)
.post(`/api/users/${testUser.id}/exports`)
.set('Authorization', `Bearer ${authToken}`)
.send({ format: 'json' })
.expect(202);
expect(response.body).toMatchObject({
id: expect.any(String),
status: 'pending',
format: 'json',
});
// Wait for background job to process
const exportId = response.body.id;
await waitForExportCompletion(exportId, { timeout: 30000 });
// Verify export completed
const exportRecord = await db.query(
'SELECT * FROM user_exports WHERE id = $1',
[exportId]
);
expect(exportRecord.rows[0].status).toBe('completed');
expect(exportRecord.rows[0].file_url).toBeDefined();
// Verify email sent
const emails = await getTestEmails(testUser.email);
expect(emails).toHaveLength(1);
expect(emails[0].subject).toContain('Your data export is ready');
expect(emails[0].body).toContain(exportRecord.rows[0].file_url);
});
it('should enforce rate limiting', async () => {
// First export succeeds
await request(app)
.post(`/api/users/${testUser.id}/exports`)
.set('Authorization', `Bearer ${authToken}`)
.send({ format: 'json' })
.expect(202);
// Second export within 24 hours fails
await request(app)
.post(`/api/users/${testUser.id}/exports`)
.set('Authorization', `Bearer ${authToken}`)
.send({ format: 'json' })
.expect(429);
});
});
Step 6: Write E2E Tests
typescript
describe('User Export E2E Flow', () => {
let page: Page;
let testUser: User;
beforeAll(async () => {
page = await browser.newPage();
testUser = await createTestUser();
});
afterAll(async () => {
await cleanupTestUser(testUser.id);
await page.close();
});
it('should complete full export workflow', async () => {
// Login
await page.goto('/login');
await page.fill('#email', testUser.email);
await page.fill('#password', testUser.password);
await page.click('button[type=submit]');
await page.waitForSelector('.dashboard');
// Navigate to privacy settings
await page.goto('/settings/privacy');
await expect(page.locator('h1')).toContainText('Privacy Settings');
// Request export
await page.click('button:has-text("Export My Data")');
await page.selectOption('#format', 'json');
await page.click('button:has-text("Create Export")');
// Verify confirmation
await expect(page.locator('.toast-success')).toContainText(
'Export requested. You will receive an email when ready.'
);
// Wait for email (in test environment)
const email = await waitForEmail(testUser.email, {
subject: 'Your data export is ready',
timeout: 60000,
});
expect(email).toBeDefined();
// Extract download link from email
const downloadLink = extractLinkFromEmail(email);
expect(downloadLink).toMatch(/^https:\/\/.*\/exports\/.*\/download/);
// Download export
const exportData = await downloadFile(downloadLink);
expect(exportData).toBeDefined();
// Verify export contents
const parsed = JSON.parse(exportData);
expect(parsed).toHaveProperty('user');
expect(parsed.user.email).toBe(testUser.email);
expect(parsed).toHaveProperty('content');
expect(parsed).toHaveProperty('activity');
});
});
Step 7: Write Security Tests
typescript
describe('Export Security Tests', () => {
let userA: User;
let userB: User;
let tokenA: string;
let tokenB: string;
beforeEach(async () => {
userA = await createTestUser();
userB = await createTestUser();
tokenA = await generateAuthToken(userA.id);
tokenB = await generateAuthToken(userB.id);
});
it('should prevent user from accessing another users export', async () => {
// User A creates export
const responseA = await request(app)
.post(`/api/users/${userA.id}/exports`)
.set('Authorization', `Bearer ${tokenA}`)
.send({ format: 'json' })
.expect(202);
const exportId = responseA.body.id;
// User B tries to access User A's export
await request(app)
.get(`/api/exports/${exportId}`)
.set('Authorization', `Bearer ${tokenB}`)
.expect(403); // or 404 (don't reveal existence)
});
it('should reject unauthenticated requests', async () => {
await request(app)
.post(`/api/users/${userA.id}/exports`)
.send({ format: 'json' })
.expect(401);
});
it('should validate JWT token signature', async () => {
const invalidToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalid.signature';
await request(app)
.post(`/api/users/${userA.id}/exports`)
.set('Authorization', `Bearer ${invalidToken}`)
.send({ format: 'json' })
.expect(401);
});
it('should prevent SQL injection', async () => {
await request(app)
.get(`/api/users/${userA.id}/exports?format=json'--`)
.set('Authorization', `Bearer ${tokenA}`)
.expect(400);
});
});
Output Format
markdown
# QAS Test Report: [Feature Name]
## Test Coverage
### Unit Tests
- **Files**: 3
- **Tests**: 24
- **Coverage**: 95%
- **Status**: ✅ All passing
### Integration Tests
- **Files**: 2
- **Tests**: 12
- **Coverage**: 85%
- **Status**: ✅ All passing
### E2E Tests
- **Files**: 1
- **Tests**: 3
- **Coverage**: 100% of user flows
- **Status**: ✅ All passing
### Security Tests
- **Files**: 1
- **Tests**: 8
- **Status**: ✅ All passing
## Acceptance Criteria Validation
| Criterion | Test | Status |
|-----------|------|--------|
| User can create export | `integration/exports.test.ts:12` | ✅ PASS |
| Rate limit enforced | `integration/exports.test.ts:45` | ✅ PASS |
| Email notification sent | `integration/exports.test.ts:23` | ✅ PASS |
| Export expires after 7 days | `unit/export-service.test.ts:67` | ✅ PASS |
| Download link expires 1 hour | `integration/download.test.ts:34` | ✅ PASS |
| RLS prevents cross-access | `security/exports.test.ts:15` | ✅ PASS |
**All acceptance criteria validated**: ✅
## Edge Cases Tested
1. ✅ Concurrent export requests (race condition)
2. ✅ Export while user is deleted (graceful failure)
3. ✅ Export of empty dataset (valid export)
4. ✅ Export exceeding size limit (handled)
5. ✅ Background job failure (retry logic)
6. ✅ Email service down (queued for retry)
7. ✅ Storage full (error reported)
## Performance Tests
- Export generation: 2.3s avg (< 5s requirement ✅)
- API response time: 45ms avg
- Database queries: All < 100ms
## Test Execution
```bash
# Run all tests
npm test
# Results
Test Suites: 7 passed, 7 total
Tests: 47 passed, 47 total
Snapshots: 0 total
Time: 42.156 s
Coverage: 91.2%
Next Steps
- File saved:
docs/features/[feature-slug]/06-test-report.md - Handoff to: RTE Agent (reads test report for PR creation)
## Boundaries
**This skill does NOT**:
- Skip TDD process (use Superpowers TDD skill)
- Test mocked behavior
- Deploy code (that's RTE)
**This skill DOES**:
- **Read all feature documentation from files**
- Create comprehensive tests
- Validate acceptance criteria
- Test security scenarios
- Report test coverage
- Test edge cases
- **Save test report to file** for next agent
## Related Skills
- TDD (`/Users/brooke/.config/superpowers/skills/skills/testing/test-driven-development/SKILL.md`) - Use for TDD process
- Security Engineer (`~/.claude/skills/crosscutting/security/policy_auditing/SKILL.md`) - Provides security test scenarios
- RTE Agent (`~/.claude/skills/crosscutting/configuration/release_management/SKILL.md`) - Next step after testing
## Version History
- 1.0.0 (2025-10-14): Initial skill creation
Didn't find tool you were looking for?