"""Unit tests for artifact validation, staging, storage, and downloads.""" import base64 import os import time from pathlib import Path import pytest from backend.artifacts import ( ArtifactStore, validate_artifact_name, ) from backend.errors import InvalidInputError def test_validate_artifact_name(): assert validate_artifact_name("checklist.md") == "checklist.md" assert validate_artifact_name("docs/summary.txt") == "docs/summary.txt" assert validate_artifact_name("تقرير.txt") == "تقرير.txt" # Rejections with pytest.raises(InvalidInputError): validate_artifact_name("/absolute/path.txt") with pytest.raises(InvalidInputError): validate_artifact_name("../escape.txt") with pytest.raises(InvalidInputError): validate_artifact_name("dir/../escape.txt") with pytest.raises(InvalidInputError): validate_artifact_name("windows\\path.txt") with pytest.raises(InvalidInputError): validate_artifact_name("line\nbreak.txt") with pytest.raises(InvalidInputError): validate_artifact_name("a" * 1025) def test_artifact_staging_and_commit(tmp_path: Path): store = ArtifactStore(tmp_path / "artifacts") staging = store.create_staging_session("query_123") content = b"# Checklist\n\n- Deploy service X\n" size = len(content) assert size == 32 # 1. Begin transfer dec, warn = staging.handle_begin("t_1", "checklist.md", size) assert dec == "accept" assert warn is None # 2. Send chunk b64_data = base64.b64encode(content).decode("utf-8") staging.handle_chunk("t_1", index=0, data_base64=b64_data) # 3. End transfer end_dec, end_warn = staging.handle_end("t_1", size_bytes=size, chunks=1) assert end_dec == "stored" assert end_warn is None # 4. Commit to session session_cookie = "sess_abc123" committed_list = staging.commit(session_id=session_cookie, ttl_seconds=60) assert len(committed_list) == 1 art_info = committed_list[0] assert art_info["name"] == "checklist.md" assert art_info["size_bytes"] == 32 aid = art_info["id"] # 5. Download with matching session res = store.get_artifact_for_download(aid, session_id=session_cookie) assert res is not None fpath, fname, fsize = res assert fname == "checklist.md" assert fsize == 32 assert fpath.read_bytes() == content # 6. Download with wrong session -> None (404) assert store.get_artifact_for_download(aid, session_id="other_session") is None # 7. Download with expired artifact -> None # Force expiry store._committed[aid].expires_at_ts = time.time() - 10 assert store.get_artifact_for_download(aid, session_id=session_cookie) is None def test_artifact_size_skip(tmp_path: Path): store = ArtifactStore(tmp_path / "artifacts") staging = store.create_staging_session("query_skip") # Try declaring 15 MiB file (limit is 10 MiB) dec, warn = staging.handle_begin("t_large", "huge.bin", 15 * 1024 * 1024) assert dec == "skip" assert warn is not None assert warn["code"] == "artifact_size_exceeded" staging.discard() def test_artifact_discard_on_failure(tmp_path: Path): store = ArtifactStore(tmp_path / "artifacts") staging = store.create_staging_session("query_fail") dec, _ = staging.handle_begin("t_1", "file.txt", 10) assert dec == "accept" assert store.global_reserved_bytes == 10 # Failure occurred -> discard staging.discard() assert store.global_reserved_bytes == 0 assert not (store.staging_dir / "query_fail").exists()