Add image_url column to repos table with migration, CLI image add/remove commands, and Media RSS <media:content> elements in generated feed output. New CLI commands: ghrel image add <repo> <url>, ghrel image remove <repo>
225 lines
7.4 KiB
Python
225 lines
7.4 KiB
Python
"""Tests for CLI commands."""
|
|
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
from src.db import _create_tables, add_repo
|
|
|
|
|
|
class TestRepoFormatValidation:
|
|
def test_valid_format(self):
|
|
from src.cli import REPO_PATTERN
|
|
assert REPO_PATTERN.match("owner/repo")
|
|
assert REPO_PATTERN.match("my-org/my_repo")
|
|
assert REPO_PATTERN.match("a.b/c-d_e")
|
|
|
|
def test_invalid_format(self):
|
|
from src.cli import REPO_PATTERN
|
|
assert not REPO_PATTERN.match("owner")
|
|
assert not REPO_PATTERN.match("repo")
|
|
assert not REPO_PATTERN.match("owner/repo/extra")
|
|
|
|
|
|
class TestParseSince:
|
|
def test_hours(self):
|
|
from src.cli import parse_since
|
|
from datetime import timedelta
|
|
assert parse_since("24h") == timedelta(hours=24)
|
|
|
|
def test_days(self):
|
|
from src.cli import parse_since
|
|
from datetime import timedelta
|
|
assert parse_since("2d") == timedelta(days=2)
|
|
|
|
def test_minutes(self):
|
|
from src.cli import parse_since
|
|
from datetime import timedelta
|
|
assert parse_since("30m") == timedelta(minutes=30)
|
|
|
|
def test_invalid_defaults(self):
|
|
from src.cli import parse_since
|
|
from datetime import timedelta
|
|
assert parse_since("invalid") == timedelta(hours=24)
|
|
|
|
|
|
class TestCliHelp:
|
|
def test_help_flag(self):
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--help"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "ghrel" in result.stdout.lower() or "track" in result.stdout.lower()
|
|
|
|
def test_version_flag(self):
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--version"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "0.1.0" in result.stdout
|
|
|
|
|
|
class TestCliList:
|
|
def test_list_empty(self, tmp_path):
|
|
db_path = str(tmp_path / "test.db")
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path, "list"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "No repositories" in result.stdout
|
|
|
|
|
|
class TestParseInterval:
|
|
def test_seconds(self):
|
|
from src.cli import parse_interval
|
|
assert parse_interval("60s") == 60
|
|
|
|
def test_minutes(self):
|
|
from src.cli import parse_interval
|
|
assert parse_interval("30m") == 1800
|
|
|
|
def test_hours(self):
|
|
from src.cli import parse_interval
|
|
assert parse_interval("2h") == 7200
|
|
|
|
def test_days(self):
|
|
from src.cli import parse_interval
|
|
assert parse_interval("1d") == 86400
|
|
|
|
def test_invalid_format(self):
|
|
from src.cli import parse_interval
|
|
with pytest.raises(SystemExit):
|
|
parse_interval("invalid")
|
|
|
|
|
|
class TestCliDaemon:
|
|
def test_daemon_help(self):
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "daemon", "--help"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "interval" in result.stdout.lower()
|
|
assert "pid-file" in result.stdout.lower()
|
|
|
|
def test_daemon_pid_file_write(self, tmp_path):
|
|
"""Test that daemon PID file writing works correctly."""
|
|
import os
|
|
from pathlib import Path
|
|
|
|
pid_file = Path(tmp_path / "daemon.pid")
|
|
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Test PID file writing logic directly
|
|
pid_file.write_text(str(os.getpid()))
|
|
assert pid_file.exists()
|
|
assert pid_file.read_text() == str(os.getpid())
|
|
pid_file.unlink()
|
|
assert not pid_file.exists()
|
|
|
|
def test_cmd_check_for_daemon_empty(self, tmp_path):
|
|
"""Test daemon check with no repos (no error raised)."""
|
|
db_path = str(tmp_path / "test.db")
|
|
from src.cli import cmd_check_for_daemon
|
|
cmd_check_for_daemon(db_path) # Should not raise
|
|
|
|
|
|
class TestCliImage:
|
|
def _setup_db(self, tmp_path):
|
|
db_path = str(tmp_path / "test.db")
|
|
conn = sqlite3.connect(db_path)
|
|
_create_tables(conn)
|
|
add_repo(conn, "owner/repo")
|
|
conn.close()
|
|
return db_path
|
|
|
|
def test_image_add(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "add", "owner/repo", "https://example.com/logo.png"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "Set image" in result.stdout
|
|
|
|
def test_image_add_invalid_url(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "add", "owner/repo", "not-a-url"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode != 0
|
|
assert "http:// or https://" in result.stderr
|
|
|
|
def test_image_add_invalid_repo(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "add", "invalid", "https://example.com/logo.png"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode != 0
|
|
assert "Invalid repo format" in result.stderr
|
|
|
|
def test_image_add_nonexistent_repo(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "add", "other/repo", "https://example.com/logo.png"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode != 0
|
|
assert "not found" in result.stderr
|
|
|
|
def test_image_remove(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "add", "owner/repo", "https://example.com/logo.png"],
|
|
capture_output=True, text=True
|
|
)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "remove", "owner/repo"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "Removed image" in result.stdout
|
|
|
|
def test_image_remove_no_image(self, tmp_path):
|
|
db_path = self._setup_db(tmp_path)
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path,
|
|
"image", "remove", "owner/repo"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode != 0
|
|
assert "No image set" in result.stderr
|
|
|
|
def test_image_help(self):
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "image", "--help"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode == 0
|
|
assert "cover image" in result.stdout.lower()
|
|
|
|
|
|
class TestCliAddRemove:
|
|
def test_add_invalid_format(self, tmp_path):
|
|
db_path = str(tmp_path / "test.db")
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "src.cli", "--db-path", db_path, "add", "invalid"],
|
|
capture_output=True, text=True
|
|
)
|
|
assert result.returncode != 0
|
|
assert "Invalid repo format" in result.stderr
|