Source: tests/test_download_hypersim_holdout_archives.py · Plain text

tests/test_download_hypersim_holdout_archives.py

import importlib.util
import io
import json
import zipfile
from pathlib import Path
from types import SimpleNamespace

import pytest
import requests
from requests.structures import CaseInsensitiveDict

SPEC = importlib.util.spec_from_file_location(
    "download_hypersim_holdout_archives",
    Path(__file__).resolve().parents[1] / "scripts/download_hypersim_holdout_archives.py",
)
download = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(download)


def _remote(expected: int = 8) -> dict[str, object]:
    return {
        "url": "https://example.test/scene.zip",
        "resolved_url": "https://cdn.example.test/scene.zip",
        "etag": '"etag-1"',
        "last_modified": "Tue, 29 Dec 2020 01:25:55 GMT",
        "expected_bytes": expected,
    }


def test_resume_identity_binds_size_and_last_modified_fallback() -> None:
    remote = _remote()
    old_source = {
        "url": remote["url"],
        "etag": None,
        "last_modified": remote["last_modified"],
        "expected_bytes": remote["expected_bytes"],
    }
    remote_without_etag = dict(remote, etag=None)

    download._validate_provenance(old_source, remote_without_etag, "manifest-hash")
    assert download._range_headers(3, 8, remote_without_etag) == {
        "Range": "bytes=3-7",
        "If-Range": remote["last_modified"],
    }

    with pytest.raises(ValueError, match="byte count"):
        download._validate_provenance(
            dict(old_source, expected_bytes=9), remote_without_etag, "manifest-hash"
        )


def test_get_response_must_match_head_identity_and_range() -> None:
    remote = _remote()
    response = SimpleNamespace(
        url=remote["resolved_url"],
        status_code=206,
        headers=CaseInsensitiveDict(
            {
                "ETag": remote["etag"],
                "Last-Modified": remote["last_modified"],
                "Content-Range": "bytes 3-7/8",
                "Content-Length": "5",
            }
        ),
    )
    download._validate_get_response(response, remote, expected=8, offset=3)

    response.headers["ETag"] = '"different"'
    with pytest.raises(ValueError, match="GET etag"):
        download._validate_get_response(response, remote, expected=8, offset=3)


def test_existing_status_hash_is_checked_and_completion_records_local_hash(tmp_path: Path) -> None:
    status_path = tmp_path / "status.json"
    status_path.write_text(
        json.dumps(
            {
                "source_manifest_sha256": "manifest-hash",
                "entries": [{"scene": "ai_001_001", "sha256": "a" * 64}],
            }
        ),
        encoding="utf-8",
    )
    hashes = download._load_status_hashes(status_path, "manifest-hash")
    assert hashes == {"ai_001_001": "a" * 64}
    with pytest.raises(ValueError, match="different source manifest"):
        download._load_status_hashes(status_path, "different-manifest")
    download._validate_local_hash({}, "a" * 64, hashes["ai_001_001"])
    with pytest.raises(ValueError, match="existing status"):
        download._validate_local_hash({}, "b" * 64, hashes["ai_001_001"])

    verification = {
        "sha256": "a" * 64,
        "zip_members": 1,
        "verification": "local SHA256",
    }
    completed = download._completion_provenance(
        {
            "url": _remote()["url"],
            "etag": _remote()["etag"],
            "last_modified": _remote()["last_modified"],
            "expected_bytes": 8,
        },
        _remote(),
        "manifest-hash",
        verification,
    )
    assert completed["local_sha256"] == "a" * 64
    assert completed["source_manifest_sha256"] == "manifest-hash"


def test_run_retries_request_failure_after_source_recorded(tmp_path: Path, monkeypatch) -> None:
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w") as archive:
        archive.writestr("manifest.txt", "synthetic download fixture")
    payload = buffer.getvalue()
    rows = [
        {"scene": f"ai_001_{index:03}", "zip_bytes": len(payload), "status": "index_read_ok"}
        for index in range(1, 27)
    ]
    source = tmp_path / "source.json"
    source.write_text(json.dumps({"verified_scenes": 26, "results": rows}), encoding="utf-8")
    monkeypatch.setattr(download, "PROJECT", tmp_path)
    monkeypatch.setattr(download, "SOURCE", source)
    monkeypatch.setattr(download, "DESTINATION", tmp_path / "data" / "archives")
    monkeypatch.setattr(download, "LOG_ROOT", tmp_path / "logs")
    monkeypatch.setattr(download.time, "sleep", lambda _: None)
    calls = {}

    class Response:
        def __init__(self, url, status_code=200):
            self.url = url
            self.status_code = status_code
            self.headers = CaseInsensitiveDict({
                "Content-Length": str(len(payload)),
                "ETag": '"fixture"',
                "Last-Modified": "Tue, 29 Dec 2020 01:25:55 GMT",
            })
            if status_code == 206:
                self.headers["Content-Range"] = f"bytes 0-{len(payload)-1}/{len(payload)}"

        def raise_for_status(self):
            pass

        def close(self):
            pass

        def __enter__(self):
            return self

        def __exit__(self, *_):
            return False

        def iter_content(self, chunk_size):
            yield payload

    class Session:
        def __enter__(self):
            return self

        def __exit__(self, *_):
            return False

        def head(self, url, **kwargs):
            return Response(url)

        def get(self, url, **kwargs):
            calls[url] = calls.get(url, 0) + 1
            if url.endswith("ai_001_001.zip") and calls[url] == 1:
                raise requests.ConnectionError("synthetic failure before partial creation")
            return Response(url, 206)

    monkeypatch.setattr(download.requests, "Session", Session)
    assert download.run(workers=1) == 0
    status = json.loads((download.LOG_ROOT / "status.json").read_text(encoding="utf-8"))
    assert status["status"] == "complete"
    assert status["completed"] == 26
    assert status["errors"] == []
    assert status["entries"][0]["attempts"] == 2
    assert len(list(download.DESTINATION.glob("*.zip"))) == 26