Skip to content

Commit 3d7574d

Browse files
swalkinshawclaude
andcommitted
Replace shell smoke test with Go integration tests
Replaces the 480-line bash smoke test with Go integration tests that are deterministic, fast, and self-contained. The new test infrastructure uses fixture data and a mock wp.org server so tests run without network dependencies on the WordPress.org API. New files: - Mock wp.org server (internal/wporg/mock_server.go) + API fixtures - Test DB helpers (internal/testutil/testdb.go) for in-memory SQLite - Integration smoke test covering full pipeline, HTTP endpoints, composer install, version pinning, and build integrity - R2 sync test using gofakes3 for S3-compat verification - Live canary test for nightly runs against real wp.org - Fixture capture script (test/capture-fixtures.sh) - Canary CI workflow (.github/workflows/canary.yml) Changes: - wporg.Client now supports configurable base URL via SetBaseURL() - S3 client uses path-style addressing (needed for R2 + test compat) - CI workflow gains integration test job (stub CSS, no Tailwind needed) - Makefile: smoke target replaced with integration target Deleted: test/smoke_test.sh, .github/workflows/smoke-test.yml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f0b3082 commit 3d7574d

File tree

22 files changed

+1174
-531
lines changed

22 files changed

+1174
-531
lines changed

.github/workflows/canary.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: canary
2+
3+
on:
4+
schedule:
5+
- cron: "0 6 * * *" # daily at 6am UTC
6+
workflow_dispatch:
7+
8+
jobs:
9+
live:
10+
name: Live wp.org Canary
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
- uses: actions/setup-go@v6
15+
with:
16+
go-version-file: go.mod
17+
- uses: shivammathur/setup-php@v2
18+
with:
19+
php-version: "8.2"
20+
tools: composer:v2
21+
- name: Create stub CSS for embed
22+
run: touch internal/http/static/assets/styles/app.css
23+
- name: Live Canary Test
24+
run: go test -tags=wporg_live -count=1 -timeout=5m -v ./internal/integration/...

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,20 @@ jobs:
9797
run: make tailwind
9898
- name: Test
9999
run: go test -v ./...
100+
101+
integration:
102+
name: Integration Test
103+
runs-on: ubuntu-latest
104+
steps:
105+
- uses: actions/checkout@v6
106+
- uses: actions/setup-go@v6
107+
with:
108+
go-version-file: go.mod
109+
- uses: shivammathur/setup-php@v2
110+
with:
111+
php-version: "8.2"
112+
tools: composer:v2
113+
- name: Create stub CSS for embed
114+
run: touch internal/http/static/assets/styles/app.css
115+
- name: Integration Test
116+
run: go test -tags=integration -count=1 -timeout=5m -v ./internal/integration/...

.github/workflows/smoke-test.yml

Lines changed: 0 additions & 45 deletions
This file was deleted.

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build install dev test smoke lint clean tailwind
1+
.PHONY: build install dev test integration lint clean tailwind
22

33
TAILWIND ?= ./bin/tailwindcss
44

@@ -38,9 +38,9 @@ dev: tailwind-install
3838
test:
3939
go test ./...
4040

41-
# End-to-end smoke test (requires composer, sqlite3)
42-
smoke: build
43-
./test/smoke_test.sh
41+
# Integration tests (requires composer)
42+
integration:
43+
go test -tags=integration -count=1 -timeout=5m -v ./internal/integration/...
4444

4545
# Lint (matches CI: golangci-lint + gofmt + go vet + go mod tidy)
4646
lint:

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/fogleman/gg v1.3.0
1010
github.com/getsentry/sentry-go v0.43.0
1111
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
12+
github.com/johannesboyne/gofakes3 v0.0.0-20260208201424-4c385a1f6a73
1213
github.com/pressly/goose/v3 v3.27.0
1314
github.com/spf13/cobra v1.10.2
1415
golang.org/x/crypto v0.49.0
@@ -36,12 +37,15 @@ require (
3637
github.com/mfridman/interpolate v0.0.2 // indirect
3738
github.com/ncruces/go-strftime v1.0.0 // indirect
3839
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
40+
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
3941
github.com/sethvargo/go-retry v0.3.0 // indirect
4042
github.com/spf13/pflag v1.0.9 // indirect
43+
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect
4144
go.uber.org/multierr v1.11.0 // indirect
4245
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
4346
golang.org/x/sys v0.42.0 // indirect
4447
golang.org/x/text v0.35.0 // indirect
48+
golang.org/x/tools v0.42.0 // indirect
4549
modernc.org/libc v1.68.0 // indirect
4650
modernc.org/mathutil v1.7.1 // indirect
4751
modernc.org/memory v1.11.0 // indirect

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/
44
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
55
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
66
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
7+
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75 h1:S61/E3N01oral6B3y9hZ2E1iFDqCZPPOBoBQretCnBI=
8+
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75/go.mod h1:bDMQbkI1vJbNjnvJYpPTSNYBkI/VIv18ngWb/K84tkk=
79
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc=
810
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
911
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw=
@@ -22,6 +24,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7
2224
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0=
2325
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
2426
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
27+
github.com/cevatbarisyilmaz/ara v0.0.4 h1:SGH10hXpBJhhTlObuZzTuFn1rrdmjQImITXnZVPSodc=
28+
github.com/cevatbarisyilmaz/ara v0.0.4/go.mod h1:BfFOxnUd6Mj6xmcvRxHN3Sr21Z1T3U2MYkYOmoQe4Ts=
2529
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2630
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2731
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -46,6 +50,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
4650
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
4751
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
4852
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
53+
github.com/johannesboyne/gofakes3 v0.0.0-20260208201424-4c385a1f6a73 h1:0xkWp+RMC2ImuKacheMHEAtrbOTMOa0kYkxyzM1Z/II=
54+
github.com/johannesboyne/gofakes3 v0.0.0-20260208201424-4c385a1f6a73/go.mod h1:S4S9jGBVlLri0OeqrSSbCGG5vsI6he06UJyuz1WT1EE=
4955
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
5056
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
5157
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -69,14 +75,22 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
6975
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
7076
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
7177
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
78+
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
79+
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
7280
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
7381
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
82+
github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M=
83+
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
7484
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
7585
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
7686
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
7787
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7888
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
7989
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
90+
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
91+
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
92+
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d h1:Ns9kd1Rwzw7t0BR8XMphenji4SmIoNZPn8zhYmaVKP8=
93+
go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3l++MlthCm+koNi0tcUCX3anayogF0Pa/sp24k=
8094
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
8195
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
8296
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -102,6 +116,8 @@ golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0
102116
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
103117
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
104118
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
119+
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
120+
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
105121
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
106122
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
107123
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=

internal/deploy/r2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ func newS3Client(cfg config.R2Config) *s3.Client {
548548
"",
549549
),
550550
BaseEndpoint: aws.String(cfg.Endpoint),
551+
UsePathStyle: true,
551552
})
552553
}
553554

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//go:build integration || wporg_live
2+
3+
package integration
4+
5+
import (
6+
"encoding/json"
7+
"io"
8+
"log/slog"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"strings"
14+
"testing"
15+
)
16+
17+
func testLogger(t *testing.T) *slog.Logger {
18+
t.Helper()
19+
return slog.Default()
20+
}
21+
22+
func httpGet(t *testing.T, url string) string {
23+
t.Helper()
24+
resp, err := http.Get(url)
25+
if err != nil {
26+
t.Fatalf("GET %s: %v", url, err)
27+
}
28+
defer resp.Body.Close()
29+
if resp.StatusCode != 200 {
30+
t.Fatalf("GET %s: status %d", url, resp.StatusCode)
31+
}
32+
body, err := io.ReadAll(resp.Body)
33+
if err != nil {
34+
t.Fatalf("reading response from %s: %v", url, err)
35+
}
36+
return string(body)
37+
}
38+
39+
func writeComposerJSON(t *testing.T, dir, repoURL string, require map[string]string) {
40+
t.Helper()
41+
data := map[string]any{
42+
"repositories": []map[string]any{
43+
{
44+
"type": "composer",
45+
"url": repoURL,
46+
},
47+
},
48+
"require": require,
49+
"config": map[string]any{
50+
"allow-plugins": map[string]any{
51+
"composer/installers": true,
52+
},
53+
},
54+
}
55+
jsonData, err := json.MarshalIndent(data, "", " ")
56+
if err != nil {
57+
t.Fatalf("marshaling composer.json: %v", err)
58+
}
59+
if err := os.WriteFile(filepath.Join(dir, "composer.json"), jsonData, 0644); err != nil {
60+
t.Fatalf("writing composer.json: %v", err)
61+
}
62+
}
63+
64+
func runComposer(t *testing.T, composerPath, dir string, args ...string) string {
65+
t.Helper()
66+
cmd := exec.Command(composerPath, args...)
67+
cmd.Dir = dir
68+
out, err := cmd.CombinedOutput()
69+
if err != nil {
70+
t.Fatalf("composer %s failed: %v\noutput: %s", strings.Join(args, " "), err, out)
71+
}
72+
return string(out)
73+
}

0 commit comments

Comments
 (0)