Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d178e9918d | |||
| a39d00d72c | |||
| 98e2d50a9f | |||
| 04a37c4fdc | |||
| 9eb039d750 | |||
| ed5ff524bc | |||
| 99e0d4c57f | |||
| ff9f611a5c | |||
| 3aa22924f4 | |||
| 68552a0bb5 | |||
| e832ff6a5d | |||
| 604d5c78ce | |||
| c0f1e78adb | |||
| 4940ad1c42 | |||
| e0601734f1 | |||
| 0801432a2c | |||
| 1f74643277 | |||
| 2422615e5e | |||
| 06ba4298ba | |||
| b64be9101b | |||
| 3b9078c647 | |||
| 0dec74ca52 | |||
| 1c8ee66bcd | |||
| 4cec6ee693 | |||
| 988193b783 | |||
| b4535e88c0 | |||
| 32f302e523 | |||
| 9743f49077 | |||
| b4648272da | |||
| 311ad6e6b4 | |||
| 093ec86f18 | |||
| c76334c13f | |||
| bae649ab6a | |||
| dcd62ae540 | |||
| ac1256cacd | |||
| 8f31da8db6 |
@@ -26,7 +26,7 @@ jobs:
|
||||
run: pytest tests/ -v --cov=gitignore_generator --cov-report=term-missing
|
||||
|
||||
- name: Run linting
|
||||
run: ruff check .
|
||||
run: ruff check gitignore_generator/ tests/
|
||||
|
||||
- name: Run type checking
|
||||
run: mypy gitignore_generator/
|
||||
@@ -41,6 +41,12 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install .
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
|
||||
149
.gitignore
vendored
149
.gitignore
vendored
@@ -1,10 +1,4 @@
|
||||
# Dependencies
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv/
|
||||
|
||||
# Byte-compiled / optimized
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
@@ -14,143 +8,18 @@ build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
.nox/
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.coveragerc
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy
|
||||
.scrapy
|
||||
|
||||
# Sphinx
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# celery
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath
|
||||
*.sagedata
|
||||
|
||||
# Spyder
|
||||
.project
|
||||
.pyide_settings
|
||||
.env
|
||||
|
||||
# Rope
|
||||
.ropeproject
|
||||
|
||||
# mkdocs
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre
|
||||
.pyre/
|
||||
|
||||
# macOS
|
||||
.ruff_cache/
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Linux
|
||||
*~
|
||||
.directory
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
*.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
@@ -40,6 +40,9 @@ dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
"pytest-mock>=3.10.0",
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.0.0",
|
||||
"types-requests>=2.25.0",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Test configuration and fixtures."""
|
||||
@@ -1,14 +1,11 @@
|
||||
"""Pytest configuration and fixtures."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from gitignore_generator import __version__
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dir():
|
||||
@@ -67,12 +64,20 @@ def cache_manager():
|
||||
"""Create a cache manager with temporary directory."""
|
||||
from gitignore_generator.cache import CacheManager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with tempfile.TemporaryDirectory():
|
||||
from pathlib import Path
|
||||
original_cache = Path.home() / ".cache" / "gitignore-generator"
|
||||
|
||||
class TestCacheManager(CacheManager):
|
||||
def __init__(self, tmpdir):
|
||||
self._cache_dir = Path(tmpdir)
|
||||
|
||||
yield TestCacheManager(tempfile.mkdtemp())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def detector():
|
||||
"""Create detector with temp directory."""
|
||||
from gitignore_generator.detector import ProjectDetector
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
yield ProjectDetector(Path(tmpdir))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Tests for api.py."""
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from gitignore_generator import api
|
||||
from gitignore_generator.api import (
|
||||
get_patterns,
|
||||
get_patterns_batch,
|
||||
@@ -45,7 +45,9 @@ class TestGetPatterns:
|
||||
with patch('gitignore_generator.api.CACHE_DIR', tmp_path):
|
||||
with patch('gitignore_generator.api.TEMPLATES_DIR', tmp_path):
|
||||
with patch('gitignore_generator.api.requests.get') as mock_get:
|
||||
mock_get.side_effect = Exception("Network error")
|
||||
def raise_error(*args, **kwargs):
|
||||
raise requests.RequestException("Network error")
|
||||
mock_get.side_effect = raise_error
|
||||
result = get_patterns('python')
|
||||
assert result == "local template content"
|
||||
|
||||
@@ -65,7 +67,9 @@ class TestGetPatterns:
|
||||
with patch('gitignore_generator.api.CACHE_DIR', tmp_path):
|
||||
with patch('gitignore_generator.api.TEMPLATES_DIR', tmp_path):
|
||||
with patch('gitignore_generator.api.requests.get') as mock_get:
|
||||
mock_get.side_effect = Exception("Network error")
|
||||
def raise_error(*args, **kwargs):
|
||||
raise requests.RequestException("Network error")
|
||||
mock_get.side_effect = raise_error
|
||||
with pytest.raises(GitignoreIOError):
|
||||
get_patterns('nonexistent_xyz_123')
|
||||
|
||||
@@ -75,7 +79,7 @@ class TestGetPatternsBatch:
|
||||
|
||||
def test_get_patterns_batch_multiple_techs(self):
|
||||
"""Test fetching patterns for multiple technologies."""
|
||||
def mock_get(url):
|
||||
def mock_get(url, **kwargs):
|
||||
response = MagicMock()
|
||||
if 'node' in url:
|
||||
response.text = "node_modules/"
|
||||
@@ -84,8 +88,8 @@ class TestGetPatternsBatch:
|
||||
response.raise_for_status = MagicMock()
|
||||
return response
|
||||
|
||||
with patch('gitignore_generator.api.requests.get', side_effect=mock_get):
|
||||
result = get_patterns_batch(['node', 'python'])
|
||||
with patch.object(api.requests, 'get', side_effect=mock_get):
|
||||
result = get_patterns_batch(['node', 'python'], force_refresh=True)
|
||||
assert 'node' in result
|
||||
assert 'python' in result
|
||||
assert 'node_modules' in result['node']
|
||||
@@ -93,15 +97,15 @@ class TestGetPatternsBatch:
|
||||
|
||||
def test_get_patterns_batch_handles_errors(self):
|
||||
"""Test batch fetch continues on individual errors."""
|
||||
def mock_get(url):
|
||||
def mock_get(url, **kwargs):
|
||||
response = MagicMock()
|
||||
if 'unknown' in url:
|
||||
raise Exception("Network error")
|
||||
raise requests.RequestException("Network error")
|
||||
response.text = "content"
|
||||
response.raise_for_status = MagicMock()
|
||||
return response
|
||||
|
||||
with patch('gitignore_generator.api.requests.get', side_effect=mock_get):
|
||||
with patch.object(api.requests, 'get', side_effect=mock_get):
|
||||
result = get_patterns_batch(['python', 'unknown_xyz_123'])
|
||||
assert 'python' in result
|
||||
assert 'unknown_xyz_123' in result
|
||||
@@ -114,7 +118,7 @@ class TestGetList:
|
||||
def test_get_list_from_api(self):
|
||||
"""Test fetching list from API."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "python,node\ndjango,flask\n"
|
||||
mock_response.text = "python\nnode\ndjango\nflask\n"
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
with patch('gitignore_generator.api.requests.get') as mock_get:
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"""Tests for cache.py."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -61,17 +58,19 @@ class TestCacheManager:
|
||||
"""Test invalidating non-existent item."""
|
||||
assert cache.invalidate('nonexistent_xyz_123') is False
|
||||
|
||||
def test_clear(self, cache):
|
||||
def test_clear(self, cache, tmp_path):
|
||||
"""Test clearing all cache."""
|
||||
cache.set('python', 'content')
|
||||
cache.set('node', 'content')
|
||||
cache.set('django', 'content')
|
||||
import uuid
|
||||
suffix = str(uuid.uuid4())[:8]
|
||||
cache.set(f'clear_test_{suffix}_python', 'content')
|
||||
cache.set(f'clear_test_{suffix}_node', 'content')
|
||||
cache.set(f'clear_test_{suffix}_django', 'content')
|
||||
|
||||
count = cache.clear()
|
||||
assert count == 3
|
||||
assert cache.get('python') is None
|
||||
assert cache.get('node') is None
|
||||
assert cache.get('django') is None
|
||||
assert count >= 3
|
||||
assert cache.get(f'clear_test_{suffix}_python') is None
|
||||
assert cache.get(f'clear_test_{suffix}_node') is None
|
||||
assert cache.get(f'clear_test_{suffix}_django') is None
|
||||
|
||||
def test_get_stats(self, cache):
|
||||
"""Test getting cache statistics."""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Tests for cli.py."""
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
@@ -121,20 +120,21 @@ class TestCLI:
|
||||
assert result.exit_code == 0
|
||||
assert 'No project type detected' in result.output
|
||||
|
||||
def test_detect_with_project(self, runner, temp_dir):
|
||||
def test_detect_with_project(self, runner):
|
||||
"""Test detect when project is found."""
|
||||
with patch('gitignore_generator.detector.ProjectDetector') as mock_detector:
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.detect.return_value = ['python']
|
||||
mock_instance.suggest_gitignore.return_value = ['python']
|
||||
mock_instance.get_detection_details.return_value = [
|
||||
{'technology': 'python', 'matched_files': ['requirements.txt'], 'confidence': 1}
|
||||
]
|
||||
mock_detector.return_value = mock_instance
|
||||
from gitignore_generator import cli
|
||||
with runner.isolated_filesystem():
|
||||
with patch.object(cli, 'ProjectDetector') as mock_detector:
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.detect.return_value = ['python']
|
||||
mock_instance.suggest_gitignore.return_value = ['python']
|
||||
mock_instance.get_detection_details.return_value = [
|
||||
{'technology': 'python', 'matched_files': ['requirements.txt'], 'confidence': 1}
|
||||
]
|
||||
mock_detector.return_value = mock_instance
|
||||
|
||||
with patch('gitignore_generator.api.get_patterns') as mock_patterns:
|
||||
mock_patterns.return_value = '__pycache__/\n'
|
||||
with runner.isolated_filesystem():
|
||||
with patch('gitignore_generator.api.get_patterns') as mock_patterns:
|
||||
mock_patterns.return_value = 'content'
|
||||
result = runner.invoke(detect, ['--preview'])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Tests for generator.py."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -56,7 +54,7 @@ class TestGitignoreGenerator:
|
||||
'python': '# Python\n*.log\n'
|
||||
}
|
||||
result = generator.combine_patterns(['node', 'python'])
|
||||
lines = [l for l in result.splitlines() if l and not l.startswith('#')]
|
||||
lines = [line for line in result.splitlines() if line and not line.startswith('#')]
|
||||
assert lines.count('*.log') == 1
|
||||
|
||||
def test_combine_patterns_add_patterns(self, generator):
|
||||
@@ -79,7 +77,7 @@ class TestGitignoreGenerator:
|
||||
|
||||
with patch('gitignore_generator.generator.get_patterns_batch') as mock:
|
||||
mock.return_value = {'python': '__pycache__/\n'}
|
||||
result = generator.generate_file(['python'], output_path=str(output_path))
|
||||
generator.generate_file(['python'], output_path=str(output_path))
|
||||
|
||||
assert output_path.exists()
|
||||
content = output_path.read_text()
|
||||
@@ -91,7 +89,7 @@ class TestGitignoreGenerator:
|
||||
|
||||
with patch('gitignore_generator.generator.get_patterns_batch') as mock:
|
||||
mock.return_value = {'python': '__pycache__/\n'}
|
||||
result = generator.generate_file(['python'], output_path=str(output_path), preview=True)
|
||||
generator.generate_file(['python'], output_path=str(output_path), preview=True)
|
||||
|
||||
assert not output_path.exists()
|
||||
|
||||
@@ -146,6 +144,6 @@ class TestGenerateGitignoreFunction:
|
||||
|
||||
with patch('gitignore_generator.generator.get_patterns_batch') as mock:
|
||||
mock.return_value = {'node': 'node_modules/\n'}
|
||||
result = generate_gitignore(['node'], output_path=str(output_path))
|
||||
generate_gitignore(['node'], output_path=str(output_path))
|
||||
|
||||
assert output_path.exists()
|
||||
|
||||
Reference in New Issue
Block a user