diff --git a/README.md b/README.md index bae1ff29..5369dea6 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,22 @@ See [Disaster Recovery Runbooks](./docs/runbooks/README.md) for detailed procedu - **Q3**: Expand security review, run load and failure-mode validation, and prepare launch readiness. - **Q4**: Complete mainnet launch checklist, monitor production stability, and gather retrospective improvements. +```bash +# Validate branch naming, PR description format, and contribution standards +npm run validate:contribution-standards + +# Validate the testing strategy document for unit, integration, and E2E coverage guidance +npm run validate:testing-strategy + +# Validate sprint label formats and issue triage taxonomy +npm run validate:sprint-and-triage + +# Validate release notes template and cliff config +npm run validate:release-notes + +# Validate NFR baselines (SLO, RTO, RPO) +npm run validate:nfr-baselines +``` ## 🤝 Contributing Fork the repository and clone it to your local machine diff --git a/docs/TESTING_STRATEGY.md b/docs/TESTING_STRATEGY.md index 1352f7ae..aa673115 100644 --- a/docs/TESTING_STRATEGY.md +++ b/docs/TESTING_STRATEGY.md @@ -100,6 +100,10 @@ Use E2E tests only for user journeys that must prove the app works in a real bro - Browser-only flows have at least one Playwright test. - New feature work adds coverage in the layer that owns the behavior, not just in the widest suite. +## Repository Enforcement + +This strategy is enforced with the repository validator at `npm run validate:testing-strategy`. The command checks that the strategy document still covers the required testing layers, layer-specific guidance, recommended commands, and Playwright-based E2E coverage expectations. + ## Core Playwright User Flows Canonical browser journeys live under `frontend/e2e/` and run with `cd frontend && npm run test:e2e` (CI: `.github/workflows/e2e.yml`). diff --git a/package.json b/package.json index 995a9459..d8333464 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "validate:frontend-env": "tsx scripts/validate-frontend-env.ts", "test:validate-frontend-env": "vitest run --config scripts/vitest.config.ts", "validate:contribution-standards": "tsx scripts/validate-contribution-standards.ts", + "validate:testing-strategy": "tsx scripts/validate-testing-strategy.ts", "validate:sprint-and-triage": "tsx scripts/validate-sprint-and-triage-conventions.ts", "validate:release-notes": "tsx scripts/validate-release-notes.ts", "validate:nfr-baselines": "tsx scripts/validate-nfr-baselines.ts" diff --git a/scripts/validate-testing-strategy.test.ts b/scripts/validate-testing-strategy.test.ts new file mode 100644 index 00000000..42d62566 --- /dev/null +++ b/scripts/validate-testing-strategy.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { runFullTestingStrategyValidation, validateTestingStrategyDoc } from './validate-testing-strategy'; + +describe('testing strategy validator', () => { + it('accepts a testing strategy document with required sections', () => { + const markdown = ` +# YieldVault-RWA Testing Strategy + +## Principles +- Keep tests close to the code they validate. + +## Test Layers +| Layer | Purpose | Owned by | Typical locations | Primary commands | +| --- | --- | --- | --- | --- | +| Unit | Local logic | Owner | src/**/*.test.ts | npm test | + +## Ownership Rules +- Frontend tests are owned by the UI team. + +## Fixture Strategy +- Keep fixtures local to the suite. + +## Coverage Expectations By Feature Type +| Feature type | Required coverage | +| --- | --- | +| Utility | Unit tests | + +## What Belongs In Each Layer +### Unit +Use unit tests for deterministic logic. + +### Integration +Use integration tests for cross-module behavior. + +### E2E +Use E2E tests for browser journeys. + +## Recommended Commands +- npm run test +- npm run test:e2e + +## Review Checklist +- The test scope matches the behavior under change. + +## Core Playwright User Flows +| Flow | Spec | What it proves | +| --- | --- | --- | +| Dashboard | dashboard-load.spec.ts | App loads correctly | +`; + + const result = validateTestingStrategyDoc(markdown); + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); + }); + + it('rejects a document that is missing required sections', () => { + const result = validateTestingStrategyDoc('# YieldVault-RWA Testing Strategy\n\n## Principles\n'); + + expect(result.valid).toBe(false); + expect(result.errors.some((error) => error.includes('Test Layers'))).toBe(true); + expect(result.errors.some((error) => error.includes('Recommended Commands'))).toBe(true); + }); + + it('accepts the repository testing strategy document', () => { + const docPath = resolve(__dirname, '../docs/TESTING_STRATEGY.md'); + expect(existsSync(docPath)).toBe(true); + + const markdown = readFileSync(docPath, 'utf8'); + const result = validateTestingStrategyDoc(markdown); + + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); + }); + + it('passes the full repository validation for the testing strategy doc', () => { + const rootDir = resolve(__dirname, '..'); + const result = runFullTestingStrategyValidation(rootDir); + + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); + }); +}); diff --git a/scripts/validate-testing-strategy.ts b/scripts/validate-testing-strategy.ts new file mode 100644 index 00000000..08bbbbcc --- /dev/null +++ b/scripts/validate-testing-strategy.ts @@ -0,0 +1,82 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +export interface ValidationResult { + valid: boolean; + errors: string[]; + warnings: string[]; +} + +const REQUIRED_SECTIONS = [ + '## Principles', + '## Test Layers', + '## Ownership Rules', + '## Fixture Strategy', + '## Coverage Expectations By Feature Type', + '## What Belongs In Each Layer', + '## Recommended Commands', + '## Review Checklist', + '## Core Playwright User Flows', +]; + +const REQUIRED_LAYER_HEADINGS = ['### Unit', '### Integration', '### E2E']; + +export function validateTestingStrategyDoc(markdownContent: string): ValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + + if (!markdownContent || markdownContent.trim() === '') { + errors.push('Testing strategy document cannot be empty.'); + return { valid: false, errors, warnings }; + } + + for (const section of REQUIRED_SECTIONS) { + if (!markdownContent.includes(section)) { + errors.push(`Testing strategy doc is missing required section: "${section}"`); + } + } + + for (const heading of REQUIRED_LAYER_HEADINGS) { + if (!markdownContent.includes(heading)) { + errors.push(`Testing strategy doc is missing layer heading: "${heading}"`); + } + } + + if (!markdownContent.includes('Playwright')) { + errors.push('Testing strategy doc must reference Playwright for browser E2E coverage.'); + } + + if (!markdownContent.includes('npm run test:e2e')) { + errors.push('Testing strategy doc must include the E2E command for browser journeys.'); + } + + return { valid: errors.length === 0, errors, warnings }; +} + +export function runFullTestingStrategyValidation(rootDir: string = process.cwd()): ValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + + const docPath = resolve(rootDir, 'docs/TESTING_STRATEGY.md'); + if (!existsSync(docPath)) { + errors.push('docs/TESTING_STRATEGY.md file does not exist.'); + return { valid: false, errors, warnings }; + } + + const result = validateTestingStrategyDoc(readFileSync(docPath, 'utf8')); + errors.push(...result.errors); + warnings.push(...result.warnings); + + return { valid: errors.length === 0, errors, warnings }; +} + +if (require.main === module) { + const result = runFullTestingStrategyValidation(); + if (!result.valid) { + console.error('❌ Testing strategy validation failed:'); + result.errors.forEach((err) => console.error(` - ${err}`)); + process.exit(1); + } else { + console.log('✅ Testing strategy validation passed successfully!'); + } +}