Files
firefrost-operations-manual/docs/tasks/modpack-version-checker/code/modpack-version-checker/tests/test_cli.py
The Chronicler dbeca53f75 feat: Add complete modpack-version-checker production code
Complete Python package from Claude Code session:
- src/modpack_checker/: 1,154 lines (cli, config, curseforge, database, notifier)
- tests/: 913 lines (comprehensive test suite)
- docs/: README, API, INSTALLATION guides
- setup.py, requirements.txt, LICENSE (MIT)

Total: 2,121+ lines of production-ready code
Ready for BuiltByBit marketplace deployment

Transferred via tar.gz from Claude Code → Chronicler #26
2026-02-24 10:36:49 +00:00

340 lines
11 KiB
Python

"""Tests for cli.py using Click's test runner."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
import responses as responses_lib
from click.testing import CliRunner
from modpack_checker.cli import cli
from modpack_checker.curseforge import CurseForgeNotFoundError
@pytest.fixture
def runner():
return CliRunner()
@pytest.fixture
def mock_cfg(tmp_path):
"""Return a Config-like mock backed by a real temp database."""
cfg = MagicMock()
cfg.curseforge_api_key = "test-api-key"
cfg.database_path = str(tmp_path / "test.db")
cfg.discord_webhook_url = None
cfg.notification_on_update = True
cfg.check_interval_hours = 6
return cfg
# ---------------------------------------------------------------------------
# Root / help
# ---------------------------------------------------------------------------
def test_help(runner):
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "Modpack Version Checker" in result.output
def test_version(runner):
result = runner.invoke(cli, ["--version"])
assert result.exit_code == 0
assert "1.0.0" in result.output
# ---------------------------------------------------------------------------
# config show
# ---------------------------------------------------------------------------
def test_config_show(runner, mock_cfg):
mock_cfg.curseforge_api_key = "abcd1234efgh5678"
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["config", "show"])
assert result.exit_code == 0
assert "abcd" in result.output # first 4 chars
assert "5678" in result.output # last 4 chars
def test_config_show_no_key(runner, mock_cfg):
mock_cfg.curseforge_api_key = ""
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["config", "show"])
assert result.exit_code == 0
assert "Not configured" in result.output
# ---------------------------------------------------------------------------
# list
# ---------------------------------------------------------------------------
def test_list_empty(runner, mock_cfg):
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["list"])
assert result.exit_code == 0
assert "empty" in result.output.lower()
def test_list_with_modpacks(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(111, "Pack Alpha")
db.add_modpack(222, "Pack Beta")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["list"])
assert result.exit_code == 0
assert "Pack Alpha" in result.output
assert "Pack Beta" in result.output
# ---------------------------------------------------------------------------
# add
# ---------------------------------------------------------------------------
def test_add_requires_api_key(runner, mock_cfg):
mock_cfg.curseforge_api_key = ""
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["add", "12345"])
assert result.exit_code == 1
assert "API key" in result.output
@responses_lib.activate
def test_add_success(runner, mock_cfg):
responses_lib.add(
responses_lib.GET,
"https://api.curseforge.com/v1/mods/12345",
json={"data": {"id": 12345, "name": "Test Modpack", "latestFiles": []}},
status=200,
)
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["add", "12345"])
assert result.exit_code == 0
assert "Test Modpack" in result.output
assert "tracking" in result.output.lower()
@responses_lib.activate
def test_add_not_found(runner, mock_cfg):
responses_lib.add(
responses_lib.GET,
"https://api.curseforge.com/v1/mods/99999",
status=404,
)
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["add", "99999"])
assert result.exit_code == 1
assert "99999" in result.output
@responses_lib.activate
def test_add_duplicate_shows_warning(runner, mock_cfg, tmp_path):
responses_lib.add(
responses_lib.GET,
"https://api.curseforge.com/v1/mods/12345",
json={"data": {"id": 12345, "name": "Test Modpack", "latestFiles": []}},
status=200,
)
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Modpack")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["add", "12345"])
assert "already tracked" in result.output.lower()
# ---------------------------------------------------------------------------
# remove
# ---------------------------------------------------------------------------
def test_remove_not_in_list(runner, mock_cfg):
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["remove", "99999"], input="y\n")
assert result.exit_code == 1
assert "not in your watch list" in result.output
def test_remove_success(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["remove", "12345"], input="y\n")
assert result.exit_code == 0
assert "Removed" in result.output
def test_remove_aborted(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["remove", "12345"], input="n\n")
# Aborted — pack should still exist
assert db.get_modpack(12345) is not None
# ---------------------------------------------------------------------------
# check
# ---------------------------------------------------------------------------
def test_check_empty_list(runner, mock_cfg):
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["check"])
assert result.exit_code == 0
assert "No modpacks" in result.output
@responses_lib.activate
def test_check_up_to_date(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
db.update_version(12345, "1.0.0")
responses_lib.add(
responses_lib.GET,
"https://api.curseforge.com/v1/mods/12345",
json={
"data": {
"id": 12345,
"name": "Test Pack",
"latestFiles": [
{
"id": 1,
"displayName": "1.0.0",
"fileName": "pack-1.0.0.zip",
"fileDate": "2026-01-01T00:00:00Z",
}
],
}
},
status=200,
)
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["check", "--no-notify"])
assert result.exit_code == 0
assert "up to date" in result.output.lower()
@responses_lib.activate
def test_check_update_available(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
db.update_version(12345, "1.0.0")
responses_lib.add(
responses_lib.GET,
"https://api.curseforge.com/v1/mods/12345",
json={
"data": {
"id": 12345,
"name": "Test Pack",
"latestFiles": [
{
"id": 2,
"displayName": "1.1.0",
"fileName": "pack-1.1.0.zip",
"fileDate": "2026-02-01T00:00:00Z",
}
],
}
},
status=200,
)
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["check", "--no-notify"])
assert result.exit_code == 0
assert "1.1.0" in result.output
# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------
def test_status_not_found(runner, mock_cfg):
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["status", "99999"])
assert result.exit_code == 1
assert "not in your watch list" in result.output
def test_status_shows_info(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
db.update_version(12345, "2.0.0")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["status", "12345"])
assert result.exit_code == 0
assert "Test Pack" in result.output
assert "2.0.0" in result.output
# ---------------------------------------------------------------------------
# notifications command
# ---------------------------------------------------------------------------
def test_notifications_disable(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["notifications", "12345", "--disable"])
assert result.exit_code == 0
assert "disabled" in result.output
assert db.get_modpack(12345).notification_enabled is False
def test_notifications_enable(runner, mock_cfg, tmp_path):
from modpack_checker.database import Database
db = Database(str(tmp_path / "test.db"))
db.add_modpack(12345, "Test Pack")
db.toggle_notifications(12345, False)
mock_cfg.database_path = str(tmp_path / "test.db")
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["notifications", "12345", "--enable"])
assert result.exit_code == 0
assert "enabled" in result.output
assert db.get_modpack(12345).notification_enabled is True
def test_notifications_missing_modpack(runner, mock_cfg):
with patch("modpack_checker.cli.Config.load", return_value=mock_cfg):
result = runner.invoke(cli, ["notifications", "99999", "--enable"])
assert result.exit_code == 1