393 lines
14 KiB
Python
393 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for code.format skill
|
|
|
|
Generated by meta.skill and enhanced with comprehensive test coverage
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
|
|
from skills.code_format import code_format
|
|
|
|
|
|
class TestCodeFormat:
|
|
"""Tests for CodeFormat skill"""
|
|
|
|
def setup_method(self):
|
|
"""Setup test fixtures"""
|
|
self.skill = code_format.CodeFormat()
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
def teardown_method(self):
|
|
"""Cleanup test fixtures"""
|
|
if os.path.exists(self.temp_dir):
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_initialization(self):
|
|
"""Test skill initializes correctly"""
|
|
assert self.skill is not None
|
|
assert self.skill.base_dir is not None
|
|
assert isinstance(self.skill.SUPPORTED_EXTENSIONS, set)
|
|
assert len(self.skill.SUPPORTED_EXTENSIONS) > 0
|
|
assert isinstance(self.skill.CONFIG_FILES, list)
|
|
assert len(self.skill.CONFIG_FILES) > 0
|
|
|
|
def test_supported_extensions(self):
|
|
"""Test that all expected file types are supported"""
|
|
extensions = self.skill.SUPPORTED_EXTENSIONS
|
|
|
|
# Check JavaScript extensions
|
|
assert '.js' in extensions
|
|
assert '.jsx' in extensions
|
|
assert '.mjs' in extensions
|
|
assert '.cjs' in extensions
|
|
|
|
# Check TypeScript extensions
|
|
assert '.ts' in extensions
|
|
assert '.tsx' in extensions
|
|
|
|
# Check style extensions
|
|
assert '.css' in extensions
|
|
assert '.scss' in extensions
|
|
assert '.less' in extensions
|
|
|
|
# Check other formats
|
|
assert '.json' in extensions
|
|
assert '.yaml' in extensions
|
|
assert '.md' in extensions
|
|
assert '.html' in extensions
|
|
|
|
def test_check_prettier_installed_with_npx(self):
|
|
"""Test Prettier detection via npx"""
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=0, stdout="3.0.0\n")
|
|
|
|
is_installed, cmd = self.skill._check_prettier_installed()
|
|
|
|
assert is_installed is True
|
|
assert cmd == 'npx prettier'
|
|
|
|
def test_check_prettier_installed_global(self):
|
|
"""Test Prettier detection via global installation"""
|
|
with patch('subprocess.run') as mock_run:
|
|
# First call (npx) fails
|
|
mock_run.side_effect = [
|
|
FileNotFoundError(),
|
|
Mock(returncode=0, stdout="3.0.0\n")
|
|
]
|
|
|
|
with patch('shutil.which', return_value='/usr/local/bin/prettier'):
|
|
is_installed, cmd = self.skill._check_prettier_installed()
|
|
|
|
assert is_installed is True
|
|
assert cmd == 'prettier'
|
|
|
|
def test_check_prettier_not_installed(self):
|
|
"""Test when Prettier is not installed"""
|
|
with patch('subprocess.run', side_effect=FileNotFoundError()):
|
|
with patch('shutil.which', return_value=None):
|
|
is_installed, cmd = self.skill._check_prettier_installed()
|
|
|
|
assert is_installed is False
|
|
assert cmd is None
|
|
|
|
def test_find_config_file_custom(self):
|
|
"""Test finding custom config file"""
|
|
# Create a custom config file
|
|
config_path = Path(self.temp_dir) / '.prettierrc.custom'
|
|
config_path.write_text('{"semi": true}')
|
|
|
|
found_config = self.skill._find_config_file(
|
|
Path(self.temp_dir),
|
|
custom_config=str(config_path)
|
|
)
|
|
|
|
assert found_config == config_path
|
|
|
|
def test_find_config_file_auto_detect(self):
|
|
"""Test auto-detecting config file"""
|
|
# Create a .prettierrc file
|
|
config_path = Path(self.temp_dir) / '.prettierrc'
|
|
config_path.write_text('{"semi": true}')
|
|
|
|
found_config = self.skill._find_config_file(Path(self.temp_dir))
|
|
|
|
assert found_config == config_path
|
|
|
|
def test_find_config_file_none(self):
|
|
"""Test when no config file exists"""
|
|
found_config = self.skill._find_config_file(Path(self.temp_dir))
|
|
assert found_config is None
|
|
|
|
def test_discover_files_single_file(self):
|
|
"""Test discovering a single file"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('console.log("test");')
|
|
|
|
files = self.skill._discover_files(test_file)
|
|
|
|
assert len(files) == 1
|
|
assert files[0] == test_file
|
|
|
|
def test_discover_files_directory(self):
|
|
"""Test discovering files in a directory"""
|
|
# Create test files
|
|
(Path(self.temp_dir) / 'test1.js').write_text('console.log("test1");')
|
|
(Path(self.temp_dir) / 'test2.ts').write_text('console.log("test2");')
|
|
(Path(self.temp_dir) / 'test.txt').write_text('not supported')
|
|
|
|
files = self.skill._discover_files(Path(self.temp_dir))
|
|
|
|
assert len(files) == 2
|
|
assert any(f.name == 'test1.js' for f in files)
|
|
assert any(f.name == 'test2.ts' for f in files)
|
|
|
|
def test_discover_files_with_patterns(self):
|
|
"""Test discovering files with glob patterns"""
|
|
# Create test files
|
|
(Path(self.temp_dir) / 'test1.js').write_text('console.log("test1");')
|
|
(Path(self.temp_dir) / 'test2.ts').write_text('console.log("test2");')
|
|
(Path(self.temp_dir) / 'test3.css').write_text('body { margin: 0; }')
|
|
|
|
files = self.skill._discover_files(Path(self.temp_dir), patterns=['*.js'])
|
|
|
|
assert len(files) == 1
|
|
assert files[0].name == 'test1.js'
|
|
|
|
def test_discover_files_ignores_node_modules(self):
|
|
"""Test that node_modules is ignored"""
|
|
# Create node_modules directory with files
|
|
node_modules = Path(self.temp_dir) / 'node_modules'
|
|
node_modules.mkdir()
|
|
(node_modules / 'test.js').write_text('console.log("test");')
|
|
|
|
# Create regular file
|
|
(Path(self.temp_dir) / 'app.js').write_text('console.log("app");')
|
|
|
|
files = self.skill._discover_files(Path(self.temp_dir))
|
|
|
|
assert len(files) == 1
|
|
assert files[0].name == 'app.js'
|
|
|
|
def test_format_file_success(self):
|
|
"""Test formatting a file successfully"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('console.log("test");')
|
|
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(returncode=0, stdout='', stderr='')
|
|
|
|
result = self.skill._format_file(test_file, 'prettier', check_only=False)
|
|
|
|
assert result['ok'] is True
|
|
assert result['status'] == 'formatted'
|
|
assert result['file'] == str(test_file)
|
|
|
|
def test_format_file_check_mode_needs_formatting(self):
|
|
"""Test check mode when file needs formatting"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('console.log("test");')
|
|
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(
|
|
returncode=1,
|
|
stdout='',
|
|
stderr='Code style issues found'
|
|
)
|
|
|
|
result = self.skill._format_file(test_file, 'prettier', check_only=True)
|
|
|
|
assert result['ok'] is True
|
|
assert result['status'] == 'needs_formatting'
|
|
|
|
def test_format_file_error(self):
|
|
"""Test formatting with error"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('invalid syntax {{{')
|
|
|
|
with patch('subprocess.run') as mock_run:
|
|
mock_run.return_value = Mock(
|
|
returncode=1,
|
|
stdout='',
|
|
stderr='Syntax error'
|
|
)
|
|
|
|
result = self.skill._format_file(test_file, 'prettier', check_only=False)
|
|
|
|
assert result['ok'] is False
|
|
assert result['status'] == 'error'
|
|
assert 'error' in result
|
|
|
|
def test_execute_prettier_not_installed(self):
|
|
"""Test execute when Prettier is not installed"""
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(False, None)):
|
|
result = self.skill.execute(path=self.temp_dir)
|
|
|
|
assert result['ok'] is False
|
|
assert result['status'] == 'failed'
|
|
assert 'not installed' in result['error'].lower()
|
|
|
|
def test_execute_invalid_path(self):
|
|
"""Test execute with invalid path"""
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
result = self.skill.execute(path='/nonexistent/path')
|
|
|
|
assert result['ok'] is False
|
|
assert result['status'] == 'failed'
|
|
assert 'does not exist' in result['error'].lower()
|
|
|
|
def test_execute_no_files(self):
|
|
"""Test execute when no files are found"""
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
with patch.object(self.skill, '_discover_files', return_value=[]):
|
|
result = self.skill.execute(path=self.temp_dir)
|
|
|
|
assert result['ok'] is True
|
|
assert result['status'] == 'success'
|
|
assert result['formatted_count'] == 0
|
|
assert 'No files found' in result['message']
|
|
|
|
def test_execute_successful_formatting(self):
|
|
"""Test successful formatting execution"""
|
|
# Create test files
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('console.log("test");')
|
|
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
with patch.object(self.skill, '_format_file') as mock_format:
|
|
mock_format.return_value = {
|
|
'ok': True,
|
|
'status': 'formatted',
|
|
'file': str(test_file)
|
|
}
|
|
|
|
result = self.skill.execute(path=str(test_file))
|
|
|
|
assert result['ok'] is True
|
|
assert result['status'] == 'success'
|
|
assert result['formatted_count'] == 1
|
|
assert result['error_count'] == 0
|
|
|
|
def test_execute_check_mode(self):
|
|
"""Test execute in check mode"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('console.log("test");')
|
|
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
with patch.object(self.skill, '_format_file') as mock_format:
|
|
mock_format.return_value = {
|
|
'ok': True,
|
|
'status': 'needs_formatting',
|
|
'file': str(test_file)
|
|
}
|
|
|
|
result = self.skill.execute(path=str(test_file), check_only=True)
|
|
|
|
assert result['ok'] is True
|
|
assert result['status'] == 'success'
|
|
assert result['needs_formatting_count'] == 1
|
|
assert 'need formatting' in result['message'].lower()
|
|
|
|
def test_execute_with_patterns(self):
|
|
"""Test execute with file patterns"""
|
|
# Create test files
|
|
(Path(self.temp_dir) / 'test.js').write_text('console.log("test");')
|
|
(Path(self.temp_dir) / 'test.ts').write_text('console.log("test");')
|
|
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
with patch.object(self.skill, '_discover_files') as mock_discover:
|
|
mock_discover.return_value = [Path(self.temp_dir) / 'test.js']
|
|
|
|
result = self.skill.execute(
|
|
path=self.temp_dir,
|
|
file_patterns='*.js'
|
|
)
|
|
|
|
# Verify patterns were parsed correctly
|
|
mock_discover.assert_called_once()
|
|
call_args = mock_discover.call_args
|
|
assert call_args[0][1] == ['*.js']
|
|
|
|
def test_execute_with_errors(self):
|
|
"""Test execute when some files have errors"""
|
|
test_file = Path(self.temp_dir) / 'test.js'
|
|
test_file.write_text('invalid')
|
|
|
|
with patch.object(self.skill, '_check_prettier_installed', return_value=(True, 'prettier')):
|
|
with patch.object(self.skill, '_format_file') as mock_format:
|
|
mock_format.return_value = {
|
|
'ok': False,
|
|
'status': 'error',
|
|
'file': str(test_file),
|
|
'error': 'Syntax error'
|
|
}
|
|
|
|
result = self.skill.execute(path=str(test_file))
|
|
|
|
assert result['ok'] is True # Overall success even with file errors
|
|
assert result['status'] == 'success'
|
|
assert result['error_count'] == 1
|
|
assert len(result['files_with_errors']) == 1
|
|
|
|
def test_execute_exception_handling(self):
|
|
"""Test execute handles exceptions gracefully"""
|
|
with patch.object(self.skill, '_check_prettier_installed', side_effect=Exception('Test error')):
|
|
result = self.skill.execute(path=self.temp_dir)
|
|
|
|
assert result['ok'] is False
|
|
assert result['status'] == 'failed'
|
|
assert 'error' in result
|
|
|
|
|
|
def test_cli_help(capsys):
|
|
"""Test CLI help message"""
|
|
sys.argv = ["code_format.py", "--help"]
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
code_format.main()
|
|
|
|
assert exc_info.value.code == 0
|
|
captured = capsys.readouterr()
|
|
assert "Format code using Prettier" in captured.out
|
|
|
|
|
|
def test_cli_missing_path(capsys):
|
|
"""Test CLI with missing required path argument"""
|
|
sys.argv = ["code_format.py"]
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
code_format.main()
|
|
|
|
assert exc_info.value.code != 0
|
|
|
|
|
|
def test_cli_execution():
|
|
"""Test CLI execution with mocked skill"""
|
|
sys.argv = ["code_format.py", "--path", "/tmp", "--check"]
|
|
|
|
with patch.object(code_format.CodeFormat, 'execute') as mock_execute:
|
|
mock_execute.return_value = {
|
|
'ok': True,
|
|
'status': 'success',
|
|
'message': 'Test'
|
|
}
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
code_format.main()
|
|
|
|
assert exc_info.value.code == 0
|
|
mock_execute.assert_called_once()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|