"""Helper functions for step function integration tests.""" from collections import defaultdict import logging from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union import uuid def generate_random_uuid() -> str: """Generate a random UUID.""" random_id = str(uuid.uuid4()) logging.info('Generated UUID: %s', random_id) return random_id def get_vendor_products(api_client: Any, vendor_id: int) -> List[int]: """ Fetch product IDs for a given vendor. """ url = f'https://qa-ows-product.theorchard.io/vendor/{vendor_id}/products' params = { 'status': 'in_content', 'page_limit': 10, 'page_offset': 0, } response = api_client.get(url, params=params) product_ids: List[int] = [item['product_id'] for item in response['items']] return product_ids def normalize_and_group_response( raw_response: Union[Any, Tuple[int, Any]], fields: List[str], ) -> Dict[int, List[Dict[str, str]]]: """ Normalize and group a response by 'product_id', keeping only specified fields. Args: raw_response: API response; can be a dict, list of dicts, or a tuple indicating an error. fields: List of field names to include in each grouped item. Returns: A dictionary grouped by product_id with filtered field data, or an empty dictionary if response status is not 200. """ if isinstance(raw_response, tuple): status_code, error_data = raw_response logging.warning( f'Response returned {status_code}: {error_data} – returning empty result.' ) return {} if isinstance(raw_response, list): response = raw_response elif isinstance(raw_response, dict): response = raw_response['items'] if 'items' in raw_response else [raw_response] else: raise ValueError(f'Unexpected API response format: {type(raw_response)}') grouped: Dict[int, List[Dict[str, str]]] = defaultdict(list) for item in response: product_id = int(item['product_id']) filtered_item = {field: str(item[field]) for field in fields if field in item} grouped[product_id].append(filtered_item) return dict(grouped) # convert back to normal dict def filter_response_by_key_contains_word( response: Union[Dict[str, Any], List[Dict[str, Any]]], key: str, words: List[str] ) -> List[Dict[str, Any]]: """ Filter response items where the value of a given key contains any of the specified words. Args: response: A single dict or list of dicts representing the response. key: The key to inspect in each item. words: A list of substrings to match in the key's value. Returns: A filtered list of dicts where the key's value contains at least one word. Returns the response if the key is missing in all items. """ if isinstance(response, dict): response = [response] if not isinstance(response, list): return [] key_exists = any(key in item for item in response) if not key_exists: return response return [ item for item in response if key in item and isinstance(item[key], str) and any(word in item[key] for word in words) ] def get_music_assets_for_products( api_client_with_token: Any, product_ids: List[int], asset_types: List[str] ) -> Dict[int, List[Dict[str, str]]]: """ Fetch music assets for a set of product IDs. """ url = 'https://qa-ows-assets.theorchard.io/v2/assets-bulk' params = { 'product_ids': ','.join(map(str, product_ids)), 'asset_types': ','.join(asset_types), } raw_response = api_client_with_token.get(url, params=params) filtered_response = filter_response_by_key_contains_word( raw_response, 's3_bucket', ['qa-'] ) music_assets = normalize_and_group_response( filtered_response, ['tuid', 'asset_type', 's3_bucket', 's3_key'] ) logging.info(f'\nMusic Assets: {music_assets}\n') return music_assets def get_video_assets_for_products( api_client_with_token: Any, product_ids: List[int], asset_types: List[str] ) -> Dict[int, List[Dict[str, str]]]: """ Fetch video asset S3 keys for a set of product IDs and asset types. """ url = 'https://qa-ows-video.theorchard.io/assets-bulk' params = { 'product_ids': ','.join(map(str, product_ids)), 'asset_types': ','.join(asset_types), } raw_response = api_client_with_token.get(url, params=params) filtered_response = filter_response_by_key_contains_word( raw_response, 's3_bucket', ['qa-'] ) video_assets = normalize_and_group_response( filtered_response, ['tuid', 'asset_type', 's3_bucket', 's3_key'] ) logging.info(f'\nVideo Assets: {video_assets}\n') return video_assets def get_tracks_for_products( api_client_with_token: Any, product_ids: List[int] ) -> Dict[int, List[Dict[str, str]]]: """ Fetch tracks for a set of product IDs. """ url = 'https://qa-ows-track.theorchard.io/tracks-bulk' params = {'product_ids': ','.join(map(str, product_ids))} raw_response = api_client_with_token.get(url, params=params) track_info = normalize_and_group_response( raw_response, ['tuid', 'upc', 'volume_number', 'track_number'] ) logging.info(f'\nTrack Info: {track_info}\n') return track_info def build_artwork_filename(upc: str) -> str: """Generate artwork filename for TIF asset.""" return f'{upc}/{upc}.tif' def build_media_filename( upc: str, volume: str, track_number: str, extension: str ) -> str: """Generate audio/video filename using UPC, volume, and track number.""" return f'{upc}/{upc}_{volume}_{track_number}.{extension}' def get_upc_from_track_info( track_info: Dict[int, List[Dict[str, str]]], product_id: int ) -> Optional[str]: """Extract UPC from the first track in the given product's track info.""" tracks = track_info.get(product_id) if tracks and 'upc' in tracks[0]: return tracks[0]['upc'] return None def get_tuid_lookup( track_info: Dict[int, List[Dict[str, str]]], ) -> Dict[str, Dict[str, str]]: """ Build a lookup of tuid → track metadata (upc, volume_number, track_number). Returns a dictionary keyed by tuid with track details as values. """ lookup: Dict[str, Dict[str, str]] = {} for tracks in track_info.values(): for track in tracks: tuid = track.get('tuid') if tuid: lookup[tuid] = { 'upc': track.get('upc', ''), 'volume_number': track.get('volume_number', ''), 'track_number': track.get('track_number', ''), } return lookup def filenames_in_output_bucket( track_info: Dict[int, List[Dict[str, str]]], assets_by_product: Dict[int, List[Dict[str, str]]], ) -> Dict[int, List[Dict[str, str]]]: """ Build filenames in output bucket from track and asset metadata. Returns: Dict mapping product_id to list of {'tuid': ..., 'filename': ...} """ output: Dict[int, List[Dict[str, str]]] = {} tuid_lookup: Dict[str, Dict[str, str]] = get_tuid_lookup(track_info) for product_id, assets in assets_by_product.items(): filenames: List[Dict[str, str]] = [] for asset in assets: asset_type = asset.get('asset_type') tuid = asset.get('tuid') if not tuid: continue # skip assets with no tuid if asset_type == 'TIF': upc = get_upc_from_track_info(track_info, product_id) if upc: filenames.append( {'tuid': tuid, 'filename': build_artwork_filename(upc)} ) elif asset_type in {'WAV', 'video_master'}: track = tuid_lookup.get(tuid) if track: upc = track['upc'] volume = track['volume_number'] track_number = track['track_number'] # Map asset_type to file extension if asset_type == 'video_master': extension = 'mov' else: extension = asset_type.lower() filenames.append( { 'tuid': tuid, 'filename': build_media_filename( upc, volume, track_number, extension ), } ) if filenames: output[product_id] = filenames logging.info(f'\nFilenames to verify in output bucket: {output}\n') return output def merge_assets( *asset_dicts: Dict[int, List[Dict[str, Any]]], ) -> Dict[int, List[Dict[str, Any]]]: merged: DefaultDict[int, List[Dict[str, Any]]] = defaultdict(list) for asset_dict in asset_dicts: for product_id, assets in asset_dict.items(): merged[product_id].extend(assets) logging.info(f'\nassets_by_product: {merged}\n') return dict(merged) def stored_checksum_for_asset( s3_client: Any, input_assets: Dict[int, List[Dict[str, str]]] ) -> Dict[int, List[Dict[str, str]]]: """ Calculate expected checksum for given assets. Get stored checksum from input bucket (qa-orcd-mezzanine-assets) """ stored_checksum_for_assets: Dict[int, List[Dict[str, str]]] = {} for product_id, assets in input_assets.items(): stored_checksum_for_assets[product_id] = [] for asset in assets: stored_metadata = s3_client.get_s3_object_data( asset['s3_bucket'], asset['s3_key'] ) stored_checksum = s3_client.calculate_sha256_base64(stored_metadata) stored_checksum_for_assets[product_id].append( { 'tuid': asset['tuid'], 'asset_type': asset['asset_type'], 's3_bucket': asset['s3_bucket'], 's3_key': asset['s3_key'], 'checksum': stored_checksum, } ) logging.info(f'\n stored_checksum_for_assets: {stored_checksum_for_assets}\n') return stored_checksum_for_assets