344 lines
13 KiB
Python
344 lines
13 KiB
Python
import pytest
|
|
|
|
from src.core.file import File
|
|
from src.core.pool_index import PoolIndex
|
|
from src.core.tag import Tag
|
|
from src.core.tag_manager import TagManager
|
|
|
|
|
|
class TestFile:
|
|
"""Testy pro třídu File (metadata žijí v pool indexu)"""
|
|
|
|
@pytest.fixture
|
|
def tag_manager(self):
|
|
return TagManager()
|
|
|
|
@pytest.fixture
|
|
def index(self, tmp_path):
|
|
"""Pool index that backs the File's metadata."""
|
|
return PoolIndex(tmp_path)
|
|
|
|
@pytest.fixture
|
|
def test_file(self, tmp_path):
|
|
f = tmp_path / "test.txt"
|
|
f.write_text("test content")
|
|
return f
|
|
|
|
def test_file_creation(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
assert file_obj.file_path == test_file
|
|
assert file_obj.filename == "test.txt"
|
|
assert file_obj.new is True
|
|
|
|
def test_file_initial_tags(self, test_file, tag_manager, index):
|
|
"""Nový soubor nemá žádné automatické tagy (Stav/Nové odstraněn)."""
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
assert file_obj.tags == []
|
|
|
|
def test_file_metadata_saved_to_index(self, test_file, tag_manager, index):
|
|
"""Metadata jsou zapsána do indexu už při vytvoření."""
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
assert index.record_for(file_obj.file_path) is not None
|
|
|
|
def test_file_no_sidecar_written(self, test_file, tag_manager, index):
|
|
"""Žádný .!tag sidecar už nevzniká."""
|
|
File(test_file, tag_manager, index=index)
|
|
assert not (test_file.parent / ".test.txt.!tag").exists()
|
|
|
|
def test_index_less_file_is_in_memory_only(self, test_file, tag_manager):
|
|
"""Bez indexu je File jen v paměti — nic se nezapíše na disk."""
|
|
file_obj = File(test_file, tag_manager)
|
|
file_obj.add_tag("Video/HD")
|
|
file_obj.save_metadata() # no-op
|
|
assert not (test_file.parent / ".test.txt.!tag").exists()
|
|
|
|
def test_file_save_metadata(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.new = False
|
|
file_obj.ignored = True
|
|
file_obj.save_metadata()
|
|
|
|
data = index.record_for(file_obj.file_path)
|
|
assert data["new"] is False
|
|
assert data["ignored"] is True
|
|
|
|
def test_file_load_metadata(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
tag = tag_manager.add_tag("Video", "HD")
|
|
file_obj.tags.append(tag)
|
|
file_obj.date = "2025-01-15"
|
|
file_obj.save_metadata()
|
|
|
|
# A fresh File over the same index reloads the metadata
|
|
file_obj2 = File(test_file, tag_manager, index=index)
|
|
assert len(file_obj2.tags) == 1 # Video/HD
|
|
assert file_obj2.date == "2025-01-15"
|
|
assert "Video/HD" in {t.full_path for t in file_obj2.tags}
|
|
|
|
def test_file_set_date(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.set_date("2025-12-25")
|
|
assert file_obj.date == "2025-12-25"
|
|
assert index.record_for(file_obj.file_path)["date"] == "2025-12-25"
|
|
|
|
def test_file_set_date_to_none(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.set_date("2025-12-25")
|
|
file_obj.set_date(None)
|
|
assert file_obj.date is None
|
|
|
|
def test_file_set_date_empty_string(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.set_date("2025-12-25")
|
|
file_obj.set_date("")
|
|
assert file_obj.date is None
|
|
|
|
def test_file_add_tag_object(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
tag = Tag("Video", "4K")
|
|
file_obj.add_tag(tag)
|
|
|
|
assert tag in file_obj.tags
|
|
assert len(file_obj.tags) == 1
|
|
|
|
def test_file_add_tag_string(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("Audio/MP3")
|
|
assert "Audio/MP3" in {t.full_path for t in file_obj.tags}
|
|
|
|
def test_file_add_tag_string_without_category(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("SimpleTag")
|
|
assert "default/SimpleTag" in {t.full_path for t in file_obj.tags}
|
|
|
|
def test_file_add_duplicate_tag(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
tag = Tag("Video", "HD")
|
|
file_obj.add_tag(tag)
|
|
file_obj.add_tag(tag)
|
|
assert sum(1 for t in file_obj.tags if t == tag) == 1
|
|
|
|
def test_file_remove_tag_object(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
tag = Tag("Video", "HD")
|
|
file_obj.add_tag(tag)
|
|
file_obj.remove_tag(tag)
|
|
assert tag not in file_obj.tags
|
|
|
|
def test_file_remove_tag_string(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("Video/HD")
|
|
file_obj.remove_tag("Video/HD")
|
|
assert "Video/HD" not in {t.full_path for t in file_obj.tags}
|
|
|
|
def test_file_remove_tag_string_without_category(
|
|
self, test_file, tag_manager, index
|
|
):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("SimpleTag")
|
|
file_obj.remove_tag("SimpleTag")
|
|
assert "default/SimpleTag" not in {t.full_path for t in file_obj.tags}
|
|
|
|
def test_file_remove_nonexistent_tag(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
initial_count = len(file_obj.tags)
|
|
file_obj.remove_tag("Nonexistent/Tag")
|
|
assert len(file_obj.tags) == initial_count
|
|
|
|
def test_file_without_tagmanager(self, test_file):
|
|
file_obj = File(test_file, tagmanager=None)
|
|
assert file_obj.tagmanager is None
|
|
assert len(file_obj.tags) == 0
|
|
|
|
def test_file_metadata_persistence(self, test_file, tag_manager, index):
|
|
file_obj1 = File(test_file, tag_manager, index=index)
|
|
file_obj1.add_tag("Video/HD")
|
|
file_obj1.add_tag("Audio/Stereo")
|
|
file_obj1.set_date("2025-01-01")
|
|
file_obj1.new = False
|
|
file_obj1.ignored = True
|
|
file_obj1.save_metadata()
|
|
|
|
file_obj2 = File(test_file, tag_manager, index=index)
|
|
assert file_obj2.new is False
|
|
assert file_obj2.ignored is True
|
|
assert file_obj2.date == "2025-01-01"
|
|
tag_paths = {t.full_path for t in file_obj2.tags}
|
|
assert "Video/HD" in tag_paths
|
|
assert "Audio/Stereo" in tag_paths
|
|
|
|
def test_file_metadata_record_shape(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("Test/Tag")
|
|
file_obj.set_date("2025-06-15")
|
|
|
|
data = index.record_for(file_obj.file_path)
|
|
assert "new" in data
|
|
assert "ignored" in data
|
|
assert "tags" in data
|
|
assert "date" in data
|
|
assert isinstance(data["tags"], list)
|
|
|
|
def test_file_unicode_handling(self, tmp_path, tag_manager, index):
|
|
test_file = tmp_path / "český_soubor.txt"
|
|
test_file.write_text("obsah")
|
|
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("Kategorie/Český tag")
|
|
file_obj.save_metadata()
|
|
|
|
file_obj2 = File(test_file, tag_manager, index=index)
|
|
assert "Kategorie/Český tag" in {t.full_path for t in file_obj2.tags}
|
|
|
|
def test_file_complex_scenario(self, test_file, tag_manager, index):
|
|
file_obj = File(test_file, tag_manager, index=index)
|
|
file_obj.add_tag("Video/HD")
|
|
file_obj.add_tag("Video/Stereo")
|
|
file_obj.add_tag("Stav/Zkontrolováno")
|
|
file_obj.set_date("2025-01-01")
|
|
file_obj.remove_tag("Stav/Nové")
|
|
|
|
tag_paths = {t.full_path for t in file_obj.tags}
|
|
assert "Video/HD" in tag_paths
|
|
assert "Video/Stereo" in tag_paths
|
|
assert "Stav/Zkontrolováno" in tag_paths
|
|
assert "Stav/Nové" not in tag_paths
|
|
assert file_obj.date == "2025-01-01"
|
|
|
|
file_obj2 = File(test_file, tag_manager, index=index)
|
|
assert {t.full_path for t in file_obj2.tags} == tag_paths
|
|
assert file_obj2.date == "2025-01-01"
|
|
|
|
|
|
class TestApplyNfoTags:
|
|
"""Tests for File.apply_nfo tag assignment (NFO parsed from disk)."""
|
|
|
|
@pytest.fixture
|
|
def tag_manager(self):
|
|
return TagManager()
|
|
|
|
@pytest.fixture
|
|
def index(self, tmp_path):
|
|
return PoolIndex(tmp_path)
|
|
|
|
@pytest.fixture
|
|
def movie_file(self, tmp_path, tag_manager, index):
|
|
path = tmp_path / "Matrix.mkv"
|
|
path.write_text("x")
|
|
return File(path, tag_manager, index=index)
|
|
|
|
def test_apply_nfo_assigns_expected_categories(self, movie_file):
|
|
from src.core.nfo import NfoData
|
|
|
|
nfo = NfoData(
|
|
title="Matrix",
|
|
year="1999",
|
|
genres=["Akční", "Sci-Fi"],
|
|
countries=["USA"],
|
|
rating=90,
|
|
)
|
|
result = movie_file.apply_nfo(nfo)
|
|
|
|
assert result["success"]
|
|
paths = {t.full_path for t in movie_file.tags}
|
|
assert "Žánr/Akční" in paths
|
|
assert "Žánr/Sci-Fi" in paths
|
|
assert "Rok/1999" in paths
|
|
assert "Země původu/USA" in paths
|
|
assert "Hodnocení/90" in paths # exact value; folder grouping happens later
|
|
|
|
def test_apply_nfo_none_is_noop(self, movie_file):
|
|
result = movie_file.apply_nfo(None)
|
|
assert not result["success"]
|
|
assert movie_file.tags == []
|
|
|
|
def test_name_context_fields(self, movie_file):
|
|
movie_file.title = "Dr. No"
|
|
movie_file.meta = {"year": 1962, "rating": 75}
|
|
ctx = movie_file.name_context()
|
|
assert ctx["title"] == "Dr. No"
|
|
assert ctx["year"] == "1962" # coerced to str for uniform template fields
|
|
assert ctx["rating"] == 75
|
|
assert ctx["ext"] == movie_file.file_path.suffix
|
|
assert ctx["filename"] == movie_file.filename
|
|
assert "{year} - {title}{ext}".format(**ctx) == f"1962 - Dr. No{ctx['ext']}"
|
|
|
|
def test_name_context_year_from_tag_when_no_cache(self, movie_file):
|
|
movie_file.meta = None
|
|
movie_file.add_tag("Rok/1999")
|
|
assert movie_file.name_context()["year"] == "1999"
|
|
|
|
def test_added_timestamp_uses_stored_added(self, movie_file):
|
|
movie_file.added = "2026-06-16T12:00:00"
|
|
from datetime import datetime
|
|
|
|
assert (
|
|
movie_file.added_timestamp()
|
|
== datetime.fromisoformat("2026-06-16T12:00:00").timestamp()
|
|
)
|
|
|
|
def test_added_timestamp_falls_back_to_mtime(self, movie_file):
|
|
movie_file.added = None
|
|
import os
|
|
|
|
expected = os.stat(movie_file.file_path).st_mtime
|
|
assert movie_file.added_timestamp() == expected
|
|
|
|
def test_set_attribute_persists_and_in_context(self, movie_file):
|
|
movie_file.set_attribute("collection_sort", "03")
|
|
assert movie_file.attributes["collection_sort"] == "03"
|
|
assert movie_file.name_context()["collection_sort"] == "03"
|
|
# reload (from index) keeps it
|
|
reloaded = File(
|
|
movie_file.file_path, movie_file.tagmanager, index=movie_file.index
|
|
)
|
|
assert reloaded.attributes["collection_sort"] == "03"
|
|
|
|
def test_set_attribute_empty_removes_it(self, movie_file):
|
|
movie_file.set_attribute("collection_sort", "03")
|
|
movie_file.set_attribute("collection_sort", None)
|
|
assert "collection_sort" not in movie_file.attributes
|
|
|
|
def test_attribute_usable_in_filename_template(self, movie_file):
|
|
movie_file.title = "Dr. No"
|
|
movie_file.set_attribute("collection_sort", "01")
|
|
ctx = movie_file.name_context()
|
|
assert (
|
|
"{collection_sort} - {title}{ext}".format(**ctx)
|
|
== f"01 - Dr. No{ctx['ext']}"
|
|
)
|
|
|
|
def test_apply_nfo_honors_custom_schema(self, movie_file):
|
|
"""A schema without the rating entry produces no Hodnocení tags."""
|
|
from src.core.nfo import NfoData
|
|
|
|
schema = [
|
|
{
|
|
"category": "Žánr",
|
|
"csfd_field": "genres",
|
|
"transform": None,
|
|
"filmoteka_root": "",
|
|
}
|
|
]
|
|
movie_file.apply_nfo(NfoData(genres=["Drama"], rating=90), schema)
|
|
|
|
paths = {t.full_path for t in movie_file.tags}
|
|
assert "Žánr/Drama" in paths
|
|
assert not any(p.startswith("Hodnocení/") for p in paths)
|
|
|
|
def test_apply_nfo_preserves_user_tags_on_refresh(self, movie_file):
|
|
"""Re-reading regenerates only auto (NFO) tags; user-added tags survive."""
|
|
from src.core.nfo import NfoData
|
|
|
|
movie_file.add_tag("Sbírka/Oblíbené") # user tag
|
|
movie_file.apply_nfo(NfoData(year="1999", genres=["Akční"]))
|
|
movie_file.apply_nfo(NfoData(year="2009", genres=["Drama"]))
|
|
|
|
paths = {t.full_path for t in movie_file.tags}
|
|
assert "Sbírka/Oblíbené" in paths # user tag kept
|
|
assert "Žánr/Drama" in paths # new NFO tag
|
|
assert "Rok/2009" in paths
|
|
assert "Žánr/Akční" not in paths # old NFO tag regenerated away
|
|
assert "Žánr/Akční" not in paths # stale ČSFD tag dropped
|
|
assert "Rok/1999" not in paths
|