"""Unit tests.""" import pytest from datadog_api_client.v2.model.state import State from src import app @pytest.fixture def mock_env(monkeypatch): """Set dummy config variables for Datadog credentials and Rule ID.""" monkeypatch.setattr(app.config, "DD_API_KEY", "fake_key") monkeypatch.setattr(app.config, "DD_APP_KEY", "fake_app_key") monkeypatch.setattr(app.config, "SCORECARD_RULE_ID", "test-rule") @pytest.fixture def mock_datadog_utils(mocker): """Mock the datadog_utils module functions. Returns: tuple: (mock_list_services, mock_list_frontends, mock_update_service_outcomes, mock_update_non_service_outcomes) """ m_srv_list = mocker.patch("src.app.list_services_with_details") m_fe_list = mocker.patch("src.app.list_frontends_with_details") m_update = mocker.patch("src.app.update_scorecard_service_outcomes_batch") m_update_non_svc = mocker.patch("src.app.update_scorecard_non_service_outcomes") return m_srv_list, m_fe_list, m_update, m_update_non_svc def test_handler_missing_creds(mocker, monkeypatch): """Ensure handler raises ValueError if credentials are missing.""" monkeypatch.setattr(app.config, "DD_API_KEY", None) m_srv_list = mocker.patch("src.app.list_services_with_details") m_fe_list = mocker.patch("src.app.list_frontends_with_details") with pytest.raises(ValueError, match="Missing DD_API_KEY or DD_APP_KEY"): app.handler({}, {}) m_srv_list.assert_not_called() m_fe_list.assert_not_called() def test_handler_various_scenarios(mock_env, mock_datadog_utils): """Test the handler against various service scenarios. Scenarios covered: 1. Unsupported language -> FAIL 2. Supported language but missing version tag -> FAIL 3. Supported language, version < minimum -> FAIL 4. Supported language, version >= minimum -> PASS 5. Supported language (case-insensitive check), version >= minimum -> PASS 6. Excluded service -> SKIP """ ( mock_list_services, mock_list_frontends, mock_update_outcomes, mock_update_non_svc_outcomes, ) = mock_datadog_utils # Define services with different scenarios services = [ # 1. Unsupported Language { "name": "service-go", "namespace": "default", "kind": "service", "languages": ["haskell"], "tags": [], }, # 2. Supported Language (Python), No Version Tag { "name": "service-py-no-tag", "namespace": "default", "kind": "service", "languages": ["python"], "tags": ["env:prod"], }, # 3. Supported Language (Python), Version Too Low (3.10 < 3.11) { "name": "service-py-old", "namespace": "default", "kind": "service", "languages": ["python"], "tags": ["language_version:3.10.1"], }, # 4. Supported Language (Python), Version OK (3.12 >= 3.11) { "name": "service-py-ok", "namespace": "default", "kind": "service", "languages": ["Python"], # Case insensitive check "tags": ["language_version:v3.12"], }, # 5. Supported Language (Java), Version OK (17 >= 17) { "name": "service-java-ok", "namespace": "default", "kind": "service", "languages": ["java"], "tags": ["language_version:17"], }, # 6. Excluded Service (Should be ignored) { "name": "excluded-svc", "namespace": "default", "kind": "service", "languages": ["python"], "tags": ["language_version:3.5"], }, ] frontends = [ # 1. Unsupported Language { "name": "frontend-html", "namespace": "default", "kind": "frontend", "languages": ["html"], "tags": [], }, # 2. Supported Language (JS), No Version Tag { "name": "frontend-js-no-tag", "namespace": "default", "kind": "frontend", "languages": ["js"], "tags": ["env:prod"], }, # 3. Supported Language (JS), Version Too Low (18 < 22) { "name": "frontend-js-old", "namespace": "default", "kind": "frontend", "languages": ["js"], "tags": ["language_version:18"], }, # 4. Supported Language (JS), Version OK (24 > 22) { "name": "frontend-js-ok", "namespace": "default", "kind": "frontend", "languages": ["js"], "tags": ["language_version:v24.3.1"], }, ] # Mock config app.config.EXCLUDED_SERVICES = [{"pattern": "excluded-svc", "reason": "test exclusion reason"}] mock_list_services.return_value = services mock_list_frontends.return_value = frontends app.handler({}, {}) # Verify service update called once assert mock_update_outcomes.call_count == 1 service_call_args = mock_update_outcomes.call_args[0][2] service_results = {o["service_name"]: o for o in service_call_args} # Verify non-service update called once assert mock_update_non_svc_outcomes.call_count == 1 non_svc_call_args = mock_update_non_svc_outcomes.call_args[0][2] non_svc_results = {o["entity_reference"]: o for o in non_svc_call_args} # 1. Unsupported -> Fail assert service_results["service-go"]["state"] == State.FAIL assert "not one of the checked languages" in service_results["service-go"]["remarks"] assert non_svc_results["frontend:frontend-html"]["state"] == State.FAIL assert "not one of the checked languages" in non_svc_results["frontend:frontend-html"]["remarks"] # 2. No Tag -> Fail assert service_results["service-py-no-tag"]["state"] == State.FAIL assert "Missing 'language_version'" in service_results["service-py-no-tag"]["remarks"] assert non_svc_results["frontend:frontend-js-no-tag"]["state"] == State.FAIL assert "Missing 'language_version'" in non_svc_results["frontend:frontend-js-no-tag"]["remarks"] # 3. Old Version -> Fail assert service_results["service-py-old"]["state"] == State.FAIL assert "python version 3.10.1 < 3.11" in service_results["service-py-old"]["remarks"] assert non_svc_results["frontend:frontend-js-old"]["state"] == State.FAIL assert "js version 18 < 22" in non_svc_results["frontend:frontend-js-old"]["remarks"] # 4. OK Version -> Pass assert service_results["service-py-ok"]["state"] == State.PASS assert "python version v3.12 >= 3.11" in service_results["service-py-ok"]["remarks"] assert non_svc_results["frontend:frontend-js-ok"]["state"] == State.PASS assert "js version v24.3.1 >= 22" in non_svc_results["frontend:frontend-js-ok"]["remarks"] # 5. Java OK -> Pass assert service_results["service-java-ok"]["state"] == State.PASS # 6. Excluded -> Skip assert service_results["excluded-svc"]["state"] == State.SKIP assert "test exclusion reason" in service_results["excluded-svc"]["remarks"] def test_handler_excluded_regex_pattern(mock_env, mock_datadog_utils): """Ensure regex patterns in EXCLUDED_SERVICES match multiple services.""" ( mock_list_services, mock_list_frontends, mock_update_outcomes, mock_update_non_svc_outcomes, ) = mock_datadog_utils services = [ { "name": "apollo-orders", "kind": "service", "languages": ["python"], "tags": ["language_version:3.5"], }, { "name": "apollo-payments", "kind": "service", "languages": ["python"], "tags": ["language_version:3.5"], }, { "name": "not-apollo", "kind": "service", "languages": ["python"], "tags": ["language_version:3.11"], }, ] frontends = [ { "name": "filtr-admin", "kind": "frontend", "languages": ["js"], "tags": ["language_version:18"], } ] app.config.EXCLUDED_SERVICES = [ {"pattern": "apollo-.*", "reason": "apollo service"}, {"pattern": "filtr-.*", "reason": "apollo legacy frontend"}, ] mock_list_services.return_value = services mock_list_frontends.return_value = frontends app.handler({}, {}) service_args = mock_update_outcomes.call_args[0][2] service_results = {o["service_name"]: o for o in service_args} non_svc_args = mock_update_non_svc_outcomes.call_args[0][2] non_svc_results = {o["entity_reference"]: o for o in non_svc_args} assert service_results["apollo-orders"]["state"] == State.SKIP assert service_results["apollo-orders"]["remarks"] == "apollo service" assert service_results["apollo-payments"]["state"] == State.SKIP assert service_results["apollo-payments"]["remarks"] == "apollo service" assert non_svc_results["frontend:filtr-admin"]["state"] == State.SKIP assert non_svc_results["frontend:filtr-admin"]["remarks"] == "apollo legacy frontend" assert service_results["not-apollo"]["state"] == State.PASS def test_handler_invalid_version_format(mock_env, mock_datadog_utils): """Ensure that invalid version results in a FAIL outcome with a remark.""" ( mock_list_services, mock_list_frontends, mock_update_outcomes, mock_update_non_svc_outcomes, ) = mock_datadog_utils services = [ { "name": "bad-ver", "kind": "service", "languages": ["python"], "tags": ["language_version:unknown"], } ] mock_list_services.return_value = services mock_list_frontends.return_value = [] app.handler({}, {}) args = mock_update_outcomes.call_args[0][2] assert args[0]["state"] == State.FAIL assert "Invalid version format" in args[0]["remarks"]