diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 1bc3014..e5630ec 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -326,6 +326,40 @@ print(soup.select_one('main')) print(soup.select_one('div[role="main"]')) ``` +## Running Tests + +**IMPORTANT: You must install the package before running tests** + +```bash +# 1. Install package in editable mode (one-time setup) +pip install -e . + +# 2. Run all tests +pytest + +# 3. Run specific test files +pytest tests/test_config_validation.py +pytest tests/test_github_scraper.py + +# 4. Run with verbose output +pytest -v + +# 5. Run with coverage report +pytest --cov=src/skill_seekers --cov-report=html +``` + +**Why install first?** +- Tests import from `skill_seekers.cli` which requires the package to be installed +- Modern Python packaging best practice (PEP 517/518) +- CI/CD automatically installs with `pip install -e .` +- conftest.py will show helpful error if package not installed + +**Test Coverage:** +- 391+ tests passing +- 39% code coverage +- All core features tested +- CI/CD tests on Ubuntu + macOS with Python 3.10-3.12 + ## Troubleshooting **No content extracted**: Check `main_content` selector. Common values: `article`, `main`, `div[role="main"]`, `div.content` diff --git a/tests/conftest.py b/tests/conftest.py index 5c432a0..77d483d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,11 +2,28 @@ Pytest configuration for tests. Configures anyio to only use asyncio backend (not trio). +Checks that the skill_seekers package is installed before running tests. """ +import sys import pytest +def pytest_configure(config): + """Check if package is installed before running tests.""" + try: + import skill_seekers + except ModuleNotFoundError: + print("\n" + "=" * 70) + print("ERROR: skill_seekers package not installed") + print("=" * 70) + print("\nPlease install the package in editable mode first:") + print(" pip install -e .") + print("\nOr activate your virtual environment if you already installed it.") + print("=" * 70 + "\n") + sys.exit(1) + + @pytest.fixture(scope="session") def anyio_backend(): """Override anyio backend to only use asyncio (not trio).""" diff --git a/tests/test_constants.py b/tests/test_constants.py index 0f81b74..0eef01f 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -109,14 +109,14 @@ class TestConstantsUsage(unittest.TestCase): def test_doc_scraper_imports_constants(self): """Test that doc_scraper imports and uses constants.""" - from cli import doc_scraper + from skill_seekers.cli import doc_scraper # Check that doc_scraper can access the constants self.assertTrue(hasattr(doc_scraper, 'DEFAULT_RATE_LIMIT')) self.assertTrue(hasattr(doc_scraper, 'DEFAULT_MAX_PAGES')) def test_estimate_pages_imports_constants(self): """Test that estimate_pages imports and uses constants.""" - from cli import estimate_pages + from skill_seekers.cli import estimate_pages # Verify function signature uses constants import inspect sig = inspect.signature(estimate_pages.estimate_pages) @@ -125,7 +125,7 @@ class TestConstantsUsage(unittest.TestCase): def test_enhance_skill_imports_constants(self): """Test that enhance_skill imports constants.""" try: - from cli import enhance_skill + from skill_seekers.cli import enhance_skill # Check module loads without errors self.assertIsNotNone(enhance_skill) except (ImportError, SystemExit) as e: @@ -135,7 +135,7 @@ class TestConstantsUsage(unittest.TestCase): def test_enhance_skill_local_imports_constants(self): """Test that enhance_skill_local imports constants.""" - from cli import enhance_skill_local + from skill_seekers.cli import enhance_skill_local self.assertIsNotNone(enhance_skill_local) @@ -144,7 +144,7 @@ class TestConstantsExports(unittest.TestCase): def test_all_exports_exist(self): """Test that all items in __all__ exist.""" - from cli import constants + from skill_seekers.cli import constants self.assertTrue(hasattr(constants, '__all__')) for name in constants.__all__: self.assertTrue( @@ -154,7 +154,7 @@ class TestConstantsExports(unittest.TestCase): def test_all_exports_count(self): """Test that __all__ has expected number of exports.""" - from cli import constants + from skill_seekers.cli import constants # We defined 18 constants (added DEFAULT_ASYNC_MODE) self.assertEqual(len(constants.__all__), 18) diff --git a/tests/test_llms_txt_detector.py b/tests/test_llms_txt_detector.py index 5d474ac..68c8b43 100644 --- a/tests/test_llms_txt_detector.py +++ b/tests/test_llms_txt_detector.py @@ -6,7 +6,7 @@ def test_detect_llms_txt_variants(): """Test detection of llms.txt file variants""" detector = LlmsTxtDetector("https://hono.dev/docs") - with patch('cli.llms_txt_detector.requests.head') as mock_head: + with patch('skill_seekers.cli.llms_txt_detector.requests.head') as mock_head: mock_response = Mock() mock_response.status_code = 200 mock_head.return_value = mock_response @@ -22,7 +22,7 @@ def test_detect_no_llms_txt(): """Test detection when no llms.txt file exists""" detector = LlmsTxtDetector("https://example.com/docs") - with patch('cli.llms_txt_detector.requests.head') as mock_head: + with patch('skill_seekers.cli.llms_txt_detector.requests.head') as mock_head: mock_response = Mock() mock_response.status_code = 404 mock_head.return_value = mock_response @@ -36,7 +36,7 @@ def test_url_parsing_with_complex_paths(): """Test URL parsing handles non-standard paths correctly""" detector = LlmsTxtDetector("https://example.com/docs/v2/guide") - with patch('cli.llms_txt_detector.requests.head') as mock_head: + with patch('skill_seekers.cli.llms_txt_detector.requests.head') as mock_head: mock_response = Mock() mock_response.status_code = 200 mock_head.return_value = mock_response @@ -55,7 +55,7 @@ def test_detect_all_variants(): """Test detecting all llms.txt variants""" detector = LlmsTxtDetector("https://hono.dev/docs") - with patch('cli.llms_txt_detector.requests.head') as mock_head: + with patch('skill_seekers.cli.llms_txt_detector.requests.head') as mock_head: # Mock responses for different variants def mock_response(url, **kwargs): response = Mock()