#!/usr/bin/env python3 """Contract tests for the managed runtime pin update workflow.""" from pathlib import Path import re import unittest REPO_ROOT = Path(__file__).resolve().parents[2] WORKFLOW = REPO_ROOT / ".github" / "workflows" / "update-agent-runtime-pins.yml" LINT_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "lint-action-pinning.yml" SCRIPT = REPO_ROOT / "scripts" / "update-agent-runtime-pins.mjs" REQUIRED_CHECK_WORKFLOWS = ( "backend-tests.yml", "frontend-tests.yml", "e2e-tests.yml", "architecture-lint.yml", "lint-action-pinning.yml", "lint-harness-files.yml", ) class ManagedRuntimePinWorkflowContractTest(unittest.TestCase): def setUp(self) -> None: self.workflow = WORKFLOW.read_text(encoding="utf-8") def test_workflow_has_weekly_and_manual_triggers(self) -> None: self.assertIn("schedule:", self.workflow) self.assertRegex(self.workflow, r"cron:\s*[\"']?[0-9]+ [0-9]+ \* \* [0-6][\"']?") self.assertIn("workflow_dispatch:", self.workflow) def test_workflow_uses_least_privilege_and_builtin_token(self) -> None: self.assertIn( "permissions:\n contents: write\n pull-requests: write", self.workflow ) self.assertIn("GH_TOKEN: ${{ github.token }}", self.workflow) self.assertNotIn("actions/create-github-app-token@", self.workflow) self.assertNotIn("MANAGED_RUNTIME_PIN_APP_PRIVATE_KEY", self.workflow) self.assertNotIn("MANAGED_RUNTIME_PIN_APP_CLIENT_ID", self.workflow) self.assertNotIn("secrets.GITHUB_TOKEN", self.workflow) def test_workflow_groups_one_conventional_commit_pr_on_stable_branch(self) -> None: self.assertIn("update-managed-runtime-pins", self.workflow) self.assertIn("chore: update managed runtime pins", self.workflow) self.assertIn("gh pr create", self.workflow) self.assertNotIn("gh pr merge", self.workflow) self.assertNotIn("--auto", self.workflow) self.assertIn("at most one", self.workflow) def test_workflow_refreshes_existing_pr_by_number_and_authenticates_push(self) -> None: self.assertIn('open_pr_number="$(gh pr list', self.workflow) self.assertIn('gh pr edit "$open_pr_number"', self.workflow) self.assertNotIn('gh pr edit \\\n --repo "$GITHUB_REPOSITORY" \\\n --head "$BOT_BRANCH"', self.workflow) push_start = self.workflow.index("- name: Commit and push one grouped change") pr_start = self.workflow.index("- name: Create or refresh the single grouped PR") push_step = self.workflow[push_start:pr_start] pr_step = self.workflow[pr_start:] self.assertIn("GH_TOKEN: ${{ github.token }}", push_step) self.assertIn("GH_TOKEN: ${{ github.token }}", pr_step) def test_workflow_dispatches_required_checks_for_builtin_token_prs(self) -> None: self.assertIn("actions: write", self.workflow) dispatch_start = self.workflow.index( "- name: Dispatch required checks for generated PR" ) dispatch_step = self.workflow[dispatch_start:] self.assertIn("gh workflow run", dispatch_step) self.assertIn('--ref "$BOT_BRANCH"', dispatch_step) self.assertIn('--repo "$GITHUB_REPOSITORY"', dispatch_step) for workflow_name in REQUIRED_CHECK_WORKFLOWS: self.assertIn(workflow_name, dispatch_step) workflow = (REPO_ROOT / ".github" / "workflows" / workflow_name).read_text( encoding="utf-8" ) self.assertRegex( workflow, r"(?m)^ workflow_dispatch:\s*$", f"{workflow_name} must accept explicit validation dispatches", ) def test_workflow_validates_before_any_commit_or_push(self) -> None: update_pos = self.workflow.index("node scripts/update-agent-runtime-pins.mjs") validation_pos = self.workflow.index("go test ./internal/agent/agents") commit_pos = self.workflow.index("git commit") push_pos = self.workflow.index("git push") self.assertLess(update_pos, validation_pos) self.assertLess(validation_pos, commit_pos) self.assertLess(commit_pos, push_pos) self.assertIn("node --test scripts/update-agent-runtime-pins.test.mjs", self.workflow) self.assertIn("--github-output", self.workflow) self.assertIn("working-directory: apps/backend", self.workflow) self.assertIn( "go test ./internal/agent/agents ./internal/agent/managedruntime " "./internal/agent/runtime/lifecycle ./internal/agent/hostutility " "./internal/agent/settings/controller ./internal/agent/settings/handlers", self.workflow, ) self.assertIn("if: steps.update.outputs.changed == 'true'", self.workflow) def test_all_actions_are_commit_pinned(self) -> None: for line in self.workflow.splitlines(): if " uses: " not in line: continue self.assertRegex(line, r"uses:\s+[^@\s]+@[0-9a-f]{40}(?:\s+#.*)?$") def test_lint_workflow_runs_this_contract(self) -> None: lint_workflow = LINT_WORKFLOW.read_text(encoding="utf-8") self.assertIn( "python3 .github/scripts/update-agent-runtime-pins-workflow-contract_test.py", lint_workflow, ) def test_updater_script_exists_and_has_no_network_in_fixture_tests(self) -> None: self.assertTrue(SCRIPT.is_file()) script = SCRIPT.read_text(encoding="utf-8") self.assertIn("registry.npmjs.org", script) self.assertIn("fs.rename", script) if __name__ == "__main__": unittest.main()