"""Unit tests for the Lambda handler.""" import json from io import BytesIO from unittest.mock import MagicMock, patch import pytest from src import app from src.exceptions import AsgvSfnFeatureDisabledError @pytest.fixture(autouse=True) def mock_ff_enabled() -> None: """Enable the ASGV feature flag for all tests by default.""" with patch("src.app.check_asgv_sfn_enabled"): yield def test_handler_raises_when_feature_flag_disabled(mock_event: dict) -> None: """Handler raises AsgvSfnFeatureDisabledError when the FF is off.""" with patch( "src.app.check_asgv_sfn_enabled", side_effect=AsgvSfnFeatureDisabledError("disabled"), ): with pytest.raises(AsgvSfnFeatureDisabledError): app.handler(mock_event, None) def test_handler_returns_cleaned_product(mock_event: dict) -> None: """Handler returns a dict with expected top-level keys.""" result = app.handler(mock_event, None) product = result["product_metadata"] assert product["release_id"] == 2427859 assert product["title"] == "Lament" assert product["format"] == "EP" assert "tracks" in product assert len(product["tracks"]) == 2 def test_handler_returns_output_s3_path(mock_event: dict) -> None: """Handler returns output_s3_path built from label and review_queue_id.""" result = app.handler(mock_event, None) assert result["output_s3_path"] == "29366/0/2427859/12345" def test_handler_strips_internal_fields(mock_event: dict) -> None: """Handler output does not contain internal or system fields.""" result = app.handler(mock_event, None) product = result["product_metadata"] for key in ( "product_code", "label", "validation", "review_history", "release_correction", ): assert key not in product def test_handler_raises_on_missing_metadata() -> None: """Handler raises ValueError when product_metadata is absent.""" event = {"data": {"product_id": 1, "product_metadata": None}} with pytest.raises(ValueError, match="Missing product_metadata"): app.handler(event, None) def test_handler_raises_on_none_event() -> None: """Handler raises ValueError when event is None.""" with pytest.raises(ValueError, match="Missing product_metadata"): app.handler(None, None) def test_handler_raises_when_both_metadata_and_s3_path_missing() -> None: """Handler raises when both metadata and S3 path are missing.""" event = {"data": {"product_id": 1}} with pytest.raises(ValueError, match="Missing product_metadata"): app.handler(event, None) def test_handler_loads_from_s3_when_metadata_missing( mock_product_metadata: dict, ) -> None: """Handler fetches product_metadata from S3 when not present inline.""" event = { "data": { "product_metadata_s3_url": "s3://my-bucket/path/to/metadata.json", "review_queue_id": 12345, } } mock_s3 = MagicMock() mock_s3.get_object.return_value = { "Body": BytesIO(json.dumps(mock_product_metadata).encode()) } with patch("src.app.boto3.client", return_value=mock_s3): result = app.handler(event, None) mock_s3.get_object.assert_called_once_with( Bucket="my-bucket", Key="path/to/metadata.json", ExpectedBucketOwner=None ) product = result["product_metadata"] assert product["release_id"] == 2427859 assert product["title"] == "Lament" def test_handler_s3_exception_is_reraised() -> None: """Exceptions from S3 (e.g. NoSuchKey) are logged and re-raised.""" event = {"data": {"product_metadata_s3_url": "s3://my-bucket/missing.json"}} mock_s3 = MagicMock() mock_s3.get_object.side_effect = Exception("NoSuchKey") with patch("src.app.boto3.client", return_value=mock_s3): with pytest.raises(Exception, match="NoSuchKey"): app.handler(event, None)