Files
gpt-load/internal/webui/workflow_test.go
T
tbphp d4bbb9193a feat: 升级 Go 与 SDK 版本 (#485)
* feat(gateway): 升级 SDK 并封口兼容合同

* fix: 修正升级后的格式与模型过滤

* fix(ci): 更新 Go 漏洞扫描器

* fix(gateway): 拒绝非标准服务层级字段
2026-08-27 22:48:16 +08:00

2359 lines
78 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package webui
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
)
const (
checkoutActionRef = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1"
setupGoActionRef = "actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e"
setupNodeActionRef = "actions/setup-node@820762786026740c76f36085b0efc47a31fe5020"
pnpmSetupActionRef = "pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271"
uploadArtifactActionRef = "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"
downloadArtifactActionRef = "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"
qemuActionRef = "docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8"
buildxActionRef = "docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c"
dockerLoginActionRef = "docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c"
dockerMetadataActionRef = "docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302"
dockerBuildActionRef = "docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a"
githubReleaseActionRef = "softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228"
)
func TestWebCICompositeActionRunsCompleteFrontendGate(t *testing.T) {
content := readRepositoryFile(t, ".github/actions/web-ci/action.yml")
for _, required := range []string{
pnpmSetupActionRef,
"version: 11.17.0",
setupNodeActionRef,
"node-version: 24.18.0",
} {
if !strings.Contains(content, required) {
t.Fatalf("web-ci action does not contain %q", required)
}
}
previousIndex := -1
for _, command := range []string{
"pnpm --dir web install --frozen-lockfile",
"pnpm --dir web audit --prod --audit-level high",
"pnpm --dir web run lint",
"pnpm --dir web run format",
"pnpm --dir web run build",
} {
if count := strings.Count(content, command); count != 1 {
t.Fatalf("web-ci action contains %q %d times, want exactly once", command, count)
}
commandIndex := strings.Index(content, command)
if commandIndex <= previousIndex {
t.Fatalf("web-ci action does not run %q in the required order", command)
}
previousIndex = commandIndex
}
if strings.Contains(content, "pnpm --dir web run type-check") {
t.Fatal("web-ci action duplicates the type-check already run by the build script")
}
// devDependencies 不进入 internal/webui/dist,审计它们只会让发布被无关 CVE 阻断。
if strings.Contains(content, "pnpm --dir web audit --audit-level") {
t.Fatal("web-ci action audits devDependencies that never reach the release artifact")
}
packageJSON := readRepositoryFile(t, "web/package.json")
if !strings.Contains(packageJSON, `"build": "pnpm run type-check && vite build"`) {
t.Fatal("web build script no longer includes the required type-check")
}
}
func TestMakeCheckDoesNotDuplicateWebTypeCheck(t *testing.T) {
content := readRepositoryFile(t, "Makefile")
if strings.Contains(content, "run type-check") {
t.Fatal("make check duplicates the type-check already run by the build script")
}
if !strings.Contains(content, "run build") {
t.Fatal("make check does not build the web application")
}
}
func TestBranchAndReleaseWorkflowsRunRaceInParallelGates(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/ci.yml")
testJob := workflowJobBlock(t, content, "test")
workflowGoFormattingScript(t, content, "test")
for _, test := range []struct {
name string
run string
}{
{name: "Check module graph", run: "go mod tidy -diff"},
{name: "Audit Go dependencies", run: "go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./..."},
{name: "Run Go vet", run: "go vet ./..."},
{name: "Check repository invariants", run: "git diff --check"},
} {
assertWorkflowGateStep(t, testJob, test.name, test.run)
}
if strings.Contains(testJob, "go test -race") {
t.Fatal("branch static job still runs race tests serially")
}
assertWorkflowGateStep(
t,
workflowJobBlock(t, content, "race-tests"),
"Run race-enabled tests",
"go test -race -count=1 -timeout=15m . ./internal/...",
)
branchCPA := workflowStepBlock(
t,
workflowJobBlock(t, content, "race-cpa"),
"Run embedded CPA bridge race-enabled tests",
)
for _, required := range []string{
"working-directory: third_party/cpaembedded",
"run: go test -race -count=1 -timeout=15m ./...",
} {
if !strings.Contains(branchCPA, required) {
t.Fatalf("branch CPA race gate does not contain %q:\n%s", required, branchCPA)
}
}
releaseContent := readRepositoryFile(t, ".github/workflows/release.yml")
releaseStaticJob := workflowJobBlock(
t,
releaseContent,
"static-checks",
)
if strings.Contains(releaseStaticJob, "go test -race") {
t.Fatal("release static job still runs race tests serially")
}
assertWorkflowGateStep(
t,
workflowJobBlock(t, releaseContent, "race-tests"),
"Run race-enabled tests",
"go test -race -count=1 -timeout=15m . ./internal/...",
)
releaseCPA := workflowStepBlock(
t,
workflowJobBlock(t, releaseContent, "race-cpa"),
"Run embedded CPA bridge race-enabled tests",
)
for _, required := range []string{
"working-directory: third_party/cpaembedded",
"run: go test -race -count=1 -timeout=15m ./...",
} {
if !strings.Contains(releaseCPA, required) {
t.Fatalf("release CPA race gate does not contain %q:\n%s", required, releaseCPA)
}
}
}
func TestWindowsCIExecutesManagedStorageACLTests(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/ci.yml")
job := workflowJobBlock(t, content, "windows-encryption-acl")
if count := strings.Count(job, "runs-on: windows-2025"); count != 1 {
t.Fatalf("Windows ACL job contains runs-on declaration %d times, want exactly once", count)
}
assertWorkflowGateStep(
t,
job,
"Test Windows secure file and storage ACLs",
"go test -v -count=1 ./internal/platform/securefile ./internal/platform/encryption ./internal/storage",
)
}
func TestWorkflowsPinExternalActionsAndHostedRunners(t *testing.T) {
actionRef := regexp.MustCompile(`(?m)^\s*uses:\s+([^#\s]+)`)
immutableRef := regexp.MustCompile(`^[^@\s]+@[0-9a-f]{40}$`)
for _, name := range []string{
".github/actions/web-ci/action.yml",
".github/workflows/ci.yml",
".github/workflows/release.yml",
} {
content := readRepositoryFile(t, name)
for _, match := range actionRef.FindAllStringSubmatch(content, -1) {
ref := match[1]
if strings.HasPrefix(ref, "./") {
continue
}
if !immutableRef.MatchString(ref) {
t.Errorf("%s contains mutable or invalid action reference %q", name, ref)
}
}
}
ci := readRepositoryFile(t, ".github/workflows/ci.yml")
for _, required := range []string{"runs-on: ubuntu-24.04", "runs-on: windows-2025"} {
if !strings.Contains(ci, required) {
t.Errorf("branch CI does not contain %q", required)
}
}
if strings.Contains(ci, "-latest") {
t.Error("branch CI contains a mutable hosted runner label")
}
}
func TestBranchWorkflowCancelsSupersededRuns(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/ci.yml")
concurrency := workflowTopLevelBlock(t, content, "concurrency")
for _, required := range []string{
`group: v2-ci-${{ github.event.pull_request.number || github.ref }}`,
"cancel-in-progress: true",
} {
if !strings.Contains(concurrency, required) {
t.Fatalf("branch CI concurrency does not contain %q:\n%s", required, concurrency)
}
}
}
func TestWorkflowsDisableCheckoutCredentialPersistence(t *testing.T) {
for _, name := range []string{
".github/workflows/ci.yml",
".github/workflows/release.yml",
} {
content := readRepositoryFile(t, name)
checkoutCount := strings.Count(content, "uses: "+checkoutActionRef)
disabledCount := strings.Count(content, "persist-credentials: false")
if checkoutCount == 0 {
t.Fatalf("%s does not contain a checkout action", name)
}
if disabledCount != checkoutCount {
t.Fatalf(
"%s disables checkout credential persistence %d times, want %d",
name,
disabledCount,
checkoutCount,
)
}
}
}
func TestGoFormattingScriptsFailClosed(t *testing.T) {
for _, workflow := range []struct {
name string
file string
job string
}{
{name: "branch", file: ".github/workflows/ci.yml", job: "test"},
{name: "release", file: ".github/workflows/release.yml", job: "static-checks"},
} {
t.Run(workflow.name, func(t *testing.T) {
script := workflowGoFormattingScript(
t,
readRepositoryFile(t, workflow.file),
workflow.job,
)
scriptPath := filepath.Join(t.TempDir(), "check-go-format.sh")
if err := os.WriteFile(
scriptPath,
[]byte("#!/usr/bin/env bash\nset -e\n"+script),
0o700,
); err != nil {
t.Fatalf("write Go formatting script: %v", err)
}
fakeBin := t.TempDir()
fakeGofmt := filepath.Join(fakeBin, "gofmt")
if err := os.WriteFile(fakeGofmt, []byte(`#!/usr/bin/env bash
case "${GOFMT_TEST_MODE}" in
clean) exit 0 ;;
unformatted) printf 'unformatted.go\n'; exit 0 ;;
failed) exit 2 ;;
*) exit 64 ;;
esac
`), 0o700); err != nil {
t.Fatalf("write fake gofmt: %v", err)
}
for _, test := range []struct {
name string
mode string
wantErr bool
}{
{name: "clean", mode: "clean"},
{name: "unformatted", mode: "unformatted", wantErr: true},
{name: "formatter failure", mode: "failed", wantErr: true},
} {
t.Run(test.name, func(t *testing.T) {
command := exec.Command("bash", scriptPath)
command.Env = append(
os.Environ(),
"GOFMT_TEST_MODE="+test.mode,
"PATH="+fakeBin+string(os.PathListSeparator)+os.Getenv("PATH"),
)
output, err := command.CombinedOutput()
if (err != nil) != test.wantErr {
t.Fatalf("Go formatting check error = %v, want error %t\n%s", err, test.wantErr, output)
}
})
}
})
}
}
func TestReleaseWorkflowUsesTagOnlyTriggerAndStrictSemverGuard(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
trigger := workflowTopLevelBlock(t, content, "on")
for _, required := range []string{"push:", "tags:", `- "v2.*"`} {
if !strings.Contains(trigger, required) {
t.Fatalf("release trigger does not contain %q:\n%s", required, trigger)
}
}
for _, forbidden := range []string{"branches:", "pull_request:", "workflow_dispatch:"} {
if strings.Contains(trigger, forbidden) {
t.Fatalf("release trigger contains forbidden %q:\n%s", forbidden, trigger)
}
}
script := workflowMarkedScript(t, content, "release-tag-validation")
scriptPath := filepath.Join(t.TempDir(), "validate-release-tag.sh")
if err := os.WriteFile(scriptPath, []byte("#!/usr/bin/env bash\nset -euo pipefail\n"+script), 0o700); err != nil {
t.Fatalf("write tag validation script: %v", err)
}
for _, test := range []struct {
tag string
valid bool
wantOutput []string
}{
{
tag: "v2.0.0",
valid: true,
wantOutput: []string{
"version=v2.0.0", "prerelease=false", "image_exact=v2.0.0",
"image_minor=2.0", "image_major=2", "beta=false",
},
},
{
tag: "v2.0.0-rc.1",
valid: true,
wantOutput: []string{
"version=v2.0.0-rc.1", "prerelease=true", "image_exact=v2.0.0-rc.1",
"image_minor=2.0", "image_major=2", "beta=false",
},
},
{
tag: "v2.0.0-beta.1",
valid: true,
wantOutput: []string{
"version=v2.0.0-beta.1", "prerelease=true", "image_exact=v2.0.0-beta.1",
"image_minor=2.0", "image_major=2", "beta=true",
},
},
{
tag: "v2.10.3-alpha-1.0",
valid: true,
wantOutput: []string{
"version=v2.10.3-alpha-1.0", "prerelease=true",
"image_exact=v2.10.3-alpha-1.0", "image_minor=2.10", "image_major=2",
"beta=false",
},
},
{tag: "v2.0.0.1", valid: false},
{tag: "v2.01.0", valid: false},
{tag: "v2.0.01", valid: false},
{tag: "v2.0.0-", valid: false},
{tag: "v2.0.0-rc..1", valid: false},
{tag: "v2.0.0-01", valid: false},
{tag: "v2.0.0+build.1", valid: false},
{tag: "v1.4.0", valid: false},
} {
t.Run(test.tag, func(t *testing.T) {
outputPath := filepath.Join(t.TempDir(), "github-output")
command := exec.Command("bash", scriptPath)
command.Env = append(
os.Environ(),
"GITHUB_REF_NAME="+test.tag,
"GITHUB_OUTPUT="+outputPath,
)
output, err := command.CombinedOutput()
if test.valid && err != nil {
t.Fatalf("tag %s rejected: %v\n%s", test.tag, err, output)
}
if !test.valid && err == nil {
t.Fatalf("tag %s accepted, want rejection\n%s", test.tag, output)
}
if test.valid {
githubOutput, readErr := os.ReadFile(outputPath)
if readErr != nil {
t.Fatalf("read tag outputs: %v", readErr)
}
for _, expected := range test.wantOutput {
if !strings.Contains(string(githubOutput), expected+"\n") {
t.Fatalf("tag %s outputs do not contain %q:\n%s", test.tag, expected, githubOutput)
}
}
}
})
}
}
func TestReleasePublicationStateClassifiesFreshConsistentConflictAndPartial(t *testing.T) {
script := filepath.Join("..", "..", ".github", "scripts", "release-publication-state.sh")
expectedSHA := "0123456789abcdef0123456789abcdef01234567"
base := map[string]string{
"RELEASE_EXPECTED_SHA": expectedSHA,
"RELEASE_GITHUB_STATE": "absent",
"RELEASE_GITHUB_TARGET_SHA": "",
"RELEASE_GITHUB_ASSETS": "absent",
"RELEASE_GHCR_STATE": "absent",
"RELEASE_GHCR_DIGEST": "absent",
"RELEASE_GHCR_REVISION": "",
"RELEASE_DOCKERHUB_STATE": "absent",
"RELEASE_DOCKERHUB_DIGEST": "absent",
"RELEASE_DOCKERHUB_REVISION": "",
}
clone := func(overrides map[string]string) map[string]string {
values := make(map[string]string, len(base))
for key, value := range base {
values[key] = value
}
for key, value := range overrides {
values[key] = value
}
return values
}
run := func(t *testing.T, values map[string]string) (string, error) {
t.Helper()
command := exec.Command("bash", script)
command.Env = []string{"PATH=" + os.Getenv("PATH")}
for key, value := range values {
command.Env = append(command.Env, key+"="+value)
}
output, err := command.CombinedOutput()
return string(output), err
}
allPresent := map[string]string{
"RELEASE_GITHUB_STATE": "present",
"RELEASE_GITHUB_TARGET_SHA": expectedSHA,
"RELEASE_GITHUB_ASSETS": "match",
"RELEASE_GHCR_STATE": "present",
"RELEASE_GHCR_DIGEST": "sha256:aaaaaaaa",
"RELEASE_GHCR_REVISION": expectedSHA,
"RELEASE_DOCKERHUB_STATE": "present",
"RELEASE_DOCKERHUB_DIGEST": "sha256:aaaaaaaa",
"RELEASE_DOCKERHUB_REVISION": expectedSHA,
}
present := func(overrides map[string]string) map[string]string {
values := clone(allPresent)
for key, value := range overrides {
values[key] = value
}
return values
}
for _, test := range []struct {
name string
values map[string]string
want string
}{
{
name: "fresh",
values: clone(nil),
want: "publication_state=fresh\nwrite_mode=publish\n",
},
{
name: "consistent",
values: present(nil),
want: "publication_state=consistent\nwrite_mode=verify\n",
},
{
name: "conflict/GitHub target",
values: present(map[string]string{
"RELEASE_GITHUB_TARGET_SHA": "fedcba9876543210fedcba9876543210fedcba98",
}),
want: "publication_state=conflict\nwrite_mode=blocked\n",
},
{
name: "conflict/GitHub assets or checksum",
values: present(map[string]string{
"RELEASE_GITHUB_ASSETS": "mismatch",
}),
want: "publication_state=conflict\nwrite_mode=blocked\n",
},
{
name: "conflict/GHCR revision",
values: present(map[string]string{
"RELEASE_GHCR_REVISION": "fedcba9876543210fedcba9876543210fedcba98",
}),
want: "publication_state=conflict\nwrite_mode=blocked\n",
},
{
name: "conflict/Docker Hub revision",
values: present(map[string]string{
"RELEASE_DOCKERHUB_REVISION": "fedcba9876543210fedcba9876543210fedcba98",
}),
want: "publication_state=conflict\nwrite_mode=blocked\n",
},
{
name: "conflict/registry digest",
values: present(map[string]string{
"RELEASE_DOCKERHUB_DIGEST": "sha256:bbbbbbbb",
}),
want: "publication_state=conflict\nwrite_mode=blocked\n",
},
{
name: "partial",
values: clone(map[string]string{
"RELEASE_GITHUB_STATE": "present",
"RELEASE_GITHUB_TARGET_SHA": expectedSHA,
"RELEASE_GITHUB_ASSETS": "match",
}),
want: "publication_state=partial\nwrite_mode=blocked\n",
},
} {
t.Run(test.name, func(t *testing.T) {
output, err := run(t, test.values)
if err != nil {
t.Fatalf("classifier rejected legal state: %v\n%s", err, output)
}
if output != test.want {
t.Fatalf("classifier output = %q, want %q", output, test.want)
}
})
}
for _, test := range []struct {
name string
values map[string]string
}{
{
name: "missing expected sha",
values: map[string]string{},
},
{
name: "invalid state enum",
values: clone(map[string]string{
"RELEASE_GHCR_STATE": "unknown",
}),
},
{
name: "absent channel with digest",
values: clone(map[string]string{
"RELEASE_GHCR_DIGEST": "sha256:aaaaaaaa",
}),
},
{
name: "present channel without revision",
values: clone(map[string]string{
"RELEASE_GHCR_STATE": "present",
"RELEASE_GHCR_DIGEST": "sha256:aaaaaaaa",
}),
},
} {
t.Run(test.name, func(t *testing.T) {
output, err := run(t, test.values)
if err == nil {
t.Fatalf("classifier accepted invalid input:\n%s", output)
}
})
}
}
func TestReleaseWorkflowDoesNotRequireUntrackedAgentInstructionFiles(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
verifyJob := workflowJobBlock(t, content, "static-checks")
if !strings.Contains(verifyJob, "git diff --check") {
t.Fatal("release verification does not run git diff --check")
}
for _, untracked := range []string{"AGENTS.md", "CLAUDE.md"} {
if strings.Contains(verifyJob, untracked) {
t.Fatalf("release verification requires untracked %s", untracked)
}
}
}
func TestReleaseWorkflowBuildsOneWebDistAndFiveVersionedBinaries(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
if count := strings.Count(content, "uses: ./.github/actions/web-ci"); count != 1 {
t.Fatalf("release workflow invokes web-ci %d times, want exactly once", count)
}
for _, required := range []string{
checkoutActionRef,
setupGoActionRef,
uploadArtifactActionRef,
downloadArtifactActionRef,
"name: verified-web-dist",
"path: internal/webui/dist",
"CGO_ENABLED: 0",
"go build -trimpath",
`gpt-load/internal/platform/version.Version=${{ github.ref_name }}`,
"gpt-load-linux-amd64",
"gpt-load-linux-arm64",
"gpt-load-macos-amd64",
"gpt-load-macos-arm64",
"gpt-load-windows-amd64.exe",
"SHA256SUMS",
} {
if !strings.Contains(content, required) {
t.Fatalf("release workflow does not contain %q", required)
}
}
for _, forbidden := range []string{"actions/checkout@v6", "actions/setup-go@v6"} {
if strings.Contains(content, forbidden) {
t.Fatalf("release workflow contains unavailable action major %q", forbidden)
}
}
if strings.Count(content, "pnpm --dir web run build") != 0 {
t.Fatal("release workflow rebuilds web outside the shared web-ci action")
}
}
func TestReleaseWorkflowGatesSingleReleaseWriterAndImagePublication(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
preflight := workflowJobBlock(t, content, "publication-preflight")
for _, dependency := range []string{
"validate-tag",
"verify-and-build-web",
"static-checks",
"race-tests",
"race-cpa",
"build-binaries",
"package-checksums",
"native-artifact-smoke",
"docker-smoke",
} {
if !strings.Contains(preflight, dependency) {
t.Fatalf("publication preflight does not need %s:\n%s", dependency, preflight)
}
}
if count := strings.Count(content, "softprops/action-gh-release@"); count != 1 {
t.Fatalf("release writer count = %d, want exactly 1", count)
}
releaseJob := workflowJobBlock(t, content, "publish-github")
if !strings.Contains(releaseJob, "contents: write") || strings.Contains(releaseJob, "packages: write") {
t.Fatalf("GitHub release permissions are not least privilege:\n%s", releaseJob)
}
imageJob := workflowJobBlock(t, content, "publish-images")
if !strings.Contains(imageJob, "packages: write") || strings.Contains(imageJob, "contents: write") {
t.Fatalf("image publication permissions are not least privilege:\n%s", imageJob)
}
}
func TestReleaseWorkflowDraftReadersRequestPushVisibility(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
for _, jobName := range []string{
"publication-preflight",
"post-publish-verify",
"reconcile-publication",
} {
job := workflowJobBlock(t, content, jobName)
if !strings.Contains(job, "contents: write") {
t.Fatalf("%s cannot discover Draft Releases without push visibility:\n%s", jobName, job)
}
}
}
func TestReleaseWorkflowUsesOneSharedPublicationPreflight(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
if count := strings.Count(content, " publication-preflight:"); count != 1 {
t.Fatalf("publication preflight job count = %d, want exactly 1", count)
}
if strings.Contains(content, " capture-channel-baseline:") {
t.Fatal("release workflow keeps a separate channel-baseline path")
}
preflight := workflowJobBlock(t, content, "publication-preflight")
for _, required := range []string{
"name: release-assets",
".github/scripts/release-verify-assets.sh release",
"git merge-base --is-ancestor",
"DOCKERHUB_READ_TOKEN: ${{ secrets.DOCKERHUB_READ_TOKEN || secrets.DOCKERHUB_TOKEN }}",
"DOCKERHUB_TOKEN",
"ghcr.io/tbphp/gpt-load:latest",
"tbphp/gpt-load:latest",
".github/scripts/release-publication-state.sh",
"publication_state:",
"write_mode:",
`test "${write_mode}" != "blocked"`,
} {
if !strings.Contains(preflight, required) {
t.Fatalf("publication preflight does not contain %q:\n%s", required, preflight)
}
}
for _, jobName := range []string{"publish-images", "publish-github"} {
job := workflowJobBlock(t, content, jobName)
if !strings.Contains(job, "publication-preflight") {
t.Fatalf("%s does not depend on the shared preflight:\n%s", jobName, job)
}
}
}
func TestReleaseWorkflowUsesTrustedCurrentRunChecksumForExistingRelease(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
for _, test := range []struct {
job string
step string
}{
{
job: "publication-preflight",
step: "Inventory publication channels",
},
{
job: "publish-github",
step: "Verify existing GitHub Release",
},
{
job: "post-publish-verify",
step: "Download and verify exact GitHub Release asset inventory",
},
} {
step := workflowStepBlock(
t,
workflowJobBlock(t, content, test.job),
test.step,
)
for _, required := range []string{
".github/scripts/release-verify-assets.sh",
"release/SHA256SUMS",
} {
if !strings.Contains(step, required) {
t.Fatalf(
"%s does not verify downloaded assets against the current-run checksum %q:\n%s",
test.step,
required,
step,
)
}
}
if strings.Contains(step, "sha256sum --check") {
t.Fatalf("%s executes a checksum file directly:\n%s", test.step, step)
}
}
// 行为验证:远端资产即使与它自带的 SHA256SUMS 完全自洽,只要与当前 run 的
// 可信副本不一致就必须被拒绝——远端那份校验和永远不会被执行。
repositoryRoot, err := filepath.Abs(filepath.Join("..", ".."))
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
verifier := filepath.Join(
repositoryRoot, ".github", "scripts", "release-verify-assets.sh",
)
var assets []string
for _, line := range strings.Split(
readRepositoryFile(t, ".github/release-assets.txt"), "\n",
) {
if name := strings.TrimSpace(line); name != "" && name != "SHA256SUMS" {
assets = append(assets, name)
}
}
if len(assets) == 0 {
t.Fatal("release asset manifest is empty")
}
build := func(directory string, replacements map[string]string) {
t.Helper()
if err := os.MkdirAll(directory, 0o700); err != nil {
t.Fatalf("create asset directory: %v", err)
}
checksums := &strings.Builder{}
for _, name := range assets {
body := "payload-" + name
if replacement, ok := replacements[name]; ok {
body = replacement
}
path := filepath.Join(directory, name)
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatalf("write asset: %v", err)
}
fmt.Fprintf(checksums, "%x %s\n", sha256.Sum256([]byte(body)), name)
}
path := filepath.Join(directory, "SHA256SUMS")
if err := os.WriteFile(path, []byte(checksums.String()), 0o600); err != nil {
t.Fatalf("write checksums: %v", err)
}
}
workspace := t.TempDir()
trusted := filepath.Join(workspace, "current-run")
build(trusted, nil)
forged := filepath.Join(workspace, "forged")
build(forged, map[string]string{"gpt-load-linux-amd64": "malicious"})
verify := func(directory string) error {
command := exec.Command(
verifier, directory, filepath.Join(trusted, "SHA256SUMS"),
)
command.Dir = repositoryRoot
return command.Run()
}
if err := verify(trusted); err != nil {
t.Fatalf("verifier rejected assets matching the current run: %v", err)
}
if err := verify(forged); err == nil {
t.Fatal("verifier accepted self-consistent forged assets over the trusted checksum")
}
}
func TestReleaseWorkflowKeepsUntrustedImageRevisionInsideJQComparison(t *testing.T) {
revisionVerifier := readRepositoryFile(
t, ".github/scripts/release-verify-image-revision.sh",
)
expectedSHA := "0123456789abcdef0123456789abcdef01234567"
validation := workflowMarkedScript(
t, revisionVerifier, "release-image-revision-validation",
)
scriptPath := filepath.Join(t.TempDir(), "validate-image-revision.sh")
script := "#!/usr/bin/env bash\nset -euo pipefail\n" +
"expected=" + expectedSHA + "\n" +
"inspection=\"${RELEASE_TEST_INSPECTION}\"\n" +
validation +
"printf '%s\\n' \"${revision}\"\n"
if err := os.WriteFile(scriptPath, []byte(script), 0o700); err != nil {
t.Fatalf("write image revision validation script: %v", err)
}
inspection := func(amd64, arm64 any) string {
value := map[string]any{
"image": map[string]any{
"linux/amd64": map[string]any{
"config": map[string]any{
"Labels": map[string]any{
"org.opencontainers.image.revision": amd64,
},
},
},
"linux/arm64": map[string]any{
"config": map[string]any{
"Labels": map[string]any{
"org.opencontainers.image.revision": arm64,
},
},
},
},
}
encoded, err := json.Marshal(value)
if err != nil {
t.Fatalf("marshal image inspection: %v", err)
}
return string(encoded)
}
for _, test := range []struct {
name string
inspection string
want string
}{
{
name: "exact",
inspection: inspection(expectedSHA, expectedSHA),
want: expectedSHA,
},
{
name: "newline",
inspection: inspection(expectedSHA+"\ninjected", expectedSHA),
want: "mismatch",
},
{
name: "pipe",
inspection: inspection(expectedSHA+"|injected", expectedSHA),
want: "mismatch",
},
{
name: "NUL",
inspection: inspection(expectedSHA+"\x00injected", expectedSHA),
want: "mismatch",
},
{
name: "unequal",
inspection: inspection("other", expectedSHA),
want: "mismatch",
},
{
name: "missing",
inspection: `{}`,
want: "mismatch",
},
{
name: "non-string",
inspection: inspection(123, expectedSHA),
want: "mismatch",
},
{
name: "malformed",
inspection: `{`,
want: "mismatch",
},
} {
t.Run(test.name, func(t *testing.T) {
command := exec.Command("bash", scriptPath)
command.Env = []string{
"PATH=" + os.Getenv("PATH"),
"RELEASE_TEST_INSPECTION=" + test.inspection,
}
output, err := command.Output()
if err != nil {
t.Fatalf("image revision validation failed: %v", err)
}
if got := strings.TrimSpace(string(output)); got != test.want {
t.Fatalf("revision = %q, want %q", got, test.want)
}
})
}
// 进入 shell 的只能是受信的 ${expected} 或字面量 mismatch;
// 实际读到的标签只允许直接写入 stderr,绝不赋给变量或送上 stdout。
for _, assignment := range regexp.MustCompile(`\brevision=\S*`).
FindAllString(revisionVerifier, -1) {
switch assignment {
case "revision=mismatch", `revision="${expected}"`:
default:
t.Fatalf("revision verifier assigns an untrusted value: %s", assignment)
}
}
content := readRepositoryFile(t, ".github/workflows/release.yml")
inventoryStep := workflowStepBlock(
t,
workflowJobBlock(t, content, "publication-preflight"),
"Inventory publication channels",
)
functionStart := strings.Index(inventoryStep, "inventory_image() {")
functionEnd := strings.Index(inventoryStep, "\n # 用命令替换而非进程替换")
if functionStart < 0 || functionEnd <= functionStart {
t.Fatalf("cannot isolate inventory_image:\n%s", inventoryStep)
}
inventoryFunction := inventoryStep[functionStart:functionEnd]
for _, required := range []string{
".github/scripts/release-verify-image-revision.sh",
"revision=mismatch",
} {
if !strings.Contains(inventoryFunction, required) {
t.Fatalf("inventory_image does not contain %q:\n%s", required, inventoryFunction)
}
}
for _, forbidden := range []string{
"child_revision",
"imagetools inspect",
"org.opencontainers.image.revision",
} {
if strings.Contains(inventoryFunction, forbidden) {
t.Fatalf(
"inventory_image parses the untrusted label itself instead of delegating (%q):\n%s",
forbidden,
inventoryFunction,
)
}
}
for _, test := range []struct {
job string
step string
}{
{
job: "publish-images",
step: "Verify exact published images",
},
{
job: "post-publish-verify",
step: "Verify published image manifests",
},
} {
step := workflowStepBlock(
t,
workflowJobBlock(t, content, test.job),
test.step,
)
if !strings.Contains(step, ".github/scripts/release-verify-image-revision.sh") {
t.Fatalf("%s does not delegate the revision comparison:\n%s", test.step, step)
}
for _, forbidden := range []string{
"org.opencontainers.image.revision",
`revision="$(`,
} {
if strings.Contains(step, forbidden) {
t.Fatalf("%s transfers a raw revision into shell (%q):\n%s", test.step, forbidden, step)
}
}
}
}
func TestReleaseWorkflowPublishesImagesBeforeGitHubRelease(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
imageJob := workflowJobBlock(t, content, "publish-images")
releaseJob := workflowJobBlock(t, content, "publish-github")
if strings.Contains(imageJob, "publish-github") {
t.Fatalf("image publication depends on GitHub publication:\n%s", imageJob)
}
for _, dependency := range []string{"publication-preflight", "publish-images"} {
if !strings.Contains(releaseJob, dependency) {
t.Fatalf("GitHub publication does not need %s:\n%s", dependency, releaseJob)
}
}
}
func TestReleaseWorkflowUpdatesAliasesOnlyAfterExactVerification(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
imageJob := workflowJobBlock(t, content, "publish-images")
exactVerification := strings.Index(imageJob, "name: Verify exact published images")
aliasUpdate := strings.Index(imageJob, "name: Update stable major and minor aliases")
if exactVerification < 0 || aliasUpdate < 0 || exactVerification >= aliasUpdate {
t.Fatalf(
"alias update is not ordered after exact verification: exact=%d alias=%d\n%s",
exactVerification,
aliasUpdate,
imageJob,
)
}
exactStep := workflowStepBlock(t, imageJob, "Verify exact published images")
for _, required := range []string{
".github/scripts/release-verify-image-revision.sh",
"GITHUB_SHA",
"ghcr_exact_digest",
"dockerhub_exact_digest",
`test "${ghcr_exact_digest}" = "${dockerhub_exact_digest}"`,
} {
if !strings.Contains(exactStep, required) {
t.Fatalf("exact image verification does not contain %q:\n%s", required, exactStep)
}
}
metadataStep := workflowStepBlock(t, imageJob, "Generate exact image metadata")
if !strings.Contains(metadataStep, "needs.validate-tag.outputs.image_exact") {
t.Fatalf("exact image metadata does not contain the exact tag:\n%s", metadataStep)
}
for _, forbidden := range []string{
"needs.validate-tag.outputs.image_minor",
"needs.validate-tag.outputs.image_major",
"latest",
} {
if strings.Contains(metadataStep, forbidden) {
t.Fatalf("exact image metadata contains non-exact tag %q:\n%s", forbidden, metadataStep)
}
}
aliasStep := workflowStepBlock(t, imageJob, "Update stable major and minor aliases")
for _, required := range []string{
"needs.publication-preflight.outputs.write_mode == 'publish'",
"needs.validate-tag.outputs.prerelease == 'false'",
"docker buildx imagetools create",
"image_minor",
"image_major",
} {
if !strings.Contains(aliasStep, required) {
t.Fatalf("alias update does not contain %q:\n%s", required, aliasStep)
}
}
if strings.Contains(strings.ToLower(aliasStep), "latest") {
t.Fatalf("alias update writes latest:\n%s", aliasStep)
}
}
func TestReleaseWorkflowMaintainsBetaChannelAlias(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
validateJob := workflowJobBlock(t, content, "validate-tag")
if !strings.Contains(validateJob, "beta: ${{ steps.tag.outputs.beta }}") {
t.Fatalf("tag validation does not expose the beta channel decision:\n%s", validateJob)
}
imageJob := workflowJobBlock(t, content, "publish-images")
exactVerification := strings.Index(imageJob, "name: Verify exact published images")
betaUpdate := strings.Index(imageJob, "name: Update beta channel alias")
if exactVerification < 0 || betaUpdate < 0 || exactVerification >= betaUpdate {
t.Fatalf(
"beta alias update is not ordered after exact verification: exact=%d beta=%d\n%s",
exactVerification,
betaUpdate,
imageJob,
)
}
betaStep := workflowStepBlock(t, imageJob, "Update beta channel alias")
for _, required := range []string{
"needs.publication-preflight.outputs.write_mode == 'publish'",
"needs.validate-tag.outputs.beta == 'true'",
"docker buildx imagetools create",
`exact="${repository}:${{ needs.validate-tag.outputs.image_exact }}"`,
`--tag "${repository}:v2beta"`,
} {
if !strings.Contains(betaStep, required) {
t.Fatalf("beta alias update does not contain %q:\n%s", required, betaStep)
}
}
verification := workflowStepBlock(
t,
workflowJobBlock(t, content, "post-publish-verify"),
"Verify published image manifests",
)
for _, required := range []string{
`needs.validate-tag.outputs.beta`,
`"${repository}:v2beta"`,
"beta_digest",
`test "${beta_digest}" = "${exact_digest}"`,
} {
if !strings.Contains(verification, required) {
t.Fatalf("beta alias verification does not contain %q:\n%s", required, verification)
}
}
reconciliation := workflowStepBlock(
t,
workflowJobBlock(t, content, "reconcile-publication"),
"Summarize exact publication inventory and job results",
)
for _, required := range []string{
"ghcr.io/tbphp/gpt-load:v2beta",
"tbphp/gpt-load:v2beta",
"GHCR v2beta inventory",
"Docker Hub v2beta inventory",
} {
if !strings.Contains(reconciliation, required) {
t.Fatalf("publication reconciliation does not contain %q:\n%s", required, reconciliation)
}
}
}
func TestReleaseWorkflowConsistentRerunIsVerifyOnly(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
for _, test := range []struct {
job string
step string
call string
}{
{
job: "publish-images",
step: "Build and publish exact multi-platform images",
call: dockerBuildActionRef,
},
{
job: "publish-github",
step: "Create or update GitHub Release draft",
call: githubReleaseActionRef,
},
} {
job := workflowJobBlock(t, content, test.job)
step := workflowStepBlock(t, job, test.step)
for _, required := range []string{
"needs.publication-preflight.outputs.write_mode == 'publish'",
test.call,
} {
if !strings.Contains(step, required) {
t.Fatalf("%s write step does not contain %q:\n%s", test.job, required, step)
}
}
}
imageVerification := workflowStepBlock(
t,
workflowJobBlock(t, content, "publish-images"),
"Verify exact published images",
)
if strings.Contains(imageVerification, "write_mode == 'publish'") {
t.Fatalf("exact image verification is disabled in consistent mode:\n%s", imageVerification)
}
releaseVerification := workflowStepBlock(
t,
workflowJobBlock(t, content, "publish-github"),
"Verify existing GitHub Release",
)
if !strings.Contains(releaseVerification, "write_mode == 'verify'") {
t.Fatalf("consistent GitHub writer does not use verify-only mode:\n%s", releaseVerification)
}
}
func TestReleaseWorkflowAlwaysReconcilesWriterAndPostPublishResults(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
job := workflowJobBlock(t, content, "reconcile-publication")
if !strings.Contains(job, "if: ${{ always() }}") {
t.Fatalf("publication reconciliation does not always run:\n%s", job)
}
for _, dependency := range []string{
"publication-preflight",
"publish-images",
"publish-github",
"post-publish-image-smoke",
"post-publish-verify",
} {
if !strings.Contains(job, dependency) {
t.Fatalf("publication reconciliation does not need %s:\n%s", dependency, job)
}
}
for _, result := range []string{
"needs.publication-preflight.result",
"needs.publish-images.result",
"needs.publish-github.result",
"needs.post-publish-image-smoke.result",
"needs.post-publish-verify.result",
} {
if !strings.Contains(job, result) {
t.Fatalf("publication reconciliation does not summarize %s:\n%s", result, job)
}
}
}
func TestReleaseWorkflowReconciliationHasNoWriteOperations(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
job := workflowJobBlock(t, content, "reconcile-publication")
for _, required := range []string{
"contents: write",
"packages: read",
"DOCKERHUB_READ_TOKEN",
"manual recovery",
"gh release view",
".github/scripts/release-image-digest.sh",
} {
if !strings.Contains(job, required) {
t.Fatalf("publication reconciliation does not contain %q:\n%s", required, job)
}
}
lower := strings.ToLower(job)
for _, forbidden := range []string{
"packages: write",
"softprops/action-gh-release",
"docker/build-push-action",
"buildx imagetools create",
"gh release delete",
"gh release create",
"gh release upload",
"docker push",
} {
if strings.Contains(lower, forbidden) {
t.Fatalf("publication reconciliation contains write/delete operation %q:\n%s", forbidden, job)
}
}
}
func TestReleaseWorkflowPreparesDraftsWithoutLatest(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
imageJob := workflowJobBlock(t, content, "publish-images")
for _, required := range []string{
dockerMetadataActionRef,
dockerLoginActionRef,
qemuActionRef,
buildxActionRef,
dockerBuildActionRef,
"linux/amd64,linux/arm64",
`value=${{ needs.validate-tag.outputs.image_exact }}`,
"docker buildx imagetools create",
`needs.validate-tag.outputs.image_minor`,
`needs.validate-tag.outputs.image_major`,
} {
if !strings.Contains(imageJob, required) {
t.Fatalf("image publication job does not contain %q:\n%s", required, imageJob)
}
}
if strings.Contains(strings.ToLower(imageJob), "latest") {
t.Fatalf("image publication job contains latest:\n%s", imageJob)
}
githubJob := workflowJobBlock(t, content, "publish-github")
draftStep := workflowStepBlock(t, githubJob, "Create or update GitHub Release draft")
for _, required := range []string{
"draft: true",
"prerelease: ${{ needs.validate-tag.outputs.prerelease }}",
"make_latest: false",
"generate_release_notes: true",
} {
if !strings.Contains(draftStep, required) {
t.Fatalf("GitHub Release draft does not contain %q:\n%s", required, draftStep)
}
}
for _, forbidden := range []string{
"draft: false",
"Publish one GitHub Release",
} {
if strings.Contains(githubJob, forbidden) {
t.Fatalf("GitHub Release draft job contains direct publication contract %q:\n%s", forbidden, githubJob)
}
}
for _, verification := range []struct {
job string
step string
}{
{job: "publish-github", step: "Verify existing GitHub Release"},
{job: "post-publish-verify", step: "Verify GitHub Release channel metadata"},
} {
step := workflowStepBlock(
t,
workflowJobBlock(t, content, verification.job),
verification.step,
)
for _, required := range []string{
`test "$(jq -r '.isDraft' <<<"${metadata}")" = "true"`,
`test "$(jq -r '.isPrerelease' <<<"${metadata}")" = \`,
`repos/${GITHUB_REPOSITORY}/releases/latest`,
`test "${latest_tag}" != "${GITHUB_REF_NAME}"`,
} {
if !strings.Contains(step, required) {
t.Fatalf("%s does not contain %q:\n%s", verification.step, required, step)
}
}
}
inventoryStep := workflowStepBlock(
t,
workflowJobBlock(t, content, "publication-preflight"),
"Inventory publication channels",
)
for _, required := range []string{
"gh release view",
"--json databaseId,targetCommitish,isDraft,isPrerelease,assets",
`test "${github_draft}" = "true"`,
`test "${github_prerelease}" = \`,
} {
if !strings.Contains(inventoryStep, required) {
t.Fatalf("draft-aware publication inventory does not contain %q:\n%s", required, inventoryStep)
}
}
if strings.Contains(inventoryStep, "/releases/tags/") {
t.Fatalf("publication inventory uses an endpoint that cannot discover drafts:\n%s", inventoryStep)
}
}
func TestReleaseWorkflowKeepsReleaseNotesConciseAndWarnsAboutDataIncompatibility(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
releaseJob := workflowJobBlock(t, content, "publish-github")
draftStep := workflowStepBlock(t, releaseJob, "Create or update GitHub Release draft")
if !strings.Contains(draftStep, "name: ${{ github.ref_name }}") {
t.Fatalf("release draft title must be the tag version only:\n%s", draftStep)
}
if strings.Contains(draftStep, "name: GPT-Load ${{ github.ref_name }}") {
t.Fatalf("release draft title must not include the product prefix:\n%s", draftStep)
}
// generate_release_notes 已经从 commit 历史生成"本次变更"部分,手写 body
// 只需要保留一次性读不到就可能造成数据损坏的警告,其余标准运维信息
// (备份、tag 语义、usage 是 estimate 等)都是跨版本不变的事实,交给 README
// 作单一事实源,不在每个 release 里复述一遍。
if !strings.Contains(draftStep, "generate_release_notes: true") {
t.Fatalf("release draft no longer generates notes from commit history:\n%s", draftStep)
}
bodyStart := strings.Index(draftStep, "body: |")
if bodyStart < 0 {
t.Fatalf("release draft step has no body:\n%s", draftStep)
}
body := draftStep[bodyStart:]
// 面向中文用户众多的社区,body 先给一段英文,再给对应的中文翻译;
// 两段必须表达同一条警告,不能只翻一半或改变语义。
paragraphs := strings.SplitN(body, "\n\n", 2)
if len(paragraphs) != 2 {
t.Fatalf("release notes are not split into exactly one English and one Chinese paragraph:\n%s", body)
}
english, chinese := paragraphs[0], paragraphs[1]
for _, required := range []string{
"not compatible with 1.x",
"start 2.x from an empty database",
"new `encryption.key`",
} {
if !strings.Contains(english, required) {
t.Fatalf("English release notes do not contain %q:\n%s", required, english)
}
}
for _, required := range []string{
"1.x 数据不兼容",
"全新数据库",
"新的 `encryption.key`",
} {
if !strings.Contains(chinese, required) {
t.Fatalf("Chinese release notes do not contain %q:\n%s", required, chinese)
}
}
if strings.Contains(body, "app.notion.com") {
t.Fatalf("public release notes depend on a private Notion page:\n%s", body)
}
// README 没有 "public operations baseline" 锚点,链接目标必须真实存在。
if strings.Contains(body, "#public-operations-baseline") {
t.Fatalf("release notes link to a heading README does not have:\n%s", body)
}
// 手写 body 只保留数据不兼容这一条警告(英文 + 中文各一段);部署、备份、
// tag 语义、usage 估算等标准信息一律不在这里重复,回归就说明有内容又被
// 搬回了每次发布都要复述的老路。用 rune 计数而非词数,因为中文没有空格分词。
if runeCount := len([]rune(body)); runeCount > 700 {
t.Fatalf("release notes body has %d runes, want at most 700:\n%s", runeCount, body)
}
}
func TestCommunityTemplatesCaptureVersionCompatibilityAndSecretSafety(t *testing.T) {
bug := readRepositoryFile(t, ".github/ISSUE_TEMPLATE/bug_report.md")
for _, required := range []string{
"当前主版本线的最新补丁版本",
"GPT-Load 版本",
"1.x 或 2.x",
"部署方式",
"操作系统与架构",
"数据库",
"客户端协议",
"实际结果及脱敏日志",
"AUTH_KEY",
"ENCRYPTION_KEY",
"AccessKey",
} {
if !strings.Contains(bug, required) {
t.Fatalf("bug report template does not contain %q", required)
}
}
feature := readRepositoryFile(t, ".github/ISSUE_TEMPLATE/feature_request.md")
for _, required := range []string{
"当前主版本线的最新补丁版本",
"目标版本线",
"1.x 维护线或 2.x",
"应用场景",
"兼容性、数据与安全影响",
"不要提交任何凭据或令牌",
} {
if !strings.Contains(feature, required) {
t.Fatalf("feature request template does not contain %q", required)
}
}
pullRequest := readRepositoryFile(t, ".github/pull_request_template.md")
for _, heading := range []string{
"### 关联 Issue / Related Issue",
"### 变更内容 / Change Content",
"### 自查清单 / Checklist",
} {
if count := strings.Count(pullRequest, heading); count != 1 {
t.Fatalf("pull request template contains heading %q %d times, want once", heading, count)
}
}
for _, required := range []string{
"make check",
"未包含无关改动",
"敏感信息",
"兼容性或数据迁移",
} {
if !strings.Contains(pullRequest, required) {
t.Fatalf("pull request template does not contain %q", required)
}
}
}
func TestReleaseWorkflowVerifiesDownloadedNativeChecksumsAndGeneratedKeys(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
nativeJob := workflowJobBlock(t, content, "native-artifact-smoke")
for _, scriptPath := range []string{
".github/scripts/release-native-smoke.sh",
".github/scripts/release-native-smoke.ps1",
} {
if !strings.Contains(nativeJob, scriptPath) {
t.Fatalf("native smoke does not invoke %s:\n%s", scriptPath, nativeJob)
}
}
nativeShellImplementation := readRepositoryFile(t, ".github/scripts/release-native-smoke.sh")
nativePowerShellImplementation := readRepositoryFile(t, ".github/scripts/release-native-smoke.ps1")
nativeImplementation := nativeJob + nativeShellImplementation + nativePowerShellImplementation
for _, required := range []string{
"name: binary-${{ matrix.filename }}",
"name: release-checksums",
"SHA256SUMS",
"sha256sum",
"shasum -a 256",
"Get-FileHash",
"auth.key",
"encryption.key",
"AreAccessRulesProtected",
"WindowsIdentity]::GetCurrent().User",
"CreateProcessW",
"CREATE_NEW_PROCESS_GROUP",
"GenerateConsoleCtrlEvent",
"CTRL_BREAK_EVENT",
"GetConsoleCP",
"AllocConsole",
"ERROR_ACCESS_DENIED",
"WaitForSingleObject",
"GetExitCodeProcess",
"$process.WaitForExit(15000)",
"$exitCode = $process.GetExitCode()",
"if ($exitCode -ne 0)",
"if (-not $process.HasExited)",
"$process.Dispose()",
} {
if !strings.Contains(nativeImplementation, required) {
t.Fatalf("native smoke does not contain %q", required)
}
}
for name, implementation := range map[string]string{
"POSIX": nativeShellImplementation,
"Windows": nativePowerShellImplementation,
} {
if !strings.Contains(implementation, "Idempotency-Key") {
t.Fatalf("%s native smoke does not send Idempotency-Key", name)
}
if strings.Contains(implementation, "00000000-0000-4000-8000-") {
t.Fatalf("%s native smoke reuses a fixed Idempotency-Key", name)
}
}
if !strings.Contains(nativeShellImplementation, "uuid.uuid4()") {
t.Fatal("POSIX native smoke does not generate a UUIDv4 for its write")
}
if !strings.Contains(nativePowerShellImplementation, "[guid]::NewGuid()") {
t.Fatal("Windows native smoke does not generate a GUID for its write")
}
if strings.Contains(nativeImplementation, "AUTH_KEY=release-native-smoke") ||
strings.Contains(nativeImplementation, `$env:AUTH_KEY = "release-native-smoke"`) {
t.Fatal("native smoke bypasses generated auth.key with an explicit AUTH_KEY")
}
if strings.Contains(nativeImplementation, "$process = Start-Process") {
t.Fatal("Windows native smoke uses Start-Process without a new console process group")
}
if strings.Contains(nativeImplementation, "CREATE_NEW_CONSOLE") {
t.Fatal("Windows native smoke gives the child a separate console that cannot receive the targeted CTRL_BREAK")
}
if strings.Contains(nativePowerShellImplementation, "Process.GetProcessById") {
t.Fatal("Windows native smoke closes the original process handle and reopens the process by ID")
}
}
func TestWindowsNativeSmokeMatchesManagedStorageACLContract(t *testing.T) {
script := readRepositoryFile(t, ".github/scripts/release-native-smoke.ps1")
for _, required := range []string{
"Assert-CurrentUserOnlyAcl",
"[bool]$RequireProtected",
"if ($RequireProtected -and -not $acl.AreAccessRulesProtected)",
"$_.IsInherited",
"managed path DACL is neither protected nor inherited: $Path",
"@{ Path = $dataDir; RequireProtected = $true }",
"@{ Path = $authFile; RequireProtected = $true }",
"@{ Path = $encryptionFile; RequireProtected = $true }",
"@{ Path = $databaseFile; RequireProtected = $false }",
"@{ Path = $walFile; RequireProtected = $false }",
"@{ Path = $shmFile; RequireProtected = $false }",
"-RequireProtected $target.RequireProtected",
} {
if !strings.Contains(script, required) {
t.Fatalf("Windows native smoke does not contain %q", required)
}
}
}
func TestReleaseWorkflowRunsCompleteLocalDockerSmoke(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
dockerJob := workflowJobBlock(t, content, "docker-smoke")
if !strings.Contains(dockerJob, ".github/scripts/release-docker-smoke.sh") {
t.Fatalf("Docker smoke job does not invoke the focused script:\n%s", dockerJob)
}
script := readRepositoryFile(t, ".github/scripts/release-docker-smoke.sh")
for _, required := range []string{
"RELEASE_SMOKE_TRIVY_IMAGE",
"--severity CRITICAL,HIGH",
"--ignore-unfixed",
"--exit-code 1",
"10001:10001",
"/app/data",
"auth.key",
"encryption.key",
"gpt-load.db",
"/api/usage",
"/api/model-prices",
"/api/groups",
"connection_type:\"api_key\"",
"/api/access-keys",
"value.data.items.some(item=>item.name===\"Task13 Release Smoke Access\")",
"/v1/chat/completions",
"finish_reason",
"prompt_tokens",
"completion_tokens",
"docker stop --time 15",
"container-first.log",
"container-second.log",
"secret_free=true",
"Idempotency-Key",
} {
if !strings.Contains(script, required) {
t.Fatalf("release Docker smoke does not contain %q", required)
}
}
if strings.Contains(script, "00000000-0000-4000-8000-") {
t.Fatal("release Docker smoke reuses fixed Idempotency-Keys")
}
if strings.Count(script, "uuid.uuid4()") < 2 {
t.Fatal("release Docker smoke does not generate independent UUIDv4 keys for its writes")
}
}
func TestReleaseDockerSmokeUsesIsolatedFakeUpstreamNetwork(t *testing.T) {
script := readRepositoryFile(t, ".github/scripts/release-docker-smoke.sh")
for _, required := range []string{
`fake_container="gpt-load-release-fake-${suffix}"`,
`network="gpt-load-release-network-${suffix}"`,
`fake_alias="fake-upstream"`,
`docker network create "${network}"`,
`--network-alias "${fake_alias}"`,
`exec nc -lk -p 8080 -e /tmp/respond`,
`"http://${fake_alias}:8080/v1"`,
`docker network rm "${network}"`,
"release Docker smoke failed at stage %s; captured output withheld to protect credentials",
} {
if !strings.Contains(script, required) {
t.Fatalf("release Docker smoke does not contain %q", required)
}
}
for _, forbidden := range []string{
"host.docker.internal",
"fake_upstream.py",
"RELEASE_SMOKE_FAKE_PORT",
} {
if strings.Contains(script, forbidden) {
t.Fatalf("release Docker smoke retains host-dependent fake upstream contract %q", forbidden)
}
}
}
func TestReleaseDockerSmokeUsesCompleteModelPriceReplacement(t *testing.T) {
script := readRepositoryFile(t, ".github/scripts/release-docker-smoke.sh")
start := strings.Index(script, `api_write PUT "/api/model-prices/${model_price_id}"`)
if start < 0 {
t.Fatal("release Docker smoke does not update the selected model price")
}
endOffset := strings.Index(script[start:], `>"${task_tmp}/price-update.json"`)
if endOffset < 0 {
t.Fatal("release Docker smoke model price update has no response target")
}
request := script[start : start+endOffset]
for _, required := range []string{
`"input":"1"`,
`"output":"2"`,
`"cache_read":"3"`,
`"cache_write":"4"`,
`"context_tiers":[]`,
`"mode_schedules":{}`,
`"confirm_unpriced":false`,
} {
if !strings.Contains(request, required) {
t.Fatalf("release Docker smoke model price replacement does not contain %q", required)
}
}
}
func TestReleaseDockerSmokeDefersOwnedResourceCleanupUntilAfterConflictChecks(t *testing.T) {
script := readRepositoryFile(t, ".github/scripts/release-docker-smoke.sh")
tempTrapIndex := strings.Index(script, "trap cleanup_temp EXIT")
containerCheckIndex := strings.Index(script, `for target in "${container}" "${probe}" "${fake_container}"; do`)
conflictExitIndex := strings.Index(script, "task owned Docker resource already exists")
fullTrapIndex := strings.Index(script, "trap cleanup EXIT")
workStartIndex := strings.Index(script, `if [[ -n "${source_image}" ]]; then`)
preflightExitIndex := -1
if conflictExitIndex >= 0 {
if offset := strings.Index(script[conflictExitIndex:], "exit 1"); offset >= 0 {
preflightExitIndex = conflictExitIndex + offset
}
}
for name, index := range map[string]int{
"temporary cleanup trap": tempTrapIndex,
"container conflict check": containerCheckIndex,
"owned resource conflict exit": conflictExitIndex,
"completed conflict exit": preflightExitIndex,
"owned-resource cleanup trap": fullTrapIndex,
"owned-resource work": workStartIndex,
} {
if index < 0 {
t.Fatalf("release Docker smoke is missing %s", name)
}
}
if !(tempTrapIndex < containerCheckIndex &&
containerCheckIndex < conflictExitIndex &&
conflictExitIndex < preflightExitIndex &&
preflightExitIndex < fullTrapIndex &&
fullTrapIndex < workStartIndex) {
t.Fatalf(
"cleanup ownership order is unsafe: temp=%d container=%d conflict=%d exit=%d full=%d work=%d",
tempTrapIndex,
containerCheckIndex,
conflictExitIndex,
preflightExitIndex,
fullTrapIndex,
workStartIndex,
)
}
}
func TestReleaseWorkflowPostPublishVerifiesAliasesAndExactDigests(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
job := workflowJobBlock(t, content, "post-publish-verify")
for _, required := range []string{
"packages: read",
dockerLoginActionRef,
"registry: ghcr.io",
"image_minor",
"image_major",
"exact_digest",
"alias_digest",
"ghcr.io/tbphp/gpt-load",
"tbphp/gpt-load",
} {
if !strings.Contains(job, required) {
t.Fatalf("post-publish verification does not contain %q:\n%s", required, job)
}
}
}
func TestReleaseWorkflowPostPublishVerifiesDraftAssetsAgainstCurrentRun(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
inventoryJob := workflowJobBlock(t, content, "post-publish-verify")
for _, required := range []string{
"gh release download",
"draft-release",
"name: release-assets",
".github/scripts/release-verify-assets.sh",
"release/SHA256SUMS",
} {
if !strings.Contains(inventoryJob, required) {
t.Fatalf("post-publish asset inventory does not contain %q:\n%s", required, inventoryJob)
}
}
// 已上传的原生产物不再重复跑第二遍五平台运行时 smoke:它们与发布前
// native-artifact-smoke 验证过的字节完全相同,这一点由上面对照当前 run
// 可信 SHA256SUMS 的校验保证,重复运行不会产生新信息。
if strings.Contains(content, "post-publish-native-smoke") {
t.Fatal("release workflow reintroduces the duplicated post-publication native smoke")
}
// 发布前的五平台原生 smoke 仍然是必须的门禁。
nativeJob := workflowJobBlock(t, content, "native-artifact-smoke")
for _, required := range []string{
"ubuntu-24.04",
"ubuntu-24.04-arm",
"macos-15-intel",
"macos-15",
"windows-2025",
"gpt-load-linux-amd64",
"gpt-load-linux-arm64",
"gpt-load-macos-amd64",
"gpt-load-macos-arm64",
"gpt-load-windows-amd64.exe",
".github/scripts/release-native-smoke.sh",
".github/scripts/release-native-smoke.ps1",
} {
if !strings.Contains(nativeJob, required) {
t.Fatalf("pre-publication native smoke does not contain %q:\n%s", required, nativeJob)
}
}
}
func TestReleaseWorkflowRunsBothPublishedImagesAndPreservesLatest(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
snapshotJob := workflowJobBlock(t, content, "publication-preflight")
for _, required := range []string{
"ghcr_latest_digest",
"dockerhub_latest_digest",
"ghcr.io/tbphp/gpt-load:latest",
"tbphp/gpt-load:latest",
".github/scripts/release-image-digest.sh",
} {
if !strings.Contains(snapshotJob, required) {
t.Fatalf("latest digest snapshot does not contain %q:\n%s", required, snapshotJob)
}
}
imagePublication := workflowJobBlock(t, content, "publish-images")
if !strings.Contains(imagePublication, "publication-preflight") {
t.Fatal("image publication is not ordered after the latest digest snapshot")
}
postPublish := workflowJobBlock(t, content, "post-publish-verify")
for _, required := range []string{
"publication-preflight",
".github/scripts/release-image-digest.sh",
"ghcr_latest_digest",
"dockerhub_latest_digest",
} {
if !strings.Contains(postPublish, required) {
t.Fatalf("published image runtime verification does not contain %q:\n%s", required, postPublish)
}
}
runtimeSmoke := workflowJobBlock(t, content, "post-publish-image-smoke")
for _, required := range []string{
"RELEASE_SMOKE_SOURCE_IMAGE",
"ghcr.io/tbphp/gpt-load",
"needs.validate-tag.outputs.image_exact",
".github/scripts/release-docker-smoke.sh",
} {
if !strings.Contains(runtimeSmoke, required) {
t.Fatalf("published image runtime smoke does not contain %q:\n%s", required, runtimeSmoke)
}
}
// Docker Hub 的 exact tag 与 GHCR 同 digest,只在 post-publish-verify 里以只读凭据
// 校验 manifest 并拉取一次,确认 layer 可达。
if !strings.Contains(postPublish, "Verify Docker Hub exact image is pullable") ||
!strings.Contains(postPublish, "docker pull") {
t.Fatalf("post-publication verification never pulls the Docker Hub image:\n%s", postPublish)
}
for name, block := range map[string]string{
"publication-preflight": snapshotJob,
"post-publish-verify": postPublish,
} {
for _, required := range []string{
"Log in to Docker Hub",
"username: ${{ secrets.DOCKERHUB_USERNAME }}",
"password: ${{ secrets.DOCKERHUB_READ_TOKEN || secrets.DOCKERHUB_TOKEN }}",
} {
if !strings.Contains(block, required) {
t.Fatalf("%s does not contain authenticated Docker Hub read %q", name, required)
}
}
if strings.Contains(block, "password: ${{ secrets.DOCKERHUB_TOKEN }}") {
t.Fatalf("%s bypasses the optional Docker Hub read-only token", name)
}
}
imageReadLogin := workflowStepBlock(
t,
imagePublication,
"Log in to Docker Hub for exact verification",
)
for _, required := range []string{
"write_mode == 'verify'",
"password: ${{ secrets.DOCKERHUB_READ_TOKEN || secrets.DOCKERHUB_TOKEN }}",
} {
if !strings.Contains(imageReadLogin, required) {
t.Fatalf("consistent image verification login does not contain %q", required)
}
}
if strings.Contains(imageReadLogin, "password: ${{ secrets.DOCKERHUB_TOKEN }}") {
t.Fatal("consistent image verification bypasses the optional read-only token")
}
imageWriteLogin := workflowStepBlock(
t,
imagePublication,
"Log in to Docker Hub for publication",
)
for _, required := range []string{
"write_mode == 'publish'",
"password: ${{ secrets.DOCKERHUB_TOKEN }}",
} {
if !strings.Contains(imageWriteLogin, required) {
t.Fatalf("fresh image publication login does not contain %q", required)
}
}
if strings.Contains(imageWriteLogin, "DOCKERHUB_READ_TOKEN") {
t.Fatal("fresh image publication unexpectedly uses the Docker Hub read-only token")
}
if !strings.Contains(
postPublish,
`test "${latest_digest}" != "${exact_digest}"`,
) {
t.Fatal("post-publication verification does not reject latest pointing at the exact 2.x image")
}
}
func TestReleaseImageDigestFailsClosedOnOperationalInspectionErrors(t *testing.T) {
for _, message := range []string{"rate limit exceeded", "docker: command not found"} {
t.Run(message, func(t *testing.T) {
script := filepath.Join("..", "..", ".github", "scripts", "release-image-digest.sh")
fakeBin := t.TempDir()
fakeDocker := filepath.Join(fakeBin, "docker")
if err := os.WriteFile(
fakeDocker,
[]byte("#!/usr/bin/env sh\nprintf '"+message+"\\n' >&2\nexit 1\n"),
0o700,
); err != nil {
t.Fatalf("write fake docker: %v", err)
}
command := exec.Command("bash", script, "example.test/gpt-load:latest")
command.Env = append(os.Environ(), "PATH="+fakeBin+":"+os.Getenv("PATH"))
output, err := command.CombinedOutput()
if err == nil {
t.Fatalf("operational inspect failure returned success: %s", output)
}
if !strings.Contains(string(output), message) {
t.Fatalf("operational inspect failure output = %q", output)
}
})
}
}
func TestReleaseImageDigestReturnsAbsentOnlyForMissingManifest(t *testing.T) {
script := filepath.Join("..", "..", ".github", "scripts", "release-image-digest.sh")
fakeBin := t.TempDir()
fakeDocker := filepath.Join(fakeBin, "docker")
if err := os.WriteFile(
fakeDocker,
[]byte("#!/usr/bin/env sh\nprintf 'manifest unknown\\n' >&2\nexit 1\n"),
0o700,
); err != nil {
t.Fatalf("write fake docker: %v", err)
}
command := exec.Command("bash", script, "example.test/gpt-load:latest")
command.Env = append(os.Environ(), "PATH="+fakeBin+":"+os.Getenv("PATH"))
output, err := command.CombinedOutput()
if err != nil {
t.Fatalf("missing manifest returned error: %v\n%s", err, output)
}
if strings.TrimSpace(string(output)) != "absent" {
t.Fatalf("missing manifest output = %q, want absent", output)
}
}
func TestReleaseDockerSmokeCanUsePublishedSourceWithoutDeletingIt(t *testing.T) {
script := readRepositoryFile(t, ".github/scripts/release-docker-smoke.sh")
for _, required := range []string{
"RELEASE_SMOKE_SOURCE_IMAGE",
`docker pull --platform "${platform}" "${source_image}"`,
`docker image tag "${source_image}" "${image}"`,
`docker image rm "${image}"`,
`model_price_list_path="/api/model-prices?usage=in_use&status=all&page=1&page_size=100"`,
`api_write PUT "/api/model-prices/${model_price_id}"`,
`item.channel_id==="openai_compatible"&&item.model_id===modelID`,
`item.model_id===modelID`,
`!Number.isSafeInteger(matches[0].id)||matches[0].id<=0`,
`api_get "${model_price_list_path}" >"${task_tmp}/prices-second.json"`,
`matches[0].id!==priceID`,
`matches[0].method!=="user_set"||matches[0].pricing_status!=="configured"`,
`persisted.input!=="1"||persisted.output!=="2"`,
`persisted.cache_read!=="3"||persisted.cache_write!=="4"`,
} {
if !strings.Contains(script, required) {
t.Fatalf("release Docker smoke source-image mode does not contain %q", required)
}
}
for _, legacy := range []string{
"prices.data.overrides",
`"pattern":"task13-release-model"`,
`"cache_write_5m"`,
`"cache_write_1h"`,
} {
if strings.Contains(script, legacy) {
t.Fatalf("release Docker smoke retains legacy model-price contract %q", legacy)
}
}
if strings.Contains(script, `docker image rm "${source_image}"`) {
t.Fatal("release Docker smoke deletes the externally owned source image")
}
}
func TestReleaseWorkflowRemovesObsoletePublishers(t *testing.T) {
for _, name := range []string{
".github/workflows/docker-build.yml",
".github/workflows/release-linux.yml",
".github/workflows/release-macos.yml",
".github/workflows/release-windows.yml",
} {
if _, err := os.Stat(filepath.Join("..", "..", name)); !os.IsNotExist(err) {
t.Fatalf("obsolete publisher still exists: %s", name)
}
}
}
func TestReadmesDoNotExposeInternalReleaseTaskNames(t *testing.T) {
for _, name := range []string{"README.md", "README_CN.md", "README_JP.md"} {
content := readRepositoryFile(t, name)
if strings.Contains(content, "T18") {
t.Fatalf("%s exposes the completed internal T18 task name", name)
}
}
}
func TestDockerfileCopiesWebInstallPolicyBeforeInstalling(t *testing.T) {
content := readRepositoryFile(t, "Dockerfile")
copyInputs := "COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./web/"
install := "RUN pnpm --dir web install --frozen-lockfile"
copyIndex := strings.Index(content, copyInputs)
if copyIndex < 0 {
t.Fatalf("Dockerfile does not copy the complete web install policy")
}
installIndex := strings.Index(content, install)
if installIndex < 0 {
t.Fatalf("Dockerfile does not install frozen web dependencies")
}
if copyIndex >= installIndex {
t.Fatal("Dockerfile copies the web install policy after installing dependencies")
}
}
func TestDockerfilePinsBuildAndRuntimeImagesByVersionAndDigest(t *testing.T) {
content := readRepositoryFile(t, "Dockerfile")
for _, required := range []string{
"node:24.18.0-alpine3.24@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
"pnpm@11.17.0",
"golang:1.27.0-alpine3.24@sha256:4c9fe60190a2a3350ddc51de80d0224b8a6698d12bdfc999fee45ea9d6c46dbc",
"alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b",
} {
if !strings.Contains(content, required) {
t.Errorf("Dockerfile does not contain %q", required)
}
}
}
func TestDockerfileRuntimePinsPatchedOpenSSLPackages(t *testing.T) {
content := readRepositoryFile(t, "Dockerfile")
runtimeStart := strings.Index(content, "\nFROM alpine:")
if runtimeStart < 0 {
t.Fatal("Dockerfile does not contain the shared runtime stage")
}
runtimeEnd := strings.Index(content[runtimeStart+1:], "\nFROM ")
if runtimeEnd < 0 {
t.Fatal("Dockerfile runtime stage has no following stage")
}
runtimeStage := content[runtimeStart : runtimeStart+1+runtimeEnd]
upgrade := "apk add --no-cache libcrypto3=3.5.8-r0 libssl3=3.5.8-r0"
upgradeIndex := strings.Index(runtimeStage, upgrade)
if upgradeIndex < 0 {
t.Fatalf("Dockerfile runtime stage does not pin patched OpenSSL packages via %q", upgrade)
}
packageInstallIndex := strings.Index(runtimeStage, "apk add --no-cache ca-certificates tzdata")
if packageInstallIndex < 0 {
t.Fatal("Dockerfile runtime stage does not install certificate and timezone packages")
}
if upgradeIndex >= packageInstallIndex {
t.Fatal("Dockerfile upgrades OpenSSL after installing certificate and timezone packages")
}
}
func readRepositoryFile(t *testing.T, name string) string {
t.Helper()
content, err := os.ReadFile(filepath.Join("..", "..", name))
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
return string(content)
}
func workflowTopLevelBlock(t *testing.T, content, key string) string {
t.Helper()
lines := strings.Split(content, "\n")
start := -1
for index, line := range lines {
if line == key+":" {
start = index
break
}
}
if start < 0 {
t.Fatalf("workflow does not contain top-level %s", key)
}
end := len(lines)
for index := start + 1; index < len(lines); index++ {
if lines[index] != "" && !strings.HasPrefix(lines[index], " ") {
end = index
break
}
}
return strings.Join(lines[start:end], "\n")
}
func workflowJobBlock(t *testing.T, content, name string) string {
t.Helper()
lines := strings.Split(content, "\n")
start := -1
for index, line := range lines {
if line == " "+name+":" {
start = index
break
}
}
if start < 0 {
t.Fatalf("workflow does not contain job %s", name)
}
end := len(lines)
for index := start + 1; index < len(lines); index++ {
line := lines[index]
if strings.HasPrefix(line, " ") && !strings.HasPrefix(line, " ") {
end = index
break
}
}
return strings.Join(lines[start:end], "\n")
}
func workflowStepBlock(t *testing.T, job, name string) string {
t.Helper()
lines := strings.Split(job, "\n")
start := -1
for index, line := range lines {
if line == " - name: "+name {
start = index
break
}
}
if start < 0 {
t.Fatalf("workflow job does not contain step %s", name)
}
end := len(lines)
for index := start + 1; index < len(lines); index++ {
if strings.HasPrefix(lines[index], " - name: ") {
end = index
break
}
}
return strings.Join(lines[start:end], "\n")
}
func assertWorkflowGateStep(t *testing.T, job, name, wantRun string) {
t.Helper()
step := strings.TrimSuffix(workflowStepBlock(t, job, name), "\n")
want := " - name: " + name + "\n run: " + wantRun
if step != want {
t.Fatalf("workflow gate %s block does not match strict allowlist:\ngot:\n%s\nwant:\n%s", name, step, want)
}
}
func workflowGoFormattingScript(t *testing.T, content, job string) string {
t.Helper()
const marker = "go-format-check"
startMarker := "# " + marker + ":start"
endMarker := "# " + marker + ":end"
jobBlock := workflowJobBlock(t, content, job)
step := workflowStepBlock(t, jobBlock, "Check Go formatting")
for scope, block := range map[string]string{
"formatting step": step,
"workflow": content,
} {
if startCount, endCount := strings.Count(block, startMarker), strings.Count(block, endMarker); startCount != 1 || endCount != 1 {
t.Fatalf("%s contains format markers start=%d end=%d, want exactly one pair", scope, startCount, endCount)
}
}
want := strings.Join([]string{
" - name: Check Go formatting",
" run: |",
" # go-format-check:start",
` formatted_files="$(gofmt -l .)"`,
` test -z "${formatted_files}"`,
" # go-format-check:end",
}, "\n")
if got := strings.TrimSuffix(step, "\n"); got != want {
t.Fatalf("workflow %s formatting step does not match strict allowlist:\ngot:\n%s\nwant:\n%s", job, got, want)
}
return workflowMarkedScript(t, step, marker)
}
func workflowMarkedScript(t *testing.T, content, marker string) string {
t.Helper()
startMarker := "# " + marker + ":start"
endMarker := "# " + marker + ":end"
start := strings.Index(content, startMarker)
end := strings.Index(content, endMarker)
if start < 0 || end <= start {
t.Fatalf("workflow does not contain marked script %s", marker)
}
body := content[start+len(startMarker) : end]
lines := strings.Split(body, "\n")
for index, line := range lines {
lines[index] = strings.TrimPrefix(line, " ")
}
return strings.TrimSpace(strings.Join(lines, "\n")) + "\n"
}
func TestReleaseWorkflowReusesCIVerdictOnlyOnProvenSuccess(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
verdict := workflowMarkedScript(t, content, "release-ci-verdict-reuse")
scriptPath := filepath.Join(t.TempDir(), "reuse-ci-verdict.sh")
script := "#!/usr/bin/env bash\nset -euo pipefail\n" +
"GITHUB_REPOSITORY=owner/repo\n" +
"GITHUB_SHA=0123456789abcdef0123456789abcdef01234567\n" +
verdict +
"printf '%s\\n' \"${ci_verified}\"\n"
if err := os.WriteFile(scriptPath, []byte(script), 0o700); err != nil {
t.Fatalf("write ci verdict script: %v", err)
}
binDir := t.TempDir()
fakeGH := "#!/usr/bin/env bash\n" +
"if [[ -n \"${RELEASE_TEST_GH_OUTPUT}\" ]]; then\n" +
" printf '%s\\n' \"${RELEASE_TEST_GH_OUTPUT}\"\n" +
"fi\n" +
"exit \"${RELEASE_TEST_GH_EXIT}\"\n"
if err := os.WriteFile(filepath.Join(binDir, "gh"), []byte(fakeGH), 0o700); err != nil {
t.Fatalf("write fake gh: %v", err)
}
for _, test := range []struct {
name string
output string
exit string
want string
}{
{name: "successful runs", output: "3", exit: "0", want: "true"},
{name: "no successful run", output: "0", exit: "0", want: "false"},
{name: "query failed", output: "", exit: "1", want: "false"},
{name: "non-numeric", output: "many", exit: "0", want: "false"},
{name: "injected count", output: "2\ninjected", exit: "0", want: "false"},
{name: "negative", output: "-1", exit: "0", want: "false"},
{name: "empty", output: "", exit: "0", want: "false"},
} {
t.Run(test.name, func(t *testing.T) {
command := exec.Command("bash", scriptPath)
command.Env = []string{
"PATH=" + binDir + string(os.PathListSeparator) + os.Getenv("PATH"),
"RELEASE_TEST_GH_OUTPUT=" + test.output,
"RELEASE_TEST_GH_EXIT=" + test.exit,
}
output, err := command.Output()
if err != nil {
t.Fatalf("ci verdict evaluation failed: %v", err)
}
if got := strings.TrimSpace(string(output)); got != test.want {
t.Fatalf("ci_verified = %q, want %q", got, test.want)
}
})
}
}
func TestReleaseWorkflowSkipsOnlyCIProvenGatesAndStillFailsClosed(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
// 只有同一 commit 上确定性重跑的 gate 才允许复用 CI 结论。
reusable := []string{"race-tests", "race-cpa", "database-contract"}
for _, jobName := range reusable {
job := workflowJobBlock(t, content, jobName)
if !strings.Contains(job, "needs.validate-tag.outputs.ci_verified != 'true'") {
t.Fatalf("%s does not reuse the CI verdict:\n%s", jobName, job)
}
}
// 静态检查依赖会随时间变化的漏洞库,每次发布都必须重跑。
staticChecks := workflowJobBlock(t, content, "static-checks")
if strings.Contains(staticChecks, "ci_verified") {
t.Fatalf("static checks must not reuse a stale vulnerability audit:\n%s", staticChecks)
}
preflight := workflowJobBlock(t, content, "publication-preflight")
if !strings.Contains(preflight, "!contains(needs.*.result, 'failure')") ||
!strings.Contains(preflight, "!cancelled()") {
t.Fatalf("publication preflight does not fail closed on gate failures:\n%s", preflight)
}
// preflight 的 if 只能看到直接 needs 的 result:上游失败会让中间 job 变成
// skipped,因此每个 gate 都必须直接列出,漏掉任何一个都会让失败无法察觉。
needsBlock := preflight[:strings.Index(preflight, "runs-on:")]
for _, gate := range []string{
"validate-tag",
"verify-and-build-web",
"static-checks",
"race-tests",
"race-cpa",
"build-binaries",
"package-checksums",
"native-artifact-smoke",
"docker-smoke",
"database-contract",
} {
if !strings.Contains(needsBlock, "- "+gate) {
t.Fatalf("publication preflight does not directly need gate %q:\n%s", gate, needsBlock)
}
}
}
func TestReleaseWorkflowJobsDownstreamOfSkippableGatesOverrideDefaultCondition(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
jobNamePattern := regexp.MustCompile(`(?m)^ ([A-Za-z][\w-]*):\n`)
var jobNames []string
for _, match := range jobNamePattern.FindAllStringSubmatch(content, -1) {
jobNames = append(jobNames, match[1])
}
if len(jobNames) < 10 {
t.Fatalf("found only %d jobs, job name pattern likely broken", len(jobNames))
}
singleNeeds := regexp.MustCompile(`(?m)^ needs: (\S+)$`)
listNeedsItem := regexp.MustCompile(`(?m)^ - (\S+)$`)
needsOf := make(map[string][]string, len(jobNames))
for _, name := range jobNames {
job := workflowJobBlock(t, content, name)
if match := singleNeeds.FindStringSubmatch(job); match != nil {
needsOf[name] = []string{match[1]}
continue
}
needsHeader := strings.Index(job, "\n needs:\n")
if needsHeader < 0 {
continue
}
var needs []string
for _, line := range strings.Split(job[needsHeader:], "\n") {
if match := listNeedsItem.FindStringSubmatch(line); match != nil {
needs = append(needs, match[1])
continue
}
if line != "" && !strings.HasPrefix(line, " - ") && strings.TrimSpace(line) != "needs:" {
break
}
}
needsOf[name] = needs
}
// GitHub Actions 的默认 job 条件会沿整条依赖链传播 skip:只要某个间接祖先
// 因 CI 结论复用等机制变成 skipped,默认条件就会让当前 job 也被跳过,即使
// 它自己列出的直接 needs 全部成功(beta.8 的真实回归:publication-preflight
// 成功了,但下游 publish-images/publish-github/... 仍被跳过)。凡是这条链上
// 存在会被跳过的祖先的 job,都必须自己写显式 if,绕开默认条件的传播。
skippable := map[string]bool{"race-tests": true, "race-cpa": true, "database-contract": true}
var ancestors func(name string, seen map[string]bool)
ancestors = func(name string, seen map[string]bool) {
for _, dep := range needsOf[name] {
if !seen[dep] {
seen[dep] = true
ancestors(dep, seen)
}
}
}
jobIfPattern := regexp.MustCompile(`(?m)^ if:`)
for _, name := range jobNames {
seen := map[string]bool{}
ancestors(name, seen)
hasSkippableAncestor := false
for gate := range skippable {
if seen[gate] {
hasSkippableAncestor = true
break
}
}
if !hasSkippableAncestor {
continue
}
job := workflowJobBlock(t, content, name)
if !jobIfPattern.MatchString(job) {
t.Fatalf(
"%s has a skippable ancestor gate but no job-level if to override "+
"the default skip-propagation condition:\n%s",
name,
job,
)
}
}
}
func TestReleaseAssetManifestIsTheSingleSourceOfTruth(t *testing.T) {
manifest := readRepositoryFile(t, ".github/release-assets.txt")
var assets []string
for _, line := range strings.Split(manifest, "\n") {
if name := strings.TrimSpace(line); name != "" {
assets = append(assets, name)
}
}
if len(assets) != 12 {
t.Fatalf("release asset manifest lists %d assets, want 12", len(assets))
}
sorted := append([]string(nil), assets...)
sort.Strings(sorted)
for index := range assets {
if assets[index] != sorted[index] {
t.Fatalf("release asset manifest is not sorted: %v", assets)
}
}
content := readRepositoryFile(t, ".github/workflows/release.yml")
// 许可证与 SBOM 资产名只允许出现在生成它们的 package-metadata 里,
// 清单与校验和一律从 .github/release-assets.txt 派生。
for _, name := range []string{
"Apache-2.0.txt",
"MIT.txt",
"MPL-2.0.txt",
"THIRD_PARTY_NOTICES.md",
"bom.cdx.json",
} {
metadataJob := workflowJobBlock(t, content, "package-metadata")
occurrences := strings.Count(content, name)
inMetadata := strings.Count(metadataJob, name)
if occurrences != inMetadata {
t.Fatalf(
"asset %q is hardcoded outside package-metadata (%d total, %d in metadata)",
name,
occurrences,
inMetadata,
)
}
}
// 资产数量与校验和行数不得再作为魔数散落在 workflow 中。
for _, magic := range []string{`= "11"`, `= "12"`, "asset_count"} {
if strings.Contains(content, magic) {
t.Fatalf("release workflow still hardcodes the asset count via %q", magic)
}
}
}