feat: initial implementation — all 35 requirements across phases 1-3
Backend (Spring Boot 3.2 / Java 21 / PostgreSQL): - JWT auth with BCrypt password hashing - User profile + Mifflin-St Jeor BMR calculator - Food search + barcode via OpenFoodFacts API with local cache - Meal CRUD with user data isolation and ownership checks - AI photo analysis (OpenAI Vision) with confidence intervals - AI correction feedback loop for personalisation - Flyway DB migrations + RFC-7807 error responses Mobile (React Native / TypeScript): - Full navigation stack (Auth → Tabs → Home stack) - Design tokens (WCAG 2.2 AA colours, 8px grid, 48px touch targets) - 10 screens: Login, Register, Home, Search, Camera, AI Result, Edit Meal, Daily Details, History, Profile - Confidence-aware calorie display (kcal ± range) - Repeat last meal shortcut + macro tracking Docs: - docs/PLAN-AND-REQUIREMENTS.md - docs/traceability.csv (35 requirements, all Implemented)
This commit is contained in:
671
.github/copilot-modules/testing-quality.md
vendored
Normal file
671
.github/copilot-modules/testing-quality.md
vendored
Normal file
@@ -0,0 +1,671 @@
|
||||
Every feature needs tests. Coverage ≥70%. Security tests 100%. No exceptions.
|
||||
|
||||
# Testing & Quality - Virsaitis
|
||||
|
||||
**Module**: Testing & Quality
|
||||
**Load**: When writing tests, checking quality gates
|
||||
**Version**: 3.0.0
|
||||
**Updated**: 2026-02-17
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Purpose
|
||||
|
||||
Defines testing standards, coverage targets, quality metrics, and validation procedures for all Virsaitis components.
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Machine Policy
|
||||
|
||||
```
|
||||
[TESTING_STANDARDS]
|
||||
FRAMEWORK_MCP=vitest
|
||||
FRAMEWORK_EXTENSION=@vscode/test-electron
|
||||
FRAMEWORK_AGENT=manual_review
|
||||
TDD=preferred
|
||||
|
||||
[COVERAGE_TARGETS]
|
||||
OVERALL=70_percent_minimum
|
||||
SECURITY_CRITICAL=100_percent_required
|
||||
GOVERNANCE=100_percent_required
|
||||
UTILITIES=70_percent
|
||||
|
||||
[QUALITY_GATES]
|
||||
BUILD=must_succeed
|
||||
TESTS=must_pass_all
|
||||
LINT=zero_errors
|
||||
COVERAGE=meet_targets
|
||||
SECURITY_TESTS=100_percent_pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Frameworks
|
||||
|
||||
### MCP Server (TypeScript)
|
||||
|
||||
**FRAMEWORK**: Vitest
|
||||
|
||||
**vitest.config.ts**:
|
||||
```typescript
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'html', 'lcov'],
|
||||
lines: 70,
|
||||
functions: 70,
|
||||
branches: 70,
|
||||
statements: 70,
|
||||
exclude: [
|
||||
'node_modules/',
|
||||
'build/',
|
||||
'**/*.test.ts',
|
||||
'**/*.spec.ts',
|
||||
],
|
||||
},
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**RUN TESTS**:
|
||||
```bash
|
||||
npm test # Run all tests
|
||||
npm run test:watch # Watch mode
|
||||
npm run test:coverage # Coverage report
|
||||
npm run test:ui # UI interface
|
||||
```
|
||||
|
||||
### VS Code Extension (TypeScript)
|
||||
|
||||
**FRAMEWORK**: @vscode/test-electron
|
||||
|
||||
**test/runTest.ts**:
|
||||
```typescript
|
||||
import * as path from 'path';
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
await runTests({
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
**RUN TESTS**:
|
||||
```bash
|
||||
npm test # Run extension tests
|
||||
```
|
||||
|
||||
Tests run in Extension Development Host (isolated VS Code instance).
|
||||
|
||||
### Agent (Markdown)
|
||||
|
||||
**VALIDATION**: Manual review
|
||||
|
||||
**CHECKLIST**:
|
||||
- [ ] Atomic sentence structure (one concept per sentence)
|
||||
- [ ] Each sentence <80 characters
|
||||
- [ ] No compound clauses
|
||||
- [ ] Clear subject-verb-object
|
||||
- [ ] Standalone comprehensibility
|
||||
|
||||
**NO AUTOMATED TESTING** (atomic structure requires human judgment)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Coverage Targets (TIER-1)
|
||||
|
||||
### Minimum Coverage
|
||||
|
||||
| Component | Overall | Security | Governance |
|
||||
|-----------|---------|----------|------------|
|
||||
| **MCP Server** | ≥70% | 100% | 100% |
|
||||
| **Extension** | ≥70% | 100% | 100% |
|
||||
| **Agent** | Manual | N/A | Manual |
|
||||
| **Skills** | Manual | N/A | Manual |
|
||||
|
||||
### What to Cover
|
||||
|
||||
**MUST COVER (100%)**:
|
||||
- Security-critical code (secret scanning, validation)
|
||||
- Governance enforcement (TIER validation, file protection)
|
||||
- MCP tool handlers (core governance tools)
|
||||
- Extension interceptors (file operation blocking)
|
||||
|
||||
**SHOULD COVER (≥70%)**:
|
||||
- Business logic
|
||||
- Data transformations
|
||||
- Error handling
|
||||
- Configuration management
|
||||
- Utility functions
|
||||
|
||||
**CAN SKIP**:
|
||||
- Generated code
|
||||
- Third-party library wrappers (covered by library tests)
|
||||
- Simple getters/setters (if trivial)
|
||||
- Type definitions only files
|
||||
|
||||
---
|
||||
|
||||
## ✅ Test Structure
|
||||
|
||||
### Unit Test Pattern
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { GovernanceValidator } from '../src/governance/validator';
|
||||
|
||||
describe('GovernanceValidator', () => {
|
||||
let validator: GovernanceValidator;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup: Create fresh validator instance
|
||||
validator = new GovernanceValidator();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup: Dispose resources
|
||||
validator.dispose();
|
||||
});
|
||||
|
||||
describe('validateFileOperation', () => {
|
||||
describe('Protected Files', () => {
|
||||
it('should block modification of copilot-instructions.md', () => {
|
||||
// Given
|
||||
const operation = 'write';
|
||||
const filePath = '.github/copilot-instructions.md';
|
||||
|
||||
// When
|
||||
const result = validator.validateFileOperation(operation, filePath);
|
||||
|
||||
// Then
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.tier).toBe('TIER-0');
|
||||
expect(result.reason).toContain('protected file');
|
||||
});
|
||||
|
||||
it('should block modification of agent files', () => {
|
||||
// Given
|
||||
const operation = 'write';
|
||||
const filePath = '.github/agents/Virsaitis.agent.md';
|
||||
|
||||
// When
|
||||
const result = validator.validateFileOperation(operation, filePath);
|
||||
|
||||
// Then
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.tier).toBe('TIER-0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non-Protected Files', () => {
|
||||
|
||||
> ⚡ CHECKPOINT — Security tests at 100% coverage? TIER-0 rules must have multiple test cases.
|
||||
it('should allow modification of source files', () => {
|
||||
// Given
|
||||
const operation = 'write';
|
||||
const filePath = 'src/my-file.ts';
|
||||
|
||||
// When
|
||||
const result = validator.validateFileOperation(operation, filePath);
|
||||
|
||||
// Then
|
||||
expect(result.allowed).toBe(true);
|
||||
expect(result.tier).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle null file path', () => {
|
||||
// Given
|
||||
const operation = 'write';
|
||||
const filePath = null as any;
|
||||
|
||||
// When/Then
|
||||
expect(() => validator.validateFileOperation(operation, filePath))
|
||||
.toThrow('File path is required');
|
||||
});
|
||||
|
||||
it('should handle path traversal attempts', () => {
|
||||
// Given
|
||||
const operation = 'write';
|
||||
const filePath = '../../../etc/passwd';
|
||||
|
||||
// When/Then
|
||||
expect(() => validator.validateFileOperation(operation, filePath))
|
||||
.toThrow('Path traversal detected');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Test Pattern
|
||||
|
||||
```typescript
|
||||
describe('MCP Server Integration', () => {
|
||||
let server: MCPServer;
|
||||
let client: Client;
|
||||
let transport: StdioClientTransport;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Start MCP server via stdio transport
|
||||
transport = new StdioClientTransport({ command: 'node', args: ['build/index.js'] });
|
||||
client = new Client({ name: 'test-client', version: '1.0.0' });
|
||||
await client.connect(transport);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Cleanup
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('should validate protected file operation via MCP', async () => {
|
||||
// Given
|
||||
const request = {
|
||||
operation: 'write',
|
||||
filePath: '.github/copilot-instructions.md',
|
||||
};
|
||||
|
||||
// When
|
||||
const response = await client.callTool('mcp_virsaitis_validate_operation', request);
|
||||
|
||||
// Then
|
||||
expect(response.allowed).toBe(false);
|
||||
expect(response.tier).toBe('TIER-0');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Testing (TIER-1)
|
||||
|
||||
### Security Test Requirements
|
||||
|
||||
**100% COVERAGE REQUIRED**:
|
||||
- Secret detection (all patterns)
|
||||
- Path traversal prevention
|
||||
- Command injection prevention
|
||||
- Input validation
|
||||
- Error handling (no information leaks)
|
||||
|
||||
### Security Test Examples
|
||||
|
||||
```typescript
|
||||
describe('Security Tests', () => {
|
||||
describe('Secret Detection', () => {
|
||||
it('should detect hardcoded API keys', () => {
|
||||
const code = 'const apiKey = "sk-abc123def456";';
|
||||
const result = secretScanner.scan(code);
|
||||
expect(result.violations).toContainEqual({
|
||||
type: 'API_KEY',
|
||||
line: 1,
|
||||
pattern: 'sk-abc123def456',
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect AWS access keys', () => {
|
||||
const code = 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE';
|
||||
const result = secretScanner.scan(code);
|
||||
expect(result.violations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not flag environment variable references', () => {
|
||||
const code = 'const apiKey = process.env.API_KEY;';
|
||||
const result = secretScanner.scan(code);
|
||||
expect(result.violations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Path Traversal Prevention', () => {
|
||||
it('should block ../ in file paths', () => {
|
||||
expect(() => validatePath('../../../etc/passwd'))
|
||||
.toThrow('Path traversal detected');
|
||||
});
|
||||
|
||||
it('should block ~/ in file paths', () => {
|
||||
expect(() => validatePath('~/sensitive-file'))
|
||||
.toThrow('Path traversal detected');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Command Injection Prevention', () => {
|
||||
it('should block shell metacharacters', () => {
|
||||
expect(() => executeCommand('npm', ['install', '; rm -rf /']))
|
||||
.toThrow('Invalid argument');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Test-Driven Development (TDD)
|
||||
|
||||
### Red-Green-Refactor Cycle
|
||||
|
||||
```
|
||||
1. RED: Write failing test
|
||||
↓
|
||||
2. GREEN: Write minimum code to pass
|
||||
↓
|
||||
3. REFACTOR: Improve code quality
|
||||
↓
|
||||
4. REPEAT
|
||||
```
|
||||
|
||||
### TDD Example
|
||||
|
||||
**STEP 1: Red (Write Failing Test)**
|
||||
```typescript
|
||||
it('should block protected file modification', () => {
|
||||
const result = validator.validateFileOperation('write', '.github/copilot-instructions.md');
|
||||
expect(result.allowed).toBe(false);
|
||||
});
|
||||
```
|
||||
|
||||
Run test: ❌ FAILS (validator not implemented)
|
||||
|
||||
**STEP 2: Green (Minimum Implementation)**
|
||||
```typescript
|
||||
validateFileOperation(operation: string, filePath: string): ValidationResult {
|
||||
if (filePath === '.github/copilot-instructions.md') {
|
||||
return { allowed: false, tier: 'TIER-0' };
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
```
|
||||
|
||||
Run test: ✅ PASSES
|
||||
|
||||
**STEP 3: Refactor (Improve)**
|
||||
```typescript
|
||||
validateFileOperation(operation: string, filePath: string): ValidationResult {
|
||||
const protectedPatterns = [
|
||||
'.github/copilot-instructions.md',
|
||||
'.github/copilot-modules/',
|
||||
'.github/agents/',
|
||||
];
|
||||
|
||||
const isProtected = protectedPatterns.some(pattern => filePath.includes(pattern));
|
||||
|
||||
if (isProtected) {
|
||||
return {
|
||||
allowed: false,
|
||||
tier: 'TIER-0',
|
||||
reason: 'Protected file modification blocked',
|
||||
};
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
```
|
||||
|
||||
Run test: ✅ STILL PASSES
|
||||
|
||||
---
|
||||
|
||||
## 📏 Quality Metrics
|
||||
|
||||
### Code Quality Standards (TIER-2)
|
||||
|
||||
**LINTING**: Zero errors, warnings acceptable
|
||||
|
||||
**COMPLEXITY**: Cyclomatic complexity <15 per function
|
||||
|
||||
**DUPLICATION**: <5% code duplication
|
||||
|
||||
**MAINTAINABILITY INDEX**: >70 (good), >50 (acceptable)
|
||||
|
||||
### Measure Quality
|
||||
|
||||
> ⚡ CHECKPOINT — Coverage ≥70% overall? All tests passing? No skipped tests?
|
||||
|
||||
```bash
|
||||
# Linting
|
||||
npm run lint
|
||||
|
||||
# Type checking
|
||||
npm run type-check
|
||||
|
||||
# Complexity (if tool available)
|
||||
npx complexity-report src/
|
||||
|
||||
# Duplication (if tool available)
|
||||
npx jscpd src/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚦 Quality Gates (TIER-1)
|
||||
|
||||
### Pre-Commit Gates
|
||||
|
||||
**ALL MUST PASS**:
|
||||
```bash
|
||||
npm run build # ✅ Build succeeds
|
||||
npm test # ✅ All tests pass
|
||||
npm run lint # ✅ Zero linter errors
|
||||
npm run type-check # ✅ TypeScript strict mode
|
||||
npm run test:coverage # ✅ Coverage ≥70%
|
||||
npm run test:security # ✅ Security tests 100% pass
|
||||
```
|
||||
|
||||
**IF ANY FAIL**: Must fix before commit
|
||||
|
||||
### Pre-Merge Gates
|
||||
|
||||
**ALL MUST PASS**:
|
||||
- [ ] All pre-commit gates passed
|
||||
- [ ] Code review approved
|
||||
- [ ] Documentation updated
|
||||
- [ ] CHANGELOG updated
|
||||
- [ ] traceability.csv updated
|
||||
- [ ] No TIER-0 violations introduced
|
||||
- [ ] Performance acceptable (no regressions)
|
||||
|
||||
### Pre-Release Gates
|
||||
|
||||
**ALL MUST PASS**:
|
||||
- [ ] All pre-merge gates passed
|
||||
- [ ] End-to-end tests pass
|
||||
- [ ] Manual testing complete (critical paths)
|
||||
- [ ] Distribution package built successfully
|
||||
- [ ] Installation instructions verified
|
||||
- [ ] Migration guide written (if breaking changes)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Continuous Integration
|
||||
|
||||
### CI Pipeline
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
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: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
- name: Coverage
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 Test Documentation
|
||||
|
||||
### Test Naming
|
||||
|
||||
**CONVENTION**:
|
||||
```
|
||||
describe('[Component/Feature]', () => {
|
||||
describe('[Method/Function]', () => {
|
||||
it('should [expected behavior] when [condition]', () => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**EXAMPLES**:
|
||||
|
||||
> ⚡ CHECKPOINT — Test names follow pattern: describe('[REQ-ID]') → describe('[AC]') → it('should...')?
|
||||
|
||||
```typescript
|
||||
describe('GovernanceValidator', () => {
|
||||
describe('validateFileOperation', () => {
|
||||
it('should block protected files when write operation', () => {});
|
||||
it('should allow non-protected files when write operation', () => {});
|
||||
it('should throw error when file path is null', () => {});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Test Comments
|
||||
|
||||
**GIVEN-WHEN-THEN**:
|
||||
```typescript
|
||||
it('should block protected file modification', () => {
|
||||
// Given: Protected file and write operation
|
||||
const operation = 'write';
|
||||
const filePath = '.github/copilot-instructions.md';
|
||||
|
||||
// When: Validation runs
|
||||
const result = validator.validateFileOperation(operation, filePath);
|
||||
|
||||
// Then: Operation is blocked with TIER-0
|
||||
expect(result.allowed).toBe(false);
|
||||
expect(result.tier).toBe('TIER-0');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### Test Independence
|
||||
|
||||
**EACH TEST SHOULD**:
|
||||
- Run independently (no order dependency)
|
||||
- Create own test data
|
||||
- Clean up after itself
|
||||
- Not share state with other tests
|
||||
|
||||
### Test Data
|
||||
|
||||
**PREFER**:
|
||||
- Inline test data (visible in test)
|
||||
- Fixtures for large data
|
||||
- Factories for object creation
|
||||
- Mocks for external dependencies
|
||||
|
||||
**AVOID**:
|
||||
- Shared mutable state
|
||||
- Real external services (use mocks)
|
||||
- Hard-coded file paths (use temp directories)
|
||||
|
||||
### Mocking
|
||||
|
||||
**WHEN TO MOCK**:
|
||||
- External services (APIs, databases)
|
||||
- File system operations (use in-memory)
|
||||
- Network requests
|
||||
- Time-dependent operations
|
||||
|
||||
**EXAMPLE**:
|
||||
```typescript
|
||||
import { vi } from 'vitest';
|
||||
|
||||
it('should call MCP server', async () => {
|
||||
// Mock fetch
|
||||
const fetchMock = vi.spyOn(global, 'fetch').mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ allowed: false }),
|
||||
} as Response);
|
||||
|
||||
// Test
|
||||
const result = await mcpClient.validateOperation('write', 'file.ts');
|
||||
|
||||
// Verify
|
||||
expect(callToolMock).toHaveBeenCalledWith(
|
||||
'validate_operation',
|
||||
expect.objectContaining({ operation: 'write' })
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Quick Reference
|
||||
|
||||
| Aspect | Standard | Tool/Command |
|
||||
|--------|----------|--------------|
|
||||
| **Framework (MCP)** | Vitest | `npm test` |
|
||||
| **Framework (Extension)** | @vscode/test-electron | `npm test` |
|
||||
| **Coverage Target** | ≥70% overall | `npm run test:coverage` |
|
||||
| **Security Coverage** | 100% required | Security test suite |
|
||||
| **Pre-Commit** | All tests pass | CI/git hooks |
|
||||
| **TDD** | Preferred | Red-Green-Refactor |
|
||||
|
||||
---
|
||||
|
||||
*Testing & Quality Module v3.0.0*
|
||||
*Comprehensive testing standards for Virsaitis*
|
||||
|
||||
---
|
||||
|
||||
## Key Rules From This Module
|
||||
|
||||
- Coverage target: ≥70% overall, 100% for security-related code.
|
||||
- TDD preferred: Red → Green → Refactor. Write failing test first.
|
||||
- Every REQ-ID must have corresponding tests. Update traceability.csv.
|
||||
- All tests must pass before commit. No skipping, no force-push.
|
||||
- Definitions: `.github/virsaitis-definition-library.md`
|
||||
|
||||
Return to hub: `.github/copilot-instructions.md`
|
||||
Reference in New Issue
Block a user