140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
"""Golden parser fixtures for immich-go reports (US05-03).
|
|
|
|
The reports below are the pinned grammar for each supported uploader version. If a
|
|
future immich-go changes its wording, the fix is a new adapter and a new golden
|
|
report — never a looser pattern here, because a loose pattern is how an unread line
|
|
becomes a false success.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from photo_pipeline.integrations import immich_go_report as parser
|
|
|
|
TEXT_REPORT = """
|
|
Scanning /lib/rome
|
|
INFO uploaded /lib/rome/new.jpg
|
|
INFO server has the same file /lib/rome/dup.jpg
|
|
INFO server has an older file, upgrading /lib/rome/old.jpg
|
|
WARN discarded /lib/rome/notes.txt: unsupported file type
|
|
ERROR error uploading /lib/rome/broken.jpg: connection reset by peer
|
|
Uploaded 1, upgraded 1, duplicates 1, skipped 1, errors 1
|
|
"""
|
|
|
|
JSON_REPORT = """
|
|
{"time":"2026-01-01T10:00:00Z","level":"INFO","msg":"uploaded","file":"/lib/rome/new.jpg"}
|
|
{"level":"INFO","msg":"server has the same file","file":"/lib/rome/dup.jpg"}
|
|
{"level":"INFO","msg":"server has an older file, upgrading","file":"/lib/rome/old.jpg"}
|
|
{"level":"WARN","msg":"discarded: unsupported file type","file":"/lib/rome/notes.txt"}
|
|
{"level":"ERROR","msg":"error uploading","file":"/lib/rome/broken.jpg"}
|
|
{"level":"INFO","msg":"report","counts":{"uploaded":1,"upgraded":1,"duplicates":1,\
|
|
"skipped":1,"errors":1}}
|
|
"""
|
|
|
|
EXPECTED = {
|
|
"/lib/rome/new.jpg": parser.UPLOADED,
|
|
"/lib/rome/dup.jpg": parser.DUPLICATE,
|
|
"/lib/rome/old.jpg": parser.UPGRADED,
|
|
"/lib/rome/notes.txt": parser.SKIPPED,
|
|
"/lib/rome/broken.jpg": parser.FAILED,
|
|
}
|
|
EXPECTED_COUNTS = {
|
|
parser.UPLOADED: 1,
|
|
parser.UPGRADED: 1,
|
|
parser.DUPLICATE: 1,
|
|
parser.SKIPPED: 1,
|
|
parser.FAILED: 1,
|
|
}
|
|
|
|
|
|
def _outcomes(parsed) -> dict:
|
|
return {entry["path"]: entry["outcome"] for entry in parsed["entries"]}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("version", "report", "expected_parser"),
|
|
[
|
|
("immich-go 0.21.0", TEXT_REPORT, "text-v1"),
|
|
("immich-go 0.22.3", TEXT_REPORT, "text-v1"),
|
|
("immich-go 0.23.1", JSON_REPORT, "json-v1"),
|
|
("immich-go 0.24.0", JSON_REPORT, "json-v1"),
|
|
],
|
|
)
|
|
def test_every_supported_version_classifies_every_outcome(version, report, expected_parser):
|
|
parsed = parser.parse(report, version)
|
|
|
|
assert parsed["parser"] == expected_parser and parsed["supported"]
|
|
assert parsed["parser_version"] == parser.PARSER_VERSION
|
|
assert _outcomes(parsed) == EXPECTED
|
|
assert parsed["counts"] == EXPECTED_COUNTS
|
|
|
|
|
|
def test_an_unsupported_version_parses_nothing_at_all():
|
|
"""A build whose output was never pinned must not be read optimistically."""
|
|
parsed = parser.parse(TEXT_REPORT, "immich-go 9.99.0")
|
|
|
|
assert parsed["parser"] is None and parsed["supported"] is False
|
|
assert parsed["entries"] == [] and parsed["counts"] is None
|
|
|
|
|
|
@pytest.mark.parametrize("version", [None, "", "immich-go dev", "unknown"])
|
|
def test_a_missing_or_unreadable_version_is_unsupported(version):
|
|
assert parser.parser_for(version) is None
|
|
|
|
|
|
def test_malformed_lines_are_counted_never_classified():
|
|
report = (
|
|
"{not json at all\n"
|
|
'{"level":"INFO","msg":"uploaded"}\n' # no file
|
|
'["not","an","object"]\n'
|
|
'{"level":"INFO","msg":"uploaded","file":"/lib/rome/new.jpg"}\n'
|
|
)
|
|
|
|
parsed = parser.parse(report, "immich-go 0.23.1")
|
|
|
|
assert _outcomes(parsed) == {"/lib/rome/new.jpg": parser.UPLOADED}
|
|
assert parsed["unparsed"] == 3
|
|
|
|
|
|
def test_a_line_with_a_path_but_no_known_verb_is_not_an_outcome():
|
|
parsed = parser.parse("INFO considering /lib/rome/a.jpg\n", "immich-go 0.21.0")
|
|
|
|
assert parsed["entries"] == [] and parsed["unparsed"] == 1
|
|
|
|
|
|
def test_the_album_name_never_classifies_the_line():
|
|
"""A folder called "errors" must not turn a successful upload into a failure."""
|
|
parsed = parser.parse("INFO uploaded /lib/errors/a.jpg\n", "immich-go 0.21.0")
|
|
|
|
assert _outcomes(parsed) == {"/lib/errors/a.jpg": parser.UPLOADED}
|
|
|
|
|
|
def test_paths_with_spaces_and_reasons_are_read_whole():
|
|
report = "ERROR error uploading /lib/summer holiday/a b.jpg: connection reset\n"
|
|
|
|
parsed = parser.parse(report, "immich-go 0.21.0")
|
|
|
|
assert _outcomes(parsed) == {"/lib/summer holiday/a b.jpg": parser.FAILED}
|
|
|
|
|
|
def test_evidence_is_bounded_per_entry():
|
|
line = "INFO uploaded /lib/rome/a.jpg " + "x" * 5_000
|
|
|
|
(entry,) = parser.parse(line, "immich-go 0.21.0")["entries"]
|
|
|
|
assert len(entry["evidence"]) == parser.MAX_EVIDENCE_CHARS
|
|
|
|
|
|
def test_a_looping_uploader_cannot_produce_unbounded_entries(monkeypatch):
|
|
monkeypatch.setattr(parser, "MAX_ENTRIES", 10)
|
|
report = "".join(f"INFO uploaded /lib/rome/{n}.jpg\n" for n in range(50))
|
|
|
|
parsed = parser.parse(report, "immich-go 0.21.0")
|
|
|
|
assert len(parsed["entries"]) == 10 and parsed["entries_truncated"] is True
|
|
|
|
|
|
def test_a_report_without_a_summary_reports_no_counts():
|
|
parsed = parser.parse("INFO uploaded /lib/rome/a.jpg\n", "immich-go 0.21.0")
|
|
|
|
assert parsed["counts"] is None, "counts are read, never invented"
|