"""Transfer-ownership tests for the bulk sound-recording POST endpoints. `POST /sound-recording/streams` and `POST /sound-recording/aggregate-streams` take a list of ISRCs in the request body. Pinned to the 13 ISRCs of product 5244974, they exercise the v2 `permissions_filter` / `*_PRODUCT_TRANSFER` path for every transfer profile. Two layers per endpoint: * a **shape** sweep over every product ISRC for every profile (every owner of the product owns every ISRC, so the response must include each one); * an **FF-pair differential** on the canonical ISRC. The bulk POST endpoints share their permission filter with the single-ISRC GETs but their own SQL path — the differential pins that legacy hides the transferred-away product from a former owner (FF-OFF total = 0) and v2 grants the historical slice (FF-ON total > 0), the same scoping the single-ISRC suite checks. """ from __future__ import annotations import pytest from analytics import config from tests.integration.endpoints.conftest import assert_endpoint from tests.integration.transfer_ownership.conftest import ( ARTIST_PROFILE_PAIRS, ARTIST_PROFILES, ISRC, PROBES, PRODUCT_ISRCS, TRANSFER_PROFILE_PAIRS, TRANSFER_PROFILES, assert_isolated_transfer, profile_key_for, ) def _aggregate_streams_url() -> str: return f"{config.BASE_URL}{config.AGGREGATE_STREAMS_PATH}" def _streams_url(probe: str) -> str: return f"{config.BASE_URL}{config.STREAMS_BULK_PATH}?{PROBES[probe]}" def _post_bulk(url: str, headers: dict) -> dict: """POST the 13 product ISRCs; return the per-ISRC keyed payload.""" return assert_endpoint( url, headers=headers, method="POST", json_body={"isrcs": PRODUCT_ISRCS}, items_key=None, ) class TestAggregateStreamsBulk: """POST /sound-recording/aggregate-streams — ROLLUP all-time per ISRC.""" @pytest.mark.parametrize("hdrs", TRANSFER_PROFILES) def test_shape_all_isrcs(self, hdrs): """Every product ISRC returns the all-time payload shape.""" payload = _post_bulk(_aggregate_streams_url(), hdrs) for isrc in PRODUCT_ISRCS: assert isrc in payload, f"{isrc} missing from aggregate-streams response" assert "streams_all_time" in payload[isrc] assert "growth_percentage_7_days" in payload[isrc] all_time = payload[ISRC].get("streams_all_time") or 0 assert all_time > 0, ( f"{profile_key_for(hdrs)} sees no lifetime streams for {ISRC}" ) @pytest.mark.parametrize("pair", TRANSFER_PROFILE_PAIRS) def test_transferred_isrc_all_time(self, pair): """Canonical ISRC's all-time figure obeys the transfer scoping.""" url = _aggregate_streams_url() on = _post_bulk(url, pair.ff_on) off = _post_bulk(url, pair.ff_off) assert_isolated_transfer( (on.get(ISRC) or {}).get("streams_all_time") or 0, (off.get(ISRC) or {}).get("streams_all_time") or 0, role_key=pair.key, label=f"{pair.key} POST aggregate-streams {ISRC} streams_all_time", ) class TestStreamsBulk: """POST /sound-recording/streams — DAILY + ROLLUP per ISRC.""" @pytest.mark.parametrize("hdrs", TRANSFER_PROFILES) def test_shape_all_isrcs(self, hdrs): """Every product ISRC returns its per-ISRC payload shape.""" payload = _post_bulk(_streams_url("full"), hdrs) for isrc in PRODUCT_ISRCS: assert isrc in payload, f"{isrc} missing from streams response" entry = payload[isrc] assert entry.get("isrc") == isrc for field in ("aggregate", "stores", "sources"): assert field in entry, f"{isrc}: missing field {field}" @pytest.mark.parametrize("pair", TRANSFER_PROFILE_PAIRS) def test_transferred_isrc_full_window(self, pair): """Canonical ISRC's all-time figure obeys the transfer scoping. Legacy hides the transferred-away product from a former owner (`aggregate.all_time` == 0); v2 grants the historical slice (`aggregate.all_time` > 0); a current-view role sees the same total under both flags. """ url = _streams_url("full") on = _post_bulk(url, pair.ff_on) off = _post_bulk(url, pair.ff_off) on_all_time = ((on.get(ISRC) or {}).get("aggregate") or {}).get("all_time") or 0 off_all_time = ((off.get(ISRC) or {}).get("aggregate") or {}).get( "all_time" ) or 0 assert_isolated_transfer( on_all_time, off_all_time, role_key=pair.key, label=f"{pair.key} POST streams {ISRC} aggregate.all_time", ) # =========================================================================== # Participation-axis probes — the originating & destination artists # =========================================================================== # # The bulk POST endpoints are viewer-scoped by `permissions_filter`, so an # artist viewer is gated by the `permission_label_participant_ids` branch. The # originating artist (USED_TO grant) and destination artist (live participation) # resolve to the same isolated-transfer shapes as ala_vendor / sued_vendor, so # these classes mirror the ownership-axis ones above on the ARTIST axes. class TestAggregateStreamsBulkArtist: """POST /sound-recording/aggregate-streams — ROLLUP all-time, participation.""" @pytest.mark.parametrize("hdrs", ARTIST_PROFILES) def test_shape_all_isrcs(self, hdrs): """Every product ISRC returns the all-time payload shape.""" payload = _post_bulk(_aggregate_streams_url(), hdrs) for isrc in PRODUCT_ISRCS: assert isrc in payload, f"{isrc} missing from aggregate-streams response" assert "streams_all_time" in payload[isrc] assert "growth_percentage_7_days" in payload[isrc] all_time = payload[ISRC].get("streams_all_time") or 0 assert all_time > 0, ( f"{profile_key_for(hdrs)} sees no lifetime streams for {ISRC}" ) @pytest.mark.parametrize("pair", ARTIST_PROFILE_PAIRS) def test_transferred_isrc_all_time(self, pair): """Canonical ISRC's all-time figure obeys the participation scoping.""" url = _aggregate_streams_url() on = _post_bulk(url, pair.ff_on) off = _post_bulk(url, pair.ff_off) assert_isolated_transfer( (on.get(ISRC) or {}).get("streams_all_time") or 0, (off.get(ISRC) or {}).get("streams_all_time") or 0, role_key=pair.key, label=f"{pair.key} POST aggregate-streams {ISRC} streams_all_time", ) class TestStreamsBulkArtist: """POST /sound-recording/streams — DAILY + ROLLUP per ISRC, participation.""" @pytest.mark.parametrize("hdrs", ARTIST_PROFILES) def test_shape_all_isrcs(self, hdrs): """Every product ISRC returns its per-ISRC payload shape.""" payload = _post_bulk(_streams_url("full"), hdrs) for isrc in PRODUCT_ISRCS: assert isrc in payload, f"{isrc} missing from streams response" entry = payload[isrc] assert entry.get("isrc") == isrc for field in ("aggregate", "stores", "sources"): assert field in entry, f"{isrc}: missing field {field}" @pytest.mark.parametrize("pair", ARTIST_PROFILE_PAIRS) def test_transferred_isrc_full_window(self, pair): """Canonical ISRC's all-time figure obeys the participation scoping. Legacy hides the transferred-away product from the originating artist (`aggregate.all_time` == 0); v2 grants the USED_TO historical slice (`aggregate.all_time` > 0); the destination artist sees the same total under both flags. """ url = _streams_url("full") on = _post_bulk(url, pair.ff_on) off = _post_bulk(url, pair.ff_off) on_all_time = ((on.get(ISRC) or {}).get("aggregate") or {}).get("all_time") or 0 off_all_time = ((off.get(ISRC) or {}).get("aggregate") or {}).get( "all_time" ) or 0 assert_isolated_transfer( on_all_time, off_all_time, role_key=pair.key, label=f"{pair.key} POST streams {ISRC} aggregate.all_time", )