Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/scripts/gate-fork-status-publication.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const GATE_CONTEXT = 'Gate / gate';
const GATE_PATH = '.github/workflows/pr-00-gate.yml';
const SUMMARY_JOB_NAMES = new Set(['summary', 'gate-summary']);
Comment thread
stranske marked this conversation as resolved.
const BLOCKED_PREFIXES = [
'.github/actions/',
'.github/scripts/',
Expand Down Expand Up @@ -74,7 +75,7 @@ function publicationState({ run, jobs, changedFiles }) {
if (run.status !== 'completed') {
return { state: 'pending', description: 'Trusted Gate run is in progress' };
}
const summaries = jobs.filter(job => String(job.name || '').toLowerCase() === 'summary');
const summaries = jobs.filter(job => SUMMARY_JOB_NAMES.has(job.name));
if (summaries.length !== 1 || jobs.some(job => job.status !== 'completed')) {
return { state: 'error', description: 'Gate job set is missing or incomplete' };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const GATE_CONTEXT = 'Gate / gate';
const GATE_PATH = '.github/workflows/pr-00-gate.yml';
const SUMMARY_JOB_NAMES = new Set(['summary', 'gate-summary']);
const BLOCKED_PREFIXES = [
'.github/actions/',
'.github/scripts/',
Expand Down Expand Up @@ -74,7 +75,7 @@ function publicationState({ run, jobs, changedFiles }) {
if (run.status !== 'completed') {
return { state: 'pending', description: 'Trusted Gate run is in progress' };
}
const summaries = jobs.filter(job => String(job.name || '').toLowerCase() === 'summary');
const summaries = jobs.filter(job => SUMMARY_JOB_NAMES.has(job.name));
if (summaries.length !== 1 || jobs.some(job => job.status !== 'completed')) {
return { state: 'error', description: 'Gate job set is missing or incomplete' };
}
Expand Down
50 changes: 43 additions & 7 deletions tests/workflows/test_gate_fork_status_publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

ROOT = pathlib.Path(__file__).parents[2]
HELPER = ROOT / ".github/scripts/gate-fork-status-publication.js"
TEMPLATE_HELPER = ROOT / "templates/consumer-repo/.github/scripts/gate-fork-status-publication.js"
WORKFLOW = ROOT / ".github/workflows/pr-00-gate-fork-status.yml"
SUPPORTED_SUMMARY_NAMES = ("summary", "gate-summary")


def _decide(run, jobs, changed_files):
Expand All @@ -34,7 +36,7 @@ def _collect_changed_paths(pr, files):
return json.loads(completed.stdout)


def _publish_scenario(*, final_run=None, same_repo=False, replay=False):
def _publish_scenario(*, final_run=None, same_repo=False, replay=False, summary_name="summary"):
initial_run = {
"id": 9,
"workflow_id": 7,
Expand Down Expand Up @@ -85,7 +87,7 @@ def _publish_scenario(*, final_run=None, same_repo=False, replay=False):
actions: {{
getWorkflowRun: async () => ({{data: runReads++ === 0 ? scenario.initialRun : scenario.finalRun}}),
getWorkflow: endpoint({{id: 7, path: '.github/workflows/pr-00-gate.yml'}}),
listJobsForWorkflowRunAttempt: endpoint([{{name: 'summary', status: 'completed', conclusion: 'success'}}]),
listJobsForWorkflowRunAttempt: endpoint([{{name: {json.dumps(summary_name)}, status: 'completed', conclusion: 'success'}}]),
listWorkflowRuns: endpoint([scenario.initialRun]),
}},
pulls: {{
Expand Down Expand Up @@ -147,8 +149,9 @@ def test_fork_gate_status_publisher_has_trusted_minimal_permissions():
assert "maxRetries: 0" in helper


def test_fork_gate_status_requires_explicit_success_and_complete_summary():
jobs = [{"name": "summary", "status": "completed", "conclusion": "success"}]
@pytest.mark.parametrize("summary_name", SUPPORTED_SUMMARY_NAMES)
def test_fork_gate_status_requires_explicit_success_and_complete_summary(summary_name):
jobs = [{"name": summary_name, "status": "completed", "conclusion": "success"}]
assert (
_decide({"status": "completed", "conclusion": "success"}, jobs, ["src/app.py"])["state"]
== "success"
Expand All @@ -164,21 +167,54 @@ def test_fork_gate_status_requires_explicit_success_and_complete_summary():
assert (
_decide(
{"status": "completed", "conclusion": "failure"},
[{"name": "summary", "status": "completed", "conclusion": "failure"}],
[{"name": summary_name, "status": "completed", "conclusion": "failure"}],
["src/app.py"],
)["state"]
== "failure"
)


def test_fork_gate_status_publishes_success_once_for_bound_evidence():
outcome = _publish_scenario()
@pytest.mark.parametrize("summary_name", SUPPORTED_SUMMARY_NAMES)
def test_fork_gate_status_publishes_success_once_for_bound_evidence(summary_name):
outcome = _publish_scenario(summary_name=summary_name)
assert outcome["result"]["state"] == "success"
assert len(outcome["writes"]) == 1
assert outcome["writes"][0]["sha"] == "abc"
assert outcome["writes"][0]["context"] == "Gate / gate"


@pytest.mark.parametrize(
"jobs",
[
[],
[
{"name": "summary", "status": "completed", "conclusion": "success"},
{"name": "summary", "status": "completed", "conclusion": "success"},
],
[
{"name": "gate-summary", "status": "completed", "conclusion": "success"},
{"name": "gate-summary", "status": "completed", "conclusion": "success"},
],
[
{"name": "summary", "status": "completed", "conclusion": "success"},
{"name": "gate-summary", "status": "completed", "conclusion": "success"},
],
[{"name": "Summary", "status": "completed", "conclusion": "success"}],
[{"name": "Gate-Summary", "status": "completed", "conclusion": "success"}],
[{"name": " gate-summary ", "status": "completed", "conclusion": "success"}],
[{"name": "gate-summary (3.12)", "status": "completed", "conclusion": "success"}],
[{"name": "Gate / gate-summary", "status": "completed", "conclusion": "success"}],
],
)
def test_fork_gate_status_rejects_missing_ambiguous_or_lookalike_summary(jobs):
result = _decide({"status": "completed", "conclusion": "success"}, jobs, ["src/app.py"])
assert result == {"state": "error", "description": "Gate job set is missing or incomplete"}


def test_fork_gate_status_source_and_template_helpers_match():
assert HELPER.read_bytes() == TEMPLATE_HELPER.read_bytes()


def test_fork_gate_status_does_not_write_after_attempt_drift():
final_run = {
"id": 9,
Expand Down
Loading