# Repo map — Unit testing (ground truth)

The survey behind this course. Every lesson's `file:line` anchors resolve here. When code moves, fix
this file first, then the lessons. Grouped by the write → mock → generate → run loop.

**Orienting facts:** ~2,778 `*_test.go` under `internal/`; **1,807 import `testify/mock`, 0 use
gomock** — mocking is 100% testify. ~2,377 call `t.Parallel()`. 764 use the house `mock/testutil`
DB helper. Best reference service: **spike** (`internal/spike/modules/email/`).

---

## 1. The house test-style rules

- **`.claude/rules/go-test-style.md`** (applies to `**/*_test.go`) — the canonical structure:
  - **Core** (`:9-14`): don't change impl just to pass; read impl first when a test fails; one
    assertion per behaviour; no logic branches in cases; **target >80% case coverage per function**.
  - **Structure** (`:18-54`): sub-test-per-case with an **inline `newSuite` factory** + an inline
    **`assertExpected` helper** (`= mock.AssertExpectationsForObjects(t, s.db, s.tx, s.mockRepo)`,
    `:34`). Rules: `t.Parallel()` at top of every `TestXxx` (`:51`); always `context.WithTimeout`
    (`:52`); call `newSuite()` at the top of every `t.Run` (`:53`); `// arrange / act / assert` (`:54`).
    The skeleton's `testSuite` holds `sut`, `db *mock_database.Ext`, `tx *mock_database.Tx`,
    `mockRepo *mock_postgres.MockMyRepo` (`:20-48`).
  - **Coverage checklist** (`:56-64`): (1) error from every external call, (2) nil/not-found, (3)
    guard/early-return, (4) happy create, (5) happy update, (6) tx commit/rollback failures.
  - **Assertions** (`:66-71`): `assert.Equal` for values; `assert.Nil/NotNil`; `assert.NotEmpty` for
    generated IDs; `.Run(func(args mock.Arguments){…})` to inspect args.
  - **Naming** (`:74-81`): `"return <result> when <condition>"`.
- **`.claude/rules/go-code-style.md`**: after changing an interface → `make gen-mock-v2` (`:41-49`);
  after editing a file → run the nearest test (`:51-60`).
- **`.claude/agents/unit-tester.md`** — the house test-authoring playbook (service skeleton `:48-101`,
  tx mocking `:103-114`, RBAC ctx `:116-121`, repo+MockDB `:123-136`). Uses a **package-level
  `TestCase` struct** style (`:40-45`) — the *legacy* alternative to the rules file's inline
  `newSuite`. Both exist; teach the rules-file version as canonical.
- **`.claude/rules/eureka-v2-conventions.md:103-109`** — a "mock strategy by layer" table: repo →
  real test DB or pgx mock; usecase → mock repo interfaces; transport → mock usecase interfaces.

---

## 2. Mock generation — the tooling

- **Makefile targets:**
  - `gen-mock-v2` (`Makefile:213`) → `go run cmd/utils/main.go mock eureka_v2`.
  - `gen-mock-service` (`:210`) → `go run cmd/utils/main.go mock $(service)` — e.g.
    `make service=spike gen-mock-service`.
  - `gen-mock-repo` (`:184`) → runs the mock cmd for every service.
  - eureka-v2 uses `//go:generate` **mockery** directives from `sqlc.yaml` (`eureka-gen-sqlc-v2`).
- **The custom CLI** `cmd/utils/mock/commands.go` — a **cobra** app (`mock <service>`); subcommands
  registered in `init()` (`:13-44`). Each service has a `genXxx.go` (e.g. `genSpike.go`):
  - `structs map[string][]interface{}` (pkg path → concrete `struct{}` values, e.g.
    `&repositories.EmailRepo{}`) → `tools.GenMockStructs(structs)` (`genSpike.go:12-33`).
  - `interfaces map[string][]string` (pkg path → interface names) → `tools.GenMockInterfaces(...)`
    (`genSpike.go:35-45`).
- **TWO generators (the bespoke part):**
  - **(a) `tools.GenMockStructs`** — a **hand-rolled reflection codegen**
    (`internal/golibs/tools/mock_struct.go`): `reflect.TypeOf` walks a struct's methods
    (`generateMockContent :101-201`) and emits `type Mock<Name> struct { mock.Mock }` + one method
    each calling `r.Called(...)` → `args.Get(i).(T)`/`args.Error(i)`. Output `internal/`→`mock/`
    (`mockPathFrom :85`), pkg `mock_<pkg>`. Header `// Code generated by mockgen. DO NOT EDIT.`
    (`:203`) — **misleading; it is NOT gomock**. Args named positionally `arg1, arg2, …`.
  - **(b) `tools.GenMockInterfaces`** — a **thin wrapper over real `mockery` v2.46.0**
    (`internal/golibs/tools/mock_interface.go:40-55`): `Config{Case:"underscore",
    Packageprefix:"mock_", UnrollVariadic:true, WithExpecter:true}`. Adds the `_Expecter`/`EXPECT()`
    fluent API.
  - **So:** concrete structs → reflection generator; named interfaces → mockery. Both emit testify
    `mock.Mock` mocks into `mock/`.

---

## 3. The `mock/` directory

- Mirrors `internal/` one-to-one (`mockPathFrom`). Groups: `mock/golibs/…` (database, kafka, nats,
  bootstrap, sendgrid…), per-service (`mock/spike/…`), and the special **`mock/testutil/`** helper.
- **`mock/golibs/database`** — the hard part. Real interfaces: `internal/golibs/database/db.go`
  `QueryExecer` (`:24`, Query/Exec/SendBatch), `Ext` (`:33`, +`Begin`). Mocks (mockery, testify):
  `ext.go`/`ext5.go`, `query_execer.go`/`5`, `tx.go` (`Tx`: Begin/`Commit :136`/`Rollback :655`),
  `row.go` (`Row`: `Scan`), `rows.go` (`Rows`: Next/Scan/FieldDescriptions/Close/Err),
  `batch_results.go`. **`_5` = the pgx v4→v5 migration** — both stacks mocked in parallel.
  You rarely mock raw pgx directly — the `testutil.MockDB` wrapper drives Next/Scan/Close for you.

---

## 4. testify usage patterns

- `.On(name, args…).Return(…)` + `.Once()`: `create_email_handler_test.go:80-81`
  (`mockDB.On("Begin", ctx).Return(mockTx, nil)`, `mockTx.On("Commit", ctx).Return(nil)`).
- **`mock.MatchedBy(func(x) bool)`** to assert on entity fields: `create_email_handler_test.go:82-148`
  (type-asserts `*model.Email`; checks Subject, non-empty EmailID, Status==CREATED; recipient counts).
- `mock.Anything` / `mock.AnythingOfType("string")`: `email_test.go:41-44`.
- **`mock.AssertExpectationsForObjects(t, …)`** — the house verifier: `create_email_handler_test.go:164`,
  `bootstrap_test.go:49`, codified as `assertExpected` (`go-test-style.md:34`).
- `.Run(func(args mock.Arguments){…})` to inspect/mutate args: `mock/testutil/mock_db_wrapper.go:35-38,134-138`.
- **`assert` almost everywhere** (Nil/Equal/NotEmpty/Len/ErrorIs); **`require` only in helpers**
  (`mock/testutil/sql.go:14-15`).

---

## 5. spike's tests, by layer (`internal/spike/modules/email/`)

### Usecase/handler — `application/commands/create_email_handler_test.go` (the flagship)
- `t.Parallel()` + `context.WithTimeout(ctx, 15*time.Second)` (`:22-24`).
- Mocks injected as **struct fields**: `handler := CreateEmailHandler{DB: mockDB, EmailRepo:
  mockEmailRepo, …}` where `mockDB = new(mock_database.Ext)`, `mockTx = new(mock_database.Tx)` (`:26-37`).
- Tx flow: `mockDB.On("Begin", ctx).Return(mockTx, nil)` + `mockTx.On("Commit", ctx).Return(nil)` (`:80-81`).
- Entity asserts via `mock.MatchedBy` on `UpsertEmail`/`BulkUpsertEmailRecipients` (`:82-148`); metric
  `.Once()` (`:149`); final `assert.*` (`:155-162`) + `AssertExpectationsForObjects` (`:164`).
- **Caveat:** this file has only a happy case — pair with the grpc test for the error-path checklist.

### Repository — `infrastructure/repositories/email_test.go`
- **Raw `mock_database.Ext` idiom** (`TestEmailRepo_UpsertEmail :21-62`): build exact variadic args
  (ctx + `mock.AnythingOfType("string")` SQL + one `mock.Anything` per scan field, `:37-44`);
  `db.On("Exec", mockValues…).Once().Return(pgconn.CommandTag(…), nil)`. `SendBatch` for bulk (`:64-105`).
- **House `testutil.MockDB` idiom** (`Test_Get :225-270`): `mockDB.MockQueryArgs(t, err, ctx,
  mock.Anything, database.TextArray(ids))` + `mockDB.MockScanArray(nil, fields, [][]interface{}{…})`
  drives multi-row scanning; happy/error-query/error-scan (`pgx.ErrNoRows`) map to the checklist.
  `TestEmailRepo_UpdateEmail` uses `MockExecArgs`. **This is how a pgx repo is unit-tested — no real DB.**

### gRPC controller — `controller/grpc/email_modifier_send_email_test.go`
- Transport tested by **mocking the usecase interface**: `createEmailHandler :=
  &mock_commands.MockCreateEmailHandler{}` injected into `&EmailModifierService{EmailCommandHandler:
  createEmailHandler, KafkaMgmt: mockKafka, …}` (`:35-52`). RBAC ctx faked via
  `interceptors.ContextWithJWTClaims` (`:57-61`).
- **Full error-path checklist:** happy (`:86-106`); kafka error → `codes.Internal` (`:108-130`);
  publish-err + update-err `multierr.Combine` (`:132-157`); publish-ok update-fails logs-only via
  `zaptest/observer` (`:159-185`).

---

## 6. Running tests & coverage (`Makefile`)

- **`make service=<svc> unit-test-service`** (`:104`): `go test -count=3 ./internal/$(service)/...
  -cover -covermode=count -coverprofile=cover.out -coverpkg=./internal/$(service)/...` → `go tool
  cover -func` → prints total. **`-count=3`** = run 3× (flake/order detection → tests must be
  deterministic + parallel-safe).
- `test-unit` (`:99`) all of `./internal/...`. `test-unit-for-coverage` (`:109`) `-count=1` +
  **strips generated code** (`grep -v "_generated_impl.go" | grep -v ".sql.go"`).
- Single test: `go test ./internal/spike/modules/email/... -run ^TestHandler_CreateEmail$ -v`.
- No numeric CI threshold in the Makefile; the rule's soft target is **>80% case coverage / function**.

---

## 7. Test helpers & the scope boundary

- **`mock/testutil/`** (NOT `internal/golibs/testutil`, which doesn't exist) — the DB-test toolkit:
  - `mock_db_wrapper.go` `type MockDB` bundles `DB *mock_database.Ext`, `DB5`, `Rows`, `Row`, `RawStmt`
    (`:15-32`). `NewMockDB()` (`:24`). `MockExecArgs`/`MockQueryArgs`/`MockQueryRowArgs`/
    `MockScanFields`/`MockScanArray` (+`5`). The clever bit: `MockScanArray` registers `Rows.Next→true`,
    `FieldDescriptions`, and a `Scan` whose `.Run` uses **reflection** to copy expected values into the
    caller's scan destinations (`reflect.…Elem().Set(…)`, `:134-138`), then `Next→false/Close/Err`.
  - `sql.go` `ParseSQL(t, sql)` wraps `pganalyze/pg_query_go/v5` → assert on the **SQL AST shape**
    (`insert.go`/`select.go`/`update.go`/`where.go`).
- **Bootstrap tests** `internal/golibs/bootstrap/*_test.go` (14 files) — framework-level examples;
  `bootstrap_test.go:29-50` mocks `MockAllService[Config]` lifecycle.
- **Scope boundary (EXCLUDE — E2E/BDD):** `features/` (godog `.feature` + steps), `internal/gandalf`
  (BDD harness), anything run by `local/run.bash bdd` (`local/run.bash:219-246`, needs the docker
  stack + real DBs), and repo tests that hit a real DB. Teach only `go test ./internal/...` +
  testify-mock + `mock/`.

---

## Surprising / bespoke
1. **Two mock generators**, both testify: reflection codegen (structs) + mockery wrapper (interfaces).
2. **The `// Code generated by mockgen` header is misleading** — it's the bespoke reflection tool, not gomock.
3. **No gomock at all** despite that header — pure testify.
4. **`testutil.MockDB`** fakes pgx `Scan` via reflection + parses SQL with a real PG parser
   (`pg_query_go`) so tests can assert SQL shape without a DB.
5. **Dual pgx v4/v5 mocks** everywhere (`Ext`/`Ext5`) — live migration.
6. **`-count=3`** on unit targets forces determinism; coverage strips generated code.
7. **Two authoring styles** — inline `newSuite`+`assertExpected` (canonical, rules file) vs
   package-level `TestCase` table (legacy, the `unit-tester` agent). Teach the former.
