Skip to content

Commit a439e56

Browse files
authored
Merge pull request #127 from basecamp/feature/e2e-role-tests
Replace legacy E2E coverage with an owner-only CLI contract suite
2 parents 3d3e09a + 86c9ca5 commit a439e56

37 files changed

Lines changed: 2251 additions & 4971 deletions

.env.test.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# e2e test credentials template.
2+
# Copy to .env.test and fill in real tokens (that file is git-excluded).
3+
4+
FIZZY_TEST_TOKEN=fizzy_your_token_here
5+
FIZZY_TEST_ACCOUNT=1234567
6+
FIZZY_TEST_API_URL=https://app.fizzy.do

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ completions/
3232
# Profiling
3333
profiles/
3434
default.pgo
35+
36+
# Local test credentials
37+
.env.test

Makefile

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
.PHONY: test test-unit test-e2e test-go test-file test-run build clean tidy help \
1+
.PHONY: test test-unit test-e2e e2e test-go test-file e2e-file test-run e2e-run build clean tidy help \
22
check-toolchain fmt fmt-check vet lint tidy-check race-test vuln secrets \
33
replace-check security check release-check release tools \
44
surface-snapshot surface-check lint-actions
55

66
BINARY := $(CURDIR)/bin/fizzy
7+
FIZZY_TEST_BINARY ?= $(BINARY)
8+
9+
# Load local test credentials if present, but refuse tracked local secret files.
10+
ifneq ($(shell git ls-files --error-unmatch .env.test >/dev/null 2>&1 && echo tracked),)
11+
$(error .env.test is tracked by Git. Remove it from version control and keep local secret files untracked)
12+
endif
13+
-include .env.test
14+
export FIZZY_TEST_TOKEN FIZZY_TEST_ACCOUNT FIZZY_TEST_API_URL FIZZY_TEST_BINARY
715
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
816
LDFLAGS := -X main.version=$(VERSION)
917

@@ -20,10 +28,13 @@ help:
2028
@echo "Usage:"
2129
@echo " make build Build the CLI"
2230
@echo " make test-unit Run unit tests (no API required)"
23-
@echo " make test-e2e Run e2e tests (requires API credentials)"
24-
@echo " make test Alias for test-e2e"
25-
@echo " make test-file Run a specific e2e test file"
26-
@echo " make test-run Run a specific e2e test by name"
31+
@echo " make e2e Run owner-only CLI contract e2e tests"
32+
@echo " make test-e2e Alias for e2e"
33+
@echo " make test Alias for e2e"
34+
@echo " make e2e-file Run a specific CLI contract e2e test file"
35+
@echo " make test-file Alias for e2e-file"
36+
@echo " make e2e-run Run a specific CLI contract e2e test by name"
37+
@echo " make test-run Alias for e2e-run"
2738
@echo " make clean Remove build artifacts"
2839
@echo " make tidy Tidy dependencies"
2940
@echo ""
@@ -45,17 +56,19 @@ help:
4556
@echo " make tools Install dev tools"
4657
@echo ""
4758
@echo "Environment variables (required for e2e tests):"
48-
@echo " FIZZY_TEST_TOKEN API token"
49-
@echo " FIZZY_TEST_ACCOUNT Account slug"
50-
@echo " FIZZY_TEST_API_URL API base URL (default: https://app.fizzy.do)"
51-
@echo " FIZZY_TEST_USER_ID User ID for user update/deactivate tests (optional)"
59+
@echo " FIZZY_TEST_TOKEN API token"
60+
@echo " FIZZY_TEST_ACCOUNT Account slug"
61+
@echo " FIZZY_TEST_API_URL API base URL (default: https://app.fizzy.do)"
62+
@echo " FIZZY_TEST_BINARY Prebuilt binary path (optional)"
63+
@echo " FIZZY_E2E_KEEP_FIXTURE Set to 1 to skip final fixture teardown"
64+
@echo " FIZZY_E2E_TEARDOWN_DELAY Delay teardown by N seconds"
5265
@echo ""
5366
@echo "Examples:"
5467
@echo " make build"
5568
@echo " make test-unit"
5669
@echo " export FIZZY_TEST_TOKEN=your-token"
5770
@echo " export FIZZY_TEST_ACCOUNT=your-account"
58-
@echo " make test-e2e"
71+
@echo " make e2e"
5972

6073
# Toolchain guard — fails fast when PATH go and GOROOT go disagree
6174
check-toolchain:
@@ -80,28 +93,33 @@ test-unit: check-toolchain
8093
go test -v ./internal/...
8194

8295
# Run e2e tests (requires API credentials)
83-
test-e2e: build
96+
e2e: build
8497
@if [ -z "$$FIZZY_TEST_TOKEN" ]; then echo "Error: FIZZY_TEST_TOKEN not set"; exit 1; fi
8598
@if [ -z "$$FIZZY_TEST_ACCOUNT" ]; then echo "Error: FIZZY_TEST_ACCOUNT not set"; exit 1; fi
86-
FIZZY_TEST_BINARY=$(BINARY) go test -v ./e2e/tests/...
99+
go test -v -count=1 -timeout 10m ./e2e/cli_tests/...
87100

88-
# Alias for test-e2e
89-
test: test-e2e
90-
test-go: test-e2e
101+
test-e2e: e2e
91102

92-
# Run a single test file (e.g., make test-file FILE=board)
93-
test-file: build
94-
@if [ -z "$(FILE)" ]; then echo "Usage: make test-file FILE=board"; exit 1; fi
103+
test: e2e
104+
test-go: e2e
105+
106+
# Run a single test file (e.g., make e2e-file FILE=crud_board)
107+
e2e-file: build
108+
@if [ -z "$(FILE)" ]; then echo "Usage: make e2e-file FILE=crud_board"; exit 1; fi
95109
@if [ -z "$$FIZZY_TEST_TOKEN" ]; then echo "Error: FIZZY_TEST_TOKEN not set"; exit 1; fi
96110
@if [ -z "$$FIZZY_TEST_ACCOUNT" ]; then echo "Error: FIZZY_TEST_ACCOUNT not set"; exit 1; fi
97-
FIZZY_TEST_BINARY=$(BINARY) go test -v ./e2e/tests/$(FILE)_test.go
111+
go test -v -count=1 ./e2e/cli_tests/$(FILE)_test.go
112+
113+
test-file: e2e-file
98114

99-
# Run a single test by name (e.g., make test-run NAME=TestBoardCRUD)
100-
test-run: build
101-
@if [ -z "$(NAME)" ]; then echo "Usage: make test-run NAME=TestBoardCRUD"; exit 1; fi
115+
# Run a single test by name (e.g., make e2e-run NAME=TestBoardList)
116+
e2e-run: build
117+
@if [ -z "$(NAME)" ]; then echo "Usage: make e2e-run NAME=TestBoardList"; exit 1; fi
102118
@if [ -z "$$FIZZY_TEST_TOKEN" ]; then echo "Error: FIZZY_TEST_TOKEN not set"; exit 1; fi
103119
@if [ -z "$$FIZZY_TEST_ACCOUNT" ]; then echo "Error: FIZZY_TEST_ACCOUNT not set"; exit 1; fi
104-
FIZZY_TEST_BINARY=$(BINARY) go test -v -run $(NAME) ./e2e/tests/...
120+
go test -v -count=1 -run $(NAME) ./e2e/cli_tests/...
121+
122+
test-run: e2e-run
105123

106124
# Format Go source
107125
fmt:

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,20 @@ fizzy skill install
188188
```bash
189189
make build # Build binary
190190
make test-unit # Run unit tests (no API required)
191-
make test-e2e # Run e2e tests (requires FIZZY_TEST_TOKEN, FIZZY_TEST_ACCOUNT)
191+
make e2e # Run owner-only CLI contract e2e suite
192+
make e2e-run NAME=TestBoardList
192193
```
193194

195+
E2E requirements:
196+
- `FIZZY_TEST_TOKEN`
197+
- `FIZZY_TEST_ACCOUNT`
198+
- optional: `FIZZY_TEST_API_URL`
199+
- optional: `FIZZY_TEST_BINARY`
200+
201+
Useful local inspection modes:
202+
- `FIZZY_E2E_KEEP_FIXTURE=1 make e2e`
203+
- `FIZZY_E2E_TEARDOWN_DELAY=120 make e2e`
204+
194205
## License
195206

196207
[MIT](LICENSE)

e2e/cli_tests/account_user_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package clitests
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
)
7+
8+
func TestAccountShow(t *testing.T) {
9+
assertOK(t, newHarness(t).Run("account", "show"))
10+
}
11+
12+
func TestAccountSettingsUpdateWithCurrentName(t *testing.T) {
13+
h := newHarness(t)
14+
show := h.Run("account", "show")
15+
assertOK(t, show)
16+
currentName := show.GetDataString("name")
17+
if currentName == "" {
18+
t.Skip("account show returned no name")
19+
}
20+
assertOK(t, h.Run("account", "settings-update", "--name", currentName))
21+
show = h.Run("account", "show")
22+
assertOK(t, show)
23+
if got := show.GetDataString("name"); got != currentName {
24+
t.Fatalf("expected account name %q after settings-update, got %q", currentName, got)
25+
}
26+
}
27+
28+
func TestAccountEntropyWithCurrentValue(t *testing.T) {
29+
h := newHarness(t)
30+
show := h.Run("account", "show")
31+
assertOK(t, show)
32+
days := show.GetDataInt("auto_postpone_period_in_days")
33+
if days == 0 {
34+
days = 7
35+
}
36+
assertOK(t, h.Run("account", "entropy", "--auto_postpone_period_in_days", strconv.Itoa(days)))
37+
show = h.Run("account", "show")
38+
assertOK(t, show)
39+
if got := show.GetDataInt("auto_postpone_period_in_days"); got != days {
40+
t.Fatalf("expected auto_postpone_period_in_days=%d, got %d", days, got)
41+
}
42+
}
43+
44+
func TestAccountJoinCodeShow(t *testing.T) {
45+
assertOK(t, newHarness(t).Run("account", "join-code-show"))
46+
}
47+
48+
func TestAccountExportCreateShow(t *testing.T) {
49+
h := newHarness(t)
50+
create := h.Run("account", "export-create")
51+
assertOK(t, create)
52+
exportID := create.GetDataString("id")
53+
if exportID == "" {
54+
exportID = mapValueString(create.GetDataMap(), "id")
55+
}
56+
if exportID == "" {
57+
t.Fatal("expected export ID in export-create response")
58+
}
59+
show := h.Run("account", "export-show", exportID)
60+
assertOK(t, show)
61+
if got := mapValueString(show.GetDataMap(), "id"); got != exportID {
62+
t.Fatalf("expected export-show id %q, got %q", exportID, got)
63+
}
64+
if got := mapValueString(show.GetDataMap(), "status"); got == "" {
65+
t.Fatal("expected export status in export-show response")
66+
}
67+
}
68+
69+
func TestUserList(t *testing.T) {
70+
result := newHarness(t).Run("user", "list")
71+
assertOK(t, result)
72+
if result.GetDataArray() == nil {
73+
t.Fatal("expected array response")
74+
}
75+
}
76+
77+
func TestUserShowAndUpdateOwnProfile(t *testing.T) {
78+
h := newHarness(t)
79+
userID := currentUserID(t, h)
80+
show := h.Run("user", "show", userID)
81+
assertOK(t, show)
82+
currentName := show.GetDataString("name")
83+
if currentName == "" {
84+
t.Skip("user show returned no name")
85+
}
86+
assertOK(t, h.Run("user", "update", userID, "--name", currentName))
87+
show = h.Run("user", "show", userID)
88+
assertOK(t, show)
89+
if got := show.GetDataString("name"); got != currentName {
90+
t.Fatalf("expected user name %q after update, got %q", currentName, got)
91+
}
92+
}
93+
94+
func TestUserAvatarUpdateAndRemove(t *testing.T) {
95+
h := newHarness(t)
96+
userID := currentUserID(t, h)
97+
fixturePath := fixtureFile(t, "test_image.png")
98+
99+
show := h.Run("user", "show", userID)
100+
assertOK(t, show)
101+
avatarURL := show.GetDataString("avatar_url")
102+
if avatarURL == "" {
103+
t.Skip("user show returned no avatar_url")
104+
}
105+
initiallyAttached := avatarRedirects(t, avatarURL)
106+
if initiallyAttached {
107+
t.Cleanup(func() {
108+
assertOK(t, newHarness(t).Run("user", "update", userID, "--avatar", fixturePath))
109+
if !avatarRedirects(t, avatarURL) {
110+
t.Fatal("expected avatar to be restored")
111+
}
112+
})
113+
}
114+
115+
assertOK(t, h.Run("user", "update", userID, "--avatar", fixturePath))
116+
if !avatarRedirects(t, avatarURL) {
117+
t.Fatal("expected uploaded avatar endpoint to redirect to an image blob")
118+
}
119+
120+
assertOK(t, h.Run("user", "avatar-remove", userID))
121+
if avatarRedirects(t, avatarURL) {
122+
t.Fatal("expected avatar endpoint to fall back to generated SVG after removal")
123+
}
124+
}

0 commit comments

Comments
 (0)