"""Tests v43 DDEX deal generation."""
from datetime import datetime, timezone
from textwrap import dedent
from unittest.mock import patch
import pytest
from freezegun import freeze_time
from soundrecording_utils.constants.ddex.constants import RuleService
from soundrecording_utils.ddex.v43 import deal
from soundrecording_utils.ddex.v43.e import E
from soundrecording_utils.metadata.types import FingerprintRule, OrchardSoundRecording, Product, Track, TrackConnection
@freeze_time('2024-02-01')
def test_generate_sound_recording_deals(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers,
):
"""Test _generate_sound_recording_deals."""
mock_t1_rules = [
mock_fingerprint_rule._replace(
territory='CA',
service='youtube',
),
mock_fingerprint_rule._replace(
territory='US',
service='youtube',
),
]
mock_t2_rules = [
mock_fingerprint_rule._replace(
territory='UK',
service='youtube',
),
mock_fingerprint_rule._replace(
territory='US',
service='youtube',
),
]
mock_t3_rules = [
mock_fingerprint_rule._replace(
territory='CA',
service='youtube',
),
mock_fingerprint_rule._replace(
territory='MX',
service='youtube',
),
]
mock_t4_rules = [
mock_fingerprint_rule._replace(
territory='FR',
policy='block_access',
end_date=datetime(2025, 1, 1, 10, 1, 40, tzinfo=timezone.utc),
service='youtube',
),
]
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
tuid='1',
rules=mock_t1_rules,
),
mock_track._replace(
tuid='2',
rules=mock_t2_rules,
),
mock_track._replace(
tuid='3',
rules=mock_t3_rules,
),
mock_track._replace(
tuid='4',
rules=mock_t4_rules,
)
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
# group deals by policy type+start date+end date
# "US" and "CA" de-duplicated
result = helpers.print_xml(result)
expected = """
CA
MX
UK
US
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
FR
2024-01-01T10:01:40
2025-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
BlockAccess
"""
assert result == dedent(expected).strip()
@freeze_time('2024-02-01')
def test_generate_sound_recording_deals_with_sixtyseconds_policy(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers,
):
"""Test that sixtyseconds policies emit conditional RightsClaimPolicy nodes."""
sixtyseconds_rules = [
mock_fingerprint_rule._replace(
policy='sixtyseconds',
territory='US',
service='youtube',
),
]
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
tuid='1',
rules=sixtyseconds_rules,
),
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
result = helpers.print_xml(result)
expected = """
US
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
60
Seconds
ConsumerResource
LessThanOrEqualTo
Audio
Monetize
60
Seconds
ConsumerResource
MoreThanOrEqualTo
Audio
BlockAccess
"""
assert result == dedent(expected).strip()
@freeze_time('2019-06-15')
def test_generate_sound_recording_deals_takedown(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers
):
"""Test _generate_sound_recording_details w/o territories."""
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
rules=[]
)
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
result = helpers.print_xml(result)
# no territories means "Worldwide" and 2 days ago as EndDateTime
expected = """
Worldwide
2019-06-13T00:00:00
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
"""
assert result == dedent(expected).strip()
def test_generate_deal_value_error():
"""Test exception on no date params."""
expected = 'Deal must include start and/or end date'
with pytest.raises(ValueError, match=expected):
deal._generate_sound_recording_deal(['US'], 'Monetize')
def test_generate_deal_non_utc_offset(helpers):
"""Test deal serialization with a non-UTC timezone offset."""
from datetime import timedelta
ist = timezone(timedelta(hours=5, minutes=30))
start = datetime(2024, 1, 1, 10, 1, 40, tzinfo=ist)
result = deal._generate_sound_recording_deal(['IN'], 'Monetize', start_date=start)
result = helpers.print_xml(result)
expected = """
IN
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
"""
assert result == dedent(expected).strip()
def test_generate_deal_date_only_fallback(helpers):
"""Test deal with a datetime derived from a bare date (UTC midnight)."""
start = datetime(2025, 3, 1, tzinfo=timezone.utc)
result = deal._generate_sound_recording_deal(['US'], 'Monetize', start_date=start)
result = helpers.print_xml(result)
expected = """
US
2025-03-01T00:00:00
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
"""
assert result == dedent(expected).strip()
def test_generate_deal_none_end_date(helpers):
"""Test deal with None end date produces no EndDateTime element."""
start = datetime(2024, 6, 1, tzinfo=timezone.utc)
result = deal._generate_sound_recording_deal(['US'], 'Monetize', start_date=start, end_date=None)
result = helpers.print_xml(result)
expected = """
US
2024-06-01T00:00:00
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
"""
assert result == dedent(expected).strip()
@freeze_time('2024-02-01')
def test_generate_sound_recording_deals_with_sixtyseconds_and_monetize(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers,
):
"""Test sixtyseconds overrides monetize in consolidated policy."""
rules = [
mock_fingerprint_rule._replace(
policy='monetize',
territory='US',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='sixtyseconds',
territory='US',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='monetize',
territory='CA',
service='youtube',
),
]
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
tuid='1',
rules=rules,
),
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
result = helpers.print_xml(result)
expected = """
CA
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
US
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
60
Seconds
ConsumerResource
LessThanOrEqualTo
Audio
Monetize
60
Seconds
ConsumerResource
MoreThanOrEqualTo
Audio
BlockAccess
"""
assert result == dedent(expected).strip()
@freeze_time('2024-02-01')
def test_generate_sound_recording_deals_with_block_overrides_sixtyseconds(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers,
):
"""Test block_access overrides sixtyseconds in consolidated policy."""
rules = [
mock_fingerprint_rule._replace(
policy='sixtyseconds',
territory='US',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='block_access',
territory='US',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='sixtyseconds',
territory='CA',
service='youtube',
),
]
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
tuid='1',
rules=rules,
),
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
result = helpers.print_xml(result)
expected = """
CA
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
60
Seconds
ConsumerResource
LessThanOrEqualTo
Audio
Monetize
60
Seconds
ConsumerResource
MoreThanOrEqualTo
Audio
BlockAccess
US
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
BlockAccess
"""
assert result == dedent(expected).strip()
@freeze_time('2024-02-01')
def test_generate_sound_recording_deals_with_mixed_all_policies(
mock_orchard_sound_recording: OrchardSoundRecording,
mock_product: Product,
mock_track: Track,
mock_fingerprint_rule: FingerprintRule,
helpers,
):
"""Test mixed monetize, sixtyseconds, and block_access policies in one deal."""
rules = [
mock_fingerprint_rule._replace(
policy='monetize',
territory='FR',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='sixtyseconds',
territory='DE',
service='youtube',
),
mock_fingerprint_rule._replace(
policy='block_access',
territory='UK',
service='youtube',
),
]
mock_orchard_sound_recording = mock_orchard_sound_recording._replace(
track_connection=TrackConnection(
tracks=[
mock_track._replace(
tuid='1',
rules=rules,
),
]
)
)
result = deal._generate_sound_recording_deals(
mock_orchard_sound_recording, RuleService.YOUTUBE
)
result = E(
'TestDealContainer',
*result,
)
result = helpers.print_xml(result)
expected = """
FR
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
Monetize
DE
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
60
Seconds
ConsumerResource
LessThanOrEqualTo
Audio
Monetize
60
Seconds
ConsumerResource
MoreThanOrEqualTo
Audio
BlockAccess
UK
2024-01-01T10:01:40
RightsClaimModel
UserMakeAvailableUserProvided
BlockAccess
"""
assert result == dedent(expected).strip()
@patch('soundrecording_utils.ddex.v43.deal._generate_sound_recording_deals')
def test_generate_release_deal(
mock_generate_sound_recording_deals,
mock_orchard_sound_recording: OrchardSoundRecording,
helpers,
):
"""Test generate_release_deal."""
mock_generate_sound_recording_deals.return_value = (
E('MockSoundRecordingDeal1'),
E('MockSoundRecordingDeal2'),
)
result = deal.generate_release_deal(
'R0',
mock_orchard_sound_recording,
RuleService.YOUTUBE
)
result = helpers.print_xml(result)
expected = """
R0
"""
assert result == dedent(expected).strip()