package main import ( "os" "os/exec" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.yaml.in/yaml/v3" ) func TestGitLabCIYAMLParses(t *testing.T) { data, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) var document yaml.Node require.NoError(t, yaml.Unmarshal(data, &document)) } func TestReleaseManifestJobKeepsPrivateKeyOutOfApplicationPipeline(t *testing.T) { data, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) ci := string(data) assert.Contains(t, ci, "RELEASE_SIGNING_ID_TOKEN:") assert.Contains(t, ci, "aud: https://signing.mpv.rocks") assert.Contains(t, ci, "-provenance-lock \"${PROVENANCE_PATH}\"") assert.Contains(t, ci, "-manager-artifacts-dir dist") assert.Contains(t, ci, "--form \"candidate=@${UNSIGNED_MANIFEST_PATH}") assert.Contains(t, ci, "--form \"provenance=@${PROVENANCE_PATH}") assert.NotContains(t, ci, "test -n \"${MANIFEST_SIGNING_KEY") assert.NotContains(t, ci, "-sign ") for _, job := range []string{ "build:linux-amd64", "build:linux-arm64", "build:windows-x86_64", "build:windows-arm64", "build:macos-intel", "build:macos-arm", } { assert.GreaterOrEqual(t, strings.Count(ci, "- "+job), 2, job+" must feed packaging and manifest provenance") } } func TestReleasePipelineIsImmutableAndNativeEvidenceGated(t *testing.T) { data, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) ci := string(data) for _, line := range strings.Split(ci, "\n") { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "image:") { assert.Contains(t, trimmed, "@sha256:", trimmed) } } assert.NotContains(t, ci, "image: alpine:latest") assert.NotContains(t, ci, "release-cli:latest") assert.Contains(t, ci, "CI_COMMIT_REF_PROTECTED == \"true\"") assert.Contains(t, ci, "resource_group: stable-release-publication") assert.GreaterOrEqual(t, strings.Count(ci, "rules: *release_rules"), 8) assert.Contains(t, ci, "native:evidence:") assert.Contains(t, ci, "name: release/native-evidence") assert.Contains(t, ci, "- native:evidence") assert.Contains(t, ci, "NATIVE_EVIDENCE_COMMIT_SHA") assert.Contains(t, ci, "NATIVE_WINDOWS_X86_64_SHA256") assert.Contains(t, ci, "NATIVE_WINDOWS_ARM64_SHA256") assert.Contains(t, ci, "NATIVE_MACOS_INTEL_SHA256") assert.Contains(t, ci, "NATIVE_MACOS_ARM_SHA256") assert.Contains(t, ci, "Refusing non-immutable publication") assert.Contains(t, ci, "Refusing non-immutable manifest publication") } func TestReleasePipelineValidatesResourcesChecksumsAndNotices(t *testing.T) { ciData, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) makeData, err := os.ReadFile("../../Makefile") require.NoError(t, err) ci := string(ciData) makefile := string(makeData) assert.Contains(t, ci, "go run github.com/tc-hib/go-winres@v0.3.3 make") assert.Contains(t, ci, "go run github.com/tc-hib/go-winres@v0.3.3 extract") assert.Contains(t, ci, `grep -F '"minimum-os": "win10"'`) assert.Contains(t, ci, "b3sum -c BLAKE3SUMS.txt") assert.GreaterOrEqual(t, strings.Count(ci, "THIRD_PARTY_NOTICES.md"), 12) assert.Contains(t, makefile, "release-build: _ensure-manifest-trust") assert.Contains(t, makefile, "--arch amd64,arm64") assert.Contains(t, makefile, "THIRD_PARTY_NOTICES.md") assert.NotContains(t, makefile, "go-winres) make || true") assert.Contains(t, makefile, "release: _ensure-manifest-trust _release-frontend-check") assert.Contains(t, makefile, "npm run vendor:frontend:check") assert.Contains(t, makefile, "npm test") } func TestCIUsesAggregateCoverageAndPinnedCorrectnessAnalysis(t *testing.T) { ciData, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) makeData, err := os.ReadFile("../../Makefile") require.NoError(t, err) ci := string(ciData) makefile := string(makeData) assert.Contains(t, ci, "go tool cover -func=coverage.out | tee coverage-summary.txt") assert.Contains(t, ci, `coverage: '/^total:\s+\(statements\)\s+\d+\.\d+%$/'`) assert.Contains(t, ci, "staticcheck@v0.8.1") assert.Contains(t, ci, "staticcheck -checks='SA*' ./...") assert.Contains(t, makefile, "STATICCHECK_VERSION=v0.8.1") assert.NotContains(t, makefile, "golangci-lint@latest") } func TestRCPublicationDoesNotInheritStableCredentials(t *testing.T) { data, err := os.ReadFile("../../.gitlab-ci.yml") require.NoError(t, err) // Execute the actual routing block with distinct stable and RC settings. // Missing RC credentials must clear stable values, never fall back to them. ci := string(data) job := strings.Index(ci, "\npublish:release-manifest:") require.NotEqual(t, -1, job) ci = ci[job:] start := strings.Index(ci, " RELEASE_VERSION=\"${VERSION#v}\"") require.NotEqual(t, -1, start) end := strings.Index(ci[start:], " PROVENANCE_PATH=") require.NotEqual(t, -1, end) script := ci[start:start+end] + `printf '%s|%s|%s|%s' "$RELEASE_CHANNEL" "$RELEASE_MANIFEST_PUBLISH_URL" "$RELEASE_MANIFEST_PUBLISH_TOKEN" "$RELEASE_MANIFEST_VERIFY_URL"` for _, tc := range []struct { version string rc bool want string }{ {"v1.3.0", false, "stable|stable-url|stable-token|stable-verify"}, {"v1.3.0+build-with-hyphen", false, "stable|stable-url|stable-token|stable-verify"}, {"v1.3.0-rc.1", false, "rc|||"}, {"v1.3.0-rc.2", true, "rc|rc-url|rc-token|rc-verify"}, } { t.Run(tc.version, func(t *testing.T) { shell, err := exec.LookPath("sh") if err != nil { t.Skip("CI shell requires sh") } cmd := exec.Command(shell, "-eu", "-c", script) cmd.Env = []string{"VERSION=" + tc.version, "RELEASE_MANIFEST_PUBLISH_URL=stable-url", "RELEASE_MANIFEST_PUBLISH_TOKEN=stable-token", "RELEASE_MANIFEST_VERIFY_URL=stable-verify"} if tc.rc { cmd.Env = append(cmd.Env, "RC_MANIFEST_PUBLISH_URL=rc-url", "RC_MANIFEST_PUBLISH_TOKEN=rc-token", "RC_MANIFEST_VERIFY_URL=rc-verify") } output, err := cmd.CombinedOutput() require.NoError(t, err, string(output)) require.Equal(t, tc.want, string(output)) }) } }