66
77import pytest
88
9+ import quant_advisor_research .preview_bundle as preview_bundle
910from quant_advisor_research .advisory_report import build_advisory_report
1011from quant_advisor_research .preview_bundle import (
1112 BUNDLE_CONTRACT ,
@@ -29,7 +30,6 @@ def report(*, cadence="daily", as_of="2026-06-20"):
2930
3031def test_daily_bundle_builds_and_readback_validates (tmp_path ):
3132 output = tmp_path / "preview"
32- output .mkdir ()
3333 result = build_preview_bundle (report (), output )
3434
3535 assert result .bundle_contract == BUNDLE_CONTRACT
@@ -64,10 +64,9 @@ def test_daily_bundle_builds_and_readback_validates(tmp_path):
6464@pytest .mark .parametrize ("cadence" , ["weekly" , "monthly" ])
6565def test_non_daily_source_is_rejected_before_output (cadence , tmp_path ):
6666 output = tmp_path / "preview"
67- output .mkdir ()
6867 with pytest .raises (PreviewBundleError , match = "daily_only" ):
6968 build_preview_bundle (report (cadence = cadence ), output )
70- assert list ( output .iterdir ()) == []
69+ assert not output .exists ()
7170
7271
7372@pytest .mark .parametrize ("mutation" , [
@@ -79,17 +78,14 @@ def test_source_contract_mutations_fail_closed_without_partial_output(mutation,
7978 value = report ()
8079 mutation (value )
8180 output = tmp_path / "preview"
82- output .mkdir ()
8381 with pytest .raises (PreviewBundleError ):
8482 build_preview_bundle (value , output )
85- assert list ( output .iterdir ()) == []
83+ assert not output .exists ()
8684
8785
8886def test_build_is_deterministic_for_equivalent_mapping_order (tmp_path ):
8987 left = tmp_path / "left"
9088 right = tmp_path / "right"
91- left .mkdir ()
92- right .mkdir ()
9389 value = report ()
9490 reordered = {key : value [key ] for key in reversed (list (value ))}
9591 build_preview_bundle (value , left )
@@ -101,7 +97,6 @@ def test_build_is_deterministic_for_equivalent_mapping_order(tmp_path):
10197
10298def test_html_escapes_snapshot_and_has_only_fixed_relative_links (tmp_path ):
10399 output = tmp_path / "preview"
104- output .mkdir ()
105100 value = report ()
106101 value ["source_artifacts" ]["political_events" ] = "<script>alert('x')</script>"
107102 build_preview_bundle (value , output )
@@ -121,7 +116,6 @@ def test_html_escapes_snapshot_and_has_only_fixed_relative_links(tmp_path):
121116])
122117def test_readback_tamper_and_extra_file_fail_closed (tamper , tmp_path ):
123118 output = tmp_path / "preview"
124- output .mkdir ()
125119 build_preview_bundle (report (), output )
126120 tamper (output )
127121 with pytest .raises (PreviewBundleError ):
@@ -133,6 +127,51 @@ def test_non_empty_output_fails_without_touching_sentinel(tmp_path):
133127 output .mkdir ()
134128 sentinel = output / "sentinel"
135129 sentinel .write_text ("keep" )
136- with pytest .raises (PreviewBundleError , match = "output_not_empty " ):
130+ with pytest .raises (PreviewBundleError , match = "output_exists " ):
137131 build_preview_bundle (report (), output )
138132 assert sentinel .read_text () == "keep"
133+
134+
135+ def test_destination_is_not_visible_during_staging_readback (tmp_path , monkeypatch ):
136+ output = tmp_path / "preview"
137+ observed = []
138+ original = preview_bundle .read_preview_bundle
139+
140+ def inspect (path ):
141+ observed .append (Path (path ))
142+ assert not output .exists ()
143+ return original (path )
144+
145+ monkeypatch .setattr (preview_bundle , "read_preview_bundle" , inspect )
146+ build_preview_bundle (report (), output )
147+ assert len (observed ) == 1
148+ assert output .is_dir ()
149+
150+
151+ def test_concurrent_destination_winner_is_not_overwritten_or_cleaned (tmp_path , monkeypatch ):
152+ output = tmp_path / "preview"
153+ real_rename = preview_bundle .os .rename
154+
155+ def concurrent_winner (source , destination ):
156+ Path (destination ).mkdir ()
157+ raise FileExistsError (destination )
158+
159+ monkeypatch .setattr (preview_bundle .os , "rename" , concurrent_winner )
160+ with pytest .raises (PreviewBundleError , match = "output_exists" ):
161+ build_preview_bundle (report (), output )
162+ assert output .is_dir ()
163+ assert not list (tmp_path .glob (".preview.staging-*" ))
164+ monkeypatch .setattr (preview_bundle .os , "rename" , real_rename )
165+
166+
167+ def test_staging_directory_is_cleaned_when_install_fails (tmp_path , monkeypatch ):
168+ output = tmp_path / "preview"
169+
170+ def fail_readback (_path ):
171+ raise PreviewBundleError ("forced_readback_failure" )
172+
173+ monkeypatch .setattr (preview_bundle , "read_preview_bundle" , fail_readback )
174+ with pytest .raises (PreviewBundleError , match = "output_write_failed" ):
175+ build_preview_bundle (report (), output )
176+ assert not output .exists ()
177+ assert not list (tmp_path .glob (".preview.staging-*" ))
0 commit comments