QTZPOC-20
QTZPOC-20: Refactor test inputs into named variables for improved readability and debugging.
This commit is contained in:
@@ -194,10 +194,10 @@ async def test_a_digest_from_another_file_fails_before_the_header_is_parsed(
|
||||
other = factory.make_bundle(password=BUNDLE_PASSWORD, salt=b'\x01' * 16)
|
||||
serve_object(minio_client, payload)
|
||||
|
||||
request = download_input(hashlib.sha256(other).hexdigest())
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.download_import_bundle(
|
||||
download_input(hashlib.sha256(other).hexdigest())
|
||||
)
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
|
||||
assert failure.value.details[0]['gate'] == 1
|
||||
@@ -209,8 +209,10 @@ async def test_a_file_that_does_not_match_is_not_left_on_the_worker(
|
||||
):
|
||||
serve_object(minio_client, factory.make_bundle(password=BUNDLE_PASSWORD))
|
||||
|
||||
request = download_input('0' * 64)
|
||||
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_activity.download_import_bundle(download_input('0' * 64))
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
assert not (tmp_path / WORKFLOW_ID / 'bundle.sientia').exists()
|
||||
|
||||
@@ -234,9 +236,10 @@ async def test_an_object_over_the_ceiling_is_refused_before_its_body_is_fetched(
|
||||
import_activity: ModelImport, minio_client: MagicMock
|
||||
):
|
||||
minio_client.stat_object.return_value = MagicMock(size=9 * 1024 * 1024)
|
||||
request = download_input('a' * 64)
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.download_import_bundle(download_input('a' * 64))
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
|
||||
minio_client.fget_object.assert_not_called()
|
||||
@@ -250,10 +253,10 @@ async def test_an_object_over_the_ceiling_is_refused_before_its_body_is_fetched(
|
||||
async def test_a_key_of_the_wrong_shape_is_refused(
|
||||
import_activity: ModelImport, minio_client: MagicMock, object_key: str
|
||||
):
|
||||
request = download_input('a' * 64, object_key=object_key)
|
||||
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_activity.download_import_bundle(
|
||||
download_input('a' * 64, object_key=object_key)
|
||||
)
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
minio_client.fget_object.assert_not_called()
|
||||
|
||||
@@ -263,9 +266,10 @@ async def test_an_unreadable_object_is_a_download_failure(
|
||||
import_activity: ModelImport, minio_client: MagicMock
|
||||
):
|
||||
minio_client.stat_object.side_effect = OSError('no such object')
|
||||
request = download_input('a' * 64)
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.download_import_bundle(download_input('a' * 64))
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
|
||||
|
||||
@@ -275,9 +279,10 @@ async def test_a_failed_transfer_is_a_download_failure(
|
||||
import_activity: ModelImport, minio_client: MagicMock
|
||||
):
|
||||
minio_client.fget_object.side_effect = OSError('connection reset')
|
||||
request = download_input('a' * 64)
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.download_import_bundle(download_input('a' * 64))
|
||||
await import_activity.download_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
|
||||
|
||||
@@ -300,8 +305,10 @@ async def test_a_missing_minio_repository_is_a_configuration_error(tmp_path: Pat
|
||||
metrics_controller=AsyncMock(),
|
||||
)
|
||||
|
||||
request = download_input('a' * 64)
|
||||
|
||||
with raises(ValueError, match='Minio repository not initialized'):
|
||||
await activity.download_import_bundle(download_input('a' * 64))
|
||||
await activity.download_import_bundle(request)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- opening the bundle
|
||||
@@ -348,9 +355,10 @@ async def test_a_wrong_envelope_key_is_not_confused_with_a_wrong_bundle_password
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
|
||||
envelope = factory.build_envelope(BUNDLE_PASSWORD, OTHER_ENVELOPE_KEY)
|
||||
request = open_input(str(path), envelope)
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.open_import_bundle(open_input(str(path), envelope))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
details = failure.value.details[0]
|
||||
assert details['step'] == ImportStep.DECRYPTION.value
|
||||
@@ -366,9 +374,10 @@ async def test_a_wrong_bundle_password_is_a_decryption_failure_of_its_own(
|
||||
):
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(factory.make_bundle(password='a-different-password'))
|
||||
request = open_input(str(path))
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.open_import_bundle(open_input(str(path)))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
details = failure.value.details[0]
|
||||
assert details['step'] == ImportStep.DECRYPTION.value
|
||||
@@ -382,9 +391,10 @@ async def test_a_missing_password_key_stops_the_import_at_decryption(
|
||||
import_activity.import_config['password_key'] = None
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
|
||||
request = open_input(str(path))
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.open_import_bundle(open_input(str(path)))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['cause'] == 'envelope'
|
||||
|
||||
@@ -400,9 +410,10 @@ async def test_a_tampered_or_malformed_envelope_is_refused(
|
||||
):
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
|
||||
request = open_input(str(path), envelope)
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.open_import_bundle(open_input(str(path), envelope))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['cause'] == 'envelope'
|
||||
|
||||
@@ -472,9 +483,10 @@ async def test_a_gate_failure_reaches_the_activity_as_its_own_step(
|
||||
):
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(build())
|
||||
request = open_input(str(path))
|
||||
|
||||
with raises(ImportGateRejectedError) as failure:
|
||||
await import_activity.open_import_bundle(open_input(str(path)))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
assert failure.value.details[0]['step'] == step.value
|
||||
assert failure.value.details[0]['gate'] == gate
|
||||
@@ -486,9 +498,10 @@ async def test_a_rejected_bundle_leaves_no_extracted_tree(
|
||||
):
|
||||
path = tmp_path / 'bundle.sientia'
|
||||
path.write_bytes(traversal())
|
||||
request = open_input(str(path))
|
||||
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_activity.open_import_bundle(open_input(str(path)))
|
||||
await import_activity.open_import_bundle(request)
|
||||
|
||||
extract_root = tmp_path / WORKFLOW_ID / 'extract'
|
||||
assert not extract_root.exists() or list(extract_root.rglob('*')) == []
|
||||
|
||||
@@ -146,9 +146,10 @@ async def test_the_experiment_keeps_the_origin_s_name(import_activity: ModelImpo
|
||||
@mark.asyncio
|
||||
async def test_a_missing_mlflow_repository_is_a_configuration_error(import_activity: ModelImport):
|
||||
import_activity.import_mlflow_repository = None
|
||||
payload = base(experiment_name='e')
|
||||
|
||||
with raises(ValueError, match='MLflow repository not initialized'):
|
||||
await import_activity.create_import_experiment(base(experiment_name='e'))
|
||||
await import_activity.create_import_experiment(payload)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- the run and its tree
|
||||
@@ -479,11 +480,10 @@ async def test_a_duplicate_id_fails_the_step_without_recomputing_it(
|
||||
"""No retry and no second guess: the unique index says the collection moved under us."""
|
||||
mongo.distinct.return_value = ['1']
|
||||
mongo.insert.side_effect = RuntimeError('E11000 duplicate key error: id_1')
|
||||
payload = base(model_name='factory_model', target='SE')
|
||||
|
||||
with raises(RuntimeError, match='duplicate key'):
|
||||
await import_activity.write_import_model_document(
|
||||
base(model_name='factory_model', target='SE')
|
||||
)
|
||||
await import_activity.write_import_model_document(payload)
|
||||
|
||||
assert mongo.distinct.await_count == 1
|
||||
assert mongo.insert.await_count == 1
|
||||
@@ -505,9 +505,10 @@ async def test_a_missing_mongo_repository_is_a_configuration_error(
|
||||
import_activity: ModelImport,
|
||||
):
|
||||
import_activity.mongo_repository = None
|
||||
payload = base(model_name='m', target='SE')
|
||||
|
||||
with raises(ValueError, match='MongoDB repository not initialized'):
|
||||
await import_activity.write_import_model_document(base(model_name='m', target='SE'))
|
||||
await import_activity.write_import_model_document(payload)
|
||||
|
||||
|
||||
def test_no_import_module_writes_an_active_document():
|
||||
|
||||
@@ -361,8 +361,10 @@ async def test_an_unclaimable_row_names_its_cause(row, expected: str):
|
||||
engine = FakeStatusEngine(rowcounts=[0], rows=[row])
|
||||
activity = build_activity(engine)
|
||||
|
||||
payload = claim_input()
|
||||
|
||||
with raises(ImportLogRowNotClaimableError) as failure:
|
||||
await activity.claim_import_status(claim_input())
|
||||
await activity.claim_import_status(payload)
|
||||
|
||||
assert expected in str(failure.value)
|
||||
assert failure.value.import_run_id == 77
|
||||
@@ -373,8 +375,10 @@ async def test_an_unclaimable_row_is_not_retried():
|
||||
engine = FakeStatusEngine(rowcounts=[0], rows=[None])
|
||||
activity = build_activity(engine)
|
||||
|
||||
payload = claim_input()
|
||||
|
||||
with raises(ImportLogRowNotClaimableError) as failure:
|
||||
await activity.claim_import_status(claim_input())
|
||||
await activity.claim_import_status(payload)
|
||||
|
||||
assert failure.value.non_retryable is True
|
||||
assert failure.value.type == 'ImportLogRowNotClaimableError'
|
||||
@@ -392,8 +396,10 @@ async def test_an_unclaimable_row_is_never_written_to():
|
||||
engine = FakeStatusEngine(rowcounts=[0], rows=[{'run_type': 'TRAIN'}])
|
||||
activity = build_activity(engine)
|
||||
|
||||
payload = claim_input()
|
||||
|
||||
with raises(ImportLogRowNotClaimableError):
|
||||
await activity.claim_import_status(claim_input())
|
||||
await activity.claim_import_status(payload)
|
||||
|
||||
assert len(engine.writes) == 1
|
||||
assert engine.writes[0].sql == engine.statements[0].sql
|
||||
@@ -407,8 +413,10 @@ async def test_an_unclaimable_row_is_reported_through_every_sink():
|
||||
engine = FakeStatusEngine(rowcounts=[0], rows=[None])
|
||||
activity = build_activity(engine)
|
||||
|
||||
payload = claim_input()
|
||||
|
||||
with raises(ImportLogRowNotClaimableError):
|
||||
await activity.claim_import_status(claim_input())
|
||||
await activity.claim_import_status(payload)
|
||||
|
||||
logged = activity.logger.custom_error.call_args_list[0].args[0]
|
||||
assert ImportErrorCode.LOG_ROW_NOT_CLAIMABLE.value in logged
|
||||
@@ -688,9 +696,10 @@ async def test_a_refused_statement_fails_on_the_first_attempt(error_class):
|
||||
write_errors=[error_class('UPDATE ...', {}, Exception('refused'))],
|
||||
)
|
||||
activity = build_activity(engine)
|
||||
payload = {**claim_input(), 'succeeded': True}
|
||||
|
||||
with raises(ImportStatusRejectedError) as failure:
|
||||
await activity.record_import_terminal_status({**claim_input(), 'succeeded': True})
|
||||
await activity.record_import_terminal_status(payload)
|
||||
|
||||
assert failure.value.non_retryable is True
|
||||
assert failure.value.type == 'ImportStatusRejectedError'
|
||||
@@ -702,9 +711,10 @@ async def test_a_connection_failure_is_left_to_the_retry():
|
||||
"""Repeating it is exactly what fixes it, so it must not be swallowed or reclassified."""
|
||||
engine = FakeStatusEngine(write_errors=[OperationalError('UPDATE ...', {}, OSError('down'))])
|
||||
activity = build_activity(engine)
|
||||
payload = {**claim_input(), 'succeeded': True}
|
||||
|
||||
with raises(OperationalError):
|
||||
await activity.record_import_terminal_status({**claim_input(), 'succeeded': True})
|
||||
await activity.record_import_terminal_status(payload)
|
||||
|
||||
|
||||
@mark.asyncio
|
||||
|
||||
@@ -156,15 +156,19 @@ def test_gate_2_rejects_kdf_parameters_above_the_ceiling_without_allocating(
|
||||
|
||||
|
||||
def test_gate_2_rejects_a_digest_mismatch(work_dir, reader):
|
||||
payload = factory.make_bundle(digest_override=bytes(32))
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(digest_override=bytes(32)))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 2
|
||||
|
||||
|
||||
def test_gate_2_rejects_a_truncated_transfer(work_dir, reader):
|
||||
payload = factory.truncate_bundle(factory.make_bundle())
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.truncate_bundle(factory.make_bundle()))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 2
|
||||
|
||||
@@ -182,8 +186,10 @@ AMBIGUOUS_REASON = 'wrong password, or the file is damaged or was altered'
|
||||
|
||||
|
||||
def test_gate_3_wrong_password(work_dir, reader):
|
||||
payload = factory.make_bundle()
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(), password='Wrong1Password!Here')
|
||||
_open(reader, work_dir, payload, password='Wrong1Password!Here')
|
||||
|
||||
assert error.value.gate == 3
|
||||
assert error.value.step is ImportStep.DECRYPTION
|
||||
@@ -195,24 +201,30 @@ def test_gate_3_altered_ciphertext_reads_the_same_as_a_wrong_password(work_dir,
|
||||
|
||||
with pytest.raises(BundleGateError) as altered:
|
||||
_open(reader, work_dir, tampered)
|
||||
|
||||
wrong_password_payload = factory.make_bundle()
|
||||
with pytest.raises(BundleGateError) as wrong_password:
|
||||
_open(reader, work_dir, factory.make_bundle(), password='Wrong1Password!Here')
|
||||
_open(reader, work_dir, wrong_password_payload, password='Wrong1Password!Here')
|
||||
|
||||
assert str(altered.value) == str(wrong_password.value)
|
||||
assert altered.value.gate == wrong_password.value.gate == 3
|
||||
|
||||
|
||||
def test_gate_3_stream_without_a_final_tag_is_rejected(work_dir, reader):
|
||||
payload = factory.make_bundle(drop_final_tag=True)
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(drop_final_tag=True))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 3
|
||||
assert AMBIGUOUS_REASON in str(error.value)
|
||||
|
||||
|
||||
def test_gate_3_leaves_no_decrypted_zip_behind(work_dir, reader):
|
||||
payload = factory.make_bundle()
|
||||
|
||||
with pytest.raises(BundleGateError):
|
||||
_open(reader, work_dir, factory.make_bundle(), password='Wrong1Password!Here')
|
||||
_open(reader, work_dir, payload, password='Wrong1Password!Here')
|
||||
|
||||
assert list(work_dir.glob('*.zip')) == []
|
||||
|
||||
@@ -277,18 +289,20 @@ def test_gate_4_rejects_hostile_central_directories(work_dir, reader, spec_kwarg
|
||||
|
||||
def test_gate_4_rejects_too_many_entries(work_dir):
|
||||
limited = BundleReader(BundleLimits(max_entries=3))
|
||||
payload = factory.make_bundle()
|
||||
|
||||
with pytest.raises(BundleGateError, match='entries') as error:
|
||||
_open(limited, work_dir, factory.make_bundle())
|
||||
_open(limited, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 4
|
||||
|
||||
|
||||
def test_gate_4_rejects_an_oversized_uncompressed_total(work_dir):
|
||||
limited = BundleReader(BundleLimits(max_uncompressed_bytes=64))
|
||||
payload = factory.make_bundle()
|
||||
|
||||
with pytest.raises(BundleGateError, match='uncompressed') as error:
|
||||
_open(limited, work_dir, factory.make_bundle())
|
||||
_open(limited, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 4
|
||||
|
||||
@@ -307,9 +321,10 @@ def test_gate_4_rejects_content_that_is_not_an_archive(work_dir, reader):
|
||||
|
||||
def test_gate_5_refuses_a_pre_existing_extraction_root(work_dir, reader):
|
||||
(work_dir / 'extract').mkdir()
|
||||
payload = factory.make_bundle()
|
||||
|
||||
with pytest.raises(BundleGateError, match='already exists') as error:
|
||||
_open(reader, work_dir, factory.make_bundle())
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 5
|
||||
assert error.value.step is ImportStep.EXTRACTION
|
||||
@@ -363,8 +378,10 @@ def test_gate_5_extraction_writes_plain_files(work_dir, reader):
|
||||
],
|
||||
)
|
||||
def test_gate_6_rejects_a_malformed_metadata_document(work_dir, reader, spec, match):
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match=match) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 6
|
||||
assert error.value.step is ImportStep.STRUCTURE_VALIDATION
|
||||
@@ -374,18 +391,20 @@ def test_gate_6_rejects_a_malformed_metadata_document(work_dir, reader, spec, ma
|
||||
def test_gate_6_rejects_unreadable_metadata_json(work_dir, reader):
|
||||
spec = factory.ZipSpec(include_metadata=False)
|
||||
spec.extra_files['metadata.json'] = b'{not json'
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match='readable JSON') as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 6
|
||||
|
||||
|
||||
def test_gate_6_rejects_a_missing_artifacts_directory(work_dir, reader):
|
||||
spec = factory.ZipSpec(files={})
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match='artifacts directory') as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 6
|
||||
|
||||
@@ -400,9 +419,10 @@ def test_gate_6_rejects_a_missing_artifacts_directory(work_dir, reader):
|
||||
def test_gate_6_rejects_an_incomplete_model_directory(work_dir, reader, dropped, match):
|
||||
spec = factory.ZipSpec()
|
||||
spec.files.pop(dropped)
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match=match) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 6
|
||||
|
||||
@@ -412,9 +432,10 @@ def test_gate_6_rejects_a_bundle_without_data_model(work_dir, reader):
|
||||
for name in list(spec.files):
|
||||
if name.startswith('artifacts/data_model/'):
|
||||
spec.files.pop(name)
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match='no data_model artifacts') as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 6
|
||||
|
||||
@@ -426,9 +447,10 @@ def test_gate_6_rejects_a_bundle_without_data_model(work_dir, reader):
|
||||
def test_gate_7_rejects_content_a_model_export_should_not_carry(work_dir, reader, name):
|
||||
spec = factory.ZipSpec()
|
||||
spec.extra_files[name] = b'#!/bin/sh\n'
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError, match='should not carry') as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 7
|
||||
assert error.value.step is ImportStep.CONTENT_POLICY
|
||||
@@ -444,9 +466,10 @@ def test_the_earlier_gate_wins(work_dir, reader):
|
||||
traversal_entry='artifacts/../escape.csv',
|
||||
document=factory.build_metadata_document(drop_parameter='target_variable'),
|
||||
)
|
||||
payload = factory.make_bundle(spec)
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(spec))
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 4
|
||||
assert _extracted_files(work_dir / 'extract') == []
|
||||
@@ -454,7 +477,9 @@ def test_the_earlier_gate_wins(work_dir, reader):
|
||||
|
||||
def test_header_is_checked_before_the_password(work_dir, reader):
|
||||
"""A bundle with a bad header and a wrong password reports the header, not the password."""
|
||||
payload = factory.make_bundle(magic=b'NOTSIENT')
|
||||
|
||||
with pytest.raises(BundleGateError) as error:
|
||||
_open(reader, work_dir, factory.make_bundle(magic=b'NOTSIENT'), password='Nope1!Nope1!')
|
||||
_open(reader, work_dir, payload, password='Nope1!Nope1!')
|
||||
|
||||
assert error.value.gate == 2
|
||||
|
||||
@@ -2077,8 +2077,10 @@ def test_find_model_pickle_ignores_nested_pickles(mlflow_repository, tmp_path):
|
||||
nested.mkdir()
|
||||
(nested / 'stacking_model.pkl').write_bytes(b'pickle-bytes')
|
||||
|
||||
local_dir = str(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
mlflow_repository._find_model_pickle(str(tmp_path), 'prediction_model')
|
||||
mlflow_repository._find_model_pickle(local_dir, 'prediction_model')
|
||||
|
||||
assert 'No .pkl file found' in str(exc_info.value)
|
||||
|
||||
@@ -2093,8 +2095,10 @@ def test_find_model_pickle_reads_the_name_log_model_writes(mlflow_repository, tm
|
||||
|
||||
|
||||
def test_find_model_pickle_empty_directory(mlflow_repository, tmp_path):
|
||||
local_dir = str(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
mlflow_repository._find_model_pickle(str(tmp_path), 'data_model')
|
||||
mlflow_repository._find_model_pickle(local_dir, 'data_model')
|
||||
|
||||
assert 'data_model' in str(exc_info.value)
|
||||
|
||||
@@ -2113,9 +2117,10 @@ def test_find_model_pickle_error_message_carries_context(mlflow_repository, tmp_
|
||||
|
||||
def test_find_model_pickle_ignores_directory_named_pkl(mlflow_repository, tmp_path):
|
||||
(tmp_path / 'algo.pkl').mkdir()
|
||||
local_dir = str(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
mlflow_repository._find_model_pickle(str(tmp_path), 'prediction_model')
|
||||
mlflow_repository._find_model_pickle(local_dir, 'prediction_model')
|
||||
|
||||
assert 'algo.pkl' in str(exc_info.value)
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ def test_wrong_key_fails_authentication():
|
||||
def test_tampered_envelope_fails_authentication():
|
||||
payload = bytearray(base64.b64decode(factory.build_envelope('Correct1Horse!Battery', KEY)))
|
||||
payload[-1] ^= 0xFF
|
||||
tampered_envelope = base64.b64encode(bytes(payload)).decode()
|
||||
|
||||
with pytest.raises(EnvelopeError, match='authenticated'):
|
||||
decrypt_password_envelope(base64.b64encode(bytes(payload)).decode(), KEY)
|
||||
decrypt_password_envelope(tampered_envelope, KEY)
|
||||
|
||||
|
||||
def test_malformed_base64_rejected():
|
||||
@@ -42,8 +43,10 @@ def test_malformed_base64_rejected():
|
||||
|
||||
|
||||
def test_envelope_too_short_rejected():
|
||||
short_envelope = base64.b64encode(bytes(20)).decode()
|
||||
|
||||
with pytest.raises(EnvelopeError, match='too short'):
|
||||
decrypt_password_envelope(base64.b64encode(bytes(20)).decode(), KEY)
|
||||
decrypt_password_envelope(short_envelope, KEY)
|
||||
|
||||
|
||||
def test_empty_envelope_rejected():
|
||||
|
||||
@@ -322,9 +322,10 @@ async def test_a_failing_step_is_recorded_as_that_step_and_stops_the_import(
|
||||
):
|
||||
recorder = Recorder(failures={failing: RuntimeError('dependency unavailable')})
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError, match='dependency unavailable'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -353,9 +354,10 @@ async def test_nothing_is_provisioned_before_the_gates_pass(import_workflow: Imp
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -377,9 +379,10 @@ async def test_a_gate_rejection_is_recorded_with_the_gate_that_rejected_it(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -398,9 +401,10 @@ async def test_an_ambiguous_decryption_failure_keeps_its_step(import_workflow: I
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -420,9 +424,10 @@ async def test_an_unknown_field_is_rejected_rather_than_ignored(
|
||||
"""A caller that believes it recorded a project link must be told it did not."""
|
||||
recorder = Recorder()
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input(**{field: 'whatever'})
|
||||
try:
|
||||
with raises(ImportInputError, match='does not accept'):
|
||||
await import_workflow.run(valid_input(**{field: 'whatever'}))
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -445,9 +450,10 @@ async def test_a_missing_or_malformed_digest_fails_before_anything_is_downloaded
|
||||
):
|
||||
recorder = Recorder()
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input(expected_digest=digest)
|
||||
try:
|
||||
with raises(ImportInputError):
|
||||
await import_workflow.run(valid_input(expected_digest=digest))
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -490,9 +496,10 @@ async def test_a_record_naming_another_object_is_a_received_failure(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportInputError, match='different object'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -513,9 +520,10 @@ async def test_a_record_naming_another_digest_is_a_received_failure(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportInputError, match='different digest'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -528,9 +536,10 @@ async def test_an_unusable_record_id_fails_before_any_activity(import_workflow:
|
||||
"""There is no record to claim and none to write: the run ends FAILED and nothing is touched."""
|
||||
recorder = Recorder()
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input(import_run_id=value)
|
||||
try:
|
||||
with raises(ImportInputError, match='positive integer'):
|
||||
await import_workflow.run(valid_input(import_run_id=value))
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -547,9 +556,10 @@ async def test_a_claim_that_matches_nothing_writes_nothing_at_all(import_workflo
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportLogRowNotClaimableError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -651,9 +661,10 @@ async def test_a_terminal_write_failure_does_not_mask_a_real_failure(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError, match='registry refused'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -709,9 +720,10 @@ async def test_the_failure_that_ends_an_import_is_reported_through_the_channel(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError, match='Activity task failed'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -732,9 +744,10 @@ async def test_the_report_comes_before_the_cleanup_and_the_terminal_write(
|
||||
failures={Activities.upload_import_artifacts: activity_failure('bucket gone', 'S3Error')}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -761,9 +774,10 @@ async def test_the_gate_that_rejected_the_bundle_travels_into_the_report(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportGateRejectedError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -783,9 +797,10 @@ async def test_a_claim_that_matches_nothing_is_not_reported_twice(import_workflo
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(ImportLogRowNotClaimableError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -803,9 +818,10 @@ async def test_a_report_that_cannot_be_made_does_not_mask_the_failure(
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError, match='Activity task failed'):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
@@ -830,9 +846,10 @@ async def test_the_report_gives_up_inside_the_short_bound(import_workflow: Impor
|
||||
}
|
||||
)
|
||||
patcher, _ = run_with(recorder)
|
||||
payload = valid_input()
|
||||
try:
|
||||
with raises(RuntimeError):
|
||||
await import_workflow.run(valid_input())
|
||||
await import_workflow.run(payload)
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user