|
| 1 | +name: Test Bootstrap Fix |
| 2 | +# Smoke-test for the branch-check and PR-creation fixes in bootstrap-copilot-sync.sh. |
| 3 | +# Runs on push to copilot/test-bootstrap-workflow to give live evidence that: |
| 4 | +# 1. GET /git/ref/heads/{branch} succeeds with the fine-grained PAT. |
| 5 | +# 2. POST /repos/{owner}/{repo}/pulls succeeds (with sleep 5 propagation guard). |
| 6 | +# |
| 7 | +# This workflow will be deleted once the PR is merged and the bootstrap has been |
| 8 | +# validated in production. |
| 9 | + |
| 10 | +on: |
| 11 | + push: |
| 12 | + branches: |
| 13 | + - copilot/test-bootstrap-workflow |
| 14 | + |
| 15 | +permissions: |
| 16 | + contents: read |
| 17 | + |
| 18 | +jobs: |
| 19 | + verify-branch-check-endpoint: |
| 20 | + name: Verify Git Data API branch check |
| 21 | + runs-on: ubuntu-latest |
| 22 | + steps: |
| 23 | + - name: Checkout |
| 24 | + uses: actions/checkout@v4 |
| 25 | + |
| 26 | + - name: Verify PAT authentication |
| 27 | + env: |
| 28 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 29 | + run: | |
| 30 | + login=$(gh api /user --jq '.login' 2>/dev/null || true) |
| 31 | + if [ -z "$login" ]; then |
| 32 | + echo "::error::PAT_WORKFLOWS is not set or cannot authenticate" |
| 33 | + exit 1 |
| 34 | + fi |
| 35 | + echo "✓ Authenticated as $login" |
| 36 | +
|
| 37 | + - name: Test /git/ref/heads/{branch} endpoint (the NEW branch check) |
| 38 | + env: |
| 39 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 40 | + run: | |
| 41 | + # This test exercises the EXACT endpoint pattern that was changed. |
| 42 | + # We use Cratis/Chronicle (a known repo) and check its default branch. |
| 43 | + # If the fine-grained PAT can read this, the propagation poll will work. |
| 44 | +
|
| 45 | + echo "--- Testing /git/ref/heads/main on Cratis/Chronicle ---" |
| 46 | + result=$(gh api "repos/Cratis/Chronicle/git/ref/heads/main" \ |
| 47 | + --jq '.object.sha' 2>/dev/null || true) |
| 48 | +
|
| 49 | + if [ -n "$result" ]; then |
| 50 | + echo "✓ GET /git/ref/heads/main → sha=$result" |
| 51 | + echo " The new branch check endpoint works with the fine-grained PAT." |
| 52 | + else |
| 53 | + echo "::error::GET /git/ref/heads/main returned empty — PAT cannot access this endpoint" |
| 54 | + exit 1 |
| 55 | + fi |
| 56 | +
|
| 57 | + echo "" |
| 58 | + echo "--- Testing /git/ref/heads/main on Cratis/Fundamentals ---" |
| 59 | + result2=$(gh api "repos/Cratis/Fundamentals/git/ref/heads/main" \ |
| 60 | + --jq '.object.sha' 2>/dev/null || true) |
| 61 | + if [ -n "$result2" ]; then |
| 62 | + echo "✓ GET /git/ref/heads/main → sha=$result2" |
| 63 | + else |
| 64 | + echo "::warning::GET /git/ref/heads/main on Fundamentals returned empty" |
| 65 | + fi |
| 66 | +
|
| 67 | + - name: Test OLD /branches/{branch} endpoint (should fail or be inaccessible) |
| 68 | + env: |
| 69 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 70 | + run: | |
| 71 | + # This step shows WHY the old endpoint was broken. |
| 72 | + # A fine-grained PAT with only Contents:write may return empty here, |
| 73 | + # causing the 60-second polling loop to always time out. |
| 74 | +
|
| 75 | + echo "--- Testing /branches/main on Cratis/Chronicle ---" |
| 76 | + result=$(gh api "repos/Cratis/Chronicle/branches/main" \ |
| 77 | + --jq '.name' 2>/dev/null || true) |
| 78 | +
|
| 79 | + if [ -n "$result" ]; then |
| 80 | + echo "ℹ /branches/main returned: name=$result" |
| 81 | + echo " This endpoint works for this PAT too." |
| 82 | + echo " The issue was likely something else in those runs." |
| 83 | + else |
| 84 | + echo "✓ /branches/main returned EMPTY — confirmed the old endpoint is broken" |
| 85 | + echo " This is why every run timed out: the PAT can read git refs" |
| 86 | + echo " but not the higher-level /branches endpoint." |
| 87 | + fi |
| 88 | + # This step always succeeds — it is diagnostic only. |
| 89 | +
|
| 90 | + verify-pr-creation: |
| 91 | + name: Verify PR creation works |
| 92 | + runs-on: ubuntu-latest |
| 93 | + needs: verify-branch-check-endpoint |
| 94 | + steps: |
| 95 | + - name: Checkout |
| 96 | + uses: actions/checkout@v4 |
| 97 | + |
| 98 | + - name: Check if add-copilot-sync-workflows branch exists on Chronicle |
| 99 | + id: check-branch |
| 100 | + env: |
| 101 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 102 | + run: | |
| 103 | + # Previous bootstrap runs created the branch but no PR. |
| 104 | + # Check if it still exists via the new endpoint pattern. |
| 105 | + branch="add-copilot-sync-workflows" |
| 106 | + sha=$(gh api "repos/Cratis/Chronicle/git/ref/heads/$branch" \ |
| 107 | + --jq '.object.sha' 2>/dev/null || true) |
| 108 | +
|
| 109 | + if [ -n "$sha" ]; then |
| 110 | + echo "✓ Branch $branch exists on Chronicle (sha=$sha)" |
| 111 | + echo "branch_exists=true" >> "$GITHUB_OUTPUT" |
| 112 | + echo "branch_sha=$sha" >> "$GITHUB_OUTPUT" |
| 113 | + else |
| 114 | + echo "branch_exists=false" >> "$GITHUB_OUTPUT" |
| 115 | + echo "ℹ Branch $branch does not exist on Chronicle (may have been cleaned up)" |
| 116 | + fi |
| 117 | +
|
| 118 | + - name: Check for existing PR on Chronicle |
| 119 | + id: check-pr |
| 120 | + if: steps.check-branch.outputs.branch_exists == 'true' |
| 121 | + env: |
| 122 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 123 | + run: | |
| 124 | + branch="add-copilot-sync-workflows" |
| 125 | + pr_num=$(gh api "repos/Cratis/Chronicle/pulls?state=open&head=Cratis:$branch" \ |
| 126 | + --jq '.[0].number // empty' 2>/dev/null || true) |
| 127 | +
|
| 128 | + if [ -n "$pr_num" ]; then |
| 129 | + echo "ℹ PR already exists: #$pr_num" |
| 130 | + echo "pr_exists=true" >> "$GITHUB_OUTPUT" |
| 131 | + echo "pr_number=$pr_num" >> "$GITHUB_OUTPUT" |
| 132 | + else |
| 133 | + echo "No open PR found for branch $branch" |
| 134 | + echo "pr_exists=false" >> "$GITHUB_OUTPUT" |
| 135 | + fi |
| 136 | +
|
| 137 | + - name: Create PR on Chronicle (proves PR creation works with sleep 5) |
| 138 | + if: >- |
| 139 | + steps.check-branch.outputs.branch_exists == 'true' && |
| 140 | + steps.check-pr.outputs.pr_exists == 'false' |
| 141 | + env: |
| 142 | + GH_TOKEN: ${{ secrets.PAT_WORKFLOWS }} |
| 143 | + run: | |
| 144 | + branch="add-copilot-sync-workflows" |
| 145 | +
|
| 146 | + default_branch=$(gh api "repos/Cratis/Chronicle" \ |
| 147 | + --jq '.default_branch' 2>/dev/null || true) |
| 148 | + echo "Default branch: $default_branch" |
| 149 | +
|
| 150 | + echo "Sleeping 5s (simulating post-branch-creation propagation guard)..." |
| 151 | + sleep 5 |
| 152 | +
|
| 153 | + pr_response=$(gh api -X POST "repos/Cratis/Chronicle/pulls" \ |
| 154 | + -f title="Bootstrap Copilot sync workflows" \ |
| 155 | + -f body="Test PR from bootstrap fix verification." \ |
| 156 | + -f head="$branch" \ |
| 157 | + -f base="$default_branch" \ |
| 158 | + 2>/tmp/pr_err || true) |
| 159 | + pr_url=$(echo "$pr_response" | jq -r '.html_url // empty' 2>/dev/null || true) |
| 160 | +
|
| 161 | + if [ -n "$pr_url" ] && [ "$pr_url" != "null" ]; then |
| 162 | + echo "✓ PR created: $pr_url" |
| 163 | + echo " PR creation with plain branch name + sleep 5 works." |
| 164 | + else |
| 165 | + pr_err=$(cat /tmp/pr_err 2>/dev/null || true) |
| 166 | + pr_msg=$(echo "$pr_response" | jq -r '.message // empty' 2>/dev/null || true) |
| 167 | + pr_errs=$(echo "$pr_response" | jq -r '(.errors // []) | map(.message // .code // "unknown") | join("; ")' 2>/dev/null || true) |
| 168 | + echo "::error::PR creation failed" |
| 169 | + [ -n "$pr_err" ] && echo " API error: $pr_err" |
| 170 | + [ -n "$pr_msg" ] && echo " GitHub message: $pr_msg" |
| 171 | + [ -n "$pr_errs" ] && echo " GitHub errors: $pr_errs" |
| 172 | + exit 1 |
| 173 | + fi |
| 174 | +
|
| 175 | + - name: Summary when branch does not exist |
| 176 | + if: steps.check-branch.outputs.branch_exists != 'true' |
| 177 | + run: | |
| 178 | + echo "Branch add-copilot-sync-workflows not found on Chronicle." |
| 179 | + echo "This likely means it was cleaned up after previous runs." |
| 180 | + echo "Run the full bootstrap workflow on main to do a complete end-to-end test." |
| 181 | + echo "The branch-check endpoint is verified by the first job." |
0 commit comments