test_reader.py 6.83 KB
from unittest.mock import MagicMock
import etl_to_crawler.reader as reader

from etl_to_crawler.reader import (
    fetch_all_song_record_relations,
    fetch_platform_records_by_record_ids,
    fetch_platform_records,
    iter_hk_songs_batches,
    select_primary_record,
)


def _make_cursor(rows):
    cur = MagicMock()
    cur.__enter__ = MagicMock(return_value=cur)
    cur.__exit__ = MagicMock(return_value=False)
    cur.fetchall.return_value = rows
    return cur


def test_fetch_platform_records_keeps_one_record_per_song_and_platform():
    conn = MagicMock()
    conn.cursor.return_value = _make_cursor([
        {
            'source_song_id': 10,
            'record_id': 100,
            'platform': '1',
            'platform_unique_key': 'qq-mid',
            'platform_mid': '100',
            'album_audio_id': None,
            'is_main_version': 0,
            'is_high': 1,
            'pub_time': '2020-01-01',
        },
        {
            'source_song_id': 10,
            'record_id': 101,
            'platform': '2',
            'platform_unique_key': '200',
            'platform_mid': 'kg-hash',
            'album_audio_id': None,
            'is_main_version': 1,
            'is_high': 0,
            'pub_time': '2021-01-01',
        },
        {
            'source_song_id': 10,
            'record_id': 102,
            'platform': '4',
            'platform_unique_key': '300',
            'platform_mid': None,
            'album_audio_id': None,
            'is_main_version': 1,
            'is_high': 1,
            'pub_time': '2022-01-01',
        },
    ])

    result = fetch_platform_records(conn, [10])

    assert len(result) == 3
    assert {row['record_id'] for row in result} == {100, 101, 102}


def test_fetch_platform_records_by_record_ids_queries_exact_records_once():
    conn = MagicMock()
    conn.cursor.return_value = _make_cursor([
        {
            'source_song_id': 10,
            'record_id': 200,
            'platform': '2',
            'platform_unique_key': '200',
            'platform_mid': 'kg-hash',
            'album_audio_id': None,
            'is_main_version': 1,
            'is_high': 0,
            'pub_time': '2021-01-01',
        },
    ])

    result = fetch_platform_records_by_record_ids(conn, [200, 200])

    cur = conn.cursor.return_value
    sql, params = cur.execute.call_args[0]
    assert 'WHERE mr.id IN (%s)' in sql
    assert params == [200]
    assert result[0]['record_id'] == 200
    assert result[0]['platform'] == '2'


def test_retry_fetch_can_include_soft_deleted_hk_songs():
    conn = MagicMock()
    conn.cursor.return_value = _make_cursor([])

    reader.fetch_hk_songs_by_source_ids(conn, [10], include_deleted=True)

    sql, params = conn.cursor.return_value.execute.call_args.args
    assert "deleted = '0'" not in sql
    assert 'WHERE 1 = 1' in sql
    assert params == [10]


def test_mark_hk_songs_active_restores_retry_sources():
    conn = MagicMock()
    conn.cursor.return_value = _make_cursor([])
    conn.cursor.return_value.rowcount = 2

    restored = reader.mark_hk_songs_active(conn, [10, 10, 11])

    sql, params = conn.cursor.return_value.execute.call_args.args
    assert "SET deleted = '0'" in sql
    assert "AND deleted = '1'" in sql
    assert params == [10, 11]
    assert restored == 2


def test_fetch_all_song_record_relations_keeps_all_records_for_a_song():
    conn = MagicMock()
    conn.cursor.return_value = _make_cursor([
        {'source_song_id': 10, 'record_id': 100, 'platform': '1'},
        {'source_song_id': 10, 'record_id': 101, 'platform': '1'},
        {'source_song_id': 10, 'record_id': 200, 'platform': '2'},
        {'source_song_id': 10, 'record_id': 101, 'platform': '1'},
    ])

    result = fetch_all_song_record_relations(conn, [10])

    sql, params = conn.cursor.return_value.execute.call_args[0]
    assert 'mr.record_name IS NOT NULL' in sql
    assert 'mr.duration IS NOT NULL AND mr.duration > 0' in sql
    assert 'mr.singer_name IS NOT NULL' in sql
    assert 'mr.platform_index_url IS NOT NULL' in sql
    assert 'mr.storage_url IS NOT NULL' in sql
    assert 'mr.platform_play_url IS NOT NULL' in sql
    assert 'mr.pub_time IS NOT NULL' in sql
    assert params == [10]
    assert [(row['record_id'], row['platform']) for row in result] == [
        (100, '1'), (101, '1'), (200, '2'),
    ]


def test_select_primary_record_prefers_main_version_then_is_high():
    rows = [
        {
            'source_song_id': 10,
            'record_id': 100,
            'platform': '1',
            'platform_unique_key': 'qq-mid',
            'platform_mid': '100',
            'album_audio_id': None,
            'is_main_version': 0,
            'is_high': 1,
            'pub_time': '2020-01-01',
        },
        {
            'source_song_id': 10,
            'record_id': 101,
            'platform': '2',
            'platform_unique_key': '200',
            'platform_mid': 'kg-hash',
            'album_audio_id': None,
            'is_main_version': 1,
            'is_high': 0,
            'pub_time': '2021-01-01',
        },
        {
            'source_song_id': 10,
            'record_id': 102,
            'platform': '4',
            'platform_unique_key': '300',
            'platform_mid': None,
            'album_audio_id': None,
            'is_main_version': 1,
            'is_high': 1,
            'pub_time': '2022-01-01',
        },
    ]

    assert select_primary_record(rows)['record_id'] == 102


def test_select_primary_record_uses_earliest_pub_time_when_priority_ties():
    rows = [
        {
            'source_song_id': 10,
            'record_id': 100,
            'platform': '1',
            'platform_unique_key': 'qq-mid',
            'platform_mid': '100',
            'album_audio_id': None,
            'is_main_version': 0,
            'is_high': 0,
            'pub_time': '2020-01-01',
        },
        {
            'source_song_id': 10,
            'record_id': 101,
            'platform': '2',
            'platform_unique_key': '200',
            'platform_mid': 'kg-hash',
            'album_audio_id': None,
            'is_main_version': 0,
            'is_high': 0,
            'pub_time': '2019-01-01',
        },
    ]

    assert select_primary_record(rows)['record_id'] == 101


def test_iter_hk_songs_batches_uses_id_cursor_instead_of_offset():
    cur = _make_cursor([])
    cur.fetchall.side_effect = [
        [{'id': 10, 'source_song_id': 100}, {'id': 20, 'source_song_id': 200}],
        [{'id': 25, 'source_song_id': 250}],
    ]
    conn = MagicMock()
    conn.cursor.return_value = cur

    batches = list(iter_hk_songs_batches(conn, batch_size=2, start_after_id=5))

    assert batches == [
        [{'id': 10, 'source_song_id': 100}, {'id': 20, 'source_song_id': 200}],
        [{'id': 25, 'source_song_id': 250}],
    ]
    assert cur.execute.call_args_list[0][0][1] == (5, 2)
    assert cur.execute.call_args_list[1][0][1] == (20, 2)