project-standalo-note-to-app/skills/guardrail-orchestrator/tests/test_workflow_validate.py

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()