Initial Aurora Core 0.1 implementation
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
Production-grade media metadata backend per docs/aurora-core-spec.md: Aurora-owned UUIDv7 identity system, normalized movie/TV/people/credits schema, provider abstraction with a TMDb adapter, an idempotent async ingestion pipeline (normalize -> match -> upsert -> provenance), a Postgres FTS/trigram search layer, a stateless FastAPI v1 API with Redis caching/rate-limiting, arq workers, dev/prod Docker Compose topologies, unit + integration + load tests, CI, and architecture/ database/API/ERD documentation. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""Unit tests for the Aurora ID system (app/core/ids.py) — section 3."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.ids import (
|
||||
ENTITY_PREFIXES,
|
||||
aurora_id,
|
||||
is_valid_aurora_id,
|
||||
new_uuid7,
|
||||
parse_aurora_id,
|
||||
)
|
||||
|
||||
|
||||
def test_new_uuid7_is_unique_and_well_formed():
|
||||
ids = {new_uuid7() for _ in range(1000)}
|
||||
assert len(ids) == 1000
|
||||
for value in list(ids)[:5]:
|
||||
assert len(value) == 36 # canonical hyphenated form
|
||||
|
||||
|
||||
def test_aurora_id_roundtrips_through_parse():
|
||||
u = new_uuid7()
|
||||
aid = aurora_id("movie", u)
|
||||
assert aid.startswith("aur_movie_")
|
||||
assert parse_aurora_id(aid, expected_type="movie") == u
|
||||
|
||||
|
||||
def test_aurora_id_never_leaks_provider_identity():
|
||||
# Aurora IDs must not be derived from or embed provider IDs (section 3).
|
||||
u = new_uuid7()
|
||||
aid = aurora_id("movie", u)
|
||||
assert "550" not in aid # a plausible TMDb id, just shouldn't appear
|
||||
assert "tmdb" not in aid
|
||||
|
||||
|
||||
def test_every_entity_prefix_produces_a_parseable_id():
|
||||
for entity_type in ENTITY_PREFIXES:
|
||||
aid = aurora_id(entity_type, new_uuid7())
|
||||
assert is_valid_aurora_id(aid, expected_type=entity_type)
|
||||
|
||||
|
||||
def test_parse_rejects_wrong_entity_type():
|
||||
aid = aurora_id("movie", new_uuid7())
|
||||
with pytest.raises(ValueError):
|
||||
parse_aurora_id(aid, expected_type="person")
|
||||
assert is_valid_aurora_id(aid, expected_type="person") is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"malformed",
|
||||
[
|
||||
"",
|
||||
"not-an-aurora-id",
|
||||
"aur_movie_",
|
||||
"aur_movie_tooshort",
|
||||
"aur_unknownprefix_" + "0" * 26,
|
||||
"aur_movie_" + "!" * 26,
|
||||
],
|
||||
)
|
||||
def test_parse_rejects_malformed_ids(malformed: str):
|
||||
with pytest.raises(ValueError):
|
||||
parse_aurora_id(malformed)
|
||||
assert is_valid_aurora_id(malformed) is False
|
||||
|
||||
|
||||
def test_aurora_ids_sort_in_creation_order():
|
||||
# UUIDv7 is time-ordered, and the Crockford base32 encoding preserves
|
||||
# byte order, so IDs generated later should sort later.
|
||||
first = aurora_id("movie", new_uuid7())
|
||||
second = aurora_id("movie", new_uuid7())
|
||||
assert first < second
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Unit tests for the TMDb normalizer (app/services/metadata/normalizer.py)
|
||||
— section 13. Uses hand-built payload fragments shaped like real TMDb
|
||||
responses rather than calling the network."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from app.services.metadata.normalizer import normalize_movie, normalize_person
|
||||
|
||||
FAKE_MOVIE = {
|
||||
"id": 603,
|
||||
"title": "The Matrix",
|
||||
"original_title": "The Matrix",
|
||||
"overview": "A computer hacker learns...",
|
||||
"tagline": "Welcome to the Real World",
|
||||
"status": "Released",
|
||||
"original_language": "en",
|
||||
"release_date": "1999-03-30",
|
||||
"runtime": 136,
|
||||
"budget": 63000000,
|
||||
"revenue": 463517383,
|
||||
"popularity": 45.5,
|
||||
"vote_average": 8.2,
|
||||
"vote_count": 24000,
|
||||
"adult": False,
|
||||
"imdb_id": "tt0133093",
|
||||
"genres": [{"id": 28, "name": "Action"}, {"id": 878, "name": "Science Fiction"}],
|
||||
"production_companies": [
|
||||
{"id": 79, "name": "Village Roadshow Pictures", "origin_country": "US"}
|
||||
],
|
||||
"poster_path": "/poster.jpg",
|
||||
"backdrop_path": "/backdrop.jpg",
|
||||
"belongs_to_collection": {"id": 2344, "name": "The Matrix Collection"},
|
||||
"credits": {
|
||||
"cast": [
|
||||
{
|
||||
"id": 6384,
|
||||
"name": "Keanu Reeves",
|
||||
"character": "Neo",
|
||||
"order": 0,
|
||||
"profile_path": "/keanu.jpg",
|
||||
"gender": 2,
|
||||
}
|
||||
],
|
||||
"crew": [
|
||||
{
|
||||
"id": 9339,
|
||||
"name": "Lana Wachowski",
|
||||
"department": "Directing",
|
||||
"job": "Director",
|
||||
"gender": 1,
|
||||
}
|
||||
],
|
||||
},
|
||||
"images": {
|
||||
"posters": [
|
||||
{"file_path": "/poster.jpg", "width": 2000, "height": 3000, "aspect_ratio": 0.667, "iso_639_1": "en", "vote_average": 8.1}
|
||||
],
|
||||
"backdrops": [],
|
||||
"logos": [],
|
||||
},
|
||||
"videos": {
|
||||
"results": [
|
||||
{
|
||||
"id": "abc123",
|
||||
"type": "Trailer",
|
||||
"site": "YouTube",
|
||||
"key": "vKQi3bBA1y8",
|
||||
"iso_639_1": "en",
|
||||
"iso_3166_1": "US",
|
||||
"official": True,
|
||||
"published_at": "1999-03-01T00:00:00.000Z",
|
||||
}
|
||||
]
|
||||
},
|
||||
"keywords": {"keywords": [{"id": 1, "name": "dystopia"}]},
|
||||
"alternative_titles": {"titles": [{"title": "Matrix", "iso_3166_1": "US"}]},
|
||||
"external_ids": {"imdb_id": "tt0133093"},
|
||||
"recommendations": {"results": [{"id": 604}]},
|
||||
}
|
||||
|
||||
FAKE_PERSON = {
|
||||
"id": 6384,
|
||||
"name": "Keanu Reeves",
|
||||
"biography": "Keanu Charles Reeves is a Canadian actor.",
|
||||
"birthday": "1964-09-02",
|
||||
"deathday": None,
|
||||
"place_of_birth": "Beirut, Lebanon",
|
||||
"gender": 2,
|
||||
"known_for_department": "Acting",
|
||||
"popularity": 30.2,
|
||||
"also_known_as": ["Keanu Charles Reeves"],
|
||||
"profile_path": "/keanu.jpg",
|
||||
"images": {"profiles": [{"file_path": "/keanu.jpg", "width": 1000, "height": 1500, "aspect_ratio": 0.667, "iso_639_1": None, "vote_average": 5.0}]},
|
||||
"external_ids": {"imdb_id": "nm0000206"},
|
||||
}
|
||||
|
||||
|
||||
def test_normalize_movie_maps_core_fields():
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
assert movie.external_id == "603"
|
||||
assert movie.title == "The Matrix"
|
||||
assert movie.status == "released"
|
||||
assert movie.release_date == date(1999, 3, 30)
|
||||
assert movie.runtime_minutes == 136
|
||||
assert movie.budget == 63000000
|
||||
assert movie.adult is False
|
||||
|
||||
|
||||
def test_normalize_movie_maps_genres_and_keywords():
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
assert movie.genres == ["Action", "Science Fiction"]
|
||||
assert movie.keywords == ["dystopia"]
|
||||
assert movie.alternate_titles == ["Matrix"]
|
||||
|
||||
|
||||
def test_normalize_movie_maps_cast_and_crew():
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
assert len(movie.cast) == 1
|
||||
assert movie.cast[0].external_id == "6384"
|
||||
assert movie.cast[0].department == "Acting"
|
||||
assert movie.cast[0].character == "Neo"
|
||||
assert movie.cast[0].gender == "male"
|
||||
|
||||
assert len(movie.crew) == 1
|
||||
assert movie.crew[0].department == "Directing"
|
||||
assert movie.crew[0].job == "Director"
|
||||
assert movie.crew[0].gender == "female"
|
||||
|
||||
|
||||
def test_normalize_movie_maps_artwork_and_videos():
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
poster_types = [a.artwork_type for a in movie.artwork]
|
||||
assert poster_types.count("poster") >= 1
|
||||
assert all(a.url.startswith("https://image.tmdb.org/t/p/original/") for a in movie.artwork)
|
||||
|
||||
assert len(movie.videos) == 1
|
||||
assert movie.videos[0].url == "https://www.youtube.com/watch?v=vKQi3bBA1y8"
|
||||
assert movie.videos[0].is_official is True
|
||||
|
||||
|
||||
def test_normalize_movie_external_ids_never_include_tmdb_itself():
|
||||
# TMDb's own id is handled separately by the ingestion pipeline via
|
||||
# `normalized.external_id`, not folded into `external_ids` (section 3:
|
||||
# Aurora doesn't conflate its own identity with provider identity).
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
assert "tmdb" not in movie.external_ids
|
||||
assert movie.external_ids["imdb"] == "tt0133093"
|
||||
|
||||
|
||||
def test_normalize_movie_maps_collection_and_recommendations():
|
||||
movie = normalize_movie(FAKE_MOVIE)
|
||||
assert movie.collection == ("2344", "The Matrix Collection")
|
||||
assert movie.recommendation_external_ids == ["604"]
|
||||
|
||||
|
||||
def test_normalize_movie_handles_missing_optional_sections():
|
||||
minimal = {"id": 1, "title": "Minimal Movie"}
|
||||
movie = normalize_movie(minimal)
|
||||
assert movie.title == "Minimal Movie"
|
||||
assert movie.genres == []
|
||||
assert movie.cast == []
|
||||
assert movie.artwork == []
|
||||
assert movie.collection is None
|
||||
|
||||
|
||||
def test_normalize_person_maps_core_fields():
|
||||
person = normalize_person(FAKE_PERSON)
|
||||
assert person.external_id == "6384"
|
||||
assert person.name == "Keanu Reeves"
|
||||
assert person.birthday == date(1964, 9, 2)
|
||||
assert person.deathday is None
|
||||
assert person.gender == "male"
|
||||
assert person.external_ids["imdb"] == "nm0000206"
|
||||
assert "Keanu Charles Reeves" in person.aliases
|
||||
|
||||
|
||||
def test_normalize_person_profile_artwork_deduplicated():
|
||||
person = normalize_person(FAKE_PERSON)
|
||||
# profile_path duplicates an entry already in images.profiles; it must
|
||||
# not be inserted twice.
|
||||
paths = [a.provider_asset_id for a in person.artwork]
|
||||
assert paths.count("/keanu.jpg") == 1
|
||||
Reference in New Issue
Block a user