[AZ-175] Media table integration with XxHash64 content hashing and status lifecycle

Made-with: Cursor
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-03-31 06:36:56 +03:00
parent 6c24d09eab
commit 40be55ac03
10 changed files with 381 additions and 30 deletions
+10 -8
View File
@@ -74,7 +74,7 @@ def test_merged_annotation_nested_sections():
assert out["altitude"] == 100
def test_build_media_detect_config_uses_api_path_and_defaults_when_api_empty():
def test_resolve_media_for_detect_uses_api_path_and_defaults_when_api_empty():
# Arrange
import main
@@ -84,13 +84,14 @@ def test_build_media_detect_config_uses_api_path_and_defaults_when_api_empty():
mock_ann.fetch_media_path.return_value = "/m/file.jpg"
with patch("main.annotations_client", mock_ann):
# Act
cfg = main._build_media_detect_config_dict("mid-1", tm, None)
cfg, path = main._resolve_media_for_detect("mid-1", tm, None)
# Assert
assert cfg["paths"] == ["/m/file.jpg"]
assert path == "/m/file.jpg"
assert "paths" not in cfg
assert "probability_threshold" not in cfg
def test_build_media_detect_config_override_wins():
def test_resolve_media_for_detect_override_wins():
# Arrange
import main
@@ -104,14 +105,15 @@ def test_build_media_detect_config_override_wins():
mock_ann.fetch_media_path.return_value = "/m/v.mp4"
with patch("main.annotations_client", mock_ann):
# Act
cfg = main._build_media_detect_config_dict("vid-1", tm, override)
cfg, path = main._resolve_media_for_detect("vid-1", tm, override)
# Assert
assert cfg["probability_threshold"] == 0.99
assert cfg["altitude"] == 500
assert cfg["paths"] == ["/m/v.mp4"]
assert path == "/m/v.mp4"
assert "paths" not in cfg
def test_build_media_detect_config_raises_when_no_media_path():
def test_resolve_media_for_detect_raises_when_no_media_path():
# Arrange
import main
@@ -122,5 +124,5 @@ def test_build_media_detect_config_raises_when_no_media_path():
with patch("main.annotations_client", mock_ann):
# Act / Assert
with pytest.raises(HTTPException) as exc:
main._build_media_detect_config_dict("missing", tm, None)
main._resolve_media_for_detect("missing", tm, None)
assert exc.value.status_code == 503
+43
View File
@@ -0,0 +1,43 @@
from unittest.mock import MagicMock, patch
def test_post_media_record_json_and_auth():
# Arrange
import main
mock_resp = MagicMock()
mock_resp.status_code = 201
payload = {
"id": "h1",
"name": "a.jpg",
"path": "/x/a.jpg",
"mediaType": "Image",
"mediaStatus": 1,
"userId": "u1",
}
with patch.object(main.http_requests, "post", return_value=mock_resp) as post:
# Act
ok = main._post_media_record(payload, "tok")
# Assert
assert ok is True
post.assert_called_once()
args, kwargs = post.call_args
assert kwargs["json"] == payload
assert kwargs["headers"]["Authorization"] == "Bearer tok"
def test_put_media_status_json():
# Arrange
import main
mock_resp = MagicMock()
mock_resp.status_code = 204
with patch.object(main.http_requests, "put", return_value=mock_resp) as put:
# Act
ok = main._put_media_status("mid", 2, "t")
# Assert
assert ok is True
put.assert_called_once()
_args, kwargs = put.call_args
assert kwargs["json"] == {"mediaStatus": 2}
assert "/api/media/mid/status" in put.call_args[0][0]
+46
View File
@@ -0,0 +1,46 @@
import xxhash
def test_compute_media_content_hash_small_file():
# Arrange
from media_hash import compute_media_content_hash
data = b"x" * 100
expected = xxhash.xxh64(len(data).to_bytes(8, "little") + data).hexdigest()
# Act
out = compute_media_content_hash(data)
# Assert
assert out == expected
def test_compute_media_content_hash_large_file():
# Arrange
from media_hash import compute_media_content_hash
data = (bytes(range(256)) * 20)[:5000]
n = len(data)
mid = (n - 1024) // 2
blob = (
n.to_bytes(8, "little")
+ data[0:1024]
+ data[mid : mid + 1024]
+ data[-1024:]
)
expected = xxhash.xxh64(blob).hexdigest()
# Act
out = compute_media_content_hash(data)
# Assert
assert out == expected
def test_compute_media_content_hash_virtual_prefix():
# Arrange
from media_hash import compute_media_content_hash
# Act
v = compute_media_content_hash(b"abc", virtual=True)
n = compute_media_content_hash(b"abc", virtual=False)
# Assert
assert v.startswith("V")
assert not n.startswith("V")
assert v == "V" + n