Files
mediaplayer/sentence_api/tests/test_api.py

191 lines
5.8 KiB
Python

import json
from dataclasses import replace
from fastapi.testclient import TestClient
from sentence_api.main import create_app
from sentence_api.config import Settings
from sentence_api.oss import VolcanoOSSUploader
from sentence_api.models import SentenceBoundary, SentenceBoundaryDocument
from sentence_api.processing import VideoProcessor
from sentence_api.repository import VideoRepository
from sentence_api.store import BoundaryStore
VIDEO_HASH = "a" * 64
def make_client(tmp_path):
index_path = tmp_path / "boundaries.json"
index_path.write_text(
json.dumps({
"videos": {
VIDEO_HASH: {
"duration_ms": 9000,
"algorithm_version": "test-v1",
"sentences": [
{"index": 0, "start_ms": 0, "end_ms": 4000, "text": "One."},
{"index": 1, "start_ms": 4000, "end_ms": 9000, "text": "Two."},
],
}
}
}),
encoding="utf-8",
)
settings = replace(
Settings.from_env(),
data_dir=tmp_path / "data",
legacy_boundaries_file=index_path,
)
settings.ensure_directories()
return TestClient(
create_app(
BoundaryStore(index_path),
settings=settings,
repository=VideoRepository(settings.database_path),
)
)
def test_lookup_returns_boundaries(tmp_path):
client = make_client(tmp_path)
response = client.get(f"/api/v1/videos/{VIDEO_HASH}/sentence-boundaries")
assert response.status_code == 200
assert response.json()["sentences"][1]["start_ms"] == 4000
def test_unknown_hash_returns_not_found(tmp_path):
client = make_client(tmp_path)
response = client.get(f"/api/v1/videos/{'b' * 64}/sentence-boundaries")
assert response.status_code == 404
assert response.json()["detail"]["code"] == "SENTENCE_BOUNDARIES_NOT_FOUND"
def test_invalid_hash_is_rejected(tmp_path):
client = make_client(tmp_path)
response = client.get("/api/v1/videos/not-a-sha256/sentence-boundaries")
assert response.status_code == 422
def test_oss_video_url_is_used_for_catalog_and_content(tmp_path):
index_path = tmp_path / "boundaries.json"
index_path.write_text("{}", encoding="utf-8")
settings = replace(
Settings.from_env(),
data_dir=tmp_path / "data",
legacy_boundaries_file=index_path,
)
settings.ensure_directories()
repository = VideoRepository(settings.database_path)
repository.upsert_upload(
video_hash=VIDEO_HASH,
title="OSS lesson",
filename="lesson.mp4",
stored_filename=f"{VIDEO_HASH}.mp4",
content_type="video/mp4",
size_bytes=1,
language=None,
)
remote_url = f"https://course-media.example.com/videos/{VIDEO_HASH}.mp4"
repository.mark_oss_uploaded(VIDEO_HASH, remote_url)
client = TestClient(
create_app(
BoundaryStore(index_path),
settings=settings,
repository=repository,
)
)
catalog = client.get("/api/v1/videos")
stream = client.get(f"/api/v1/videos/{VIDEO_HASH}/content", follow_redirects=False)
assert catalog.status_code == 200
assert catalog.json()["videos"][0]["stream_url"] == remote_url
assert stream.status_code == 307
assert stream.headers["location"] == remote_url
def test_volcano_oss_object_key_and_public_url(tmp_path):
settings = replace(
Settings.from_env(),
volcano_oss_access_key="ak",
volcano_oss_secret_key="sk",
volcano_oss_bucket="course-media",
volcano_oss_endpoint="tos-cn-beijing.volces.com",
volcano_oss_region="cn-beijing",
volcano_oss_key_prefix="lessons/videos/",
)
uploader = VolcanoOSSUploader(settings)
key = uploader.object_key(VIDEO_HASH, f"{VIDEO_HASH}.mp4")
assert settings.volcano_oss_enabled is True
assert key == f"lessons/videos/{VIDEO_HASH}.mp4"
assert uploader.public_url(key) == (
f"https://course-media.tos-cn-beijing.volces.com/"
f"lessons/videos/{VIDEO_HASH}.mp4"
)
def test_processor_marks_ready_only_after_oss_upload(tmp_path, monkeypatch):
settings = replace(Settings.from_env(), data_dir=tmp_path / "data")
settings.ensure_directories()
media_path = settings.videos_dir / f"{VIDEO_HASH}.mp4"
media_path.write_bytes(b"video")
repository = VideoRepository(settings.database_path)
repository.upsert_upload(
video_hash=VIDEO_HASH,
title="OSS lesson",
filename="lesson.mp4",
stored_filename=f"{VIDEO_HASH}.mp4",
content_type="video/mp4",
size_bytes=5,
language=None,
)
document = SentenceBoundaryDocument(
video_hash=VIDEO_HASH,
duration_ms=1000,
algorithm_version="test",
sentences=[SentenceBoundary(index=0, start_ms=0, end_ms=1000)],
)
uploads = []
class EnabledUploader:
enabled = True
def upload(self, **kwargs):
uploads.append(kwargs)
return "https://media.example.com/lesson.mp4"
class UnavailableTranscriber:
available = False
monkeypatch.setattr(
"sentence_api.processing.make_entry",
lambda *args, **kwargs: (
{
"duration_ms": 1000,
"sentences": [{"index": 0, "start_ms": 0, "end_ms": 1000}],
},
{},
),
)
processor = VideoProcessor(
settings,
repository,
UnavailableTranscriber(),
EnabledUploader(),
)
processor.process(VIDEO_HASH)
video = repository.get_video(VIDEO_HASH)
assert uploads[0]["stored_filename"] == f"{VIDEO_HASH}.mp4"
assert video["status"] == "ready"
assert video["remote_url"] == "https://media.example.com/lesson.mp4"