import logging from unittest.mock import MagicMock, call, patch import pytest from backfill.cli.lint import ( ValidateManifestError, _do_manifest_keys_exist, _validate_manifest, ) from backfill.constants import JobType from backfill.models import Job, Manifest @pytest.fixture() def fake_path() -> str: """Reusable fake path.""" return "/this/path" @patch("backfill.cli.lint.os") def test__validate_manifest_no_file( mock_os: MagicMock, fake_path: str, ) -> None: """Test validate_manifest raises when the file does not exist.""" mock_os.path.isfile.return_value = False with pytest.raises(ValidateManifestError) as e: _validate_manifest(fake_path) mock_os.path.isfile.assert_called_once_with(fake_path) assert str(e.value) == f"('missing file %s', '{fake_path}')" @patch("backfill.cli.lint.open") @patch("backfill.cli.lint.os") def test__validate_manifest_open_raises( mock_os: MagicMock, mock_open: MagicMock, fake_path: str, ) -> None: """Test validate_manifest raises when file cannot be opened.""" mock_os.path.isfile.return_value = True mock_open.side_effect = Exception("some error") with pytest.raises(ValidateManifestError) as e: _validate_manifest(fake_path) mock_os.path.isfile.assert_called_once_with(fake_path) mock_open.assert_called_once_with(fake_path) assert str(e.value) == f"('failed to load file %s', '{fake_path}')" @patch("backfill.cli.lint.json") @patch("backfill.cli.lint.open") @patch("backfill.cli.lint.os") def test__validate_manifest_is_not_json_raises( mock_os: MagicMock, mock_open: MagicMock, mock_json: MagicMock, fake_path: str, ) -> None: """Test validate_manifest raises when file does not contain json.""" mock_os.path.isfile.return_value = True mock_open.return_value.__enter__.return_value = "a legit file pointer" mock_json.load.side_effect = Exception("cant load string to json") with pytest.raises(ValidateManifestError) as e: _validate_manifest(fake_path) mock_os.path.isfile.assert_called_once_with(fake_path) mock_open.assert_called_once_with(fake_path) mock_json.load.assert_called_once_with("a legit file pointer") assert str(e.value) == f"('failed to load file %s', '{fake_path}')" @patch("backfill.cli.lint.json") @patch("backfill.cli.lint.open") @patch("backfill.cli.lint.os") def test__validate_manifest( mock_os: MagicMock, mock_open: MagicMock, mock_json: MagicMock, fake_path: str, ) -> None: """Test validate_manifest validates a legit json.""" mock_os.path.isfile.return_value = True mock_open.return_value.__enter__.return_value = "a legit file pointer" mock_json.load.return_value = {"bucket": "qa bucket", "jobs": []} manifest = _validate_manifest(fake_path) assert manifest == Manifest(bucket="qa bucket", jobs=[]) mock_os.path.isfile.assert_called_once_with(fake_path) mock_open.assert_called_once_with(fake_path) mock_json.load.assert_called_once_with("a legit file pointer") @patch("backfill.cli.lint.json") @patch("backfill.cli.lint.open") @patch("backfill.cli.lint.os") def test__validate_manifest_invalid( mock_os: MagicMock, mock_open: MagicMock, mock_json: MagicMock, fake_path: str, ) -> None: """Test validate_manifest validates a legit json.""" mock_os.path.isfile.return_value = True mock_open.return_value.__enter__.return_value = "a legit file pointer" mock_json.load.return_value = {"bucket": "bad bucket", "jobs": []} with pytest.raises(ValidateManifestError) as e: _validate_manifest(fake_path) mock_os.path.isfile.assert_called_once_with(fake_path) mock_open.assert_called_once_with(fake_path) mock_json.load.assert_called_once_with("a legit file pointer") assert ( str(e.value) == f"('does not comply with manifest file format', '{fake_path}')" ) @patch("backfill.cli.lint.os") def test__do_manifest_keys_exist( mock_os: MagicMock, ) -> None: """Test _do_manifest_keys_exist happy path.""" mock_os.path.isfile.side_effect = [ (True), (True), (True), ] manifest = Manifest( bucket="dev-pdp-backfill", jobs=[ Job(job_type=JobType.JOB_TYPE_ATTACH_AND_DETACH, keys=["key1", "key2"]), Job(job_type=JobType.JOB_TYPE_ATTACH_AND_DETACH, keys=["key3"]), ], ) actual = _do_manifest_keys_exist(manifest) assert actual mock_os.path.isfile.assert_has_calls( [ call("key1"), call("key2"), call("key3"), ] ) @patch("backfill.cli.lint.os") def test__do_manifest_keys_exist_partial( mock_os: MagicMock, caplog: pytest.LogCaptureFixture, ) -> None: """Test _do_manifest_keys_exist partial.""" mock_os.path.isfile.side_effect = [ (False), (True), (False), ] manifest = Manifest( bucket="dev-pdp-backfill", jobs=[ Job(job_type=JobType.JOB_TYPE_ATTACH_AND_DETACH, keys=["key1", "key2"]), Job(job_type=JobType.JOB_TYPE_ATTACH_AND_DETACH, keys=["key3"]), ], ) with caplog.at_level(logging.INFO): actual = _do_manifest_keys_exist(manifest) assert not actual mock_os.path.isfile.assert_has_calls( [ call("key1"), call("key2"), call("key3"), ] ) log_record = caplog.records[0] assert "missing keys locally" == log_record.msg assert {"missing_keys": ["key1", "key3"]} == log_record.resources # type: ignore[attr-defined]