358 lines
10 KiB
Python
358 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for workflow_manager.py validate command
|
|
|
|
Run with: python3 -m pytest tests/test_workflow_validate.py -v
|
|
Or: python3 tests/test_workflow_validate.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import shutil
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
# Add scripts directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts'))
|
|
|
|
from workflow_manager import (
|
|
validate_implementation,
|
|
show_validation_checklist,
|
|
)
|
|
|
|
|
|
class TestValidateImplementation(unittest.TestCase):
|
|
"""Test validate_implementation function."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
# Create .workflow directory structure
|
|
self.workflow_dir = Path('.workflow')
|
|
self.workflow_dir.mkdir()
|
|
|
|
def tearDown(self):
|
|
"""Clean up test fixtures."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_no_active_workflow(self):
|
|
"""Test validation with no active workflow."""
|
|
result = validate_implementation()
|
|
|
|
self.assertIn('errors', result)
|
|
self.assertTrue(
|
|
any('No active workflow' in e or 'not found' in e for e in result['errors'])
|
|
)
|
|
|
|
def test_no_active_version(self):
|
|
"""Test validation with current.yml but no active version."""
|
|
current_yml = self.workflow_dir / 'current.yml'
|
|
current_yml.write_text('some_other_key: value\n')
|
|
|
|
result = validate_implementation()
|
|
|
|
self.assertIn('errors', result)
|
|
|
|
def test_missing_design_document(self):
|
|
"""Test validation when design document is missing."""
|
|
# Create current.yml with active version
|
|
current_yml = self.workflow_dir / 'current.yml'
|
|
current_yml.write_text('active_version: v001\n')
|
|
|
|
# Create version directory without design document
|
|
version_dir = self.workflow_dir / 'versions' / 'v001'
|
|
version_dir.mkdir(parents=True)
|
|
|
|
result = validate_implementation()
|
|
|
|
self.assertIn('errors', result)
|
|
self.assertTrue(
|
|
any('Design document not found' in e or 'not found' in e for e in result['errors'])
|
|
)
|
|
|
|
|
|
class TestValidateImplementationWithDesign(unittest.TestCase):
|
|
"""Test validate_implementation with a valid design document."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures with design document."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
# Create .workflow directory structure
|
|
self.workflow_dir = Path('.workflow')
|
|
self.workflow_dir.mkdir()
|
|
|
|
# Create current.yml
|
|
current_yml = self.workflow_dir / 'current.yml'
|
|
current_yml.write_text('active_version: v001\n')
|
|
|
|
# Create version directory with design document
|
|
version_dir = self.workflow_dir / 'versions' / 'v001' / 'design'
|
|
version_dir.mkdir(parents=True)
|
|
|
|
# Create minimal design document
|
|
design_doc = version_dir / 'design_document.yml'
|
|
design_doc.write_text("""
|
|
components:
|
|
- id: component_button
|
|
name: Button
|
|
props:
|
|
- name: label
|
|
type: string
|
|
required: true
|
|
events:
|
|
- name: onClick
|
|
payload: void
|
|
|
|
api_endpoints:
|
|
- id: api_get_users
|
|
method: GET
|
|
path: /api/users
|
|
responses:
|
|
- status: 200
|
|
schema:
|
|
properties:
|
|
- name: users
|
|
type: array
|
|
|
|
data_models:
|
|
- id: model_user
|
|
name: User
|
|
fields:
|
|
- name: id
|
|
type: uuid
|
|
- name: email
|
|
type: string
|
|
""")
|
|
|
|
def tearDown(self):
|
|
"""Clean up test fixtures."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_component_not_found(self):
|
|
"""Test validation when component file doesn't exist."""
|
|
result = validate_implementation()
|
|
|
|
self.assertIn('errors', result)
|
|
# Should report missing component file
|
|
self.assertTrue(
|
|
any('Button' in e and 'not found' in e for e in result['errors'])
|
|
)
|
|
|
|
def test_component_found_with_correct_imports(self):
|
|
"""Test validation when component exists with correct imports."""
|
|
# Create component file
|
|
comp_dir = Path('app/components')
|
|
comp_dir.mkdir(parents=True)
|
|
|
|
button_tsx = comp_dir / 'Button.tsx'
|
|
button_tsx.write_text("""
|
|
import type { ButtonProps } from '@/types/component-props';
|
|
|
|
export function Button({ label, onClick }: ButtonProps) {
|
|
return <button onClick={onClick}>{label}</button>;
|
|
}
|
|
""")
|
|
|
|
result = validate_implementation()
|
|
|
|
# Should pass the import check
|
|
self.assertTrue(
|
|
any('Props imported' in p and 'Button' in p for p in result['passed'])
|
|
)
|
|
# Should pass the event check
|
|
self.assertTrue(
|
|
any('onClick' in p and 'implemented' in p for p in result['passed'])
|
|
)
|
|
|
|
def test_component_with_inline_interface(self):
|
|
"""Test validation detects inline interface definition."""
|
|
# Create component file with inline interface (bad practice)
|
|
comp_dir = Path('app/components')
|
|
comp_dir.mkdir(parents=True)
|
|
|
|
button_tsx = comp_dir / 'Button.tsx'
|
|
button_tsx.write_text("""
|
|
interface ButtonProps {
|
|
label: string;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export function Button({ label, onClick }: ButtonProps) {
|
|
return <button onClick={onClick}>{label}</button>;
|
|
}
|
|
""")
|
|
|
|
result = validate_implementation()
|
|
|
|
# Should report error for inline interface
|
|
self.assertTrue(
|
|
any('Defines own props' in e or 'should import' in e for e in result['errors'])
|
|
)
|
|
|
|
def test_api_route_not_found(self):
|
|
"""Test validation when API route file doesn't exist."""
|
|
result = validate_implementation()
|
|
|
|
# Should report missing route
|
|
self.assertTrue(
|
|
any('Route file not found' in e or 'api_get_users' in e for e in result['errors'])
|
|
)
|
|
|
|
def test_api_route_found_with_correct_handler(self):
|
|
"""Test validation when API route exists with correct handler."""
|
|
# Create API route file
|
|
api_dir = Path('app/api/users')
|
|
api_dir.mkdir(parents=True)
|
|
|
|
route_ts = api_dir / 'route.ts'
|
|
route_ts.write_text("""
|
|
import type { GetUsersResponse } from '@/types/api-types';
|
|
|
|
export async function GET(request: Request) {
|
|
const users = await getUsers();
|
|
return Response.json({ users });
|
|
}
|
|
""")
|
|
|
|
result = validate_implementation()
|
|
|
|
# Should pass the handler check
|
|
self.assertTrue(
|
|
any('GET handler found' in p for p in result['passed'])
|
|
)
|
|
|
|
def test_prisma_schema_not_found(self):
|
|
"""Test validation when Prisma schema doesn't exist."""
|
|
result = validate_implementation()
|
|
|
|
# Should warn about missing Prisma schema
|
|
self.assertTrue(
|
|
any('prisma/schema.prisma not found' in w for w in result['warnings'])
|
|
)
|
|
|
|
def test_prisma_model_found(self):
|
|
"""Test validation when Prisma model exists."""
|
|
# Create Prisma schema
|
|
prisma_dir = Path('prisma')
|
|
prisma_dir.mkdir()
|
|
|
|
schema = prisma_dir / 'schema.prisma'
|
|
schema.write_text("""
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
}
|
|
""")
|
|
|
|
result = validate_implementation()
|
|
|
|
# Should pass model check
|
|
self.assertTrue(
|
|
any('User' in p and 'exists' in p for p in result['passed'])
|
|
)
|
|
|
|
|
|
class TestShowValidationChecklist(unittest.TestCase):
|
|
"""Test show_validation_checklist function."""
|
|
|
|
def test_display_passed(self):
|
|
"""Test display of passed checks."""
|
|
result = {
|
|
'passed': ['Check 1 passed', 'Check 2 passed'],
|
|
'warnings': [],
|
|
'errors': [],
|
|
'stats': {'components_checked': 1, 'apis_checked': 1, 'models_checked': 1}
|
|
}
|
|
|
|
# Should not raise any exceptions
|
|
show_validation_checklist(result)
|
|
|
|
def test_display_with_errors(self):
|
|
"""Test display with errors."""
|
|
result = {
|
|
'passed': ['Check 1 passed'],
|
|
'warnings': ['Warning 1'],
|
|
'errors': ['Error 1', 'Error 2'],
|
|
'stats': {'components_checked': 1, 'apis_checked': 0, 'models_checked': 0}
|
|
}
|
|
|
|
# Should not raise any exceptions
|
|
show_validation_checklist(result)
|
|
|
|
def test_display_empty_result(self):
|
|
"""Test display with empty results."""
|
|
result = {
|
|
'passed': [],
|
|
'warnings': [],
|
|
'errors': [],
|
|
'stats': {'components_checked': 0, 'apis_checked': 0, 'models_checked': 0}
|
|
}
|
|
|
|
# Should not raise any exceptions
|
|
show_validation_checklist(result)
|
|
|
|
|
|
class TestValidationStats(unittest.TestCase):
|
|
"""Test validation statistics."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.original_cwd = os.getcwd()
|
|
os.chdir(self.temp_dir)
|
|
|
|
# Create minimal workflow structure
|
|
workflow_dir = Path('.workflow')
|
|
workflow_dir.mkdir()
|
|
(workflow_dir / 'current.yml').write_text('active_version: v001\n')
|
|
|
|
version_dir = workflow_dir / 'versions' / 'v001' / 'design'
|
|
version_dir.mkdir(parents=True)
|
|
(version_dir / 'design_document.yml').write_text("""
|
|
components:
|
|
- id: comp1
|
|
name: Comp1
|
|
props: []
|
|
events: []
|
|
- id: comp2
|
|
name: Comp2
|
|
props: []
|
|
events: []
|
|
api_endpoints:
|
|
- id: api1
|
|
method: GET
|
|
path: /api/test
|
|
responses: []
|
|
data_models:
|
|
- id: model1
|
|
name: Model1
|
|
fields: []
|
|
""")
|
|
|
|
def tearDown(self):
|
|
"""Clean up."""
|
|
os.chdir(self.original_cwd)
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_stats_count(self):
|
|
"""Test that stats count entities correctly."""
|
|
result = validate_implementation()
|
|
|
|
self.assertEqual(result['stats']['components_checked'], 2)
|
|
self.assertEqual(result['stats']['apis_checked'], 1)
|
|
self.assertEqual(result['stats']['models_checked'], 0) # No prisma schema
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|