@@ -731,6 +731,9 @@ def test_poll_and_collect_complete_success() -> None:
731731 "internal_index_image_copy" : "internal/img:v1" ,
732732 "index_image" : "registry/idx:v4.12" ,
733733 "from_index" : "registry/idx:v4.12" ,
734+ }
735+ full_build : iib .IIBBuild = {
736+ ** build ,
734737 "logs" : {"url" : "https://logs/1" },
735738 }
736739 manifest = {
@@ -741,9 +744,12 @@ def test_poll_and_collect_complete_success() -> None:
741744 }
742745 ],
743746 }
744- with mock .patch (
745- "skopeo.subprocess.run" ,
746- return_value = _skopeo_result (json .dumps (manifest )),
747+ with (
748+ mock .patch ("iib.get_build" , return_value = full_build ),
749+ mock .patch (
750+ "skopeo.subprocess.run" ,
751+ return_value = _skopeo_result (json .dumps (manifest )),
752+ ),
747753 ):
748754 result = update_fbc_catalog ._poll_and_collect (
749755 "https://iib" ,
@@ -765,13 +771,14 @@ def test_poll_and_collect_failed_build() -> None:
765771 "state" : "failed" ,
766772 "state_reason" : "something broke" ,
767773 }
768- result = update_fbc_catalog ._poll_and_collect (
769- "https://iib" ,
770- build ,
771- 3600 ,
772- False ,
773- False ,
774- )
774+ with mock .patch ("iib.get_build" , return_value = build ):
775+ result = update_fbc_catalog ._poll_and_collect (
776+ "https://iib" ,
777+ build ,
778+ 3600 ,
779+ False ,
780+ False ,
781+ )
775782 assert result .exit_code == 1
776783 assert result .state == "failed"
777784
@@ -830,6 +837,12 @@ def _setup_result_env(
830837 return paths
831838
832839
840+ def _assert_all_results_exist (paths : dict [str , Path ]) -> None :
841+ """Assert that all five Tekton result files exist."""
842+ for name , path in paths .items ():
843+ assert path .exists (), f"{ name } result file was not created"
844+
845+
833846def test_main_invalid_fbc_fragments (
834847 monkeypatch : pytest .MonkeyPatch ,
835848 tmp_path : Path ,
@@ -848,6 +861,7 @@ def test_main_invalid_fbc_fragments(
848861 state = json .loads (paths ["RESULT_BUILD_STATE" ].read_text (encoding = "utf-8" ))
849862 assert state ["state" ] == "failed"
850863 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "1"
864+ _assert_all_results_exist (paths )
851865
852866
853867def test_main_empty_fbc_fragments (
@@ -866,6 +880,7 @@ def test_main_empty_fbc_fragments(
866880 ]
867881 )
868882 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "1"
883+ _assert_all_results_exist (paths )
869884
870885
871886def test_main_writes_results_on_success (
@@ -924,6 +939,30 @@ def test_main_writes_results_on_success(
924939 )
925940
926941
942+ def test_main_writes_iib_log_when_url_empty (
943+ monkeypatch : pytest .MonkeyPatch ,
944+ tmp_path : Path ,
945+ ) -> None :
946+ """All result files are written even when iib_log_url is empty."""
947+ paths = _setup_result_env (monkeypatch , tmp_path )
948+ _setup_mount_env (monkeypatch , tmp_path )
949+
950+ fake_result = update_fbc_catalog .RunResult (
951+ build_info = {"id" : 1 , "state" : "complete" },
952+ state = "complete" ,
953+ state_reason = "ok" ,
954+ index_image_digests = "sha256:abc" ,
955+ iib_log_url = "" ,
956+ exit_code = 0 ,
957+ )
958+ with mock .patch ("update_fbc_catalog.run" , return_value = fake_result ):
959+ rc = update_fbc_catalog .main (["--fbc-fragments" , '["frag"]' , "--from-index" , "idx:v1" ])
960+
961+ assert rc == 0
962+ _assert_all_results_exist (paths )
963+ assert paths ["RESULT_IIB_LOG" ].read_text (encoding = "utf-8" ) == ""
964+
965+
927966def test_main_check_step_error_writes_failure (
928967 monkeypatch : pytest .MonkeyPatch ,
929968 tmp_path : Path ,
@@ -961,6 +1000,7 @@ def test_main_check_step_error_writes_failure(
9611000 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "1"
9621001 state = json .loads (paths ["RESULT_BUILD_STATE" ].read_text (encoding = "utf-8" ))
9631002 assert state ["state" ] == "failed"
1003+ _assert_all_results_exist (paths )
9641004
9651005
9661006def test_main_missing_result_env_exits () -> None :
@@ -994,6 +1034,7 @@ def test_main_invalid_build_tags_json(
9941034 ]
9951035 )
9961036 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "1"
1037+ _assert_all_results_exist (paths )
9971038
9981039
9991040def test_main_returns_nonzero_on_failed_build (
@@ -1030,6 +1071,7 @@ def test_main_returns_nonzero_on_failed_build(
10301071 state = json .loads (paths ["RESULT_BUILD_STATE" ].read_text (encoding = "utf-8" ))
10311072 assert state ["state" ] == "failed"
10321073 assert state ["state_reason" ] == "Build failed with exit code 1"
1074+ _assert_all_results_exist (paths )
10331075
10341076
10351077def test_main_timeout_returns_124 (
@@ -1063,6 +1105,7 @@ def test_main_timeout_returns_124(
10631105
10641106 assert rc == 124
10651107 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "124"
1108+ _assert_all_results_exist (paths )
10661109
10671110
10681111def _setup_mount_env (
@@ -1144,13 +1187,14 @@ def test_poll_and_collect_validation_error() -> None:
11441187 "index_image" : "registry/idx:wrong" ,
11451188 "from_index" : "registry/idx:v4.12" ,
11461189 }
1147- result = update_fbc_catalog ._poll_and_collect (
1148- "https://iib" ,
1149- build ,
1150- 3600 ,
1151- True ,
1152- True ,
1153- )
1190+ with mock .patch ("iib.get_build" , return_value = build ):
1191+ result = update_fbc_catalog ._poll_and_collect (
1192+ "https://iib" ,
1193+ build ,
1194+ 3600 ,
1195+ True ,
1196+ True ,
1197+ )
11541198 assert result .exit_code == 1
11551199 assert result .state == "failed"
11561200 assert "Index image mismatch" in result .state_reason
@@ -1164,13 +1208,14 @@ def test_poll_and_collect_missing_internal_copy() -> None:
11641208 "index_image" : "registry/idx:v4.12" ,
11651209 "from_index" : "registry/idx:v4.12" ,
11661210 }
1167- result = update_fbc_catalog ._poll_and_collect (
1168- "https://iib" ,
1169- build ,
1170- 3600 ,
1171- True ,
1172- True ,
1173- )
1211+ with mock .patch ("iib.get_build" , return_value = build ):
1212+ result = update_fbc_catalog ._poll_and_collect (
1213+ "https://iib" ,
1214+ build ,
1215+ 3600 ,
1216+ True ,
1217+ True ,
1218+ )
11741219 assert result .exit_code == 1
11751220 assert "Missing internal_index_image_copy" in result .state_reason
11761221
@@ -1182,9 +1227,12 @@ def test_poll_and_collect_digest_extraction_fails() -> None:
11821227 "state" : "complete" ,
11831228 "internal_index_image_copy" : "internal/img:v1" ,
11841229 }
1185- with mock .patch (
1186- "skopeo.subprocess.run" ,
1187- return_value = _skopeo_result (returncode = 1 ),
1230+ with (
1231+ mock .patch ("iib.get_build" , return_value = build ),
1232+ mock .patch (
1233+ "skopeo.subprocess.run" ,
1234+ return_value = _skopeo_result (returncode = 1 ),
1235+ ),
11881236 ):
11891237 result = update_fbc_catalog ._poll_and_collect (
11901238 "https://iib" ,
@@ -1241,26 +1289,28 @@ def test_poll_and_collect_failed_state_reason() -> None:
12411289 "state" : "failed" ,
12421290 "state_reason" : "IIB internal error" ,
12431291 }
1244- result = update_fbc_catalog ._poll_and_collect (
1245- "https://iib" ,
1246- build_with_reason ,
1247- 3600 ,
1248- False ,
1249- False ,
1250- )
1292+ with mock .patch ("iib.get_build" , return_value = build_with_reason ):
1293+ result = update_fbc_catalog ._poll_and_collect (
1294+ "https://iib" ,
1295+ build_with_reason ,
1296+ 3600 ,
1297+ False ,
1298+ False ,
1299+ )
12511300 assert result .state_reason == "IIB internal error"
12521301
12531302 build_no_reason : iib .IIBBuild = {
12541303 "id" : 2 ,
12551304 "state" : "failed" ,
12561305 }
1257- result2 = update_fbc_catalog ._poll_and_collect (
1258- "https://iib" ,
1259- build_no_reason ,
1260- 3600 ,
1261- False ,
1262- False ,
1263- )
1306+ with mock .patch ("iib.get_build" , return_value = build_no_reason ):
1307+ result2 = update_fbc_catalog ._poll_and_collect (
1308+ "https://iib" ,
1309+ build_no_reason ,
1310+ 3600 ,
1311+ False ,
1312+ False ,
1313+ )
12641314 assert result2 .state_reason == "Build failed with exit code 1"
12651315
12661316
@@ -1627,6 +1677,7 @@ def test_main_empty_from_index(
16271677 ]
16281678 )
16291679 assert paths ["RESULT_EXIT_CODE" ].read_text (encoding = "utf-8" ) == "1"
1680+ _assert_all_results_exist (paths )
16301681
16311682
16321683def test_poll_and_collect_removes_state_history_without_polling () -> None :
@@ -1637,6 +1688,13 @@ def test_poll_and_collect_removes_state_history_without_polling() -> None:
16371688 "internal_index_image_copy" : "internal/img:v1" ,
16381689 "state_history" : [{"state" : "in_progress" }],
16391690 }
1691+ full_build : iib .IIBBuild = {
1692+ "id" : 1 ,
1693+ "state" : "complete" ,
1694+ "internal_index_image_copy" : "internal/img:v1" ,
1695+ "state_history" : [{"state" : "in_progress" }],
1696+ "logs" : {"url" : "https://logs/1" },
1697+ }
16401698 manifest = {
16411699 "manifests" : [
16421700 {
@@ -1645,9 +1703,12 @@ def test_poll_and_collect_removes_state_history_without_polling() -> None:
16451703 }
16461704 ],
16471705 }
1648- with mock .patch (
1649- "skopeo.subprocess.run" ,
1650- return_value = _skopeo_result (json .dumps (manifest )),
1706+ with (
1707+ mock .patch ("iib.get_build" , return_value = full_build ),
1708+ mock .patch (
1709+ "skopeo.subprocess.run" ,
1710+ return_value = _skopeo_result (json .dumps (manifest )),
1711+ ),
16511712 ):
16521713 result = update_fbc_catalog ._poll_and_collect (
16531714 "https://iib" ,
@@ -1658,3 +1719,79 @@ def test_poll_and_collect_removes_state_history_without_polling() -> None:
16581719 )
16591720 assert result .exit_code == 0
16601721 assert "state_history" not in result .build_info
1722+
1723+
1724+ def test_poll_and_collect_fetches_full_details_for_reused_build () -> None :
1725+ """Reused completed build is re-fetched via the detail endpoint."""
1726+ list_build : iib .IIBBuild = {
1727+ "id" : 42 ,
1728+ "state" : "complete" ,
1729+ "internal_index_image_copy" : "internal/img:v1" ,
1730+ }
1731+ detail_build : iib .IIBBuild = {
1732+ "id" : 42 ,
1733+ "state" : "complete" ,
1734+ "internal_index_image_copy" : "internal/img:v1" ,
1735+ "logs" : {"url" : "https://logs/42" },
1736+ }
1737+ manifest = {
1738+ "manifests" : [
1739+ {
1740+ "mediaType" : "application/vnd.docker.distribution.manifest.v2+json" ,
1741+ "digest" : "sha256:abc" ,
1742+ }
1743+ ],
1744+ }
1745+ with (
1746+ mock .patch ("iib.get_build" , return_value = detail_build ) as get_mock ,
1747+ mock .patch (
1748+ "skopeo.subprocess.run" ,
1749+ return_value = _skopeo_result (json .dumps (manifest )),
1750+ ),
1751+ ):
1752+ result = update_fbc_catalog ._poll_and_collect (
1753+ "https://iib" ,
1754+ list_build ,
1755+ 3600 ,
1756+ False ,
1757+ False ,
1758+ )
1759+ get_mock .assert_called_once_with ("https://iib" , 42 )
1760+ assert result .iib_log_url == "https://logs/42"
1761+ assert result .exit_code == 0
1762+
1763+
1764+ def test_poll_and_collect_detail_fetch_failure_is_non_fatal () -> None :
1765+ """Failure to fetch full details does not break the flow."""
1766+ list_build : iib .IIBBuild = {
1767+ "id" : 42 ,
1768+ "state" : "complete" ,
1769+ "internal_index_image_copy" : "internal/img:v1" ,
1770+ }
1771+ manifest = {
1772+ "manifests" : [
1773+ {
1774+ "mediaType" : "application/vnd.docker.distribution.manifest.v2+json" ,
1775+ "digest" : "sha256:abc" ,
1776+ }
1777+ ],
1778+ }
1779+ with (
1780+ mock .patch (
1781+ "iib.get_build" ,
1782+ side_effect = requests .ConnectionError ("timeout" ),
1783+ ),
1784+ mock .patch (
1785+ "skopeo.subprocess.run" ,
1786+ return_value = _skopeo_result (json .dumps (manifest )),
1787+ ),
1788+ ):
1789+ result = update_fbc_catalog ._poll_and_collect (
1790+ "https://iib" ,
1791+ list_build ,
1792+ 3600 ,
1793+ False ,
1794+ False ,
1795+ )
1796+ assert result .exit_code == 0
1797+ assert result .iib_log_url == ""
0 commit comments