450 lines
15 KiB
Python
450 lines
15 KiB
Python
"""Tests for SQLite-backed cache and package state."""
|
|
|
|
import datetime
|
|
from pathlib import Path
|
|
|
|
import debian_todo
|
|
|
|
|
|
class TestLoadCache:
|
|
"""Tests for load_cache function."""
|
|
|
|
def test_returns_none_when_db_missing(self, tmp_path, monkeypatch):
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", tmp_path / "cache.sqlite3")
|
|
|
|
result = debian_todo.load_cache([])
|
|
assert result is None
|
|
|
|
def test_returns_none_on_version_mismatch(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
conn = debian_todo.get_db_connection()
|
|
with conn:
|
|
conn.execute(
|
|
"INSERT INTO metadata (key, value) VALUES ('source_cache_version', ?)",
|
|
(str(debian_todo.CACHE_VERSION - 1),),
|
|
)
|
|
conn.close()
|
|
|
|
result = debian_todo.load_cache([])
|
|
assert result is None
|
|
|
|
def test_returns_none_when_mtime_mismatch(self, tmp_path, monkeypatch):
|
|
source_file = tmp_path / "Sources"
|
|
source_file.write_text("dummy")
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
conn = debian_todo.get_db_connection()
|
|
with conn:
|
|
conn.execute(
|
|
"INSERT INTO metadata (key, value) VALUES ('source_cache_version', ?)",
|
|
(str(debian_todo.CACHE_VERSION),),
|
|
)
|
|
conn.execute(
|
|
"INSERT INTO source_file_mtimes (path, mtime) VALUES (?, ?)",
|
|
(str(source_file), 0.0),
|
|
)
|
|
conn.close()
|
|
|
|
result = debian_todo.load_cache([str(source_file)])
|
|
assert result is None
|
|
|
|
def test_returns_cache_when_valid(self, tmp_path, monkeypatch):
|
|
import os
|
|
|
|
source_file = tmp_path / "Sources"
|
|
source_file.write_text("dummy")
|
|
mtime = os.path.getmtime(str(source_file))
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
conn = debian_todo.get_db_connection()
|
|
with conn:
|
|
conn.execute(
|
|
"INSERT INTO metadata (key, value) VALUES ('source_cache_version', ?)",
|
|
(str(debian_todo.CACHE_VERSION),),
|
|
)
|
|
conn.execute(
|
|
"INSERT INTO source_file_mtimes (path, mtime) VALUES (?, ?)",
|
|
(str(source_file), mtime),
|
|
)
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO source_info_cache (source, team, uploaders, vcs_git)
|
|
VALUES (?, ?, ?, ?)
|
|
""",
|
|
("foo", "python-team", "John <j@e.com>", "https://example.invalid/foo"),
|
|
)
|
|
conn.close()
|
|
|
|
result = debian_todo.load_cache([str(source_file)])
|
|
assert result == {
|
|
"foo": {
|
|
"team": "python-team",
|
|
"uploaders": "John <j@e.com>",
|
|
"vcs_git": "https://example.invalid/foo",
|
|
}
|
|
}
|
|
|
|
|
|
class TestSaveCache:
|
|
"""Tests for save_cache function."""
|
|
|
|
def test_saves_cache_to_sqlite(self, tmp_path, monkeypatch):
|
|
source_file = tmp_path / "Sources"
|
|
source_file.write_text("dummy")
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
|
|
vcs_by_source = {
|
|
"foo": {
|
|
"team": "python-team",
|
|
"uploaders": "",
|
|
"vcs_git": "https://example.invalid/foo",
|
|
}
|
|
}
|
|
debian_todo.save_cache([str(source_file)], vcs_by_source)
|
|
|
|
conn = debian_todo.get_db_connection()
|
|
row = conn.execute(
|
|
"SELECT value FROM metadata WHERE key = 'source_cache_version'"
|
|
).fetchone()
|
|
cached = conn.execute(
|
|
"SELECT source, team, uploaders, vcs_git FROM source_info_cache"
|
|
).fetchone()
|
|
conn.close()
|
|
|
|
assert row["value"] == str(debian_todo.CACHE_VERSION)
|
|
assert dict(cached) == {
|
|
"source": "foo",
|
|
"team": "python-team",
|
|
"uploaders": "",
|
|
"vcs_git": "https://example.invalid/foo",
|
|
}
|
|
|
|
def test_handles_missing_source_file(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
|
|
debian_todo.save_cache(["/nonexistent/file"], {})
|
|
|
|
result = debian_todo.load_cache([])
|
|
assert result is None
|
|
|
|
|
|
class TestPackageState:
|
|
"""Tests for package discovery tracking."""
|
|
|
|
def test_sync_records_first_seen_upstream_version(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
seen_at = datetime.datetime(2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc)
|
|
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":type": "New upstream",
|
|
":details": "1.2.3 (currently in unstable: 1.2.2-1)",
|
|
}
|
|
],
|
|
seen_at=seen_at,
|
|
)
|
|
|
|
discovery_map = debian_todo.load_package_discovery_map()
|
|
assert discovery_map == {
|
|
"foo": ("1.2.3", "2026-05-05T12:00:00Z", True)
|
|
}
|
|
|
|
def test_sync_preserves_discovery_time_for_same_version(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3",
|
|
}
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3",
|
|
}
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 6, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
|
|
discovery_map = debian_todo.load_package_discovery_map()
|
|
assert discovery_map["foo"] == ("1.2.3", "2026-05-05T12:00:00Z", True)
|
|
|
|
def test_sync_updates_discovery_time_when_version_changes(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3",
|
|
}
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.4",
|
|
}
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 7, 8, 30, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
|
|
discovery_map = debian_todo.load_package_discovery_map()
|
|
assert discovery_map["foo"] == ("1.2.4", "2026-05-07T08:30:00Z", True)
|
|
|
|
def test_sync_without_discovery_recording_leaves_timestamp_empty(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3",
|
|
}
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
record_discovery=False,
|
|
)
|
|
|
|
discovery_map = debian_todo.load_package_discovery_map()
|
|
assert discovery_map["foo"] == (None, None, False)
|
|
|
|
def test_sorts_by_oldest_discovery_date(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "older",
|
|
":shortname": "newupstream_older",
|
|
":details": "1.0.0",
|
|
},
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 6, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
debian_todo.sync_package_state(
|
|
[
|
|
{
|
|
":source": "newer",
|
|
":shortname": "newupstream_newer",
|
|
":details": "2.0.0",
|
|
},
|
|
{
|
|
":source": "older",
|
|
":shortname": "newupstream_older",
|
|
":details": "1.0.0",
|
|
},
|
|
],
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
)
|
|
|
|
sorted_items = debian_todo.sort_todo_list_by_discovery(
|
|
[
|
|
{
|
|
":source": "newer",
|
|
":shortname": "newupstream_newer",
|
|
":details": "2.0.0",
|
|
},
|
|
{
|
|
":source": "older",
|
|
":shortname": "newupstream_older",
|
|
":details": "1.0.0",
|
|
},
|
|
]
|
|
)
|
|
|
|
assert [item[":source"] for item in sorted_items] == ["newer", "older"]
|
|
|
|
def test_save_watch_snapshot(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
repo_dir = tmp_path / "pkg"
|
|
debian_dir = repo_dir / "debian"
|
|
debian_dir.mkdir(parents=True)
|
|
(debian_dir / "watch").write_text("Version: 5\n")
|
|
|
|
debian_todo.save_watch_snapshot("mypkg", repo_dir)
|
|
|
|
conn = debian_todo.get_db_connection()
|
|
row = conn.execute(
|
|
"SELECT watch_content FROM package_state WHERE source = ?",
|
|
("mypkg",),
|
|
).fetchone()
|
|
conn.close()
|
|
assert row["watch_content"] == "Version: 5\n"
|
|
|
|
|
|
class TestListOutput:
|
|
"""Tests for list output using discovery timestamps."""
|
|
|
|
def test_list_shows_discovery_datetime(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
todo_path = tmp_path / "todo.json"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
monkeypatch.setattr(debian_todo, "TODO_PATH", todo_path)
|
|
monkeypatch.setattr(debian_todo, "load_source_info_map", lambda: {})
|
|
monkeypatch.setattr(debian_todo, "load_notes", lambda: {})
|
|
|
|
todo_list = [
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3 (currently in unstable: 1.2.2-1)",
|
|
}
|
|
]
|
|
debian_todo.save_todo_list(todo_list)
|
|
debian_todo.sync_package_state(
|
|
todo_list,
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
record_discovery=True,
|
|
)
|
|
|
|
class FakeConsole:
|
|
def __init__(self) -> None:
|
|
self.width = 80
|
|
self.output: list[str] = []
|
|
|
|
def print(self, message) -> None:
|
|
self.output.append(str(message))
|
|
|
|
fake_console = FakeConsole()
|
|
monkeypatch.setattr(debian_todo, "Console", lambda: fake_console)
|
|
|
|
debian_todo.list_todos(include_prerelease=False)
|
|
|
|
assert any("D: 2026-05-05 12:00 UTC" in line for line in fake_console.output)
|
|
|
|
def test_list_hides_pre_cutoff_discovery_datetime(self, tmp_path, monkeypatch):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
todo_path = tmp_path / "todo.json"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
monkeypatch.setattr(debian_todo, "TODO_PATH", todo_path)
|
|
monkeypatch.setattr(debian_todo, "load_source_info_map", lambda: {})
|
|
monkeypatch.setattr(debian_todo, "load_notes", lambda: {})
|
|
|
|
todo_list = [
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3 (currently in unstable: 1.2.2-1)",
|
|
}
|
|
]
|
|
debian_todo.save_todo_list(todo_list)
|
|
debian_todo.sync_package_state(
|
|
todo_list,
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 0, tzinfo=datetime.timezone.utc
|
|
),
|
|
record_discovery=False,
|
|
)
|
|
|
|
class FakeConsole:
|
|
def __init__(self) -> None:
|
|
self.width = 80
|
|
self.output: list[str] = []
|
|
|
|
def print(self, message) -> None:
|
|
self.output.append(str(message))
|
|
|
|
fake_console = FakeConsole()
|
|
monkeypatch.setattr(debian_todo, "Console", lambda: fake_console)
|
|
|
|
debian_todo.list_todos(include_prerelease=False)
|
|
|
|
assert any("D: -" in line for line in fake_console.output)
|
|
|
|
def test_list_shows_post_cutoff_discovery_datetime_even_without_flag(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
db_path = tmp_path / "cache.sqlite3"
|
|
todo_path = tmp_path / "todo.json"
|
|
monkeypatch.setattr(debian_todo, "CACHE_PATH", db_path)
|
|
monkeypatch.setattr(debian_todo, "TODO_PATH", todo_path)
|
|
monkeypatch.setattr(debian_todo, "load_source_info_map", lambda: {})
|
|
monkeypatch.setattr(debian_todo, "load_notes", lambda: {})
|
|
|
|
todo_list = [
|
|
{
|
|
":source": "foo",
|
|
":shortname": "newupstream_foo",
|
|
":details": "1.2.3 (currently in unstable: 1.2.2-1)",
|
|
}
|
|
]
|
|
debian_todo.save_todo_list(todo_list)
|
|
debian_todo.sync_package_state(
|
|
todo_list,
|
|
seen_at=datetime.datetime(
|
|
2026, 5, 5, 12, 1, tzinfo=datetime.timezone.utc
|
|
),
|
|
record_discovery=False,
|
|
)
|
|
conn = debian_todo.get_db_connection()
|
|
with conn:
|
|
conn.execute(
|
|
"""
|
|
UPDATE package_state
|
|
SET discovered_version = ?, discovered_at = ?, discovered_by_refresh = 0
|
|
WHERE source = ?
|
|
""",
|
|
("1.2.3", "2026-05-05T12:01:00Z", "foo"),
|
|
)
|
|
conn.close()
|
|
|
|
class FakeConsole:
|
|
def __init__(self) -> None:
|
|
self.width = 80
|
|
self.output: list[str] = []
|
|
|
|
def print(self, message) -> None:
|
|
self.output.append(str(message))
|
|
|
|
fake_console = FakeConsole()
|
|
monkeypatch.setattr(debian_todo, "Console", lambda: fake_console)
|
|
|
|
debian_todo.list_todos(include_prerelease=False)
|
|
|
|
assert any("D: 2026-05-05 12:01 UTC" in line for line in fake_console.output)
|