"""Integration tests.""" import json import os import time from typing import Any import pytest import requests from tests.integration import config from tests.integration.mysql_client import MySQLClient from tests.integration.s3_client import S3Client @pytest.fixture def upc() -> str: """UPC of QA product fixture.""" return "192562876558" @pytest.fixture def md5() -> str: """md5 of video asset fixture.""" return "cadf79ad7e08d06b34d713d0a72459c4" def select_video_type(validity: str) -> dict[str, Any]: """Choose video type.""" video_data: dict[str, Any] = {} if validity == "invalid": video_data["video_key"] = "mv-test-assets/invalid_video.mov" video_data["video_size"] = 8043274 elif validity == "valid": video_data["video_key"] = "mv-test-assets/valid_video.mp4" video_data["video_size"] = 72249727 return video_data @pytest.fixture(scope="session", autouse=True) def seed_reference_data() -> None: """Seed reference tables required by FK constraints (local only).""" if os.environ.get("Environment", "qa") == "qa": return mysql_client = MySQLClient() product_types = [ (1, "Music"), (2, "TV Show"), (3, "Movie"), (4, "Video Etc"), (5, "Web Videos"), ] for type_id, type_name in product_types: mysql_client.execute_query( "INSERT IGNORE INTO product_type (id, product_type) VALUES (%s, %s)", type_id, type_name, ) for distribution_format_id in [1, 57]: mysql_client.execute_query( "INSERT IGNORE INTO distribution_format (distribution_format_id) VALUES (%s)", distribution_format_id, ) mysql_client.execute_query( "INSERT IGNORE INTO project (project_id) VALUES (%s)", 3323569, ) mysql_client.execute_query( "INSERT IGNORE INTO artist_info (artist_id, vendor_id) VALUES (%s, %s)", 780747, 16156, ) def new_video_product() -> Any: """Create new music video product.""" url = "{qa_base_url}/create_product".format(qa_base_url=config.QA_BASE_URL) header = {"Content-Type": "application/json"} payload = {"project_id": "3323569"} response_body = requests.post(url, headers=header, data=json.dumps(payload)).json() if "code" in response_body: pytest.fail('Failed on {0} with code "{1}"'.format(url, response_body["code"])) return response_body def workflow_response(video_data: dict[str, Any]) -> dict[str, Any]: """Start a new workflow.""" url = "{qa_base_url}/workflow/workflow_ingest_from_browser".format( qa_base_url=config.QA_BASE_URL ) release_id = new_video_product()["release_id"] header = { "Content-Type": "application/json", "Grass-Account-Type": "vendor", "Grass-Account-Id": "16156", "Orchard-User-Id": "alw:17411", } payload = { "type": "workflow_ingest_from_browser", "inputs": {}, "context": {"product_id": release_id}, "name": video_data["video_key"].split("/")[-1], "size": video_data["video_size"], } response_body = requests.post(url, headers=header, data=json.dumps(payload)).json() if "code" in response_body: pytest.fail('Failed on {0} with code "{1}"'.format(url, response_body["code"])) response = { "parent_id": response_body[0]["parent_id"], "release_id": release_id, "inputs": response_body[0]["inputs"], } for i in response_body: response[i["type"]] = str(i["id"]) return response def test_music_video_product_deletion() -> None: """Tests that music video products can be deleted.""" # Create new product product_id = new_video_product()["release_id"] # Send a DELETE request for newly created product ID header = {"Content-Type": "application/json"} url = "{qa_base_url}/product/{product_id}".format( qa_base_url=config.QA_BASE_URL, product_id=product_id ) assert requests.delete(url, headers=header).status_code == 200 # Assert that attempting to fetch metadata returns a 404 metadata_url = "{qa_base_url}/metadata/{product_id}".format( qa_base_url=config.QA_BASE_URL, product_id=product_id ) assert requests.get(metadata_url).status_code == 404 @pytest.mark.skipif( os.environ.get("Environment", "qa") != "qa", reason="Requires QA environment", ) def test_workflow_ingest_from_browser_valid_video() -> None: """Tests transfer_from_browser_to_s3 workflow for invalid video.""" video_data = select_video_type("valid") workflow_data = workflow_response(video_data) S3Client.upload_test_video(workflow_data["inputs"], video_data) link_product_to_pipeline_run(workflow_data) populate_metadata(workflow_data) wait_for_job_complete(180, "transfer_from_browser_to_s3", workflow_data) wait_for_job_complete(360, "extract_metadata", workflow_data) wait_for_job_complete(180, "validate_metadata", workflow_data) wait_for_job_complete(180, "calculate_audio_stats", workflow_data) wait_for_job_complete( 180, "detect_black_intervals_at_beginning_and_end_of_video", workflow_data ) wait_for_job_complete(180, "detect_max_volume", workflow_data) wait_for_job_complete( 180, "detect_silent_intervals_at_beginning_and_end_of_audio", workflow_data ) wait_for_job_complete( 180, "detect_video_location_and_dimensions_within_black_border", workflow_data ) wait_for_job_complete(180, "validate_analysis", workflow_data) wait_for_job_complete( 240, "create_streamable_preview_and_thumbnails", workflow_data ) submit_and_approve_product(workflow_data["release_id"]) approval_workflow_data = kick_off_approval_workflow(workflow_data) wait_for_job_complete(180, "get_create_mezzanines_inputs", approval_workflow_data) wait_for_job_complete(60, "get_product_metadata", approval_workflow_data) wait_for_job_complete(240, "create_mezzanines", approval_workflow_data) wait_for_job_complete( 30, "write_mezz_video_locations_to_video_asset_table", approval_workflow_data ) wait_for_job_complete(30, "convert_thumbnails_to_tiffs", approval_workflow_data) wait_for_job_complete( 30, "write_thumbnail_locations_to_video_asset_table", approval_workflow_data ) wait_for_job_complete(30, "mark_video_product_as_approved", approval_workflow_data) def wait_for_job_complete( secs: int, job_type: str, workflow_data: dict[str, Any] ) -> None: """Wait for a job's status to be COMPLETE.""" url = "{0}/job/{1}".format(config.QA_BASE_URL, workflow_data[job_type]) start_time = time.time() must_end = start_time + secs while True: response_body = requests.get(url).json() job_status = response_body["status"] if job_status == "COMPLETE": break elif job_status == "CANCELLED" or job_status == "ERROR": raise AssertionError( "\nThe job_type {0} failed with status {1}\nJob ID: {2}\nParent ID: {3}".format( job_type, job_status, response_body["id"], workflow_data["parent_id"], ) ) if time.time() > must_end: raise AssertionError( "\nStatus for job_type {0} is stuck at {1}\nJob ID: {2}\nParent ID: {3}".format( job_type, job_status, response_body["id"], workflow_data["parent_id"], ) ) time.sleep(10) return def link_product_to_pipeline_run(workflow_data: dict[str, Any]) -> None: """Link video product to its pipeline run (parent) id.""" parent_id = workflow_data["parent_id"] release_id = workflow_data["release_id"] mysql_client = MySQLClient() update_pipeline_run_id = """ UPDATE product_video SET latest_pipeline_run_id = {0} WHERE release_id = {1}; """ mysql_client.execute_query(update_pipeline_run_id.format(parent_id, release_id)) return def populate_metadata(workflow_data: dict[str, Any]) -> None: """Add metadata to video product.""" metadata_url = "{qa_base_url}/metadata/{product_id}".format( qa_base_url=config.QA_BASE_URL, product_id=workflow_data["release_id"] ) timestamp = int(time.time()) header = {"Content-Type": "application/json"} payload = { "type_of_video": "Official Music Video", "language_of_video_title": "ENG", "video_title": "Watir Video {0}".format(timestamp), "version": "Dope version", "product_code": str(timestamp), "description": "Dope description", "imprint": "Imprint {0}".format(timestamp), "language_of_video_content": "ENG", "lyrics": "sweet lyrics", "c_line_year": "2019", "c_line_copyright_holder": "Watir C Line", "p_line_year": "2019", "p_line_copyright_holder": "Watir P Line", "new_release": True, "release_date": time.strftime("%Y-%m-%d", time.localtime(time.time() + 150000)), "special_instructions": "My special instructions", "deliver_to_all": True, "parental_advisory": "Clean Version", "genre_id": 20, "subgenre_id": 662, "keywords": ["sweetrelease"], "contributors": [ {"role": "composer", "name": "Test Composer"}, {"role": "featuring", "name": "The Gian"}, ], "primary_artist_id": 780747, "is_valid": True, "preview_start_time": 15000, "thumbnail_path": "{0}/{1}.0000015.jpg".format( workflow_data["parent_id"], workflow_data["release_id"] ), "thumbnail_at_milliseconds": 15000, "channel_selection": set_youtube_channel(workflow_data["release_id"]), "not_for_distribution": "N", } requests.post(metadata_url, headers=header, data=json.dumps(payload)) return def set_youtube_channel(product_id: int) -> Any: """Select first channel name associated with product.""" url = "{qa_base_url}/product/{product_id}/get_available_channels".format( qa_base_url=config.QA_BASE_URL, product_id=product_id ) response = requests.get(url).json() if "code" in response: pytest.fail('Failed on {0} with code "{1}"'.format(url, response["code"])) return response[0]["channel_name"] def submit_and_approve_product(product_id: int) -> None: """Submit and approve the product.""" # Submit the product submit_url = "{qa_base_url}/product/{product_id}/submit".format( qa_base_url=config.QA_BASE_URL, product_id=product_id ) assert requests.post(submit_url).status_code == 200 # Reject the product approval_url = "{qa_base_url}/product/{product_id}/approval".format( qa_base_url=config.QA_BASE_URL, product_id=product_id ) header = {"Content-Type": "application/json", "Orchard-User-Id": "oa:1241"} rejection_payload = {"approval_type": "rejection", "value": "saaaaad"} requests.post(approval_url, headers=header, data=json.dumps(rejection_payload)) assert requests.get(approval_url).json()["rejection_reason"] == "saaaaad" # Resubmit the product assert requests.post(submit_url).status_code == 200 # Approve content, approve release, confirm final approval approvals = ["content", "release", "final"] for approval in approvals: assert ( requests.post( approval_url, headers=header, data=json.dumps(set_approval_type(approval)), ).status_code == 200 ) return def set_approval_type(approval_type: str) -> dict[str, Any]: """Update the post request payload based on approval type.""" payload = {"approval_type": approval_type, "value": True} if approval_type == "final": payload["approval_in_progress"] = True return payload def kick_off_approval_workflow(workflow_data: dict[str, Any]) -> dict[str, Any]: """Start an approval workflow.""" workflow_approval_url = "{qa_base_url}/workflow/workflow_approval".format( qa_base_url=config.QA_BASE_URL ) header = {"Content-Type": "application/json", "Orchard-User-Id": "oa:1241"} payload = { "workflow_ingest_job_id": workflow_data["parent_id"], "context": {"product_id": workflow_data["release_id"]}, } response_body = requests.post( workflow_approval_url, headers=header, data=json.dumps(payload) ).json() response = { "parent_id": response_body[0]["parent_id"], } for i in response_body: response[i["type"]] = str(i["id"]) return response