package release_test import ( "slices" "strings" "testing" ) func TestReleaseArtifactsRunOnEveryMainPush(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") if len(workflow.On.Push.Paths) != 0 { t.Fatalf("release artifact pushes are restricted to %v", workflow.On.Push.Paths) } } func TestReleaseDetectionCanSeeDrafts(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") version := requireJob(t, workflow, "version") if version.Permissions["contents"] != "write" { t.Fatal("release detection cannot see draft releases") } } func TestReleaseArtifactVersionContract(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") version := requireJob(t, workflow, "version") for _, output := range []string{"version", "pending", "sha"} { if version.Outputs[output] == "" { t.Errorf("version job has no %s output", output) } } read := requireStep(t, version, "read") if !strings.Contains(read.Run, "scripts/release-pending.sh") { t.Fatal("version job does not ask the release-pending checker") } if !strings.Contains(read.Run, "scripts/release-commit.sh") { t.Fatal("version job builds the dispatch head instead of the release commit") } } func TestReleaseArtifactBuildsAreGatedOnPendingWork(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") jobs := []string{"dist", "image", "chart", "desktop", "desktop-linux", "publish"} for _, name := range jobs { job := requireJob(t, workflow, name) if !strings.Contains(job.If, "pending") { t.Errorf("%s job is not gated on pending release work", name) } } } func TestPublishedClusterModeCanUpgradeAndRollBackEveryNight(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/nightly.yaml") clusterMode := requireJob(t, workflow, "cluster-mode-release") if !contains(clusterMode.Needs, "published") { t.Fatal("the cluster-mode lifecycle check does not read the published releases") } if clusterMode.Permissions["packages"] != "read" { t.Fatal("the cluster-mode lifecycle check cannot pull the published packages") } if !containsRun(clusterMode.Steps, "helm registry login") { t.Fatal("the cluster-mode lifecycle check does not authenticate to the chart registry") } lifecycle := requireRunStep(t, clusterMode, "test-cluster-mode-release") if lifecycle.Env["PREVIOUS"] != "${{ needs.published.outputs.previous }}" { t.Fatalf("previous release = %q", lifecycle.Env["PREVIOUS"]) } if lifecycle.Env["CURRENT"] != "${{ needs.published.outputs.current }}" { t.Fatalf("current release = %q", lifecycle.Env["CURRENT"]) } requireClusterModeFailureHandling(t, clusterMode) published := requireJob(t, workflow, "published") releases := requireStep(t, published, "releases") if !strings.Contains(releases.Run, "--exclude-drafts") { t.Fatal("the published releases step would compare against a draft") } for _, output := range []string{"current", "previous", "tag"} { if published.Outputs[output] == "" { t.Errorf("published job has no %s output", output) } } report := requireJob(t, workflow, "report") if !contains(report.Needs, "cluster-mode-release") { t.Fatal("the nightly report does not wait for the cluster-mode lifecycle check") } } func TestClusterModeBrowsersRunEveryEngine(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/e2e.yaml") browser := requireJob(t, workflow, "cluster-mode-browser") matrix, ok := browser.Strategy["matrix"].(map[string]any) if !ok { t.Fatalf("cluster-mode matrix = %#v, want a mapping", browser.Strategy["matrix"]) } engines, ok := matrix["browser"].(string) if !ok { t.Fatalf("cluster-mode browsers = %#v, want the selected engine list", matrix["browser"]) } if !strings.Contains(engines, "needs.select.outputs.browsers") { t.Fatalf("cluster-mode browsers = %q, want the engines the tier selected", engines) } suite := readJSON[suiteTiers](t, "e2e/suite.json") want := []string{"chromium", "firefox", "webkit"} if !slices.Equal(suite.NightlyBrowsers, want) { t.Fatalf("nightly browsers = %v, want %v", suite.NightlyBrowsers, want) } for _, engine := range suite.CommitBrowsers { if !contains(want, engine) { t.Fatalf("commit browser %q is not an engine the suite knows", engine) } } failFast, ok := browser.Strategy["fail-fast"].(bool) if !ok { t.Fatalf("cluster-mode fail-fast = %#v, want a boolean", browser.Strategy["fail-fast"]) } if failFast { t.Fatal("one cluster-mode browser failure cancels the other engines") } if !strings.Contains(browser.If, "cluster-mode-auth") { t.Fatal("cluster-mode browser validation is not selected with its capability group") } if !containsRun(browser.Steps, "test-cluster-mode-browser") { t.Fatal("cluster-mode browser job does not run the dedicated browser target") } run := requireRunStep(t, browser, "test-cluster-mode-browser") if run.Env["BROWSER"] != "${{ matrix.browser }}" { t.Fatalf("cluster-mode browser target receives %q, want the selected engine", run.Env["BROWSER"]) } requireClusterModeFailureHandling(t, browser) auth := requireJob(t, workflow, "cluster-mode-auth") requireClusterModeFailureHandling(t, auth) } func TestTheReleaseEndsPublished(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") dist := requireJob(t, workflow, "dist") if !containsRun(dist.Steps, "gh release upload") { t.Fatal("the dist job builds artifacts without uploading them to the release") } publish := requireJob(t, workflow, "publish") for _, command := range []string{"just package-manifests", "just publish-asset", "gh release edit"} { if !containsRun(publish.Steps, command) { t.Errorf("the publish job does not run %q", command) } } last := publish.Steps[len(publish.Steps)-1] if !strings.Contains(last.Run, "--draft=false --latest") { t.Fatalf("the release ends on %q, want the draft flipped to the latest release", last.Run) } } func TestReleaseImagePublication(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") image := requireJob(t, workflow, "image") if image.Permissions["packages"] != "write" { t.Fatal("image job cannot publish packages") } push := requireStep(t, image, "push") pushed, ok := push.With["push"].(bool) if !ok || !pushed { t.Fatal("image build does not push") } if push.With["platforms"] != "linux/amd64,linux/arm64" { t.Fatalf("image platforms are %v", push.With["platforms"]) } tags, ok := push.With["tags"].(string) if !ok { t.Fatal("image tags are not a string") } if !strings.Contains(tags, "needs.version.outputs.version") { t.Fatal("image tags do not use the release version") } } func TestReleaseChartPublication(t *testing.T) { workflow := readYAML[workflowFile](t, ".github/workflows/release-artifacts.yaml") chart := requireJob(t, workflow, "chart") if !contains(chart.Needs, "image") { t.Fatal("chart job does not wait for the image") } if chart.Permissions["packages"] != "write" { t.Fatal("chart job cannot publish packages") } commands := []string{"helm package", "helm push"} for _, command := range commands { if !containsRun(chart.Steps, command) { t.Errorf("chart job does not run %q", command) } } publish := requireJob(t, workflow, "publish") if !contains(publish.Needs, "chart") { t.Fatal("publish job does not wait for the chart") } } func requireRunStep(t *testing.T, job workflowJob, command string) workflowStep { t.Helper() for _, step := range job.Steps { if strings.Contains(step.Run, command) { return step } } t.Fatalf("job does not run %q", command) return workflowStep{} } func requireClusterModeFailureHandling(t *testing.T, job workflowJob) { t.Helper() diagnostics := requireRunStep(t, job, "cluster-mode-diagnostics") if diagnostics.If != "failure()" { t.Fatalf("cluster-mode diagnostics condition = %q, want failure()", diagnostics.If) } cleanup := requireRunStep(t, job, "cluster-mode-down") if cleanup.If != "always()" { t.Fatalf("cluster-mode cleanup condition = %q, want always()", cleanup.If) } }