Ctrl K
ComunidadGuías…

backend/tests/test_diff_endpoint.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Tests for POST /tools/diff and POST /tools/diff/apply endpoints."""

import os

import pytest


@pytest.fixture
def client(temp_db, tmp_path):
    os.environ["SUBLARR_MEDIA_PATH"] = str(tmp_path)
    from config import reload_settings

    reload_settings()
    from app import create_app

    app = create_app(testing=True)
    with app.test_client() as c, app.app_context():
        yield c
    del os.environ["SUBLARR_MEDIA_PATH"]
    reload_settings()


ORIG_ASS = """\
[Script Info]
ScriptType: v4.00+

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello world
Dialogue: 0,0:00:04.00,0:00:06.00,Default,,0,0,0,,This is unchanged
"""

MOD_ASS = """\
[Script Info]
ScriptType: v4.00+

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hallo Welt
Dialogue: 0,0:00:04.00,0:00:06.00,Default,,0,0,0,,This is unchanged
"""


# ── POST /tools/diff ────────────────────────────────────────────────────────────


def test_diff_returns_diffs(client):
    resp = client.post("/api/v1/tools/diff", json={"original": ORIG_ASS, "modified": MOD_ASS})
    assert resp.status_code == 200
    data = resp.get_json()
    assert "diffs" in data
    assert data["changed"] >= 1


def test_diff_missing_original(client):
    resp = client.post("/api/v1/tools/diff", json={"modified": MOD_ASS})
    assert resp.status_code == 400


def test_diff_malformed_input(client):
    resp = client.post(
        "/api/v1/tools/diff", json={"original": "not valid ass content", "modified": MOD_ASS}
    )
    assert resp.status_code == 400
    assert "error" in (resp.get_json() or {})


def test_diff_identical_files_no_changes(client):
    resp = client.post("/api/v1/tools/diff", json={"original": ORIG_ASS, "modified": ORIG_ASS})
    assert resp.status_code == 200
    data = resp.get_json()
    assert data["changed"] == 0


def test_diff_missing_modified(client):
    resp = client.post("/api/v1/tools/diff", json={"original": ORIG_ASS})
    assert resp.status_code == 400


def test_diff_response_structure(client):
    resp = client.post("/api/v1/tools/diff", json={"original": ORIG_ASS, "modified": MOD_ASS})
    assert resp.status_code == 200
    data = resp.get_json()
    assert "total" in data
    assert "changed" in data
    assert "diffs" in data
    assert isinstance(data["diffs"], list)
    # Check a modified entry has correct structure
    modified_entries = [d for d in data["diffs"] if d["type"] == "modified"]
    assert len(modified_entries) >= 1
    entry = modified_entries[0]
    assert "original" in entry
    assert "modified" in entry
    assert "start" in entry["original"]
    assert "end" in entry["original"]
    assert "text" in entry["original"]


# ── POST /tools/diff/apply ──────────────────────────────────────────────────────


def _write_sub_file(tmp_path, content, filename="test.de.ass"):
    """Write subtitle content to a file in tmp_path and return its path."""
    sub_file = tmp_path / filename
    sub_file.write_text(content, encoding="utf-8")
    return str(sub_file)


def test_apply_creates_backup(client, tmp_path):
    sub_file = _write_sub_file(tmp_path, MOD_ASS)
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": sub_file,
            "last_modified": os.path.getmtime(sub_file),
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 200
    data = resp.get_json()
    assert "backup" in data
    assert os.path.exists(data["backup"])


def test_apply_returns_status_applied(client, tmp_path):
    sub_file = _write_sub_file(tmp_path, MOD_ASS)
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": sub_file,
            "last_modified": os.path.getmtime(sub_file),
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "applied"


def test_apply_accept_all_writes_modified_content(client, tmp_path):
    sub_file = _write_sub_file(tmp_path, MOD_ASS)
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": sub_file,
            "last_modified": os.path.getmtime(sub_file),
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 200
    written = open(sub_file, encoding="utf-8").read()
    assert "Hallo Welt" in written


def test_apply_reject_all_restores_original_content(client, tmp_path):
    sub_file = _write_sub_file(tmp_path, MOD_ASS)
    # Compute diff to get the index of the changed cue
    diff_resp = client.post("/api/v1/tools/diff", json={"original": ORIG_ASS, "modified": MOD_ASS})
    diffs = diff_resp.get_json()["diffs"]
    rejected = [i for i, d in enumerate(diffs) if d["type"] != "unchanged"]
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": sub_file,
            "last_modified": os.path.getmtime(sub_file),
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": rejected,
        },
    )
    assert resp.status_code == 200
    written = open(sub_file, encoding="utf-8").read()
    assert "Hello world" in written


def test_apply_missing_file_path(client):
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 400


def test_apply_path_traversal_rejected(client, tmp_path):
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": "/etc/passwd",
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 403


def test_apply_nonexistent_file(client, tmp_path):
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": str(tmp_path / "nonexistent.ass"),
            "original": ORIG_ASS,
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 404


def test_apply_malformed_original(client, tmp_path):
    """Sends a valid last_modified so the 400 is the parse failure's."""
    sub_file = _write_sub_file(tmp_path, MOD_ASS)
    resp = client.post(
        "/api/v1/tools/diff/apply",
        json={
            "file_path": sub_file,
            "last_modified": os.path.getmtime(sub_file),
            "original": "not valid",
            "modified": MOD_ASS,
            "rejected_indices": [],
        },
    )
    assert resp.status_code == 400
    assert "parse" in resp.get_json()["error"].lower()