name: test-go # Go server: vet + golangci-lint + short race tests. Runs on push to # dev/main and PRs to main, scoped to Go-side files only — web-only or # Flutter-only diffs don't trigger this workflow. # # Two jobs: `test` (fast — vet + lint + `go test -short -race`, no DB) and # `integration` (full `go test -race` against an ephemeral Postgres). # # Integration-job DB wiring follows the act_runner shared-daemon pattern: # the runner's Docker daemon also runs the operator's dev compose stack, # so service containers get NO published ports (collision) and no # service-name DNS. We discover the service container by the job-scoped # name filter via the mounted docker socket and reach it by bridge IP. # The exactly-one assertion is a hard guard — pointing tests at the dev # Postgres would truncate it (the disaster Fable #339 exists to prevent). # # `web/build/` has a committed placeholder index.html so go:embed succeeds # without needing the SPA to be freshly built. Real builds happen in # release.yml (container) and locally during dev. on: push: branches: [dev, main] paths: - '**/*.go' - 'go.mod' - 'go.sum' - 'sqlc.yaml' - 'internal/**' - 'cmd/**' - '.forgejo/workflows/test-go.yml' pull_request: branches: [main] paths: - '**/*.go' - 'go.mod' - 'go.sum' - 'sqlc.yaml' - 'internal/**' - 'cmd/**' - '.forgejo/workflows/test-go.yml' jobs: test: runs-on: go-ci steps: - name: Checkout uses: actions/checkout@v4 - name: Toolchain versions run: | go version golangci-lint --version - name: go vet run: go vet ./... - name: golangci-lint run: golangci-lint run ./... - name: go test (short, race) run: go test -short -race ./... integration: runs-on: go-ci services: postgres: image: postgres:16-alpine env: POSTGRES_USER: minstrel POSTGRES_PASSWORD: minstrel POSTGRES_DB: minstrel_test # No `ports:` — the runner shares the operator's dev compose # Docker daemon; publishing a fixed host port collides. steps: - name: Checkout uses: actions/checkout@v4 - name: Integration suite (discover service by bridge IP, migrate, test) run: | set -eux # Discover THIS job's Postgres service container via the # mounted docker socket. Scope by the job-name filter so the # operator's dev compose `minstrel-postgres-*` (same image, # same daemon) can never match. Require exactly one — abort # loudly otherwise; a wrong target would truncate real data. PG_LIST=$(docker ps --filter "name=integration" --filter "ancestor=postgres:16-alpine" --format '{{.ID}} {{.Names}}') echo "candidates: ${PG_LIST:-}" PG_COUNT=$(printf '%s\n' "$PG_LIST" | grep -c . || true) test "$PG_COUNT" = "1" || { echo "FATAL: expected exactly 1 postgres service container, got $PG_COUNT"; exit 1; } PG_ID=$(printf '%s' "$PG_LIST" | awk '{print $1}') PG_NAME=$(printf '%s' "$PG_LIST" | awk '{print $2}') case "$PG_NAME" in *minstrel-postgres*|*_postgres_*) echo "FATAL: matched the dev compose container ($PG_NAME), refusing"; exit 1 ;; esac PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$PG_ID") test -n "$PG_IP" export MINSTREL_TEST_DATABASE_URL="postgres://minstrel:minstrel@${PG_IP}:5432/minstrel_test?sslmode=disable" # Wait for Postgres to accept TCP (no health-check dependency). for i in $(seq 1 60); do (echo > "/dev/tcp/${PG_IP}/5432") 2>/dev/null && break; sleep 2; done # Apply embedded migrations to the fresh test DB, then run the # full suite (no -short → integration tests execute). MINSTREL_DATABASE_URL="$MINSTREL_TEST_DATABASE_URL" go run ./cmd/minstrel migrate go test -race ./...