Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 1 addition & 23 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,15 @@ jobs:
with:
go-version-file: 'go.mod'

- name: Make Machinekey Directory Writable
working-directory: acceptance
run: "chmod -R 777 keys"

- name: Setup ZITADEL
working-directory: acceptance
run: docker compose run setup

- name: Download Go Modules
run: go mod download

- name: Run Acceptance Tests
run: TF_ACC=1 go test -p 1 -coverprofile=profile.cov ./...
run: go run ./mage.go test:cover

- name: Publish Coverage
uses: codecov/[email protected]
with:
file: profile.cov
name: acceptance-tests
flags: acceptance-tests

- name: Save Docker Compose Logs
if: always()
working-directory: acceptance
run: docker compose logs > .docker-compose.log

- name: Archive Docker Compose Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: pull-request-tests
path: |
acceptance/.docker-compose.log
retention-days: 30
133 changes: 133 additions & 0 deletions Magefile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//go:build mage
// +build mage

// Package main provides Mage build targets for linting, running tests, and
// orchestrating a Docker‑Compose test stack. All public functions are
// exported targets; every docstring is wrapped to 80 columns so that the file
// remains readable in split‑screen editors.
package main

import (
"context"
"fmt"
"os"
"os/exec"
"strings"

"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)

const (
composeFile = "./acceptance/docker-compose.yaml" // path to Compose file
projectName = "tests" // Compose project name
)

// composeEnv builds the environment passed to every docker‑compose command. It
// injects the current user‑ID so containers can create files with the correct
// ownership, enables Terraform acceptance tests, and forces Compose to emit
// plain progress output (useful in CI logs).
func composeEnv() map[string]string {
uidBytes, _ := exec.Command("id", "-u").Output()
return map[string]string{
"ZITADEL_DEV_UID": strings.TrimSpace(string(uidBytes)),
"TF_ACC": "1",
"COMPOSE_PROGRESS": "plain",
}
}

// runTests brings the stack up, executes `go test`, and always tears the stack
// down. Extra arguments (e.g. –coverprofile flags) are forwarded to the `go
// test` invocation so that callers can run both plain and coverage runs with
// the same orchestration logic.
func runTests(ctx context.Context, extraArgs ...string) error {
if err := Up(); err != nil {
return err
}
defer Down()

args := append([]string{"test"}, extraArgs...)
args = append(args, "./...")
return sh.RunV("go", args...)
}

// Test is a Mage namespace. `mage test:default` runs the normal test suite,
// while `mage test:cover` writes a coverage profile.
type Test mg.Namespace

// Default executes the full acceptance test suite against the Compose stack.
func (Test) Default(ctx context.Context) error {
return runTests(ctx)
}

// Cover runs the same suite but saves coverage data to profile.cov so that CI
// can upload the report.
func (Test) Cover(ctx context.Context) error {
return runTests(ctx, "-coverprofile=profile.cov")
}

// Lint invokes golangci‑lint if it is available; otherwise it falls back to
// `go vet` so that developers without the linter installed can still run the
// target locally without errors.
func Lint() error {
if err := sh.Run("golangci-lint", "run", "./..."); err == nil {
return nil
}
fmt.Println("golangci-lint not found – falling back to `go vet`")
return sh.RunV("go", "vet", "./...")
Comment on lines +76 to +77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to have reproducible results by requiring a specific version of golangci-lint pinned in go.mod?
https://go.dev/doc/go1.24#tools

}

// Up starts the Compose stack in detached mode and waits for all health
// checks. Using a dedicated project name isolates the network and volumes
// from any other Compose projects that might be running on the same host.
// Up starts the Compose stack in detached mode, waits for health checks, and
// then sleeps an extra 30 seconds to give services a buffer before the test
// suite begins. This is occasionally helpful when containers report healthy
// but still need a moment to accept connections (e.g. databases warming up).
func Up() error {
if err := sh.RunWith(composeEnv(),
"docker", "compose",
"--file", composeFile,
"--project-name="+projectName,
"up", "--detach", "--wait"); err != nil {
return err
}
// Extra buffer so that flaky startup races are less likely.
if err := sh.RunV("sleep", "30"); err != nil {
fmt.Println("warning: sleep command failed:", err)
}
Comment on lines +95 to +98
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a condition we could retry quering until it is satisfied?

return nil
}

// Down stops the stack and removes volumes. If one of the well‑known debug
// flags is present (DEBUG, ACTIONS_STEP_DEBUG, or ACTIONS_RUNNER_DEBUG), it
// streams Compose logs to stdout first so developers can diagnose failures in
// CI without hunting for artifacts.
func Down() error {
if os.Getenv("DEBUG") != "" ||
os.Getenv("ACTIONS_STEP_DEBUG") == "true" ||
os.Getenv("ACTIONS_RUNNER_DEBUG") == "true" {
if err := sh.RunWith(composeEnv(),
"docker", "compose",
"--file", composeFile,
"--project-name="+projectName,
"logs"); err != nil {
fmt.Println("warning: failed to fetch compose logs:", err)
}
}
return sh.RunWith(composeEnv(),
"docker", "compose",
"--file", composeFile,
"--project-name="+projectName,
"down", "--volumes", "--remove-orphans")
}

// Default target: `mage` with no arguments lints the codebase and then runs
// the default test suite so that a single command ensures code quality and a
// passing set of integration tests.
var Default = All

// All wires the high‑level workflow of linting first and then running tests.
func All(ctx context.Context) {
mg.SerialDeps(Lint, Test{}.Default)
}
3 changes: 0 additions & 3 deletions acceptance/keys/.gitignore

This file was deleted.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
Expand Down
12 changes: 12 additions & 0 deletions mage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build mage_install
// +build mage_install

package main

import (
"os"

"github.com/magefile/mage/mage"
)

func main() { os.Exit(mage.Main()) }