style: Auto-format 48 files with ruff format

- Fixed formatting to comply with ruff standards
- No functional changes, only formatting/style
- Completes CI/CD pipeline formatting requirements
This commit is contained in:
yusyus
2026-02-15 20:24:32 +03:00
parent 83b03d9f9f
commit 57061b7daf
48 changed files with 626 additions and 548 deletions

View File

@@ -13,15 +13,14 @@ import pytest
import subprocess
import argparse
class TestParserSync:
"""E2E tests for parser synchronization (Issue #285)."""
def test_scrape_interactive_flag_works(self):
"""Test that --interactive flag (previously missing) now works."""
result = subprocess.run(
["skill-seekers", "scrape", "--interactive", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--interactive", "--help"], capture_output=True, text=True
)
assert result.returncode == 0, "Command should execute successfully"
assert "--interactive" in result.stdout, "Help should show --interactive flag"
@@ -30,9 +29,7 @@ class TestParserSync:
def test_scrape_chunk_for_rag_flag_works(self):
"""Test that --chunk-for-rag flag (previously missing) now works."""
result = subprocess.run(
["skill-seekers", "scrape", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
)
assert "--chunk-for-rag" in result.stdout, "Help should show --chunk-for-rag flag"
assert "--chunk-size" in result.stdout, "Help should show --chunk-size flag"
@@ -41,9 +38,7 @@ class TestParserSync:
def test_scrape_verbose_flag_works(self):
"""Test that --verbose flag (previously missing) now works."""
result = subprocess.run(
["skill-seekers", "scrape", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
)
assert "--verbose" in result.stdout, "Help should show --verbose flag"
assert "-v" in result.stdout, "Help should show short form -v"
@@ -51,18 +46,14 @@ class TestParserSync:
def test_scrape_url_flag_works(self):
"""Test that --url flag (previously missing) now works."""
result = subprocess.run(
["skill-seekers", "scrape", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
)
assert "--url URL" in result.stdout, "Help should show --url flag"
def test_github_all_flags_present(self):
"""Test that github command has all expected flags."""
result = subprocess.run(
["skill-seekers", "github", "--help"],
capture_output=True,
text=True
["skill-seekers", "github", "--help"], capture_output=True, text=True
)
# Key github flags that should be present
expected_flags = [
@@ -74,15 +65,14 @@ class TestParserSync:
for flag in expected_flags:
assert flag in result.stdout, f"Help should show {flag} flag"
class TestPresetSystem:
"""E2E tests for preset system (Issue #268)."""
def test_analyze_preset_flag_exists(self):
"""Test that analyze command has --preset flag."""
result = subprocess.run(
["skill-seekers", "analyze", "--help"],
capture_output=True,
text=True
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
)
assert "--preset" in result.stdout, "Help should show --preset flag"
assert "quick" in result.stdout, "Help should mention 'quick' preset"
@@ -92,18 +82,14 @@ class TestPresetSystem:
def test_analyze_preset_list_flag_exists(self):
"""Test that analyze command has --preset-list flag."""
result = subprocess.run(
["skill-seekers", "analyze", "--help"],
capture_output=True,
text=True
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
)
assert "--preset-list" in result.stdout, "Help should show --preset-list flag"
def test_preset_list_shows_presets(self):
"""Test that --preset-list shows all available presets."""
result = subprocess.run(
["skill-seekers", "analyze", "--preset-list"],
capture_output=True,
text=True
["skill-seekers", "analyze", "--preset-list"], capture_output=True, text=True
)
assert result.returncode == 0, "Command should execute successfully"
assert "Available presets" in result.stdout, "Should show preset list header"
@@ -118,7 +104,7 @@ class TestPresetSystem:
result = subprocess.run(
["skill-seekers", "analyze", "--directory", ".", "--quick"],
capture_output=True,
text=True
text=True,
)
# Note: Deprecation warnings go to stderr
output = result.stdout + result.stderr
@@ -131,22 +117,19 @@ class TestPresetSystem:
result = subprocess.run(
["skill-seekers", "analyze", "--directory", ".", "--comprehensive"],
capture_output=True,
text=True
text=True,
)
output = result.stdout + result.stderr
assert "DEPRECATED" in output, "Should show deprecation warning"
assert "--preset comprehensive" in output, "Should suggest alternative"
class TestBackwardCompatibility:
"""E2E tests for backward compatibility."""
def test_old_scrape_command_still_works(self):
"""Test that old scrape command invocations still work."""
result = subprocess.run(
["skill-seekers-scrape", "--help"],
capture_output=True,
text=True
)
result = subprocess.run(["skill-seekers-scrape", "--help"], capture_output=True, text=True)
assert result.returncode == 0, "Old command should still work"
assert "documentation" in result.stdout.lower(), "Help should mention documentation"
@@ -154,16 +137,12 @@ class TestBackwardCompatibility:
"""Test that unified CLI and standalone have identical arguments."""
# Get help from unified CLI
unified_result = subprocess.run(
["skill-seekers", "scrape", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
)
# Get help from standalone
standalone_result = subprocess.run(
["skill-seekers-scrape", "--help"],
capture_output=True,
text=True
["skill-seekers-scrape", "--help"], capture_output=True, text=True
)
# Both should have the same key flags
@@ -180,6 +159,7 @@ class TestBackwardCompatibility:
assert flag in unified_result.stdout, f"Unified should have {flag}"
assert flag in standalone_result.stdout, f"Standalone should have {flag}"
class TestProgrammaticAPI:
"""Test that the shared argument functions work programmatically."""
@@ -221,16 +201,13 @@ class TestProgrammaticAPI:
# Note: enhance_level is not part of AnalysisPreset anymore.
# It's controlled separately via --enhance-level flag (default 2)
class TestIntegration:
"""Integration tests for the complete flow."""
def test_unified_cli_subcommands_registered(self):
"""Test that all subcommands are properly registered."""
result = subprocess.run(
["skill-seekers", "--help"],
capture_output=True,
text=True
)
result = subprocess.run(["skill-seekers", "--help"], capture_output=True, text=True)
# All major commands should be listed
expected_commands = [
@@ -250,9 +227,7 @@ class TestIntegration:
def test_scrape_help_detailed(self):
"""Test that scrape help shows all argument details."""
result = subprocess.run(
["skill-seekers", "scrape", "--help"],
capture_output=True,
text=True
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
)
# Check for argument categories
@@ -263,13 +238,14 @@ class TestIntegration:
def test_analyze_help_shows_presets(self):
"""Test that analyze help prominently shows preset information."""
result = subprocess.run(
["skill-seekers", "analyze", "--help"],
capture_output=True,
text=True
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
)
assert "--preset" in result.stdout, "Should show --preset flag"
assert "DEFAULT" in result.stdout or "default" in result.stdout, "Should indicate default preset"
assert "DEFAULT" in result.stdout or "default" in result.stdout, (
"Should indicate default preset"
)
class TestE2EWorkflow:
"""End-to-end workflow tests."""
@@ -279,15 +255,18 @@ class TestE2EWorkflow:
"""Test scraping with previously missing arguments (dry run)."""
result = subprocess.run(
[
"skill-seekers", "scrape",
"--url", "https://example.com",
"--interactive", "false", # Would fail if arg didn't exist
"skill-seekers",
"scrape",
"--url",
"https://example.com",
"--interactive",
"false", # Would fail if arg didn't exist
"--verbose", # Would fail if arg didn't exist
"--dry-run",
],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
# Dry run should complete without errors
@@ -314,5 +293,6 @@ class TestE2EWorkflow:
assert "--preset" in result.stdout, "Should have --preset flag"
assert "unrecognized arguments" not in result.stderr.lower()
if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])

View File

@@ -19,6 +19,7 @@ from skill_seekers.cli.arguments.create import (
add_create_arguments,
)
class TestUniversalArguments:
"""Test universal argument definitions."""
@@ -29,25 +30,34 @@ class TestUniversalArguments:
def test_universal_argument_names(self):
"""Universal arguments should have expected names."""
expected_names = {
'name', 'description', 'output',
'enhance_level', 'api_key', # Phase 1: consolidated from enhance + enhance_local
'dry_run', 'verbose', 'quiet',
'chunk_for_rag', 'chunk_size', 'chunk_overlap', # Phase 2: RAG args from common.py
'preset', 'config'
"name",
"description",
"output",
"enhance_level",
"api_key", # Phase 1: consolidated from enhance + enhance_local
"dry_run",
"verbose",
"quiet",
"chunk_for_rag",
"chunk_size",
"chunk_overlap", # Phase 2: RAG args from common.py
"preset",
"config",
}
assert set(UNIVERSAL_ARGUMENTS.keys()) == expected_names
def test_all_universal_have_flags(self):
"""All universal arguments should have flags."""
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
assert 'flags' in arg_def
assert len(arg_def['flags']) > 0
assert "flags" in arg_def
assert len(arg_def["flags"]) > 0
def test_all_universal_have_kwargs(self):
"""All universal arguments should have kwargs."""
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
assert 'kwargs' in arg_def
assert 'help' in arg_def['kwargs']
assert "kwargs" in arg_def
assert "help" in arg_def["kwargs"]
class TestSourceSpecificArguments:
"""Test source-specific argument definitions."""
@@ -55,29 +65,29 @@ class TestSourceSpecificArguments:
def test_web_arguments_exist(self):
"""Web-specific arguments should be defined."""
assert len(WEB_ARGUMENTS) > 0
assert 'max_pages' in WEB_ARGUMENTS
assert 'rate_limit' in WEB_ARGUMENTS
assert 'workers' in WEB_ARGUMENTS
assert "max_pages" in WEB_ARGUMENTS
assert "rate_limit" in WEB_ARGUMENTS
assert "workers" in WEB_ARGUMENTS
def test_github_arguments_exist(self):
"""GitHub-specific arguments should be defined."""
assert len(GITHUB_ARGUMENTS) > 0
assert 'repo' in GITHUB_ARGUMENTS
assert 'token' in GITHUB_ARGUMENTS
assert 'max_issues' in GITHUB_ARGUMENTS
assert "repo" in GITHUB_ARGUMENTS
assert "token" in GITHUB_ARGUMENTS
assert "max_issues" in GITHUB_ARGUMENTS
def test_local_arguments_exist(self):
"""Local-specific arguments should be defined."""
assert len(LOCAL_ARGUMENTS) > 0
assert 'directory' in LOCAL_ARGUMENTS
assert 'languages' in LOCAL_ARGUMENTS
assert 'skip_patterns' in LOCAL_ARGUMENTS
assert "directory" in LOCAL_ARGUMENTS
assert "languages" in LOCAL_ARGUMENTS
assert "skip_patterns" in LOCAL_ARGUMENTS
def test_pdf_arguments_exist(self):
"""PDF-specific arguments should be defined."""
assert len(PDF_ARGUMENTS) > 0
assert 'pdf' in PDF_ARGUMENTS
assert 'ocr' in PDF_ARGUMENTS
assert "pdf" in PDF_ARGUMENTS
assert "ocr" in PDF_ARGUMENTS
def test_no_duplicate_flags_across_sources(self):
"""Source-specific arguments should not have duplicate flags."""
@@ -86,21 +96,25 @@ class TestSourceSpecificArguments:
for source_args in [WEB_ARGUMENTS, GITHUB_ARGUMENTS, LOCAL_ARGUMENTS, PDF_ARGUMENTS]:
for arg_name, arg_def in source_args.items():
flags = arg_def['flags']
flags = arg_def["flags"]
for flag in flags:
# Check if this flag already exists in source-specific args
if flag not in [f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg['flags']]:
if flag not in [
f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg["flags"]
]:
assert flag not in all_flags, f"Duplicate flag: {flag}"
all_flags.add(flag)
class TestAdvancedArguments:
"""Test advanced/rare argument definitions."""
def test_advanced_arguments_exist(self):
"""Advanced arguments should be defined."""
assert len(ADVANCED_ARGUMENTS) > 0
assert 'no_rate_limit' in ADVANCED_ARGUMENTS
assert 'interactive_enhancement' in ADVANCED_ARGUMENTS
assert "no_rate_limit" in ADVANCED_ARGUMENTS
assert "interactive_enhancement" in ADVANCED_ARGUMENTS
class TestArgumentHelpers:
"""Test helper functions."""
@@ -110,106 +124,108 @@ class TestArgumentHelpers:
names = get_universal_argument_names()
assert isinstance(names, set)
assert len(names) == 13
assert 'name' in names
assert 'enhance_level' in names # Phase 1: consolidated flag
assert "name" in names
assert "enhance_level" in names # Phase 1: consolidated flag
def test_get_source_specific_web(self):
"""Should return web-specific arguments."""
args = get_source_specific_arguments('web')
args = get_source_specific_arguments("web")
assert args == WEB_ARGUMENTS
def test_get_source_specific_github(self):
"""Should return github-specific arguments."""
args = get_source_specific_arguments('github')
args = get_source_specific_arguments("github")
assert args == GITHUB_ARGUMENTS
def test_get_source_specific_local(self):
"""Should return local-specific arguments."""
args = get_source_specific_arguments('local')
args = get_source_specific_arguments("local")
assert args == LOCAL_ARGUMENTS
def test_get_source_specific_pdf(self):
"""Should return pdf-specific arguments."""
args = get_source_specific_arguments('pdf')
args = get_source_specific_arguments("pdf")
assert args == PDF_ARGUMENTS
def test_get_source_specific_config(self):
"""Config should return empty dict (no extra args)."""
args = get_source_specific_arguments('config')
args = get_source_specific_arguments("config")
assert args == {}
def test_get_source_specific_unknown(self):
"""Unknown source should return empty dict."""
args = get_source_specific_arguments('unknown')
args = get_source_specific_arguments("unknown")
assert args == {}
class TestCompatibleArguments:
"""Test compatible argument detection."""
def test_web_compatible_arguments(self):
"""Web source should include universal + web + advanced."""
compatible = get_compatible_arguments('web')
compatible = get_compatible_arguments("web")
# Should include universal arguments
assert 'name' in compatible
assert 'enhance_level' in compatible # Phase 1: consolidated flag
assert "name" in compatible
assert "enhance_level" in compatible # Phase 1: consolidated flag
# Should include web-specific arguments
assert 'max_pages' in compatible
assert 'rate_limit' in compatible
assert "max_pages" in compatible
assert "rate_limit" in compatible
# Should include advanced arguments
assert 'no_rate_limit' in compatible
assert "no_rate_limit" in compatible
def test_github_compatible_arguments(self):
"""GitHub source should include universal + github + advanced."""
compatible = get_compatible_arguments('github')
compatible = get_compatible_arguments("github")
# Should include universal arguments
assert 'name' in compatible
assert "name" in compatible
# Should include github-specific arguments
assert 'repo' in compatible
assert 'token' in compatible
assert "repo" in compatible
assert "token" in compatible
# Should include advanced arguments
assert 'interactive_enhancement' in compatible
assert "interactive_enhancement" in compatible
def test_local_compatible_arguments(self):
"""Local source should include universal + local + advanced."""
compatible = get_compatible_arguments('local')
compatible = get_compatible_arguments("local")
# Should include universal arguments
assert 'description' in compatible
assert "description" in compatible
# Should include local-specific arguments
assert 'directory' in compatible
assert 'languages' in compatible
assert "directory" in compatible
assert "languages" in compatible
def test_pdf_compatible_arguments(self):
"""PDF source should include universal + pdf + advanced."""
compatible = get_compatible_arguments('pdf')
compatible = get_compatible_arguments("pdf")
# Should include universal arguments
assert 'output' in compatible
assert "output" in compatible
# Should include pdf-specific arguments
assert 'pdf' in compatible
assert 'ocr' in compatible
assert "pdf" in compatible
assert "ocr" in compatible
def test_config_compatible_arguments(self):
"""Config source should include universal + advanced only."""
compatible = get_compatible_arguments('config')
compatible = get_compatible_arguments("config")
# Should include universal arguments
assert 'config' in compatible
assert "config" in compatible
# Should include advanced arguments
assert 'no_preserve_code_blocks' in compatible
assert "no_preserve_code_blocks" in compatible
# Should not include source-specific arguments
assert 'repo' not in compatible
assert 'directory' not in compatible
assert "repo" not in compatible
assert "directory" not in compatible
class TestAddCreateArguments:
"""Test add_create_arguments function."""
@@ -217,16 +233,17 @@ class TestAddCreateArguments:
def test_default_mode_adds_universal_only(self):
"""Default mode should add only universal arguments + source positional."""
import argparse
parser = argparse.ArgumentParser()
add_create_arguments(parser, mode='default')
add_create_arguments(parser, mode="default")
# Parse to get all arguments
args = vars(parser.parse_args([]))
# Should have universal arguments
assert 'name' in args
assert 'enhance_level' in args
assert 'chunk_for_rag' in args
assert "name" in args
assert "enhance_level" in args
assert "chunk_for_rag" in args
# Should not have source-specific arguments (they're not added in default mode)
# Note: argparse won't error on unknown args, but they won't be in namespace
@@ -234,62 +251,60 @@ class TestAddCreateArguments:
def test_web_mode_adds_web_arguments(self):
"""Web mode should add universal + web arguments."""
import argparse
parser = argparse.ArgumentParser()
add_create_arguments(parser, mode='web')
add_create_arguments(parser, mode="web")
args = vars(parser.parse_args([]))
# Should have universal arguments
assert 'name' in args
assert "name" in args
# Should have web-specific arguments
assert 'max_pages' in args
assert 'rate_limit' in args
assert "max_pages" in args
assert "rate_limit" in args
def test_all_mode_adds_all_arguments(self):
"""All mode should add every argument."""
import argparse
parser = argparse.ArgumentParser()
add_create_arguments(parser, mode='all')
add_create_arguments(parser, mode="all")
args = vars(parser.parse_args([]))
# Should have universal arguments
assert 'name' in args
assert "name" in args
# Should have all source-specific arguments
assert 'max_pages' in args # web
assert 'repo' in args # github
assert 'directory' in args # local
assert 'pdf' in args # pdf
assert "max_pages" in args # web
assert "repo" in args # github
assert "directory" in args # local
assert "pdf" in args # pdf
# Should have advanced arguments
assert 'no_rate_limit' in args
assert "no_rate_limit" in args
def test_positional_source_argument_always_added(self):
"""Source positional argument should always be added."""
import argparse
for mode in ['default', 'web', 'github', 'local', 'pdf', 'all']:
for mode in ["default", "web", "github", "local", "pdf", "all"]:
parser = argparse.ArgumentParser()
add_create_arguments(parser, mode=mode)
# Should accept source as positional
args = parser.parse_args(['some_source'])
assert args.source == 'some_source'
args = parser.parse_args(["some_source"])
assert args.source == "some_source"
class TestNoDuplicates:
"""Test that there are no duplicate arguments across tiers."""
def test_no_duplicates_between_universal_and_web(self):
"""Universal and web args should not overlap."""
universal_flags = {
flag for arg in UNIVERSAL_ARGUMENTS.values()
for flag in arg['flags']
}
web_flags = {
flag for arg in WEB_ARGUMENTS.values()
for flag in arg['flags']
}
universal_flags = {flag for arg in UNIVERSAL_ARGUMENTS.values() for flag in arg["flags"]}
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
# Allow some overlap since we intentionally include common args
# in multiple places, but check that they're properly defined
@@ -299,10 +314,10 @@ class TestNoDuplicates:
def test_no_duplicates_between_source_specific_args(self):
"""Different source-specific arg groups should not overlap."""
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg['flags']}
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg['flags']}
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg['flags']}
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg['flags']}
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg["flags"]}
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg["flags"]}
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg["flags"]}
# No overlap between different source types
assert len(web_flags & github_flags) == 0
@@ -312,6 +327,7 @@ class TestNoDuplicates:
assert len(github_flags & pdf_flags) == 0
assert len(local_flags & pdf_flags) == 0
class TestArgumentQuality:
"""Test argument definition quality."""
@@ -327,8 +343,8 @@ class TestArgumentQuality:
}
for arg_name, arg_def in all_args.items():
assert 'help' in arg_def['kwargs'], f"{arg_name} missing help text"
assert len(arg_def['kwargs']['help']) > 0, f"{arg_name} has empty help text"
assert "help" in arg_def["kwargs"], f"{arg_name} missing help text"
assert len(arg_def["kwargs"]["help"]) > 0, f"{arg_name} has empty help text"
def test_boolean_arguments_use_store_true(self):
"""Boolean flags should use store_true action."""
@@ -342,13 +358,25 @@ class TestArgumentQuality:
}
boolean_args = [
'dry_run', 'verbose', 'quiet',
'chunk_for_rag', 'skip_scrape', 'resume', 'fresh', 'async_mode',
'no_issues', 'no_changelog', 'no_releases', 'scrape_only',
'skip_patterns', 'skip_test_examples', 'ocr', 'no_rate_limit'
"dry_run",
"verbose",
"quiet",
"chunk_for_rag",
"skip_scrape",
"resume",
"fresh",
"async_mode",
"no_issues",
"no_changelog",
"no_releases",
"scrape_only",
"skip_patterns",
"skip_test_examples",
"ocr",
"no_rate_limit",
]
for arg_name in boolean_args:
if arg_name in all_args:
action = all_args[arg_name]['kwargs'].get('action')
assert action == 'store_true', f"{arg_name} should use store_true"
action = all_args[arg_name]["kwargs"].get("action")
assert action == "store_true", f"{arg_name} should use store_true"

View File

@@ -6,21 +6,21 @@ and routes to the correct scrapers without actually scraping.
import pytest
class TestCreateCommandBasic:
"""Basic integration tests for create command (dry-run mode)."""
def test_create_command_help(self):
"""Test that create command help works."""
import subprocess
result = subprocess.run(
['skill-seekers', 'create', '--help'],
capture_output=True,
text=True
["skill-seekers", "create", "--help"], capture_output=True, text=True
)
assert result.returncode == 0
assert 'Auto-detects source type' in result.stdout
assert 'auto-detected' in result.stdout
assert '--help-web' in result.stdout
assert "Auto-detects source type" in result.stdout
assert "auto-detected" in result.stdout
assert "--help-web" in result.stdout
def test_create_detects_web_url(self):
"""Test that web URLs are detected and routed correctly."""
@@ -31,11 +31,12 @@ class TestCreateCommandBasic:
def test_create_detects_github_repo(self):
"""Test that GitHub repos are detected."""
import subprocess
result = subprocess.run(
['skill-seekers', 'create', 'facebook/react', '--help'],
["skill-seekers", "create", "facebook/react", "--help"],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
# Just verify help works - actual scraping would need API token
assert result.returncode in [0, 2] # 0 for success, 2 for argparse help
@@ -49,10 +50,10 @@ class TestCreateCommandBasic:
test_dir.mkdir()
result = subprocess.run(
['skill-seekers', 'create', str(test_dir), '--help'],
["skill-seekers", "create", str(test_dir), "--help"],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
# Verify help works
assert result.returncode in [0, 2]
@@ -66,10 +67,10 @@ class TestCreateCommandBasic:
pdf_file.touch()
result = subprocess.run(
['skill-seekers', 'create', str(pdf_file), '--help'],
["skill-seekers", "create", str(pdf_file), "--help"],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
# Verify help works
assert result.returncode in [0, 2]
@@ -81,17 +82,14 @@ class TestCreateCommandBasic:
# Create a minimal config file
config_file = tmp_path / "test.json"
config_data = {
"name": "test",
"base_url": "https://example.com/"
}
config_data = {"name": "test", "base_url": "https://example.com/"}
config_file.write_text(json.dumps(config_data))
result = subprocess.run(
['skill-seekers', 'create', str(config_file), '--help'],
["skill-seekers", "create", str(config_file), "--help"],
capture_output=True,
text=True,
timeout=10
timeout=10,
)
# Verify help works
assert result.returncode in [0, 2]
@@ -105,20 +103,19 @@ class TestCreateCommandBasic:
def test_create_supports_universal_flags(self):
"""Test that universal flags are accepted."""
import subprocess
result = subprocess.run(
['skill-seekers', 'create', '--help'],
capture_output=True,
text=True,
timeout=10
["skill-seekers", "create", "--help"], capture_output=True, text=True, timeout=10
)
assert result.returncode == 0
# Check that universal flags are present
assert '--name' in result.stdout
assert '--enhance' in result.stdout
assert '--chunk-for-rag' in result.stdout
assert '--preset' in result.stdout
assert '--dry-run' in result.stdout
assert "--name" in result.stdout
assert "--enhance" in result.stdout
assert "--chunk-for-rag" in result.stdout
assert "--preset" in result.stdout
assert "--dry-run" in result.stdout
class TestBackwardCompatibility:
"""Test that old commands still work."""
@@ -126,53 +123,45 @@ class TestBackwardCompatibility:
def test_scrape_command_still_works(self):
"""Old scrape command should still function."""
import subprocess
result = subprocess.run(
['skill-seekers', 'scrape', '--help'],
capture_output=True,
text=True,
timeout=10
["skill-seekers", "scrape", "--help"], capture_output=True, text=True, timeout=10
)
assert result.returncode == 0
assert 'scrape' in result.stdout.lower()
assert "scrape" in result.stdout.lower()
def test_github_command_still_works(self):
"""Old github command should still function."""
import subprocess
result = subprocess.run(
['skill-seekers', 'github', '--help'],
capture_output=True,
text=True,
timeout=10
["skill-seekers", "github", "--help"], capture_output=True, text=True, timeout=10
)
assert result.returncode == 0
assert 'github' in result.stdout.lower()
assert "github" in result.stdout.lower()
def test_analyze_command_still_works(self):
"""Old analyze command should still function."""
import subprocess
result = subprocess.run(
['skill-seekers', 'analyze', '--help'],
capture_output=True,
text=True,
timeout=10
["skill-seekers", "analyze", "--help"], capture_output=True, text=True, timeout=10
)
assert result.returncode == 0
assert 'analyze' in result.stdout.lower()
assert "analyze" in result.stdout.lower()
def test_main_help_shows_all_commands(self):
"""Main help should show both old and new commands."""
import subprocess
result = subprocess.run(
['skill-seekers', '--help'],
capture_output=True,
text=True,
timeout=10
["skill-seekers", "--help"], capture_output=True, text=True, timeout=10
)
assert result.returncode == 0
# Should show create command
assert 'create' in result.stdout
assert "create" in result.stdout
# Should still show old commands
assert 'scrape' in result.stdout
assert 'github' in result.stdout
assert 'analyze' in result.stdout
assert "scrape" in result.stdout
assert "github" in result.stdout
assert "analyze" in result.stdout

View File

@@ -167,7 +167,8 @@ class TestIssue219Problem2CLIFlags(unittest.TestCase):
"test/test",
"--name",
"test",
"--enhance-level", "2",
"--enhance-level",
"2",
]
with (

View File

@@ -7,6 +7,7 @@ the same arguments as the standalone scraper modules. This prevents the
import argparse
class TestScrapeParserSync:
"""Ensure scrape_parser has all arguments from doc_scraper."""
@@ -17,12 +18,12 @@ class TestScrapeParserSync:
# Get source arguments from doc_scraper
source_parser = setup_argument_parser()
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
source_count = len([a for a in source_parser._actions if a.dest != "help"])
# Get target arguments from unified CLI parser
target_parser = argparse.ArgumentParser()
ScrapeParser().add_arguments(target_parser)
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
target_count = len([a for a in target_parser._actions if a.dest != "help"])
assert source_count == target_count, (
f"Argument count mismatch: doc_scraper has {source_count}, "
@@ -36,12 +37,12 @@ class TestScrapeParserSync:
# Get source arguments from doc_scraper
source_parser = setup_argument_parser()
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
# Get target arguments from unified CLI parser
target_parser = argparse.ArgumentParser()
ScrapeParser().add_arguments(target_parser)
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
# Check for missing arguments
missing = source_dests - target_dests
@@ -64,27 +65,28 @@ class TestScrapeParserSync:
break
assert subparsers_action is not None, "No subparsers found"
assert 'scrape' in subparsers_action.choices, "scrape subparser not found"
assert "scrape" in subparsers_action.choices, "scrape subparser not found"
scrape_parser = subparsers_action.choices['scrape']
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != 'help'}
scrape_parser = subparsers_action.choices["scrape"]
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != "help"}
# Check key arguments that were missing in Issue #285
required_args = [
'interactive',
'url',
'verbose',
'quiet',
'resume',
'fresh',
'rate_limit',
'no_rate_limit',
'chunk_for_rag',
"interactive",
"url",
"verbose",
"quiet",
"resume",
"fresh",
"rate_limit",
"no_rate_limit",
"chunk_for_rag",
]
for arg in required_args:
assert arg in arg_dests, f"Required argument '{arg}' missing from scrape parser"
class TestGitHubParserSync:
"""Ensure github_parser has all arguments from github_scraper."""
@@ -95,12 +97,12 @@ class TestGitHubParserSync:
# Get source arguments from github_scraper
source_parser = setup_argument_parser()
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
source_count = len([a for a in source_parser._actions if a.dest != "help"])
# Get target arguments from unified CLI parser
target_parser = argparse.ArgumentParser()
GitHubParser().add_arguments(target_parser)
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
target_count = len([a for a in target_parser._actions if a.dest != "help"])
assert source_count == target_count, (
f"Argument count mismatch: github_scraper has {source_count}, "
@@ -114,12 +116,12 @@ class TestGitHubParserSync:
# Get source arguments from github_scraper
source_parser = setup_argument_parser()
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
# Get target arguments from unified CLI parser
target_parser = argparse.ArgumentParser()
GitHubParser().add_arguments(target_parser)
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
# Check for missing arguments
missing = source_dests - target_dests
@@ -128,6 +130,7 @@ class TestGitHubParserSync:
assert not missing, f"github_parser missing arguments: {missing}"
assert not extra, f"github_parser has extra arguments not in github_scraper: {extra}"
class TestUnifiedCLI:
"""Test the unified CLI main parser."""
@@ -154,7 +157,7 @@ class TestUnifiedCLI:
assert subparsers_action is not None, "No subparsers found"
# Check expected subcommands
expected_commands = ['scrape', 'github']
expected_commands = ["scrape", "github"]
for cmd in expected_commands:
assert cmd in subparsers_action.choices, f"Subcommand '{cmd}' not found"
@@ -166,7 +169,7 @@ class TestUnifiedCLI:
# This should not raise an exception
try:
parser.parse_args(['scrape', '--help'])
parser.parse_args(["scrape", "--help"])
except SystemExit as e:
# --help causes SystemExit(0) which is expected
assert e.code == 0
@@ -179,7 +182,7 @@ class TestUnifiedCLI:
# This should not raise an exception
try:
parser.parse_args(['github', '--help'])
parser.parse_args(["github", "--help"])
except SystemExit as e:
# --help causes SystemExit(0) which is expected
assert e.code == 0

View File

@@ -13,47 +13,49 @@ import pytest
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
class TestWebDetection:
"""Test web URL detection."""
def test_detect_full_https_url(self):
"""Full HTTPS URL should be detected as web."""
info = SourceDetector.detect("https://docs.react.dev/")
assert info.type == 'web'
assert info.parsed['url'] == "https://docs.react.dev/"
assert info.suggested_name == 'react'
assert info.type == "web"
assert info.parsed["url"] == "https://docs.react.dev/"
assert info.suggested_name == "react"
def test_detect_full_http_url(self):
"""Full HTTP URL should be detected as web."""
info = SourceDetector.detect("http://example.com/docs")
assert info.type == 'web'
assert info.parsed['url'] == "http://example.com/docs"
assert info.type == "web"
assert info.parsed["url"] == "http://example.com/docs"
def test_detect_domain_only(self):
"""Domain without protocol should add https:// and detect as web."""
info = SourceDetector.detect("docs.react.dev")
assert info.type == 'web'
assert info.parsed['url'] == "https://docs.react.dev"
assert info.suggested_name == 'react'
assert info.type == "web"
assert info.parsed["url"] == "https://docs.react.dev"
assert info.suggested_name == "react"
def test_detect_complex_url(self):
"""Complex URL with path should be detected as web."""
info = SourceDetector.detect("https://docs.python.org/3/library/")
assert info.type == 'web'
assert info.parsed['url'] == "https://docs.python.org/3/library/"
assert info.suggested_name == 'python'
assert info.type == "web"
assert info.parsed["url"] == "https://docs.python.org/3/library/"
assert info.suggested_name == "python"
def test_suggested_name_removes_www(self):
"""Should remove www. prefix from suggested name."""
info = SourceDetector.detect("https://www.example.com/")
assert info.type == 'web'
assert info.suggested_name == 'example'
assert info.type == "web"
assert info.suggested_name == "example"
def test_suggested_name_removes_docs(self):
"""Should remove docs. prefix from suggested name."""
info = SourceDetector.detect("https://docs.vue.org/")
assert info.type == 'web'
assert info.suggested_name == 'vue'
assert info.type == "web"
assert info.suggested_name == "vue"
class TestGitHubDetection:
"""Test GitHub repository detection."""
@@ -61,37 +63,38 @@ class TestGitHubDetection:
def test_detect_owner_repo_format(self):
"""owner/repo format should be detected as GitHub."""
info = SourceDetector.detect("facebook/react")
assert info.type == 'github'
assert info.parsed['repo'] == "facebook/react"
assert info.suggested_name == 'react'
assert info.type == "github"
assert info.parsed["repo"] == "facebook/react"
assert info.suggested_name == "react"
def test_detect_github_https_url(self):
"""Full GitHub HTTPS URL should be detected."""
info = SourceDetector.detect("https://github.com/facebook/react")
assert info.type == 'github'
assert info.parsed['repo'] == "facebook/react"
assert info.suggested_name == 'react'
assert info.type == "github"
assert info.parsed["repo"] == "facebook/react"
assert info.suggested_name == "react"
def test_detect_github_url_with_git_suffix(self):
"""GitHub URL with .git should strip suffix."""
info = SourceDetector.detect("https://github.com/facebook/react.git")
assert info.type == 'github'
assert info.parsed['repo'] == "facebook/react"
assert info.suggested_name == 'react'
assert info.type == "github"
assert info.parsed["repo"] == "facebook/react"
assert info.suggested_name == "react"
def test_detect_github_url_without_protocol(self):
"""GitHub URL without protocol should be detected."""
info = SourceDetector.detect("github.com/vuejs/vue")
assert info.type == 'github'
assert info.parsed['repo'] == "vuejs/vue"
assert info.suggested_name == 'vue'
assert info.type == "github"
assert info.parsed["repo"] == "vuejs/vue"
assert info.suggested_name == "vue"
def test_owner_repo_with_dots_and_dashes(self):
"""Repo names with dots and dashes should work."""
info = SourceDetector.detect("microsoft/vscode-python")
assert info.type == 'github'
assert info.parsed['repo'] == "microsoft/vscode-python"
assert info.suggested_name == 'vscode-python'
assert info.type == "github"
assert info.parsed["repo"] == "microsoft/vscode-python"
assert info.suggested_name == "vscode-python"
class TestLocalDetection:
"""Test local directory detection."""
@@ -107,9 +110,9 @@ class TestLocalDetection:
try:
os.chdir(tmp_path)
info = SourceDetector.detect("./my_project")
assert info.type == 'local'
assert 'my_project' in info.parsed['directory']
assert info.suggested_name == 'my_project'
assert info.type == "local"
assert "my_project" in info.parsed["directory"]
assert info.suggested_name == "my_project"
finally:
os.chdir(original_cwd)
@@ -120,16 +123,17 @@ class TestLocalDetection:
test_dir.mkdir()
info = SourceDetector.detect(str(test_dir))
assert info.type == 'local'
assert info.parsed['directory'] == str(test_dir.resolve())
assert info.suggested_name == 'test_repo'
assert info.type == "local"
assert info.parsed["directory"] == str(test_dir.resolve())
assert info.suggested_name == "test_repo"
def test_detect_current_directory(self):
"""Current directory (.) should be detected."""
cwd = os.getcwd()
info = SourceDetector.detect(".")
assert info.type == 'local'
assert info.parsed['directory'] == cwd
assert info.type == "local"
assert info.parsed["directory"] == cwd
class TestPDFDetection:
"""Test PDF file detection."""
@@ -137,22 +141,23 @@ class TestPDFDetection:
def test_detect_pdf_extension(self):
"""File with .pdf extension should be detected."""
info = SourceDetector.detect("tutorial.pdf")
assert info.type == 'pdf'
assert info.parsed['file_path'] == "tutorial.pdf"
assert info.suggested_name == 'tutorial'
assert info.type == "pdf"
assert info.parsed["file_path"] == "tutorial.pdf"
assert info.suggested_name == "tutorial"
def test_detect_pdf_with_path(self):
"""PDF file with path should be detected."""
info = SourceDetector.detect("/path/to/guide.pdf")
assert info.type == 'pdf'
assert info.parsed['file_path'] == "/path/to/guide.pdf"
assert info.suggested_name == 'guide'
assert info.type == "pdf"
assert info.parsed["file_path"] == "/path/to/guide.pdf"
assert info.suggested_name == "guide"
def test_suggested_name_removes_pdf_extension(self):
"""Suggested name should not include .pdf extension."""
info = SourceDetector.detect("my-awesome-guide.pdf")
assert info.type == 'pdf'
assert info.suggested_name == 'my-awesome-guide'
assert info.type == "pdf"
assert info.suggested_name == "my-awesome-guide"
class TestConfigDetection:
"""Test config file detection."""
@@ -160,16 +165,17 @@ class TestConfigDetection:
def test_detect_json_extension(self):
"""File with .json extension should be detected as config."""
info = SourceDetector.detect("react.json")
assert info.type == 'config'
assert info.parsed['config_path'] == "react.json"
assert info.suggested_name == 'react'
assert info.type == "config"
assert info.parsed["config_path"] == "react.json"
assert info.suggested_name == "react"
def test_detect_config_with_path(self):
"""Config file with path should be detected."""
info = SourceDetector.detect("configs/django.json")
assert info.type == 'config'
assert info.parsed['config_path'] == "configs/django.json"
assert info.suggested_name == 'django'
assert info.type == "config"
assert info.parsed["config_path"] == "configs/django.json"
assert info.suggested_name == "django"
class TestValidation:
"""Test source validation."""
@@ -191,10 +197,10 @@ class TestValidation:
# First try to detect it (will succeed since it looks like a path)
with pytest.raises(ValueError, match="Directory does not exist"):
info = SourceInfo(
type='local',
parsed={'directory': nonexistent},
suggested_name='test',
raw_input=nonexistent
type="local",
parsed={"directory": nonexistent},
suggested_name="test",
raw_input=nonexistent,
)
SourceDetector.validate_source(info)
@@ -211,10 +217,10 @@ class TestValidation:
"""Validation should fail for nonexistent PDF."""
with pytest.raises(ValueError, match="PDF file does not exist"):
info = SourceInfo(
type='pdf',
parsed={'file_path': '/tmp/nonexistent.pdf'},
suggested_name='test',
raw_input='/tmp/nonexistent.pdf'
type="pdf",
parsed={"file_path": "/tmp/nonexistent.pdf"},
suggested_name="test",
raw_input="/tmp/nonexistent.pdf",
)
SourceDetector.validate_source(info)
@@ -231,13 +237,14 @@ class TestValidation:
"""Validation should fail for nonexistent config."""
with pytest.raises(ValueError, match="Config file does not exist"):
info = SourceInfo(
type='config',
parsed={'config_path': '/tmp/nonexistent.json'},
suggested_name='test',
raw_input='/tmp/nonexistent.json'
type="config",
parsed={"config_path": "/tmp/nonexistent.json"},
suggested_name="test",
raw_input="/tmp/nonexistent.json",
)
SourceDetector.validate_source(info)
class TestAmbiguousCases:
"""Test handling of ambiguous inputs."""
@@ -255,8 +262,8 @@ class TestAmbiguousCases:
"""GitHub URL should be detected as github, not web."""
# Even though this is a URL, it should be detected as GitHub
info = SourceDetector.detect("https://github.com/owner/repo")
assert info.type == 'github'
assert info.parsed['repo'] == "owner/repo"
assert info.type == "github"
assert info.parsed["repo"] == "owner/repo"
def test_directory_takes_precedence_over_domain(self, tmp_path):
"""Existing directory should be detected even if it looks like domain."""
@@ -266,7 +273,8 @@ class TestAmbiguousCases:
info = SourceDetector.detect(str(dir_like_domain))
# Should detect as local directory, not web
assert info.type == 'local'
assert info.type == "local"
class TestRawInputPreservation:
"""Test that raw_input is preserved correctly."""
@@ -292,6 +300,7 @@ class TestRawInputPreservation:
info = SourceDetector.detect(original)
assert info.raw_input == original
class TestEdgeCases:
"""Test edge cases and corner cases."""
@@ -300,19 +309,19 @@ class TestEdgeCases:
info1 = SourceDetector.detect("https://docs.react.dev/")
info2 = SourceDetector.detect("https://docs.react.dev")
assert info1.type == 'web'
assert info2.type == 'web'
assert info1.type == "web"
assert info2.type == "web"
def test_uppercase_in_github_repo(self):
"""GitHub repos with uppercase should be detected."""
info = SourceDetector.detect("Microsoft/TypeScript")
assert info.type == 'github'
assert info.parsed['repo'] == "Microsoft/TypeScript"
assert info.type == "github"
assert info.parsed["repo"] == "Microsoft/TypeScript"
def test_numbers_in_repo_name(self):
"""GitHub repos with numbers should be detected."""
info = SourceDetector.detect("python/cpython3.11")
assert info.type == 'github'
assert info.type == "github"
def test_nested_directory_path(self, tmp_path):
"""Nested directory paths should work."""
@@ -320,5 +329,5 @@ class TestEdgeCases:
nested.mkdir(parents=True)
info = SourceDetector.detect(str(nested))
assert info.type == 'local'
assert info.suggested_name == 'c'
assert info.type == "local"
assert info.suggested_name == "c"