""" Unit tests for SitomatCallbackOperator (F16 Pipeline Hub Integration). Menggunakan unittest.mock standar tanpa dependensi eksternal tambahan. """ import os import sys import pytest from unittest.mock import patch, MagicMock # Ensure airflow/dags directory is on python path DAGS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "dags")) if DAGS_DIR not in sys.path: sys.path.insert(0, DAGS_DIR) from helpers.sitomat_callback_operator import SitomatCallbackOperator, create_sitomat_session class TestSitomatCallbackOperator: def test_invalid_layer_raises_value_error(self): with pytest.raises(ValueError, match="Layer 'platinum' tidak valid"): SitomatCallbackOperator( task_id="test_invalid_layer", run_id="run-123", layer="platinum", artifact_ref="table.name" ) def test_mock_mode_execution(self): operator = SitomatCallbackOperator( task_id="test_mock", run_id="run-001", layer="bronze", artifact_ref="iceberg.bronze.permits", checksum="sha256-mock", mock_mode=True ) result = operator.execute() assert result["status"] == "mock_success" assert result["acknowledged"] is True assert result["layer"] == "bronze" assert result["runId"] == "run-001" @patch("helpers.sitomat_callback_operator.create_sitomat_session") def test_successful_bronze_callback(self, mock_create_session): mock_session = MagicMock() mock_response = MagicMock() mock_response.status_code = 201 mock_response.json.return_value = {"success": True, "versionId": "v1-uuid", "layer": "bronze"} mock_session.post.return_value = mock_response mock_create_session.return_value = mock_session sitomat_url = "http://sitomat.bpkp.go.id:3000" run_id = "run-20261005-001" operator = SitomatCallbackOperator( task_id="test_bronze_callback", run_id=run_id, layer="bronze", artifact_ref="lakehouse.bronze.permits_cdc", checksum="sha256-111", sitomat_base_url=sitomat_url, api_key="secret-pipeline-key", metadata_payload={"record_count": 8, "ge_status": "PASSED"}, mock_mode=False ) result = operator.execute() assert result["status"] == "success" assert result["statusCode"] == 201 assert result["response"]["versionId"] == "v1-uuid" mock_session.post.assert_called_once() @patch("helpers.sitomat_callback_operator.create_sitomat_session") def test_idempotent_replay_conflict_409(self, mock_create_session): mock_session = MagicMock() mock_response = MagicMock() mock_response.status_code = 409 mock_response.text = "conflict_artifact: run already registered" mock_session.post.return_value = mock_response mock_create_session.return_value = mock_session sitomat_url = "http://sitomat.bpkp.go.id:3000" run_id = "run-20261005-002" operator = SitomatCallbackOperator( task_id="test_conflict_silver", run_id=run_id, layer="silver", artifact_ref="lakehouse.silver.permits_clean", derived_from_version_id="v1-uuid", checksum="sha256-222", sitomat_base_url=sitomat_url, mock_mode=False ) result = operator.execute() assert result["status"] == "conflict_acknowledged" assert result["statusCode"] == 409 assert "conflict_artifact" in result["response"] @patch("helpers.sitomat_callback_operator.create_sitomat_session") def test_connection_error_graceful_handling(self, mock_create_session): import requests mock_session = MagicMock() mock_session.post.side_effect = requests.exceptions.ConnectionError("Connection refused to Sitomat") mock_create_session.return_value = mock_session operator = SitomatCallbackOperator( task_id="test_conn_error", run_id="run-unreachable", layer="gold", artifact_ref="lakehouse.gold.mart", sitomat_base_url="http://192.0.2.1:9999", mock_mode=False ) result = operator.execute() assert result["status"] == "connection_warning" assert result["acknowledged"] is False assert "Connection refused" in result["error"]