5cf6382e42
Implement the main CLI entry point for the Polymarket Insider Tracker. Features: - argparse-based CLI with version, config-check, dry-run, log-level flags - Structured logging configuration with multiple formatters - Configuration validation with friendly error messages - Startup banner display - Config check mode for validation without running - Integration with Pipeline orchestrator Tests: 20 new tests covering parser, logging, banner, config validation, and integration scenarios. Closes #55 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
"""Tests for the CLI entry point."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from polymarket_insider_tracker.__main__ import (
|
|
EXIT_CONFIG_ERROR,
|
|
EXIT_SUCCESS,
|
|
configure_logging,
|
|
create_parser,
|
|
main,
|
|
print_banner,
|
|
run_config_check,
|
|
validate_config,
|
|
)
|
|
|
|
|
|
class TestCreateParser:
|
|
"""Tests for argument parser creation."""
|
|
|
|
def test_parser_has_version(self):
|
|
"""Parser should have version flag."""
|
|
parser = create_parser()
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
parser.parse_args(["--version"])
|
|
assert exc_info.value.code == 0
|
|
|
|
def test_parser_config_check(self):
|
|
"""Parser should accept --config-check flag."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["--config-check"])
|
|
assert args.config_check is True
|
|
|
|
def test_parser_log_level(self):
|
|
"""Parser should accept --log-level option."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["--log-level", "DEBUG"])
|
|
assert args.log_level == "DEBUG"
|
|
|
|
def test_parser_dry_run(self):
|
|
"""Parser should accept --dry-run flag."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["--dry-run"])
|
|
assert args.dry_run is True
|
|
|
|
def test_parser_health_port(self):
|
|
"""Parser should accept --health-port option."""
|
|
parser = create_parser()
|
|
args = parser.parse_args(["--health-port", "9090"])
|
|
assert args.health_port == 9090
|
|
|
|
def test_parser_default_values(self):
|
|
"""Parser should have correct defaults."""
|
|
parser = create_parser()
|
|
args = parser.parse_args([])
|
|
assert args.config_check is False
|
|
assert args.log_level is None
|
|
assert args.dry_run is False
|
|
assert args.health_port is None
|
|
|
|
|
|
class TestConfigureLogging:
|
|
"""Tests for logging configuration."""
|
|
|
|
def test_configure_logging_info(self):
|
|
"""Should configure logging at INFO level."""
|
|
configure_logging("INFO")
|
|
import logging
|
|
|
|
assert logging.getLogger().level == logging.INFO
|
|
|
|
def test_configure_logging_debug(self):
|
|
"""Should configure logging at DEBUG level."""
|
|
configure_logging("DEBUG")
|
|
import logging
|
|
|
|
assert logging.getLogger().level == logging.DEBUG
|
|
|
|
|
|
class TestPrintBanner:
|
|
"""Tests for banner printing."""
|
|
|
|
def test_banner_contains_app_name(self, capsys):
|
|
"""Banner should contain application name."""
|
|
print_banner()
|
|
captured = capsys.readouterr()
|
|
assert "Polymarket Insider Tracker" in captured.out
|
|
|
|
def test_banner_contains_version(self, capsys):
|
|
"""Banner should contain version."""
|
|
print_banner()
|
|
captured = capsys.readouterr()
|
|
assert "v0.1.0" in captured.out
|
|
|
|
|
|
class TestValidateConfig:
|
|
"""Tests for configuration validation."""
|
|
|
|
def test_validate_config_success(self, monkeypatch):
|
|
"""Should return settings on valid config."""
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
|
|
|
|
settings = validate_config()
|
|
assert settings is not None
|
|
|
|
def test_validate_config_failure(self, monkeypatch, capsys):
|
|
"""Should return None on invalid config."""
|
|
# Clear any existing DATABASE_URL
|
|
monkeypatch.delenv("DATABASE_URL", raising=False)
|
|
|
|
settings = validate_config()
|
|
assert settings is None
|
|
|
|
captured = capsys.readouterr()
|
|
assert "Configuration validation failed" in captured.err
|
|
|
|
|
|
class TestRunConfigCheck:
|
|
"""Tests for config check mode."""
|
|
|
|
def test_config_check_prints_summary(self, monkeypatch, capsys):
|
|
"""Config check should print configuration summary."""
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
|
|
|
|
settings = validate_config()
|
|
assert settings is not None
|
|
|
|
result = run_config_check(settings)
|
|
assert result == EXIT_SUCCESS
|
|
|
|
captured = capsys.readouterr()
|
|
assert "Configuration is valid!" in captured.out
|
|
assert "Configuration:" in captured.out
|
|
|
|
|
|
class TestMain:
|
|
"""Tests for main entry point."""
|
|
|
|
def test_main_with_config_check(self, monkeypatch):
|
|
"""Main should exit successfully with --config-check."""
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main(["--config-check"])
|
|
|
|
assert exc_info.value.code == EXIT_SUCCESS
|
|
|
|
def test_main_with_invalid_config(self, monkeypatch):
|
|
"""Main should exit with config error on invalid config."""
|
|
monkeypatch.delenv("DATABASE_URL", raising=False)
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main([])
|
|
|
|
assert exc_info.value.code == EXIT_CONFIG_ERROR
|
|
|
|
def test_main_with_dry_run_and_config_check(self, monkeypatch):
|
|
"""Main should handle dry-run with config-check."""
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main(["--config-check", "--dry-run"])
|
|
|
|
assert exc_info.value.code == EXIT_SUCCESS
|
|
|
|
@patch("polymarket_insider_tracker.__main__.run_pipeline")
|
|
@patch("polymarket_insider_tracker.__main__.asyncio.run")
|
|
def test_main_runs_pipeline(self, mock_asyncio_run, _mock_run_pipeline, monkeypatch):
|
|
"""Main should run pipeline when not in config-check mode."""
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
|
|
mock_asyncio_run.return_value = EXIT_SUCCESS
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main([])
|
|
|
|
assert exc_info.value.code == EXIT_SUCCESS
|
|
mock_asyncio_run.assert_called_once()
|
|
|
|
|
|
class TestIntegration:
|
|
"""Integration tests for CLI invocation."""
|
|
|
|
def test_cli_help_option(self, capsys):
|
|
"""CLI should display help with -h option."""
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main(["-h"])
|
|
|
|
assert exc_info.value.code == 0
|
|
captured = capsys.readouterr()
|
|
assert "polymarket-insider-tracker" in captured.out
|
|
assert "--config-check" in captured.out
|
|
assert "--dry-run" in captured.out
|
|
assert "--log-level" in captured.out
|
|
|
|
def test_cli_version_option(self, capsys):
|
|
"""CLI should display version with --version option."""
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main(["--version"])
|
|
|
|
assert exc_info.value.code == 0
|
|
captured = capsys.readouterr()
|
|
assert "0.1.0" in captured.out
|
|
|
|
def test_cli_invalid_log_level(self, capsys):
|
|
"""CLI should reject invalid log level."""
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main(["--log-level", "INVALID"])
|
|
|
|
assert exc_info.value.code != 0
|
|
captured = capsys.readouterr()
|
|
assert "invalid choice" in captured.err
|