QTZPOC-20
QTZPOC-20: Enhance system robustness and test coverage across bundle processing, activity error handling, and worker operations. * Strengthen bundle reader validation with new tests for archive integrity, security vulnerabilities (e.g., path traversal, duplicate entries, special file types), and I/O error resilience. * Improve error handling and status propagation in PI Web API, MLflow, and OPC activities, including partial write scenarios and OPC session management. * Add comprehensive tests for worker startup, configuration, graceful shutdown, and Prometheus metrics server initialization. * Introduce a new utility for intelligent DataFrame debugging, summarizing large dataframes for concise logging, and refine exception chain summarization. * Update code coverage configuration to exclude @overload decorator lines.
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
"""The seven gates: what each one rejects, in what order, and what it leaves on disk."""
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import stat
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import nacl.bindings as sodium
|
||||
@@ -173,6 +177,16 @@ def test_gate_2_rejects_a_truncated_transfer(work_dir, reader):
|
||||
assert error.value.gate == 2
|
||||
|
||||
|
||||
def test_gate_2_wraps_an_os_error_reading_the_file(work_dir, reader):
|
||||
"""A download that never landed on disk is an OS error, not a format one."""
|
||||
missing = work_dir / 'never-downloaded.sientia'
|
||||
|
||||
with pytest.raises(BundleGateError, match='could not be read') as error:
|
||||
reader.open(missing, factory.DEFAULT_PASSWORD, work_dir / 'extract', work_dir)
|
||||
|
||||
assert error.value.gate == 2
|
||||
|
||||
|
||||
def _never_called(message: str):
|
||||
def _raise(*_args, **_kwargs):
|
||||
raise AssertionError(message)
|
||||
@@ -220,6 +234,22 @@ def test_gate_3_stream_without_a_final_tag_is_rejected(work_dir, reader):
|
||||
assert AMBIGUOUS_REASON in str(error.value)
|
||||
|
||||
|
||||
def test_gate_3_wraps_an_os_error_while_writing_the_decrypted_zip(work_dir, reader, monkeypatch):
|
||||
"""A write failure while streaming the plaintext to disk is reported as disk trouble, not as
|
||||
a bad password — the AEAD never even got to disagree."""
|
||||
payload = factory.make_bundle()
|
||||
|
||||
def _raise_os_error(*_args, **_kwargs):
|
||||
raise OSError('disk full')
|
||||
|
||||
monkeypatch.setattr(reader, '_pull_stream', _raise_os_error)
|
||||
|
||||
with pytest.raises(BundleGateError, match='could not be written') as error:
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 3
|
||||
|
||||
|
||||
def test_gate_3_leaves_no_decrypted_zip_behind(work_dir, reader):
|
||||
payload = factory.make_bundle()
|
||||
|
||||
@@ -316,6 +346,27 @@ def test_gate_4_rejects_content_that_is_not_an_archive(work_dir, reader):
|
||||
assert error.value.gate == 4
|
||||
|
||||
|
||||
def test_gate_4_rejects_an_empty_archive(work_dir, reader):
|
||||
payload = factory.make_bundle(factory.ZipSpec(files={}, include_metadata=False))
|
||||
|
||||
with pytest.raises(BundleGateError, match='archive is empty') as error:
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 4
|
||||
|
||||
|
||||
def test_gate_4_rejects_an_entry_that_is_neither_a_file_nor_a_directory(work_dir, reader):
|
||||
"""A FIFO's mode bits are neither a symlink's, a regular file's, nor a directory's."""
|
||||
payload = factory.make_bundle(
|
||||
factory.ZipSpec(special_mode_entries={'artifacts/pipe': stat.S_IFIFO | 0o644})
|
||||
)
|
||||
|
||||
with pytest.raises(BundleGateError, match='not a regular file') as error:
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 4
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- gate 5
|
||||
|
||||
|
||||
@@ -338,6 +389,61 @@ def test_gate_5_extraction_writes_plain_files(work_dir, reader):
|
||||
assert path.stat().st_mode & 0o111 == 0, path
|
||||
|
||||
|
||||
def test_gate_5_extraction_creates_declared_directory_entries(work_dir, reader):
|
||||
"""The producer never writes directory entries, but a bundle from anywhere else might."""
|
||||
payload = factory.make_bundle(factory.ZipSpec(directory_entries=['artifacts/empty_dir']))
|
||||
|
||||
opened = _open(reader, work_dir, payload)
|
||||
|
||||
assert (opened.extracted_dir / 'artifacts' / 'empty_dir').is_dir()
|
||||
|
||||
|
||||
def test_gate_5_rejects_a_duplicate_entry_name(work_dir, reader):
|
||||
"""Two archive entries claiming the same path: gate 4 checks each name on its own and cannot
|
||||
see the collision, so extraction is where an overwrite is caught — and rejected."""
|
||||
payload = factory.make_bundle(factory.ZipSpec(repeat_file='artifacts/prediction_model/MLmodel'))
|
||||
|
||||
with pytest.raises(BundleGateError, match='overwrite') as error:
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 5
|
||||
assert not (work_dir / 'extract').exists()
|
||||
|
||||
|
||||
def test_gate_5_wraps_an_os_error_during_unpacking(work_dir, reader, monkeypatch):
|
||||
"""A write failure partway through extraction (e.g. disk full) is reported as unpacking
|
||||
trouble, not surfaced as the raw OSError."""
|
||||
payload = factory.make_bundle()
|
||||
|
||||
def _raise_os_error(*_args, **_kwargs):
|
||||
raise OSError('disk full')
|
||||
|
||||
monkeypatch.setattr('laborious.utils.bundle.reader.shutil.copyfileobj', _raise_os_error)
|
||||
|
||||
with pytest.raises(BundleGateError, match='could not be unpacked') as error:
|
||||
_open(reader, work_dir, payload)
|
||||
|
||||
assert error.value.gate == 5
|
||||
assert not (work_dir / 'extract').exists()
|
||||
|
||||
|
||||
def test_extract_member_rechecks_containment_on_the_resolved_path(work_dir, reader):
|
||||
"""Gate 4 already rejects a traversal by name; extraction re-checks on the *resolved* path as
|
||||
a second, independent line of defense."""
|
||||
root = work_dir / 'root'
|
||||
root.mkdir()
|
||||
buffer = io.BytesIO()
|
||||
with zipfile.ZipFile(buffer, 'w') as archive:
|
||||
archive.writestr('safe.txt', b'hello')
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(buffer.getvalue())) as archive:
|
||||
entry = archive.infolist()[0]
|
||||
with pytest.raises(BundleGateError, match='resolves outside') as error:
|
||||
reader._extract_member(archive, entry, root, work_dir / 'somewhere-else')
|
||||
|
||||
assert error.value.gate == 5
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- gate 6
|
||||
|
||||
|
||||
@@ -375,6 +481,10 @@ def test_gate_5_extraction_writes_plain_files(work_dir, reader):
|
||||
),
|
||||
'experiment name',
|
||||
),
|
||||
(
|
||||
factory.ZipSpec(document={'metadata': 'not-a-dict', 'parameters': {}}),
|
||||
'not objects',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_gate_6_rejects_a_malformed_metadata_document(work_dir, reader, spec, match):
|
||||
@@ -457,6 +567,19 @@ def test_gate_7_rejects_content_a_model_export_should_not_carry(work_dir, reader
|
||||
assert not (work_dir / 'extract').exists()
|
||||
|
||||
|
||||
def test_gate_7_rejects_a_non_regular_file_left_in_the_tree(work_dir, reader):
|
||||
"""Extraction itself only ever writes plain files and directories; this is the check that
|
||||
would catch anything else showing up in the tree by some other means."""
|
||||
root = work_dir / 'weird_root'
|
||||
root.mkdir()
|
||||
os.mkfifo(root / 'pipe')
|
||||
|
||||
with pytest.raises(BundleGateError, match='not a file') as error:
|
||||
reader._gate_7_content_policy(root)
|
||||
|
||||
assert error.value.gate == 7
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ ordering
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user