Skip to content

Development

Tyler Conlee edited this page Apr 1, 2026 · 1 revision

Development

Local Development Setup

Prerequisites

  • Go 1.25+ (see go.mod)
  • A .env file with at least GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and DB_FILEPATH

Running Locally

# Direct
go run .

# Build and run
make run

# With hot reload (installs Air if needed)
make dev

Hot Reload with Air

The project includes an .air.toml configuration for Air, which watches for file changes and rebuilds automatically:

  • Watches: .go, .html, .css, .js files
  • Builds to ./tmp/main
  • Proxy on port 8090 forwarding to app on port 8080
make dev

Makefile Targets

Target Command Description
build go build -o ticketpulse . Build the binary
run Build + ./ticketpulse Build and run
dev air (installs if missing) Hot reload development
test go test -race ./... Run all tests with race detector
cover Tests + coverage report Run tests with coverage and print total
lint golangci-lint run ./... Run linter
vet go vet ./... Run go vet
clean Remove binary and coverage Clean build artifacts
docker-build docker build -t tylerconlee/ticketpulse:latest . Build Docker image

Testing

Running Tests

# All tests with race detector
go test -race ./...

# With coverage
make cover

# Verbose
go test -v ./...

Test Patterns

In-memory SQLite: Model and integration tests use db.InitDB(":memory:") for a fresh database with all migrations applied. No external database is needed.

database := db.InitDB(":memory:")
defer database.Close()
models.SetDatabase(database)

Mock HTTP servers: Zendesk API tests use httptest.Server to simulate API responses:

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(responseData)
}))
defer server.Close()
zc := services.NewZendeskClientForTesting("test", "test@test.com", "token", server.Client())

Mock Clock: Time-dependent tests use the MockClock interface to control time:

clock := &services.MockClock{}
clock.Set(time.Date(2024, 1, 15, 9, 0, 0, 0, time.UTC))
ps := services.NewPollingServiceWithClock(db, sse, slack, clock, cache)

testify: All tests use github.com/stretchr/testify for assertions:

assert.Equal(t, expected, actual)
require.NoError(t, err)

httptest for handlers: Handler tests use httptest.NewRecorder and httptest.NewRequest:

req := httptest.NewRequest("GET", "/alerts", nil)
w := httptest.NewRecorder()
handler.AlertsPageHandler(w, req, slackService)
assert.Equal(t, http.StatusOK, w.Code)

Test Organization

Directory Test Focus
models/*_test.go Database CRUD, data model logic
services/*_test.go Business logic, API interactions, SLA matching
handlers/*_test.go HTTP handlers, middleware, request/response
db/*_test.go Database interface, sqlx operations
logging/*_test.go Logger initialization, file output
integration/ End-to-end flows (SLA, daily summary)

Integration Tests

Integration tests in the integration/ directory test complete flows:

  • sla_flow_test.go -- SLA cache deduplication, label changes, metric types, alert log creation
  • daily_summary_flow_test.go -- Summary deduplication, work day settings, filter modes, scheduler timing

These use in-memory SQLite and do not require external services.

Linting

golangci-lint Configuration

The project uses golangci-lint v2 with the following linters enabled:

Linter Description
errcheck Check for unchecked errors (includes type assertions)
govet Report suspicious constructs
staticcheck Static analysis checks
unused Find unused code
ineffassign Detect ineffectual assignments
gosimple Suggest code simplifications
gocritic Opinionated linter (diagnostic + performance tags)
misspell Find commonly misspelled words

Excluded paths: static, templates, testutil

Formatter: gofmt

Limits: 50 issues per linter, 5 same issues

make lint

CI/CD

GitHub Actions Workflows

The project has two CI workflows:

ci.yml -- CI/CD Pipeline

Triggers on push/PR to main:

  1. Checkout code
  2. Set up Go (version from go.mod)
  3. go mod tidy
  4. Run tests (go test ./... -v)
  5. Build binary (go build -o ticketpulse)
  6. Build Docker image
  7. On push to main only: Log in to Docker Hub and push tylerconlee/ticketpulse:latest

test.yml -- Tests

Triggers on push/PR to main/master:

Test job:

  1. Checkout, setup Go, download and verify dependencies
  2. Build all packages
  3. Run tests with race detector and coverage: go test -v -race -coverprofile=coverage.out ./...
  4. Display coverage report
  5. Coverage threshold: Fails if total coverage drops below 30%
  6. Upload coverage artifact

Lint job:

  1. Checkout, setup Go
  2. Run golangci-lint v2.11 via golangci-lint-action@v7

Docker Hub

The tylerconlee/ticketpulse:latest image is automatically published on every push to main via the ci.yml workflow. It requires DOCKER_USERNAME and DOCKER_PASSWORD secrets configured in the repository.

Clone this wiki locally