| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- #!/usr/bin/env python3
- """
- Test suite for configuration validation
- Tests the validate_config() function with various valid and invalid configs
- """
- import sys
- import os
- import unittest
- # Add parent directory to path
- sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- from skill_seekers.cli.doc_scraper import validate_config
- class TestConfigValidation(unittest.TestCase):
- """Test configuration validation"""
- def test_valid_minimal_config(self):
- """Test valid minimal configuration"""
- config = {
- 'name': 'test-skill',
- 'base_url': 'https://example.com/'
- }
- errors, _ = validate_config(config)
- # Should have warnings about missing selectors, but no critical errors
- self.assertIsInstance(errors, list)
- def test_valid_complete_config(self):
- """Test valid complete configuration"""
- config = {
- 'name': 'godot',
- 'base_url': 'https://docs.godotengine.org/en/stable/',
- 'description': 'Godot Engine documentation',
- 'selectors': {
- 'main_content': 'div[role="main"]',
- 'title': 'title',
- 'code_blocks': 'pre code'
- },
- 'url_patterns': {
- 'include': ['/guide/', '/api/'],
- 'exclude': ['/blog/']
- },
- 'categories': {
- 'getting_started': ['intro', 'tutorial'],
- 'api': ['api', 'reference']
- },
- 'rate_limit': 0.5,
- 'max_pages': 500
- }
- errors, _ = validate_config(config)
- self.assertEqual(len(errors), 0, f"Valid config should have no errors, got: {errors}")
- def test_missing_name(self):
- """Test missing required field 'name'"""
- config = {
- 'base_url': 'https://example.com/'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('name' in error.lower() for error in errors))
- def test_missing_base_url(self):
- """Test missing required field 'base_url'"""
- config = {
- 'name': 'test'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('base_url' in error.lower() for error in errors))
- def test_invalid_name_special_chars(self):
- """Test invalid name with special characters"""
- config = {
- 'name': 'test@skill!',
- 'base_url': 'https://example.com/'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('invalid name' in error.lower() for error in errors))
- def test_valid_name_formats(self):
- """Test various valid name formats"""
- valid_names = ['test', 'test-skill', 'test_skill', 'TestSkill123', 'my-awesome-skill_v2']
- for name in valid_names:
- config = {
- 'name': name,
- 'base_url': 'https://example.com/'
- }
- errors, _ = validate_config(config)
- name_errors = [e for e in errors if 'invalid name' in e.lower()]
- self.assertEqual(len(name_errors), 0, f"Name '{name}' should be valid")
- def test_invalid_base_url_no_protocol(self):
- """Test invalid base_url without protocol"""
- config = {
- 'name': 'test',
- 'base_url': 'example.com'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('base_url' in error.lower() for error in errors))
- def test_valid_url_protocols(self):
- """Test valid URL protocols"""
- for protocol in ['http://', 'https://']:
- config = {
- 'name': 'test',
- 'base_url': f'{protocol}example.com/'
- }
- errors, _ = validate_config(config)
- url_errors = [e for e in errors if 'base_url' in e.lower() and 'invalid' in e.lower()]
- self.assertEqual(len(url_errors), 0, f"Protocol '{protocol}' should be valid")
- def test_invalid_selectors_not_dict(self):
- """Test invalid selectors (not a dictionary)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'selectors': 'invalid'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('selectors' in error.lower() and 'dictionary' in error.lower() for error in errors))
- def test_missing_recommended_selectors(self):
- """Test warning for missing recommended selectors"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'selectors': {
- 'main_content': 'article'
- # Missing 'title' and 'code_blocks'
- }
- }
- _, warnings = validate_config(config)
- self.assertTrue(any('title' in warning.lower() for warning in warnings))
- self.assertTrue(any('code_blocks' in warning.lower() for warning in warnings))
- def test_invalid_url_patterns_not_dict(self):
- """Test invalid url_patterns (not a dictionary)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'url_patterns': []
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('url_patterns' in error.lower() and 'dictionary' in error.lower() for error in errors))
- def test_invalid_url_patterns_include_not_list(self):
- """Test invalid url_patterns.include (not a list)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'url_patterns': {
- 'include': 'not-a-list'
- }
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('include' in error.lower() and 'list' in error.lower() for error in errors))
- def test_invalid_categories_not_dict(self):
- """Test invalid categories (not a dictionary)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'categories': []
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('categories' in error.lower() and 'dictionary' in error.lower() for error in errors))
- def test_invalid_category_keywords_not_list(self):
- """Test invalid category keywords (not a list)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'categories': {
- 'getting_started': 'not-a-list'
- }
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('getting_started' in error.lower() and 'list' in error.lower() for error in errors))
- def test_invalid_rate_limit_negative(self):
- """Test invalid rate_limit (negative)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'rate_limit': -1
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('rate_limit' in error.lower() for error in errors))
- def test_invalid_rate_limit_too_high(self):
- """Test invalid rate_limit (too high)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'rate_limit': 20
- }
- _, warnings = validate_config(config)
- self.assertTrue(any('rate_limit' in warning.lower() for warning in warnings))
- def test_invalid_rate_limit_not_number(self):
- """Test invalid rate_limit (not a number)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'rate_limit': 'fast'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('rate_limit' in error.lower() for error in errors))
- def test_valid_rate_limit_range(self):
- """Test valid rate_limit range"""
- for rate in [0, 0.1, 0.5, 1, 5, 10]:
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'rate_limit': rate
- }
- errors, _ = validate_config(config)
- rate_errors = [e for e in errors if 'rate_limit' in e.lower()]
- self.assertEqual(len(rate_errors), 0, f"Rate limit {rate} should be valid")
- def test_invalid_max_pages_zero(self):
- """Test invalid max_pages (zero)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'max_pages': 0
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('max_pages' in error.lower() for error in errors))
- def test_invalid_max_pages_too_high(self):
- """Test invalid max_pages (too high)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'max_pages': 20000
- }
- _, warnings = validate_config(config)
- self.assertTrue(any('max_pages' in warning.lower() for warning in warnings))
- def test_invalid_max_pages_not_int(self):
- """Test invalid max_pages (not an integer)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'max_pages': 'many'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('max_pages' in error.lower() for error in errors))
- def test_valid_max_pages_range(self):
- """Test valid max_pages range"""
- for max_p in [1, 10, 100, 500, 5000, 10000]:
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'max_pages': max_p
- }
- errors, _ = validate_config(config)
- max_errors = [e for e in errors if 'max_pages' in e.lower()]
- self.assertEqual(len(max_errors), 0, f"Max pages {max_p} should be valid")
- def test_invalid_start_urls_not_list(self):
- """Test invalid start_urls (not a list)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'start_urls': 'https://example.com/page1'
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('start_urls' in error.lower() and 'list' in error.lower() for error in errors))
- def test_invalid_start_urls_bad_protocol(self):
- """Test invalid start_urls (bad protocol)"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'start_urls': ['ftp://example.com/page1']
- }
- errors, _ = validate_config(config)
- self.assertTrue(any('start_url' in error.lower() for error in errors))
- def test_valid_start_urls(self):
- """Test valid start_urls"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/',
- 'start_urls': [
- 'https://example.com/page1',
- 'http://example.com/page2',
- 'https://example.com/api/docs'
- ]
- }
- errors, _ = validate_config(config)
- url_errors = [e for e in errors if 'start_url' in e.lower()]
- self.assertEqual(len(url_errors), 0, "Valid start_urls should pass validation")
- def test_config_with_llms_txt_url(self):
- """Test config validation with explicit llms_txt_url"""
- config = {
- 'name': 'test',
- 'llms_txt_url': 'https://example.com/llms-full.txt',
- 'base_url': 'https://example.com/docs'
- }
- # Should be valid
- self.assertEqual(config.get('llms_txt_url'), 'https://example.com/llms-full.txt')
- def test_config_with_skip_llms_txt(self):
- """Test config validation accepts skip_llms_txt"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/docs',
- 'skip_llms_txt': True
- }
- errors, warnings = validate_config(config)
- self.assertEqual(errors, [])
- self.assertTrue(config.get('skip_llms_txt'))
- def test_config_with_skip_llms_txt_false(self):
- """Test config validation accepts skip_llms_txt as False"""
- config = {
- 'name': 'test',
- 'base_url': 'https://example.com/docs',
- 'skip_llms_txt': False
- }
- errors, warnings = validate_config(config)
- self.assertEqual(errors, [])
- self.assertFalse(config.get('skip_llms_txt'))
- if __name__ == '__main__':
- unittest.main()
|