Single reference for getting the project running locally, executing the full
test suite, and resolving the most common failures. Supersedes the scattered
guidance in QUICK_START.md, TEST_EXECUTION.md, and
README_REPOSITORY_TESTS.md.
| Tool | Minimum version | Check |
|---|---|---|
| Go | 1.25 | go version |
| Git | any | git --version |
| Docker | 20+ (for integration tests) | docker info |
| PostgreSQL | optional (Docker handles it) | — |
Install Go from go.dev/dl. Docker is only required when running the integration test suite; unit tests have no external dependencies.
git clone https://github.com/YOUR_ORG/stellabill-backend.git
cd stellabill-backend
go mod downloadCreate a .env file in the project root — never commit it; it is already
in .gitignore.
# .env — local development only, do not commit
ENV=development
PORT=8080
# Required for the server to start (use placeholder values locally)
DATABASE_URL=postgres://postgres:postgres@localhost:5432/stellarbill?sslmode=disable
JWT_SECRET=Dev-Only-Secret-Change-In-Prod-1!
# Optional
ADMIN_TOKEN=dev-admin-token
AUDIT_HMAC_SECRET=stellarbill-dev-audit
AUDIT_LOG_PATH=audit.log
# Request ID trusted proxies (comma-separated CIDRs; empty = untrusted mode)
REQUEST_ID_TRUSTED_PROXIES=
# Rate limiting (disabled by default in dev)
RATE_LIMIT_ENABLED=falseExport them before running the server:
export $(grep -v '^#' .env | xargs)Security: Use your cloud provider's secrets manager in production. Never put real credentials in
.envor any file tracked by Git.
go run ./cmd/serverVerify it is up:
curl http://localhost:8080/api/health
# {"service":"stellarbill-backend","status":"ok",...}go test ./internal/... -count=1 -timeout 60sExpected: all packages pass. Coverage is enforced at ≥ 95% on internal/.
Generate a coverage report:
go test ./internal/... \
-covermode=atomic \
-coverpkg=./internal/... \
-coverprofile=coverage.out \
-count=1 \
-timeout 60s
go tool cover -html=coverage.out # opens browserIntegration tests spin up an ephemeral Postgres container automatically via
testcontainers-go. No manual database setup is needed.
go test -tags integration -v -race -count=1 -timeout 120s ./integration/...TestMain applies all SQL migrations before any test case runs. The container
is torn down automatically when the suite finishes.
go test ./internal/... -race -count=1 -timeout 60s# Middleware
go test ./internal/middleware/... -v -count=1
# Worker
go test ./internal/worker/... -v -count=1
# Outbox
go test ./internal/outbox/... -v -count=1
# Config
go test ./internal/config/... -v -count=1
# Audit
go test ./internal/audit/... -v -count=1go build ./... # must compile cleanly
go vet ./... # no vet warnings
go fmt ./... # code is formatted
go test ./internal/... -count=1 -timeout 60s # all tests passgo run ./cmd/migrate upMigrations live in migrations/. See docs/migrations.md for conventions.
Symptom:
config error [MISSING_ENV_VAR]: required secret is missing (key=DATABASE_URL)
Fix: Export the required variables before running:
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/stellarbill?sslmode=disable
export JWT_SECRET=Dev-Only-Secret-Change-In-Prod-1!
go run ./cmd/serverSymptom:
config error [WEAK_SECRET]: must be at least 12 characters and contain mixed
alphanumeric and special characters (key=JWT_SECRET)
Fix: Use a secret with uppercase, lowercase, digits, and a special character, minimum 12 characters:
export JWT_SECRET=Dev-Only-Secret-Change-In-Prod-1!Symptom:
dial tcp 127.0.0.1:5432: connect: connection refused
Fix options:
a) Start Postgres locally:
docker run -d \
--name stellarbill-pg \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=stellarbill \
-p 5432:5432 \
postgres:16-alpineb) Or point DATABASE_URL at an existing instance.
c) The server runs without a real DB for most endpoints (mock repositories are used by default in dev). Only endpoints that require persistence will fail.
Symptom:
pq: relation "plans" already exists
Fix: Run the down migration first, then up:
go run ./cmd/migrate down
go run ./cmd/migrate upOr drop and recreate the database:
docker exec -it stellarbill-pg psql -U postgres -c "DROP DATABASE stellarbill;"
docker exec -it stellarbill-pg psql -U postgres -c "CREATE DATABASE stellarbill;"
go run ./cmd/migrate upSymptom:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock
Fix: Start Docker Desktop (macOS/Windows) or the Docker daemon (Linux):
sudo systemctl start docker # LinuxThen re-run:
go test -tags integration -v -count=1 -timeout 120s ./integration/...Symptom:
context deadline exceeded waiting for container to be ready
Fix: Increase the timeout or check Docker resource limits:
go test -tags integration -v -count=1 -timeout 300s ./integration/...If Docker is resource-constrained, increase memory allocation in Docker Desktop settings (≥ 4 GB recommended).
Symptom: Tests that hit protected endpoints return 401 unexpectedly.
Cause: The Authorization header is missing or the bearer token does not
match JWT_SECRET.
Fix: Ensure the test sets the correct header:
req.Header.Set("Authorization", "Bearer "+jwtSecret)For integration tests, JWT_SECRET is set in TestMain via
os.Setenv("JWT_SECRET", ...). Check integration/main_test.go if the value
has drifted.
Symptom: Responses lack X-Request-ID or logs show a different ID than
the one sent.
Cause: The inbound X-Request-ID is only accepted from trusted sources.
If REQUEST_ID_TRUSTED_PROXIES is empty (the default), all inbound IDs are
discarded and a new one is generated.
Fix for local testing with curl:
# Without trusted proxies — server generates its own ID
curl -H "X-Request-ID: my-trace-id" http://localhost:8080/api/health
# Response X-Request-ID will be a generated ID, not "my-trace-id"
# To accept inbound IDs, add your IP to the allowlist
export REQUEST_ID_TRUSTED_PROXIES=127.0.0.1/32
go run ./cmd/server
curl -H "X-Request-ID: my-trace-id" http://localhost:8080/api/health
# Response X-Request-ID: my-trace-idSymptom: go test hangs and eventually times out with a goroutine dump
showing cleanupExpiredBuckets blocked on a ticker.
Cause: APIRateLimiter starts a background goroutine. Tests that create
one must call rl.Stop() to release it.
Fix: Always defer Stop() in tests:
rl := middleware.NewAPIRateLimiter(config)
defer rl.Stop()Symptom:
./requestid.go:8:2: RequestIDHeader redeclared in this block
Cause: internal/middleware/requestid.go (now deleted) conflicted with
internal/middleware/middleware.go. If you see this after a merge or rebase,
ensure internal/middleware/requestid.go does not exist:
ls internal/middleware/requestid.go # should not existIf it does, delete it — all request ID logic now lives in
internal/requestid/requestid.go.
Symptom:
FAIL stellarbill-backend/cmd/server [setup failed]
Cause: cmd/server/main.go is the process entry point and cannot be
instrumented as a unit-testable package. This is expected.
Fix: Run tests on internal/ only:
go test ./internal/... -count=1 -timeout 60sSymptom:
coverage: 87.3% of statements
Fix: Find uncovered lines:
go test ./internal/... \
-coverprofile=coverage.out \
-coverpkg=./internal/... \
-count=1
go tool cover -func=coverage.out | sort -t% -k1 -n | head -20Add tests for the uncovered paths, focusing on error branches and edge cases.
The GitHub Actions workflow (.github/workflows/ci.yml) runs on every push
and pull request:
| Step | Command |
|---|---|
| Build | go build ./... |
| Vet | go vet ./... |
| Unit tests + coverage | go test ./internal/... -covermode=atomic -coverpkg=./internal/... |
| Coverage threshold | ≥ 95% on internal/ |
Coverage artifacts are retained for 14 days.
- Never commit
.env, JWT secrets, database credentials, or API keys. REQUEST_ID_TRUSTED_PROXIESdefaults to empty (untrusted mode). Only add CIDRs you control (e.g., your load balancer's egress range).- The in-memory rate limiter is process-local. In multi-instance deployments, use a shared store (Redis) or rely on upstream rate limiting.
- CORS is configured as
*in development. Set an explicit origin in production via theCORS_ALLOW_ORIGINenvironment variable. - Audit log entries are HMAC-signed. Protect
AUDIT_HMAC_SECRETas you would any signing key.
| Document | Purpose |
|---|---|
README.md |
Project overview, API reference, contributing guide |
docs/migrations.md |
Migration conventions and production runbook |
docs/RATE_LIMITING.md |
Rate limiting configuration and security notes |
docs/outbox-pattern.md |
Outbox pattern design and operational notes |
docs/runbooks/outbox-operations.md |
Outbox operational runbook |
docs/security-notes.md |
Security analysis and threat model |
internal/worker/README.md |
Background worker documentation |