Zum Inhalt

pipeline/tests/test_cli_review.py

# SPDX-License-Identifier: AGPL-3.0-or-later

from __future__ import annotations

import json
from pathlib import Path

from typer.testing import CliRunner

from autosplat.cli import app
from autosplat.lifecycle import review_marker_path, review_status

runner = CliRunner()


def _capture(tmp_path: Path) -> Path:
    capture = tmp_path / "2026-07-19_lgs"
    (capture / "output").mkdir(parents=True)
    (capture / "output" / "scene.ply").write_bytes(b"ply\n" + b"x" * 100)
    return capture


def test_confirm_unchanged_marks_the_capture_reviewed(tmp_path: Path) -> None:
    capture = _capture(tmp_path)
    result = runner.invoke(app, ["review", str(capture), "--confirm-unchanged"])
    assert result.exit_code == 0, result.output
    assert review_status(capture) == "reviewed"
    data = json.loads(review_marker_path(capture).read_text())
    assert data["source_ply_mtime"] == (capture / "output" / "scene.ply").stat().st_mtime


def test_upload_stores_the_cleaned_ply(tmp_path: Path) -> None:
    capture = _capture(tmp_path)
    cleaned = tmp_path / "cleaned.ply"
    cleaned.write_bytes(b"ply\n" + b"y" * 50)
    result = runner.invoke(app, ["review", str(capture), "--upload", str(cleaned)])
    assert result.exit_code == 0, result.output
    assert (capture / "output" / "scene_reviewed.ply").read_bytes() == cleaned.read_bytes()
    assert review_status(capture) == "reviewed"


def test_confirm_unchanged_removes_a_stale_reviewed_ply(tmp_path: Path) -> None:
    """Same trap the WebUI route documents: a leftover scene_reviewed.ply
    would keep being published after a rebuild."""
    capture = _capture(tmp_path)
    (capture / "output" / "scene_reviewed.ply").write_bytes(b"ply\nstale")
    runner.invoke(app, ["review", str(capture), "--confirm-unchanged"])
    assert not (capture / "output" / "scene_reviewed.ply").exists()


def test_both_flags_is_an_error(tmp_path: Path) -> None:
    capture = _capture(tmp_path)
    cleaned = tmp_path / "c.ply"
    cleaned.write_bytes(b"ply\n")
    result = runner.invoke(
        app, ["review", str(capture), "--confirm-unchanged", "--upload", str(cleaned)]
    )
    assert result.exit_code != 0


def test_neither_flag_is_an_error(tmp_path: Path) -> None:
    result = runner.invoke(app, ["review", str(_capture(tmp_path))])
    assert result.exit_code != 0


def test_non_ply_upload_is_rejected(tmp_path: Path) -> None:
    capture = _capture(tmp_path)
    bad = tmp_path / "notaply.ply"
    bad.write_bytes(b"this is not a ply")
    result = runner.invoke(app, ["review", str(capture), "--upload", str(bad)])
    assert result.exit_code != 0
    assert review_status(capture) == "unreviewed"


def test_review_refuses_a_capture_with_no_scene_ply(tmp_path: Path) -> None:
    """review's own no-PLY guard (cli.py:944-947) — the sibling of `share`'s,
    which test_cli_share.py already covers. `capture_dir` only requires the
    directory itself to exist (`exists=True, file_okay=False` on the typer
    argument); output/scene.ply is checked separately, after that."""
    capture = tmp_path / "2026-07-19_no_ply"
    capture.mkdir()
    result = runner.invoke(app, ["review", str(capture), "--confirm-unchanged"])
    assert result.exit_code != 0
    assert "No output/scene.ply" in result.output
    assert review_status(capture) == "unreviewed"