|
14 | 14 | ) |
15 | 15 |
|
16 | 16 |
|
| 17 | +def _step_block(workflow: str, name_fragment: str) -> str: |
| 18 | + """Return the YAML of the first step whose ``name:`` contains *name_fragment*, |
| 19 | + up to (but excluding) the next ``- name:`` step. Lets assertions target one |
| 20 | + step instead of the whole file, so an unrelated step can't satisfy or break |
| 21 | + them.""" |
| 22 | + lines = workflow.splitlines() |
| 23 | + start = next( |
| 24 | + i |
| 25 | + for i, line in enumerate(lines) |
| 26 | + if line.lstrip().startswith("- name:") and name_fragment in line |
| 27 | + ) |
| 28 | + block = [lines[start]] |
| 29 | + for line in lines[start + 1 :]: |
| 30 | + if line.lstrip().startswith("- name:"): |
| 31 | + break |
| 32 | + block.append(line) |
| 33 | + return "\n".join(block) |
| 34 | + |
| 35 | + |
17 | 36 | def test_builders_create_release_as_prerelease() -> None: |
18 | 37 | """Each builder must mark the Release prerelease. |
19 | 38 |
|
@@ -86,11 +105,15 @@ def test_site_dispatch_uses_scoped_github_app_token_and_degrades_gracefully() -> |
86 | 105 | assert re.search(r"permissions:\s+contents:\s*read", workflow) |
87 | 106 |
|
88 | 107 | # Best-effort and non-blocking: a missing/rotated App must never red-line |
89 | | - # main. The token step continues on error and the sync degrades (exit 0) |
90 | | - # rather than failing loud — the daily pythinker-home cron is the real sync |
91 | | - # guarantee. |
92 | | - assert "continue-on-error: true" in workflow |
93 | | - assert "exit 1" not in workflow |
| 108 | + # main. Scoped to the relevant steps so unrelated steps can't satisfy/break |
| 109 | + # them: the token step continues on error, and the dispatch step degrades |
| 110 | + # (exit 0) rather than failing loud — the daily pythinker-home cron is the |
| 111 | + # real sync guarantee. |
| 112 | + token_step = _step_block(workflow, "Mint GitHub App token") |
| 113 | + assert "continue-on-error: true" in token_step |
| 114 | + dispatch_step = _step_block(workflow, "Trigger pythinker-home sync") |
| 115 | + assert "exit 1" not in dispatch_step |
| 116 | + assert "exit 0" in dispatch_step |
94 | 117 |
|
95 | 118 | # ...but degradation is SURFACED, not silently swallowed: a step-summary |
96 | 119 | # warning plus a Slack alert keep a broken App visible. |
|
0 commit comments