Add tests.
This commit is contained in:
parent
e80f511155
commit
a7d4bc4ae9
10 changed files with 915 additions and 418 deletions
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
120
tests/test_cache.py
Normal file
120
tests/test_cache.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"""Tests for cache loading and saving."""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
import debian_todo
|
||||
|
||||
|
||||
class TestLoadCache:
|
||||
"""Tests for load_cache function."""
|
||||
|
||||
def test_returns_none_when_file_missing(self, tmp_path, monkeypatch):
|
||||
cache_file = tmp_path / "cache.json"
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
result = debian_todo.load_cache([])
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_on_invalid_json(self, tmp_path, monkeypatch):
|
||||
cache_file = tmp_path / "cache.json"
|
||||
cache_file.write_text("not valid json")
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
result = debian_todo.load_cache([])
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_on_version_mismatch(self, tmp_path, monkeypatch):
|
||||
cache_file = tmp_path / "cache.json"
|
||||
cache_file.write_text(json.dumps({
|
||||
"cache_version": debian_todo.CACHE_VERSION - 1,
|
||||
"sources_mtimes": {},
|
||||
"vcs_by_source": {},
|
||||
}))
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
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")
|
||||
cache_file = tmp_path / "cache.json"
|
||||
cache_file.write_text(json.dumps({
|
||||
"cache_version": debian_todo.CACHE_VERSION,
|
||||
"sources_mtimes": {str(source_file): 0.0},
|
||||
"vcs_by_source": {},
|
||||
}))
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
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))
|
||||
|
||||
cache_file = tmp_path / "cache.json"
|
||||
cache_file.write_text(json.dumps({
|
||||
"cache_version": debian_todo.CACHE_VERSION,
|
||||
"sources_mtimes": {str(source_file): mtime},
|
||||
"vcs_by_source": {
|
||||
"foo": {"vcs_git": "python-team", "uploaders": "John <j@e.com>"}
|
||||
},
|
||||
}))
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
result = debian_todo.load_cache([str(source_file)])
|
||||
assert result == {"foo": {"vcs_git": "python-team", "uploaders": "John <j@e.com>"}}
|
||||
|
||||
def test_normalizes_legacy_string_format(self, tmp_path, monkeypatch):
|
||||
import os
|
||||
|
||||
source_file = tmp_path / "Sources"
|
||||
source_file.write_text("dummy")
|
||||
mtime = os.path.getmtime(str(source_file))
|
||||
|
||||
cache_file = tmp_path / "cache.json"
|
||||
cache_file.write_text(json.dumps({
|
||||
"cache_version": debian_todo.CACHE_VERSION,
|
||||
"sources_mtimes": {str(source_file): mtime},
|
||||
"vcs_by_source": {"foo": "python-team"}, # legacy string format
|
||||
}))
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
result = debian_todo.load_cache([str(source_file)])
|
||||
assert result == {"foo": {"vcs_git": "python-team", "uploaders": ""}}
|
||||
|
||||
|
||||
class TestSaveCache:
|
||||
"""Tests for save_cache function."""
|
||||
|
||||
def test_saves_cache_file(self, tmp_path, monkeypatch):
|
||||
import os
|
||||
|
||||
source_file = tmp_path / "Sources"
|
||||
source_file.write_text("dummy")
|
||||
cache_file = tmp_path / "cache.json"
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
vcs_by_source = {"foo": {"vcs_git": "python-team", "uploaders": ""}}
|
||||
debian_todo.save_cache([str(source_file)], vcs_by_source)
|
||||
|
||||
assert cache_file.exists()
|
||||
data = json.loads(cache_file.read_text())
|
||||
assert data["cache_version"] == debian_todo.CACHE_VERSION
|
||||
assert data["vcs_by_source"] == vcs_by_source
|
||||
assert str(source_file) in data["sources_mtimes"]
|
||||
|
||||
def test_handles_missing_source_file(self, tmp_path, monkeypatch):
|
||||
cache_file = tmp_path / "cache.json"
|
||||
monkeypatch.setattr(debian_todo, "CACHE_PATH", cache_file)
|
||||
|
||||
debian_todo.save_cache(["/nonexistent/file"], {})
|
||||
|
||||
# Should not create cache file if source file is missing
|
||||
assert not cache_file.exists()
|
||||
117
tests/test_filter.py
Normal file
117
tests/test_filter.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""Tests for TODO list filtering functions."""
|
||||
|
||||
import pytest
|
||||
|
||||
from debian_todo import filter_todo_list, summarize_sources
|
||||
|
||||
|
||||
class TestFilterTodoList:
|
||||
"""Tests for filter_todo_list function."""
|
||||
|
||||
def test_filters_non_upstream_items(self):
|
||||
todo_list = [
|
||||
{":shortname": "newupstream_foo", ":details": "1.0.0", ":source": "foo"},
|
||||
{":shortname": "rc_bug", ":details": "bug info", ":source": "bar"},
|
||||
{":shortname": "other_type", ":details": "other", ":source": "baz"},
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 1
|
||||
assert result[0][":source"] == "foo"
|
||||
|
||||
def test_filters_prerelease_by_default(self):
|
||||
todo_list = [
|
||||
{":shortname": "newupstream_foo", ":details": "1.0.0", ":source": "foo"},
|
||||
{":shortname": "newupstream_bar", ":details": "2.0.0rc1", ":source": "bar"},
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 1
|
||||
assert result[0][":source"] == "foo"
|
||||
|
||||
def test_includes_prerelease_when_requested(self):
|
||||
todo_list = [
|
||||
{":shortname": "newupstream_foo", ":details": "1.0.0", ":source": "foo"},
|
||||
{":shortname": "newupstream_bar", ":details": "2.0.0rc1", ":source": "bar"},
|
||||
]
|
||||
result = filter_todo_list(todo_list, include_prerelease=True)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_filters_matching_versions(self):
|
||||
# When normalized versions match, the item should be filtered out
|
||||
todo_list = [
|
||||
{
|
||||
":shortname": "newupstream_foo",
|
||||
":details": "1.0.0 (currently in unstable: 1.0.0-1)",
|
||||
":source": "foo",
|
||||
},
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 0
|
||||
|
||||
def test_keeps_different_versions(self):
|
||||
todo_list = [
|
||||
{
|
||||
":shortname": "newupstream_foo",
|
||||
":details": "1.1.0 (currently in unstable: 1.0.0-1)",
|
||||
":source": "foo",
|
||||
},
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 1
|
||||
|
||||
def test_handles_epoch_in_current(self):
|
||||
# Epoch should be stripped for comparison
|
||||
todo_list = [
|
||||
{
|
||||
":shortname": "newupstream_foo",
|
||||
":details": "1.0.0 (currently in unstable: 1:1.0.0-1)",
|
||||
":source": "foo",
|
||||
},
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 0
|
||||
|
||||
def test_skips_invalid_items(self):
|
||||
todo_list = [
|
||||
{":shortname": "newupstream_foo", ":source": "foo"}, # missing :details
|
||||
{":details": "1.0.0", ":source": "bar"}, # missing :shortname
|
||||
{":shortname": 123, ":details": "1.0.0", ":source": "baz"}, # wrong type
|
||||
]
|
||||
result = filter_todo_list(todo_list)
|
||||
assert len(result) == 0
|
||||
|
||||
def test_empty_list(self):
|
||||
assert filter_todo_list([]) == []
|
||||
|
||||
|
||||
class TestSummarizeSources:
|
||||
"""Tests for summarize_sources function."""
|
||||
|
||||
def test_extracts_sources(self):
|
||||
todo_list = [
|
||||
{":source": "foo", ":shortname": "newupstream_foo"},
|
||||
{":source": "bar", ":shortname": "newupstream_bar"},
|
||||
{":source": "baz", ":shortname": "rc_bug"},
|
||||
]
|
||||
result = summarize_sources(todo_list)
|
||||
assert result == {"foo", "bar", "baz"}
|
||||
|
||||
def test_deduplicates_sources(self):
|
||||
todo_list = [
|
||||
{":source": "foo", ":shortname": "type1"},
|
||||
{":source": "foo", ":shortname": "type2"},
|
||||
]
|
||||
result = summarize_sources(todo_list)
|
||||
assert result == {"foo"}
|
||||
|
||||
def test_skips_non_string_sources(self):
|
||||
todo_list = [
|
||||
{":source": "foo"},
|
||||
{":source": 123},
|
||||
{":source": None},
|
||||
{},
|
||||
]
|
||||
result = summarize_sources(todo_list)
|
||||
assert result == {"foo"}
|
||||
|
||||
def test_empty_list(self):
|
||||
assert summarize_sources([]) == set()
|
||||
57
tests/test_notes.py
Normal file
57
tests/test_notes.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""Tests for notes loading."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
import debian_todo
|
||||
|
||||
|
||||
class TestLoadNotes:
|
||||
"""Tests for load_notes function."""
|
||||
|
||||
def test_loads_notes_from_file(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
notes_file.write_text("foo some note\nbar another note\n")
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {"foo": "some note", "bar": "another note"}
|
||||
|
||||
def test_returns_empty_when_file_missing(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {}
|
||||
|
||||
def test_handles_package_without_note(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
notes_file.write_text("foo\n")
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {"foo": ""}
|
||||
|
||||
def test_joins_multiple_notes(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
notes_file.write_text("foo first note\nfoo second note\n")
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {"foo": "first note; second note"}
|
||||
|
||||
def test_skips_blank_lines(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
notes_file.write_text("foo note one\n\nbar note two\n\n")
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {"foo": "note one", "bar": "note two"}
|
||||
|
||||
def test_handles_multiword_notes(self, tmp_path, monkeypatch):
|
||||
notes_file = tmp_path / "notes"
|
||||
notes_file.write_text("foo this is a long note with many words\n")
|
||||
monkeypatch.setattr(debian_todo, "NOTES_PATH", notes_file)
|
||||
|
||||
result = debian_todo.load_notes()
|
||||
assert result == {"foo": "this is a long note with many words"}
|
||||
71
tests/test_vcs.py
Normal file
71
tests/test_vcs.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""Tests for VCS and uploader related functions."""
|
||||
|
||||
import pytest
|
||||
|
||||
from debian_todo import vcs_git_to_team, normalize_uploaders, HIDE_UPLOADER
|
||||
|
||||
|
||||
class TestVcsGitToTeam:
|
||||
"""Tests for vcs_git_to_team function."""
|
||||
|
||||
def test_salsa_python_team(self):
|
||||
url = "https://salsa.debian.org/python-team/packages/python-foo.git"
|
||||
assert vcs_git_to_team(url) == "python-team"
|
||||
|
||||
def test_salsa_homeassistant_team(self):
|
||||
url = "https://salsa.debian.org/homeassistant-team/python-bar.git"
|
||||
assert vcs_git_to_team(url) == "homeassistant-team"
|
||||
|
||||
def test_salsa_personal_repo(self):
|
||||
url = "https://salsa.debian.org/username/package.git"
|
||||
assert vcs_git_to_team(url) == "username"
|
||||
|
||||
def test_non_salsa_url(self):
|
||||
url = "https://github.com/user/repo.git"
|
||||
assert vcs_git_to_team(url) == url
|
||||
|
||||
def test_none_input(self):
|
||||
assert vcs_git_to_team(None) is None
|
||||
|
||||
def test_empty_string(self):
|
||||
assert vcs_git_to_team("") is None
|
||||
|
||||
def test_salsa_with_branch(self):
|
||||
url = "https://salsa.debian.org/python-team/packages/foo.git -b debian/main"
|
||||
assert vcs_git_to_team(url) == "python-team"
|
||||
|
||||
|
||||
class TestNormalizeUploaders:
|
||||
"""Tests for normalize_uploaders function."""
|
||||
|
||||
def test_single_uploader(self):
|
||||
uploaders = "John Doe <john@example.com>"
|
||||
assert normalize_uploaders(uploaders) == "John Doe <john@example.com>"
|
||||
|
||||
def test_multiple_uploaders(self):
|
||||
uploaders = "John Doe <john@example.com>, Jane Doe <jane@example.com>"
|
||||
result = normalize_uploaders(uploaders)
|
||||
assert result == "John Doe <john@example.com>\nJane Doe <jane@example.com>"
|
||||
|
||||
def test_hides_configured_uploader(self):
|
||||
uploaders = f"John Doe <john@example.com>, {HIDE_UPLOADER}"
|
||||
result = normalize_uploaders(uploaders)
|
||||
assert result == "John Doe <john@example.com>"
|
||||
assert HIDE_UPLOADER not in result
|
||||
|
||||
def test_only_hidden_uploader(self):
|
||||
uploaders = HIDE_UPLOADER
|
||||
assert normalize_uploaders(uploaders) == ""
|
||||
|
||||
def test_strips_whitespace(self):
|
||||
uploaders = " John Doe <john@example.com> , Jane Doe <jane@example.com> "
|
||||
result = normalize_uploaders(uploaders)
|
||||
assert result == "John Doe <john@example.com>\nJane Doe <jane@example.com>"
|
||||
|
||||
def test_empty_string(self):
|
||||
assert normalize_uploaders("") == ""
|
||||
|
||||
def test_extra_commas(self):
|
||||
uploaders = "John Doe <john@example.com>,, Jane Doe <jane@example.com>,"
|
||||
result = normalize_uploaders(uploaders)
|
||||
assert result == "John Doe <john@example.com>\nJane Doe <jane@example.com>"
|
||||
104
tests/test_version.py
Normal file
104
tests/test_version.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
"""Tests for version parsing and normalization functions."""
|
||||
|
||||
import pytest
|
||||
|
||||
from debian_todo import (
|
||||
parse_details,
|
||||
is_prerelease_version,
|
||||
normalize_upstream_version,
|
||||
)
|
||||
|
||||
|
||||
class TestParseDetails:
|
||||
"""Tests for parse_details function."""
|
||||
|
||||
def test_with_current_version(self):
|
||||
details = "1.2.3 (currently in unstable: 1.2.2-1)"
|
||||
new, current = parse_details(details)
|
||||
assert new == "1.2.3"
|
||||
assert current == "1.2.2-1"
|
||||
|
||||
def test_without_current_version(self):
|
||||
details = "1.2.3"
|
||||
new, current = parse_details(details)
|
||||
assert new == "1.2.3"
|
||||
assert current is None
|
||||
|
||||
def test_with_epoch_in_current(self):
|
||||
details = "2.0.0 (currently in unstable: 1:1.9.0-2)"
|
||||
new, current = parse_details(details)
|
||||
assert new == "2.0.0"
|
||||
assert current == "1:1.9.0-2"
|
||||
|
||||
def test_strips_whitespace(self):
|
||||
details = " 1.2.3 (currently in unstable: 1.2.2-1 ) "
|
||||
new, current = parse_details(details)
|
||||
assert new == "1.2.3"
|
||||
assert current == "1.2.2-1"
|
||||
|
||||
def test_empty_string(self):
|
||||
new, current = parse_details("")
|
||||
assert new == ""
|
||||
assert current is None
|
||||
|
||||
|
||||
class TestIsPrereleaseVersion:
|
||||
"""Tests for is_prerelease_version function."""
|
||||
|
||||
def test_alpha_version(self):
|
||||
assert is_prerelease_version("1.0.0alpha1") is True
|
||||
assert is_prerelease_version("1.0.0-alpha2") is True
|
||||
assert is_prerelease_version("1.0.0.alpha") is True
|
||||
|
||||
def test_beta_version(self):
|
||||
assert is_prerelease_version("1.0.0beta1") is True
|
||||
assert is_prerelease_version("1.0.0-beta2") is True
|
||||
assert is_prerelease_version("2.0b1") is True
|
||||
|
||||
def test_rc_version(self):
|
||||
assert is_prerelease_version("1.0.0rc1") is True
|
||||
assert is_prerelease_version("1.0.0-rc2") is True
|
||||
assert is_prerelease_version("1.0.0.rc3") is True
|
||||
|
||||
def test_short_prerelease(self):
|
||||
assert is_prerelease_version("1.0a1") is True
|
||||
assert is_prerelease_version("1.0b2") is True
|
||||
|
||||
def test_stable_version(self):
|
||||
assert is_prerelease_version("1.0.0") is False
|
||||
assert is_prerelease_version("2.3.4") is False
|
||||
assert is_prerelease_version("1.0.0-1") is False
|
||||
|
||||
def test_version_with_current(self):
|
||||
assert is_prerelease_version("1.0.0rc1 (currently in unstable: 0.9.0-1)") is True
|
||||
assert is_prerelease_version("1.0.0 (currently in unstable: 0.9.0-1)") is False
|
||||
|
||||
def test_case_insensitive(self):
|
||||
assert is_prerelease_version("1.0.0Alpha1") is True
|
||||
assert is_prerelease_version("1.0.0BETA1") is True
|
||||
assert is_prerelease_version("1.0.0RC1") is True
|
||||
|
||||
|
||||
class TestNormalizeUpstreamVersion:
|
||||
"""Tests for normalize_upstream_version function."""
|
||||
|
||||
def test_simple_version(self):
|
||||
assert normalize_upstream_version("1.2.3") == "1.2.3"
|
||||
|
||||
def test_strips_debian_revision(self):
|
||||
assert normalize_upstream_version("1.2.3-1") == "1.2.3"
|
||||
assert normalize_upstream_version("1.2.3-2ubuntu1") == "1.2.3"
|
||||
|
||||
def test_strips_epoch(self):
|
||||
assert normalize_upstream_version("1:1.2.3") == "1.2.3"
|
||||
assert normalize_upstream_version("2:1.2.3") == "1.2.3"
|
||||
|
||||
def test_strips_epoch_and_revision(self):
|
||||
assert normalize_upstream_version("1:1.2.3-4") == "1.2.3"
|
||||
|
||||
def test_strips_whitespace(self):
|
||||
assert normalize_upstream_version(" 1.2.3 ") == "1.2.3"
|
||||
|
||||
def test_version_with_hyphen_in_upstream(self):
|
||||
# Only the last hyphen is treated as revision separator
|
||||
assert normalize_upstream_version("1.2.3-beta-1") == "1.2.3-beta"
|
||||
Loading…
Add table
Add a link
Reference in a new issue