QTZPOC-20

QTZPOC-20: Refactor test inputs into named variables for improved readability and debugging.
This commit is contained in:
2026-09-04 11:35:34 -03:00
parent 7b2e90a9f9
commit d257e70465
7 changed files with 141 additions and 67 deletions

View File

@@ -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) other = factory.make_bundle(password=BUNDLE_PASSWORD, salt=b'\x01' * 16)
serve_object(minio_client, payload) serve_object(minio_client, payload)
request = download_input(hashlib.sha256(other).hexdigest())
with raises(ImportGateRejectedError) as failure: with raises(ImportGateRejectedError) as failure:
await import_activity.download_import_bundle( await import_activity.download_import_bundle(request)
download_input(hashlib.sha256(other).hexdigest())
)
assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
assert failure.value.details[0]['gate'] == 1 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)) serve_object(minio_client, factory.make_bundle(password=BUNDLE_PASSWORD))
request = download_input('0' * 64)
with raises(ImportGateRejectedError): 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() 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 import_activity: ModelImport, minio_client: MagicMock
): ):
minio_client.stat_object.return_value = MagicMock(size=9 * 1024 * 1024) minio_client.stat_object.return_value = MagicMock(size=9 * 1024 * 1024)
request = download_input('a' * 64)
with raises(ImportGateRejectedError) as failure: 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 assert failure.value.details[0]['step'] == ImportStep.DOWNLOAD.value
minio_client.fget_object.assert_not_called() 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( async def test_a_key_of_the_wrong_shape_is_refused(
import_activity: ModelImport, minio_client: MagicMock, object_key: str import_activity: ModelImport, minio_client: MagicMock, object_key: str
): ):
request = download_input('a' * 64, object_key=object_key)
with raises(ImportGateRejectedError): with raises(ImportGateRejectedError):
await import_activity.download_import_bundle( await import_activity.download_import_bundle(request)
download_input('a' * 64, object_key=object_key)
)
minio_client.fget_object.assert_not_called() 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 import_activity: ModelImport, minio_client: MagicMock
): ):
minio_client.stat_object.side_effect = OSError('no such object') minio_client.stat_object.side_effect = OSError('no such object')
request = download_input('a' * 64)
with raises(ImportGateRejectedError) as failure: 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 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 import_activity: ModelImport, minio_client: MagicMock
): ):
minio_client.fget_object.side_effect = OSError('connection reset') minio_client.fget_object.side_effect = OSError('connection reset')
request = download_input('a' * 64)
with raises(ImportGateRejectedError) as failure: 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 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(), metrics_controller=AsyncMock(),
) )
request = download_input('a' * 64)
with raises(ValueError, match='Minio repository not initialized'): 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 # ----------------------------------------------------------------------- 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 = tmp_path / 'bundle.sientia'
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD)) path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
envelope = factory.build_envelope(BUNDLE_PASSWORD, OTHER_ENVELOPE_KEY) envelope = factory.build_envelope(BUNDLE_PASSWORD, OTHER_ENVELOPE_KEY)
request = open_input(str(path), envelope)
with raises(ImportGateRejectedError) as failure: 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] details = failure.value.details[0]
assert details['step'] == ImportStep.DECRYPTION.value 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 = tmp_path / 'bundle.sientia'
path.write_bytes(factory.make_bundle(password='a-different-password')) path.write_bytes(factory.make_bundle(password='a-different-password'))
request = open_input(str(path))
with raises(ImportGateRejectedError) as failure: 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] details = failure.value.details[0]
assert details['step'] == ImportStep.DECRYPTION.value 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 import_activity.import_config['password_key'] = None
path = tmp_path / 'bundle.sientia' path = tmp_path / 'bundle.sientia'
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD)) path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
request = open_input(str(path))
with raises(ImportGateRejectedError) as failure: 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' 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 = tmp_path / 'bundle.sientia'
path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD)) path.write_bytes(factory.make_bundle(password=BUNDLE_PASSWORD))
request = open_input(str(path), envelope)
with raises(ImportGateRejectedError) as failure: 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' 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 = tmp_path / 'bundle.sientia'
path.write_bytes(build()) path.write_bytes(build())
request = open_input(str(path))
with raises(ImportGateRejectedError) as failure: 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]['step'] == step.value
assert failure.value.details[0]['gate'] == gate 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 = tmp_path / 'bundle.sientia'
path.write_bytes(traversal()) path.write_bytes(traversal())
request = open_input(str(path))
with raises(ImportGateRejectedError): 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' extract_root = tmp_path / WORKFLOW_ID / 'extract'
assert not extract_root.exists() or list(extract_root.rglob('*')) == [] assert not extract_root.exists() or list(extract_root.rglob('*')) == []

View File

@@ -146,9 +146,10 @@ async def test_the_experiment_keeps_the_origin_s_name(import_activity: ModelImpo
@mark.asyncio @mark.asyncio
async def test_a_missing_mlflow_repository_is_a_configuration_error(import_activity: ModelImport): async def test_a_missing_mlflow_repository_is_a_configuration_error(import_activity: ModelImport):
import_activity.import_mlflow_repository = None import_activity.import_mlflow_repository = None
payload = base(experiment_name='e')
with raises(ValueError, match='MLflow repository not initialized'): 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 # ------------------------------------------------------------------- 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.""" """No retry and no second guess: the unique index says the collection moved under us."""
mongo.distinct.return_value = ['1'] mongo.distinct.return_value = ['1']
mongo.insert.side_effect = RuntimeError('E11000 duplicate key error: id_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'): with raises(RuntimeError, match='duplicate key'):
await import_activity.write_import_model_document( await import_activity.write_import_model_document(payload)
base(model_name='factory_model', target='SE')
)
assert mongo.distinct.await_count == 1 assert mongo.distinct.await_count == 1
assert mongo.insert.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: ModelImport,
): ):
import_activity.mongo_repository = None import_activity.mongo_repository = None
payload = base(model_name='m', target='SE')
with raises(ValueError, match='MongoDB repository not initialized'): 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(): def test_no_import_module_writes_an_active_document():

View File

@@ -361,8 +361,10 @@ async def test_an_unclaimable_row_names_its_cause(row, expected: str):
engine = FakeStatusEngine(rowcounts=[0], rows=[row]) engine = FakeStatusEngine(rowcounts=[0], rows=[row])
activity = build_activity(engine) activity = build_activity(engine)
payload = claim_input()
with raises(ImportLogRowNotClaimableError) as failure: 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 expected in str(failure.value)
assert failure.value.import_run_id == 77 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]) engine = FakeStatusEngine(rowcounts=[0], rows=[None])
activity = build_activity(engine) activity = build_activity(engine)
payload = claim_input()
with raises(ImportLogRowNotClaimableError) as failure: 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.non_retryable is True
assert failure.value.type == 'ImportLogRowNotClaimableError' 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'}]) engine = FakeStatusEngine(rowcounts=[0], rows=[{'run_type': 'TRAIN'}])
activity = build_activity(engine) activity = build_activity(engine)
payload = claim_input()
with raises(ImportLogRowNotClaimableError): with raises(ImportLogRowNotClaimableError):
await activity.claim_import_status(claim_input()) await activity.claim_import_status(payload)
assert len(engine.writes) == 1 assert len(engine.writes) == 1
assert engine.writes[0].sql == engine.statements[0].sql 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]) engine = FakeStatusEngine(rowcounts=[0], rows=[None])
activity = build_activity(engine) activity = build_activity(engine)
payload = claim_input()
with raises(ImportLogRowNotClaimableError): 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] logged = activity.logger.custom_error.call_args_list[0].args[0]
assert ImportErrorCode.LOG_ROW_NOT_CLAIMABLE.value in logged 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'))], write_errors=[error_class('UPDATE ...', {}, Exception('refused'))],
) )
activity = build_activity(engine) activity = build_activity(engine)
payload = {**claim_input(), 'succeeded': True}
with raises(ImportStatusRejectedError) as failure: 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.non_retryable is True
assert failure.value.type == 'ImportStatusRejectedError' 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.""" """Repeating it is exactly what fixes it, so it must not be swallowed or reclassified."""
engine = FakeStatusEngine(write_errors=[OperationalError('UPDATE ...', {}, OSError('down'))]) engine = FakeStatusEngine(write_errors=[OperationalError('UPDATE ...', {}, OSError('down'))])
activity = build_activity(engine) activity = build_activity(engine)
payload = {**claim_input(), 'succeeded': True}
with raises(OperationalError): with raises(OperationalError):
await activity.record_import_terminal_status({**claim_input(), 'succeeded': True}) await activity.record_import_terminal_status(payload)
@mark.asyncio @mark.asyncio

View File

@@ -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): 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: 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 assert error.value.gate == 2
def test_gate_2_rejects_a_truncated_transfer(work_dir, reader): def test_gate_2_rejects_a_truncated_transfer(work_dir, reader):
payload = factory.truncate_bundle(factory.make_bundle())
with pytest.raises(BundleGateError) as error: 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 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): def test_gate_3_wrong_password(work_dir, reader):
payload = factory.make_bundle()
with pytest.raises(BundleGateError) as error: 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.gate == 3
assert error.value.step is ImportStep.DECRYPTION 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: with pytest.raises(BundleGateError) as altered:
_open(reader, work_dir, tampered) _open(reader, work_dir, tampered)
wrong_password_payload = factory.make_bundle()
with pytest.raises(BundleGateError) as wrong_password: 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 str(altered.value) == str(wrong_password.value)
assert altered.value.gate == wrong_password.value.gate == 3 assert altered.value.gate == wrong_password.value.gate == 3
def test_gate_3_stream_without_a_final_tag_is_rejected(work_dir, reader): 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: 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 error.value.gate == 3
assert AMBIGUOUS_REASON in str(error.value) assert AMBIGUOUS_REASON in str(error.value)
def test_gate_3_leaves_no_decrypted_zip_behind(work_dir, reader): def test_gate_3_leaves_no_decrypted_zip_behind(work_dir, reader):
payload = factory.make_bundle()
with pytest.raises(BundleGateError): 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')) == [] 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): def test_gate_4_rejects_too_many_entries(work_dir):
limited = BundleReader(BundleLimits(max_entries=3)) limited = BundleReader(BundleLimits(max_entries=3))
payload = factory.make_bundle()
with pytest.raises(BundleGateError, match='entries') as error: 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 assert error.value.gate == 4
def test_gate_4_rejects_an_oversized_uncompressed_total(work_dir): def test_gate_4_rejects_an_oversized_uncompressed_total(work_dir):
limited = BundleReader(BundleLimits(max_uncompressed_bytes=64)) limited = BundleReader(BundleLimits(max_uncompressed_bytes=64))
payload = factory.make_bundle()
with pytest.raises(BundleGateError, match='uncompressed') as error: 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 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): def test_gate_5_refuses_a_pre_existing_extraction_root(work_dir, reader):
(work_dir / 'extract').mkdir() (work_dir / 'extract').mkdir()
payload = factory.make_bundle()
with pytest.raises(BundleGateError, match='already exists') as error: 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.gate == 5
assert error.value.step is ImportStep.EXTRACTION 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): 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: 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.gate == 6
assert error.value.step is ImportStep.STRUCTURE_VALIDATION 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): def test_gate_6_rejects_unreadable_metadata_json(work_dir, reader):
spec = factory.ZipSpec(include_metadata=False) spec = factory.ZipSpec(include_metadata=False)
spec.extra_files['metadata.json'] = b'{not json' spec.extra_files['metadata.json'] = b'{not json'
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError, match='readable JSON') as error: 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 assert error.value.gate == 6
def test_gate_6_rejects_a_missing_artifacts_directory(work_dir, reader): def test_gate_6_rejects_a_missing_artifacts_directory(work_dir, reader):
spec = factory.ZipSpec(files={}) spec = factory.ZipSpec(files={})
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError, match='artifacts directory') as error: 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 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): def test_gate_6_rejects_an_incomplete_model_directory(work_dir, reader, dropped, match):
spec = factory.ZipSpec() spec = factory.ZipSpec()
spec.files.pop(dropped) spec.files.pop(dropped)
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError, match=match) as error: 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.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): for name in list(spec.files):
if name.startswith('artifacts/data_model/'): if name.startswith('artifacts/data_model/'):
spec.files.pop(name) spec.files.pop(name)
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError, match='no data_model artifacts') as error: 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 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): def test_gate_7_rejects_content_a_model_export_should_not_carry(work_dir, reader, name):
spec = factory.ZipSpec() spec = factory.ZipSpec()
spec.extra_files[name] = b'#!/bin/sh\n' spec.extra_files[name] = b'#!/bin/sh\n'
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError, match='should not carry') as error: 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.gate == 7
assert error.value.step is ImportStep.CONTENT_POLICY 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', traversal_entry='artifacts/../escape.csv',
document=factory.build_metadata_document(drop_parameter='target_variable'), document=factory.build_metadata_document(drop_parameter='target_variable'),
) )
payload = factory.make_bundle(spec)
with pytest.raises(BundleGateError) as error: 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 error.value.gate == 4
assert _extracted_files(work_dir / 'extract') == [] 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): 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.""" """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: 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 assert error.value.gate == 2

View File

@@ -2077,8 +2077,10 @@ def test_find_model_pickle_ignores_nested_pickles(mlflow_repository, tmp_path):
nested.mkdir() nested.mkdir()
(nested / 'stacking_model.pkl').write_bytes(b'pickle-bytes') (nested / 'stacking_model.pkl').write_bytes(b'pickle-bytes')
local_dir = str(tmp_path)
with pytest.raises(ValueError) as exc_info: 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) 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): def test_find_model_pickle_empty_directory(mlflow_repository, tmp_path):
local_dir = str(tmp_path)
with pytest.raises(ValueError) as exc_info: 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) 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): def test_find_model_pickle_ignores_directory_named_pkl(mlflow_repository, tmp_path):
(tmp_path / 'algo.pkl').mkdir() (tmp_path / 'algo.pkl').mkdir()
local_dir = str(tmp_path)
with pytest.raises(ValueError) as exc_info: 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) assert 'algo.pkl' in str(exc_info.value)

View File

@@ -31,9 +31,10 @@ def test_wrong_key_fails_authentication():
def test_tampered_envelope_fails_authentication(): def test_tampered_envelope_fails_authentication():
payload = bytearray(base64.b64decode(factory.build_envelope('Correct1Horse!Battery', KEY))) payload = bytearray(base64.b64decode(factory.build_envelope('Correct1Horse!Battery', KEY)))
payload[-1] ^= 0xFF payload[-1] ^= 0xFF
tampered_envelope = base64.b64encode(bytes(payload)).decode()
with pytest.raises(EnvelopeError, match='authenticated'): 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(): def test_malformed_base64_rejected():
@@ -42,8 +43,10 @@ def test_malformed_base64_rejected():
def test_envelope_too_short_rejected(): def test_envelope_too_short_rejected():
short_envelope = base64.b64encode(bytes(20)).decode()
with pytest.raises(EnvelopeError, match='too short'): 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(): def test_empty_envelope_rejected():

View File

@@ -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')}) recorder = Recorder(failures={failing: RuntimeError('dependency unavailable')})
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError, match='dependency unavailable'): with raises(RuntimeError, match='dependency unavailable'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -353,9 +354,10 @@ async def test_nothing_is_provisioned_before_the_gates_pass(import_workflow: Imp
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportGateRejectedError): with raises(ImportGateRejectedError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -377,9 +379,10 @@ async def test_a_gate_rejection_is_recorded_with_the_gate_that_rejected_it(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportGateRejectedError): with raises(ImportGateRejectedError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -398,9 +401,10 @@ async def test_an_ambiguous_decryption_failure_keeps_its_step(import_workflow: I
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportGateRejectedError): with raises(ImportGateRejectedError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() 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.""" """A caller that believes it recorded a project link must be told it did not."""
recorder = Recorder() recorder = Recorder()
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input(**{field: 'whatever'})
try: try:
with raises(ImportInputError, match='does not accept'): with raises(ImportInputError, match='does not accept'):
await import_workflow.run(valid_input(**{field: 'whatever'})) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -445,9 +450,10 @@ async def test_a_missing_or_malformed_digest_fails_before_anything_is_downloaded
): ):
recorder = Recorder() recorder = Recorder()
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input(expected_digest=digest)
try: try:
with raises(ImportInputError): with raises(ImportInputError):
await import_workflow.run(valid_input(expected_digest=digest)) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -490,9 +496,10 @@ async def test_a_record_naming_another_object_is_a_received_failure(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportInputError, match='different object'): with raises(ImportInputError, match='different object'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -513,9 +520,10 @@ async def test_a_record_naming_another_digest_is_a_received_failure(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportInputError, match='different digest'): with raises(ImportInputError, match='different digest'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() 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.""" """There is no record to claim and none to write: the run ends FAILED and nothing is touched."""
recorder = Recorder() recorder = Recorder()
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input(import_run_id=value)
try: try:
with raises(ImportInputError, match='positive integer'): with raises(ImportInputError, match='positive integer'):
await import_workflow.run(valid_input(import_run_id=value)) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -547,9 +556,10 @@ async def test_a_claim_that_matches_nothing_writes_nothing_at_all(import_workflo
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportLogRowNotClaimableError): with raises(ImportLogRowNotClaimableError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -651,9 +661,10 @@ async def test_a_terminal_write_failure_does_not_mask_a_real_failure(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError, match='registry refused'): with raises(RuntimeError, match='registry refused'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -709,9 +720,10 @@ async def test_the_failure_that_ends_an_import_is_reported_through_the_channel(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError, match='Activity task failed'): with raises(RuntimeError, match='Activity task failed'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() 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')} failures={Activities.upload_import_artifacts: activity_failure('bucket gone', 'S3Error')}
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError): with raises(RuntimeError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -761,9 +774,10 @@ async def test_the_gate_that_rejected_the_bundle_travels_into_the_report(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportGateRejectedError): with raises(ImportGateRejectedError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -783,9 +797,10 @@ async def test_a_claim_that_matches_nothing_is_not_reported_twice(import_workflo
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(ImportLogRowNotClaimableError): with raises(ImportLogRowNotClaimableError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -803,9 +818,10 @@ async def test_a_report_that_cannot_be_made_does_not_mask_the_failure(
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError, match='Activity task failed'): with raises(RuntimeError, match='Activity task failed'):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()
@@ -830,9 +846,10 @@ async def test_the_report_gives_up_inside_the_short_bound(import_workflow: Impor
} }
) )
patcher, _ = run_with(recorder) patcher, _ = run_with(recorder)
payload = valid_input()
try: try:
with raises(RuntimeError): with raises(RuntimeError):
await import_workflow.run(valid_input()) await import_workflow.run(payload)
finally: finally:
patcher.stop() patcher.stop()