Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0c64e5ef4 | ||
|
|
7a140e59c3 |
@@ -34,8 +34,8 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: read
|
||||
# 该 job 仅处理 PR;评论写入也限定在 PR Conversation 这一权限域。
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check out default branch tooling
|
||||
uses: actions/checkout@v4
|
||||
@@ -69,12 +69,10 @@ jobs:
|
||||
PARSE_ERROR: ${{ steps.parse.outputs.error }}
|
||||
run: |
|
||||
body="❌ /eval 命令解析失败:${PARSE_ERROR}"
|
||||
jq -n --arg body "$body" '{body: $body}' | curl --fail --silent --show-error \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
--data @- \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" > /dev/null
|
||||
gh api --method POST \
|
||||
"repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
||||
--raw-field body="$body" \
|
||||
> /dev/null
|
||||
exit 1
|
||||
|
||||
- name: Verify reviewed PR head
|
||||
@@ -103,13 +101,9 @@ jobs:
|
||||
set -euo pipefail
|
||||
placeholder_body="🛰️ /eval 请求已通过权限与版本校验,正在生成可验证的评测请求。"
|
||||
response="$(
|
||||
jq -n --arg body "$placeholder_body" '{body: $body}' \
|
||||
| curl --fail --silent --show-error \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
--data @- \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments"
|
||||
gh api --method POST \
|
||||
"repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
||||
--raw-field body="$placeholder_body"
|
||||
)"
|
||||
comment_id="$(
|
||||
printf '%s' "$response" \
|
||||
@@ -277,13 +271,9 @@ jobs:
|
||||
fi
|
||||
body="<!-- eval-dispatch: ${marker_json} -->"$'\n'"🛰️ /eval 已受理:产品集 \`${PRODUCTS}\`${cases_note},评测对象 \`${PR_HEAD_SHA}\`。"$'\n'"受控评测服务将在数分钟内处理,完成后由 bot 回贴报告。"
|
||||
response="$(
|
||||
jq -n --arg body "$body" '{body: $body}' \
|
||||
| curl --fail --silent --show-error \
|
||||
-X PATCH \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
--data @- \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}"
|
||||
gh api --method PATCH \
|
||||
"repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}" \
|
||||
--raw-field body="$body"
|
||||
)"
|
||||
printf '%s' "$response" \
|
||||
| jq -e \
|
||||
@@ -299,12 +289,8 @@ jobs:
|
||||
DISPATCH_COMMENT_ID: ${{ steps.placeholder.outputs.comment_id }}
|
||||
run: |
|
||||
failure_body="❌ /eval 请求准备失败,未生成可消费的评测请求。请稍后重试。"
|
||||
jq -n --arg body "$failure_body" '{body: $body}' \
|
||||
| curl --fail --silent --show-error \
|
||||
-X PATCH \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
--data @- \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}" \
|
||||
gh api --method PATCH \
|
||||
"repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}" \
|
||||
--raw-field body="$failure_body" \
|
||||
> /dev/null \
|
||||
|| true
|
||||
|
||||
@@ -44,6 +44,84 @@ func TestEvalDispatchWorkflowUsesRepositoryPermissionAndReviewedSHA(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalDispatchWorkflowCanWritePRConversationComments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatalf("resolve repository root: %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "eval-dispatch.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read eval-dispatch workflow: %v", err)
|
||||
}
|
||||
workflow := string(data)
|
||||
|
||||
dispatchStart := strings.Index(workflow, "jobs:\n dispatch:")
|
||||
if dispatchStart < 0 {
|
||||
t.Fatal("eval-dispatch workflow missing dispatch job")
|
||||
}
|
||||
stepsOffset := strings.Index(workflow[dispatchStart:], "\n steps:")
|
||||
if stepsOffset < 0 {
|
||||
t.Fatal("eval-dispatch workflow missing dispatch steps")
|
||||
}
|
||||
permissions := workflow[dispatchStart : dispatchStart+stepsOffset]
|
||||
if !containsTrimmedLine(permissions, "pull-requests: write") {
|
||||
t.Error("dispatch job must grant write permission for PR conversation comments")
|
||||
}
|
||||
if containsTrimmedLine(permissions, "pull-requests: read") {
|
||||
t.Error("dispatch job still limits pull requests to read-only")
|
||||
}
|
||||
if containsTrimmedLine(permissions, "issues: write") {
|
||||
t.Error("dispatch job must keep a single write domain instead of granting issue-wide writes")
|
||||
}
|
||||
|
||||
commentWrites := []struct {
|
||||
step string
|
||||
method string
|
||||
endpoint string
|
||||
}{
|
||||
{
|
||||
step: "Reply usage on parse failure",
|
||||
method: "POST",
|
||||
endpoint: "issues/${PR_NUMBER}/comments",
|
||||
},
|
||||
{
|
||||
step: "Create dispatch placeholder",
|
||||
method: "POST",
|
||||
endpoint: "issues/${PR_NUMBER}/comments",
|
||||
},
|
||||
{
|
||||
step: "Finalize dispatch marker",
|
||||
method: "PATCH",
|
||||
endpoint: "issues/comments/${DISPATCH_COMMENT_ID}",
|
||||
},
|
||||
{
|
||||
step: "Mark dispatch preparation failure",
|
||||
method: "PATCH",
|
||||
endpoint: "issues/comments/${DISPATCH_COMMENT_ID}",
|
||||
},
|
||||
}
|
||||
for _, write := range commentWrites {
|
||||
write := write
|
||||
t.Run(write.step, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
step := evalDispatchWorkflowStep(t, workflow, write.step)
|
||||
if !strings.Contains(step, "gh api --method "+write.method) {
|
||||
t.Errorf("step %q must use gh api so GitHub's HTTP error message remains visible", write.step)
|
||||
}
|
||||
if !strings.Contains(step, write.endpoint) {
|
||||
t.Errorf("step %q missing comment endpoint %q", write.step, write.endpoint)
|
||||
}
|
||||
for _, forbidden := range []string{"curl --fail", "Authorization: Bearer", "2>/dev/null"} {
|
||||
if strings.Contains(step, forbidden) {
|
||||
t.Errorf("step %q hides or manually transports API diagnostics via %q", write.step, forbidden)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalDispatchWorkflowPublishesArtifactBoundRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -102,7 +180,7 @@ func TestEvalDispatchWorkflowPublishesArtifactBoundRequest(t *testing.T) {
|
||||
"ARTIFACT_ID: ${{ steps.artifact.outputs.artifact-id }}",
|
||||
"ARTIFACT_DIGEST: ${{ steps.artifact.outputs.artifact-digest }}",
|
||||
"<!-- eval-dispatch: ${marker_json} -->",
|
||||
"-X PATCH",
|
||||
"gh api --method PATCH",
|
||||
"issues/comments/${DISPATCH_COMMENT_ID}",
|
||||
} {
|
||||
if !strings.Contains(workflow, want) {
|
||||
@@ -120,6 +198,30 @@ func TestEvalDispatchWorkflowPublishesArtifactBoundRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func evalDispatchWorkflowStep(t *testing.T, workflow, name string) string {
|
||||
t.Helper()
|
||||
|
||||
marker := " - name: " + name + "\n"
|
||||
start := strings.Index(workflow, marker)
|
||||
if start < 0 {
|
||||
t.Fatalf("eval-dispatch workflow missing step %q", name)
|
||||
}
|
||||
rest := workflow[start+len(marker):]
|
||||
if end := strings.Index(rest, "\n - name: "); end >= 0 {
|
||||
return rest[:end]
|
||||
}
|
||||
return rest
|
||||
}
|
||||
|
||||
func containsTrimmedLine(text, want string) bool {
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
if strings.TrimSpace(line) == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestEvalPollValidatePython(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user