agentic-team-templates 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,326 @@
1
+ # Go Testing
2
+
3
+ Go has testing built into the language and toolchain. No excuses. Tests are written in `_test.go` files, run with `go test`, and that's it.
4
+
5
+ ## Fundamentals
6
+
7
+ ### Table-Driven Tests
8
+
9
+ The standard pattern. Use it for any function with multiple input/output cases.
10
+
11
+ ```go
12
+ func TestParseAge(t *testing.T) {
13
+ tests := []struct {
14
+ name string
15
+ input string
16
+ want int
17
+ wantErr bool
18
+ }{
19
+ {name: "valid age", input: "25", want: 25},
20
+ {name: "zero", input: "0", want: 0},
21
+ {name: "negative", input: "-1", wantErr: true},
22
+ {name: "non-numeric", input: "abc", wantErr: true},
23
+ {name: "empty string", input: "", wantErr: true},
24
+ {name: "max int boundary", input: "150", wantErr: true},
25
+ {name: "leading zeros", input: "025", want: 25},
26
+ }
27
+
28
+ for _, tt := range tests {
29
+ t.Run(tt.name, func(t *testing.T) {
30
+ got, err := ParseAge(tt.input)
31
+ if tt.wantErr {
32
+ if err == nil {
33
+ t.Errorf("ParseAge(%q) expected error, got %d", tt.input, got)
34
+ }
35
+ return
36
+ }
37
+ if err != nil {
38
+ t.Fatalf("ParseAge(%q) unexpected error: %v", tt.input, err)
39
+ }
40
+ if got != tt.want {
41
+ t.Errorf("ParseAge(%q) = %d, want %d", tt.input, got, tt.want)
42
+ }
43
+ })
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Test Naming
49
+
50
+ ```go
51
+ // Test function: Test<FunctionName> or Test<Type>_<Method>
52
+ func TestUserService_Create(t *testing.T) { ... }
53
+
54
+ // Subtests: describe the scenario, not the expected result
55
+ t.Run("duplicate email returns ErrAlreadyExists", func(t *testing.T) { ... })
56
+ t.Run("empty name returns validation error", func(t *testing.T) { ... })
57
+
58
+ // Not:
59
+ t.Run("test1", func(t *testing.T) { ... })
60
+ t.Run("success", func(t *testing.T) { ... })
61
+ ```
62
+
63
+ ### Test Helpers
64
+
65
+ ```go
66
+ // Use t.Helper() to mark helper functions
67
+ // so failure messages point to the caller, not the helper
68
+ func assertNoError(t *testing.T, err error) {
69
+ t.Helper()
70
+ if err != nil {
71
+ t.Fatalf("unexpected error: %v", err)
72
+ }
73
+ }
74
+
75
+ // Return cleanup functions for setup helpers
76
+ func setupTestDB(t *testing.T) *sql.DB {
77
+ t.Helper()
78
+ db, err := sql.Open("sqlite", ":memory:")
79
+ if err != nil {
80
+ t.Fatalf("opening test db: %v", err)
81
+ }
82
+ t.Cleanup(func() { db.Close() })
83
+ return db
84
+ }
85
+ ```
86
+
87
+ ### Golden Files
88
+
89
+ ```go
90
+ // Use testdata/ directory for fixture files
91
+ func TestRender(t *testing.T) {
92
+ input := readTestdata(t, "input.html")
93
+ want := readTestdata(t, "expected_output.html")
94
+
95
+ got, err := Render(input)
96
+ if err != nil {
97
+ t.Fatalf("Render: %v", err)
98
+ }
99
+
100
+ if diff := cmp.Diff(want, got); diff != "" {
101
+ t.Errorf("Render mismatch (-want +got):\n%s", diff)
102
+ }
103
+ }
104
+
105
+ func readTestdata(t *testing.T, name string) string {
106
+ t.Helper()
107
+ data, err := os.ReadFile(filepath.Join("testdata", name))
108
+ if err != nil {
109
+ t.Fatalf("reading testdata/%s: %v", name, err)
110
+ }
111
+ return string(data)
112
+ }
113
+ ```
114
+
115
+ ## Testing Patterns
116
+
117
+ ### Dependency Injection via Interfaces
118
+
119
+ ```go
120
+ // Define the interface at the consumer
121
+ type UserStore interface {
122
+ GetByID(ctx context.Context, id string) (*User, error)
123
+ }
124
+
125
+ // Production implementation
126
+ type PostgresUserStore struct { db *sql.DB }
127
+
128
+ // Test implementation — simple and explicit
129
+ type fakeUserStore struct {
130
+ users map[string]*User
131
+ err error
132
+ }
133
+
134
+ func (f *fakeUserStore) GetByID(_ context.Context, id string) (*User, error) {
135
+ if f.err != nil {
136
+ return nil, f.err
137
+ }
138
+ u, ok := f.users[id]
139
+ if !ok {
140
+ return nil, ErrNotFound
141
+ }
142
+ return u, nil
143
+ }
144
+
145
+ func TestHandler_GetUser(t *testing.T) {
146
+ store := &fakeUserStore{
147
+ users: map[string]*User{
148
+ "123": {ID: "123", Name: "Alice"},
149
+ },
150
+ }
151
+ h := NewHandler(store)
152
+
153
+ req := httptest.NewRequest("GET", "/users/123", nil)
154
+ rec := httptest.NewRecorder()
155
+ h.ServeHTTP(rec, req)
156
+
157
+ if rec.Code != http.StatusOK {
158
+ t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
159
+ }
160
+ }
161
+ ```
162
+
163
+ ### HTTP Handler Tests
164
+
165
+ ```go
166
+ func TestHealthEndpoint(t *testing.T) {
167
+ srv := NewServer(Config{})
168
+
169
+ req := httptest.NewRequest("GET", "/healthz", nil)
170
+ rec := httptest.NewRecorder()
171
+
172
+ srv.ServeHTTP(rec, req)
173
+
174
+ if rec.Code != http.StatusOK {
175
+ t.Errorf("GET /healthz status = %d, want %d", rec.Code, http.StatusOK)
176
+ }
177
+
178
+ var body map[string]string
179
+ if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
180
+ t.Fatalf("decoding response body: %v", err)
181
+ }
182
+ if body["status"] != "ok" {
183
+ t.Errorf("status = %q, want %q", body["status"], "ok")
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Integration Tests with Build Tags
189
+
190
+ ```go
191
+ //go:build integration
192
+
193
+ package repository_test
194
+
195
+ func TestPostgresUserRepo_Create(t *testing.T) {
196
+ if testing.Short() {
197
+ t.Skip("skipping integration test in short mode")
198
+ }
199
+
200
+ db := setupTestPostgres(t)
201
+ repo := NewUserRepo(db)
202
+
203
+ user := &User{Name: "Alice", Email: "alice@example.com"}
204
+ err := repo.Create(context.Background(), user)
205
+ if err != nil {
206
+ t.Fatalf("Create: %v", err)
207
+ }
208
+
209
+ got, err := repo.GetByID(context.Background(), user.ID)
210
+ if err != nil {
211
+ t.Fatalf("GetByID: %v", err)
212
+ }
213
+ if got.Email != user.Email {
214
+ t.Errorf("email = %q, want %q", got.Email, user.Email)
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Testing Error Cases
220
+
221
+ ```go
222
+ func TestService_Create_ValidationErrors(t *testing.T) {
223
+ svc := NewService(newFakeRepo())
224
+
225
+ tests := []struct {
226
+ name string
227
+ input CreateInput
228
+ want error
229
+ }{
230
+ {
231
+ name: "empty name",
232
+ input: CreateInput{Name: "", Email: "a@b.com"},
233
+ want: ErrNameRequired,
234
+ },
235
+ {
236
+ name: "invalid email",
237
+ input: CreateInput{Name: "Alice", Email: "not-an-email"},
238
+ want: ErrInvalidEmail,
239
+ },
240
+ }
241
+
242
+ for _, tt := range tests {
243
+ t.Run(tt.name, func(t *testing.T) {
244
+ _, err := svc.Create(context.Background(), tt.input)
245
+ if !errors.Is(err, tt.want) {
246
+ t.Errorf("Create() error = %v, want %v", err, tt.want)
247
+ }
248
+ })
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## go-cmp for Comparisons
254
+
255
+ ```go
256
+ import "github.com/google/go-cmp/cmp"
257
+
258
+ func TestTransform(t *testing.T) {
259
+ got := Transform(input)
260
+ want := Expected{
261
+ Name: "Alice",
262
+ Items: []string{"a", "b", "c"},
263
+ }
264
+
265
+ if diff := cmp.Diff(want, got); diff != "" {
266
+ t.Errorf("Transform() mismatch (-want +got):\n%s", diff)
267
+ }
268
+ }
269
+
270
+ // Ignore unexported fields or specific fields
271
+ if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(User{}, "CreatedAt")); diff != "" {
272
+ t.Errorf("mismatch (-want +got):\n%s", diff)
273
+ }
274
+ ```
275
+
276
+ ## Benchmarks
277
+
278
+ ```go
279
+ func BenchmarkProcess(b *testing.B) {
280
+ input := generateInput(1000)
281
+
282
+ b.ResetTimer()
283
+ for b.Loop() {
284
+ Process(input)
285
+ }
286
+ }
287
+
288
+ // Run: go test -bench=BenchmarkProcess -benchmem ./...
289
+ // Output: BenchmarkProcess-8 15234 78432 ns/op 4096 B/op 12 allocs/op
290
+ ```
291
+
292
+ ## Race Detection
293
+
294
+ ```go
295
+ // Always run tests with race detector in CI
296
+ // go test -race ./...
297
+
298
+ // The race detector finds data races at runtime.
299
+ // It has zero false positives — every report is a real bug.
300
+ // It does NOT find all races — absence of reports doesn't prove safety.
301
+ ```
302
+
303
+ ## Test Anti-Patterns
304
+
305
+ ```go
306
+ // Never: Tests that depend on execution order
307
+ // Each test must be independently runnable
308
+
309
+ // Never: Sleeping for synchronization
310
+ time.Sleep(500 * time.Millisecond) // Flaky — use channels, WaitGroups, or polling
311
+
312
+ // Never: Testing unexported functions directly
313
+ // Test through the public API — if you can't, the API design needs work
314
+
315
+ // Never: Excessive mocking
316
+ // If you're mocking more than one or two dependencies, the unit is too large
317
+
318
+ // Never: Ignoring the -race flag
319
+ // A test suite without -race is incomplete
320
+
321
+ // Never: t.Error when you should t.Fatal
322
+ // If subsequent code will panic on the error condition, use t.Fatal
323
+ if err != nil {
324
+ t.Fatalf("setup failed: %v", err) // Not t.Errorf — continuing will panic
325
+ }
326
+ ```
@@ -0,0 +1,361 @@
1
+ # Go Expert Development Guide
2
+
3
+ Principal-level guidelines for Go engineering. Idiomatic Go, production systems, and deep runtime knowledge.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ This guide applies to:
10
+ - HTTP services and gRPC servers
11
+ - CLI tools and system utilities
12
+ - Libraries and shared packages
13
+ - Distributed systems and microservices
14
+ - Data pipelines and stream processing
15
+ - Infrastructure tooling and platform code
16
+
17
+ ### Core Philosophy
18
+
19
+ Go is a language of restraint. The best Go code is boring Go code.
20
+
21
+ - **Simplicity is the highest virtue.** If a junior engineer can't read it, it's too clever.
22
+ - **The standard library is your first dependency.** Reach for third-party packages only when the stdlib genuinely falls short.
23
+ - **Errors are values, not exceptions.** Handle them explicitly at every call site.
24
+ - **Concurrency is a tool, not a default.** Don't use goroutines because you can — use them because the problem demands it.
25
+ - **Interfaces are discovered, not designed.** Accept interfaces, return structs.
26
+ - **If you don't know, say you don't know.** Guessing at behavior you haven't verified is worse than admitting uncertainty.
27
+
28
+ ### Key Principles
29
+
30
+ 1. **Effective Go Is the Baseline** — The official docs are the floor, not the ceiling
31
+ 2. **Accept Interfaces, Return Structs** — Narrow inputs, concrete outputs
32
+ 3. **Zero Values Are Useful** — Design types so the zero value is valid
33
+ 4. **Error Handling Is Not Boilerplate** — Every check is a conscious decision
34
+ 5. **Package Design Matters** — A package name IS the documentation
35
+
36
+ ### Project Structure
37
+
38
+ ```
39
+ project/
40
+ ├── cmd/ # Main applications (wiring only)
41
+ │ └── myapp/main.go
42
+ ├── internal/ # Private application code
43
+ │ ├── domain/ # Core business types and logic
44
+ │ ├── service/ # Application services / use cases
45
+ │ ├── repository/ # Data access implementations
46
+ │ ├── handler/ # HTTP/gRPC handlers
47
+ │ └── platform/ # Infrastructure (logging, metrics, config)
48
+ ├── pkg/ # Public library code (use sparingly)
49
+ ├── api/ # API definitions (OpenAPI, protobuf)
50
+ ├── migrations/ # Database migrations
51
+ ├── testdata/ # Test fixtures
52
+ ├── go.mod
53
+ └── Makefile
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Error Handling
59
+
60
+ Errors are the primary control flow for failure cases.
61
+
62
+ ### Wrap With Context
63
+
64
+ ```go
65
+ result, err := db.QueryContext(ctx, query, args...)
66
+ if err != nil {
67
+ return fmt.Errorf("querying users by email %q: %w", email, err)
68
+ }
69
+ ```
70
+
71
+ ### Sentinel Errors and Custom Types
72
+
73
+ ```go
74
+ var ErrNotFound = errors.New("not found")
75
+
76
+ if errors.Is(err, ErrNotFound) {
77
+ http.Error(w, "not found", http.StatusNotFound)
78
+ }
79
+
80
+ type ValidationError struct {
81
+ Field string
82
+ Message string
83
+ }
84
+ func (e *ValidationError) Error() string {
85
+ return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
86
+ }
87
+ ```
88
+
89
+ ### The run() Pattern
90
+
91
+ ```go
92
+ func main() {
93
+ if err := run(); err != nil {
94
+ fmt.Fprintf(os.Stderr, "error: %v\n", err)
95
+ os.Exit(1)
96
+ }
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Concurrency
103
+
104
+ ### Rules
105
+
106
+ - Don't start a goroutine you can't stop
107
+ - The caller decides concurrency
108
+ - Share memory by communicating
109
+ - Always select on `ctx.Done()`
110
+
111
+ ### errgroup
112
+
113
+ ```go
114
+ g, ctx := errgroup.WithContext(ctx)
115
+ for i, url := range urls {
116
+ g.Go(func() error {
117
+ resp, err := fetch(ctx, url)
118
+ if err != nil { return err }
119
+ responses[i] = resp
120
+ return nil
121
+ })
122
+ }
123
+ if err := g.Wait(); err != nil { return nil, err }
124
+ ```
125
+
126
+ ### Bounded Concurrency
127
+
128
+ ```go
129
+ sem := make(chan struct{}, maxConcurrent)
130
+ g, ctx := errgroup.WithContext(ctx)
131
+ for _, item := range items {
132
+ g.Go(func() error {
133
+ sem <- struct{}{}
134
+ defer func() { <-sem }()
135
+ return process(ctx, item)
136
+ })
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Interfaces and Types
143
+
144
+ ### Small Interfaces
145
+
146
+ ```go
147
+ type Reader interface { Read(p []byte) (n int, err error) }
148
+ type Writer interface { Write(p []byte) (n int, err error) }
149
+ ```
150
+
151
+ ### Define Interfaces at the Consumer
152
+
153
+ ```go
154
+ // In package "handler":
155
+ type UserFinder interface {
156
+ FindByID(ctx context.Context, id string) (*User, error)
157
+ }
158
+ ```
159
+
160
+ ### Functional Options
161
+
162
+ ```go
163
+ type Option func(*Server)
164
+
165
+ func WithReadTimeout(d time.Duration) Option {
166
+ return func(s *Server) { s.readTimeout = d }
167
+ }
168
+
169
+ func NewServer(addr string, opts ...Option) *Server {
170
+ s := &Server{addr: addr, readTimeout: 30 * time.Second}
171
+ for _, opt := range opts { opt(s) }
172
+ return s
173
+ }
174
+ ```
175
+
176
+ ### Generics
177
+
178
+ ```go
179
+ func Map[T, U any](s []T, f func(T) U) []U {
180
+ result := make([]U, len(s))
181
+ for i, v := range s { result[i] = f(v) }
182
+ return result
183
+ }
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Testing
189
+
190
+ ### Table-Driven Tests
191
+
192
+ ```go
193
+ func TestParseAge(t *testing.T) {
194
+ tests := []struct {
195
+ name string
196
+ input string
197
+ want int
198
+ wantErr bool
199
+ }{
200
+ {name: "valid age", input: "25", want: 25},
201
+ {name: "negative", input: "-1", wantErr: true},
202
+ {name: "non-numeric", input: "abc", wantErr: true},
203
+ }
204
+ for _, tt := range tests {
205
+ t.Run(tt.name, func(t *testing.T) {
206
+ got, err := ParseAge(tt.input)
207
+ if tt.wantErr {
208
+ if err == nil { t.Errorf("expected error") }
209
+ return
210
+ }
211
+ if err != nil { t.Fatalf("unexpected error: %v", err) }
212
+ if got != tt.want { t.Errorf("got %d, want %d", got, tt.want) }
213
+ })
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### Dependency Injection via Interfaces
219
+
220
+ ```go
221
+ type fakeUserStore struct {
222
+ users map[string]*User
223
+ err error
224
+ }
225
+ func (f *fakeUserStore) GetByID(_ context.Context, id string) (*User, error) {
226
+ if f.err != nil { return nil, f.err }
227
+ u, ok := f.users[id]
228
+ if !ok { return nil, ErrNotFound }
229
+ return u, nil
230
+ }
231
+ ```
232
+
233
+ ### HTTP Handler Tests
234
+
235
+ ```go
236
+ req := httptest.NewRequest("GET", "/users/123", nil)
237
+ rec := httptest.NewRecorder()
238
+ srv.ServeHTTP(rec, req)
239
+ if rec.Code != http.StatusOK {
240
+ t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
241
+ }
242
+ ```
243
+
244
+ ### Race Detection
245
+
246
+ Always run `go test -race ./...` in CI. Every report is a real bug.
247
+
248
+ ---
249
+
250
+ ## Standard Library and Tooling
251
+
252
+ ### net/http (Production-Ready)
253
+
254
+ ```go
255
+ mux := http.NewServeMux()
256
+ mux.HandleFunc("GET /users/{id}", getUser)
257
+
258
+ srv := &http.Server{
259
+ Addr: ":8080",
260
+ Handler: mux,
261
+ ReadTimeout: 10 * time.Second,
262
+ WriteTimeout: 10 * time.Second,
263
+ }
264
+ ```
265
+
266
+ ### log/slog
267
+
268
+ ```go
269
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
270
+ logger.Info("request", "method", r.Method, "path", r.URL.Path, "duration", elapsed)
271
+ ```
272
+
273
+ ### Essential Commands
274
+
275
+ ```bash
276
+ go test -race ./... # Tests with race detection
277
+ go vet ./... # Static analysis
278
+ golangci-lint run # Comprehensive linting
279
+ go mod tidy # Clean up dependencies
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Performance
285
+
286
+ ### Profile First
287
+
288
+ ```go
289
+ import _ "net/http/pprof"
290
+ // go tool pprof http://localhost:6060/debug/pprof/heap
291
+ ```
292
+
293
+ ### Key Patterns
294
+
295
+ - Preallocate slices when length is known
296
+ - Use `sync.Pool` for large, frequently allocated buffers
297
+ - Use `strings.Builder` for concatenation
298
+ - Use `sync.RWMutex` for read-heavy workloads
299
+ - Use `atomic` for hot counters
300
+ - Always set `http.Server` timeouts
301
+ - Always set database connection pool limits
302
+
303
+ ---
304
+
305
+ ## Production Patterns
306
+
307
+ ### Graceful Shutdown
308
+
309
+ ```go
310
+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
311
+ defer cancel()
312
+ // Start server, then <-ctx.Done(), then srv.Shutdown(shutdownCtx)
313
+ ```
314
+
315
+ ### Health Checks
316
+
317
+ ```go
318
+ mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
319
+ if err := db.PingContext(r.Context()); err != nil {
320
+ http.Error(w, "unhealthy", http.StatusServiceUnavailable)
321
+ return
322
+ }
323
+ w.Write([]byte(`{"status":"ok"}`))
324
+ })
325
+ ```
326
+
327
+ ### Retry with Backoff
328
+
329
+ Exponential backoff with jitter. Always respect context cancellation.
330
+
331
+ ### Database Transactions
332
+
333
+ ```go
334
+ func WithTransaction(ctx context.Context, db *sql.DB, fn TxFunc) error {
335
+ tx, err := db.BeginTx(ctx, nil)
336
+ if err != nil { return err }
337
+ if err := fn(ctx, tx); err != nil {
338
+ tx.Rollback()
339
+ return err
340
+ }
341
+ return tx.Commit()
342
+ }
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Definition of Done
348
+
349
+ A Go feature is complete when:
350
+
351
+ - [ ] `go vet ./...` reports zero issues
352
+ - [ ] `staticcheck ./...` reports zero issues
353
+ - [ ] `golangci-lint run` passes
354
+ - [ ] `go test -race ./...` passes with no race conditions
355
+ - [ ] Test coverage covers meaningful behavior
356
+ - [ ] Error paths are tested
357
+ - [ ] Documentation comments on all exported identifiers
358
+ - [ ] Context propagation is correct
359
+ - [ ] Resource cleanup verified (deferred closes, connection pools)
360
+ - [ ] No `TODO` without an associated issue
361
+ - [ ] Code reviewed and approved