Files
miti99bot/internal/modules/wordle/pick_random_test.go
T
tiennm99 12a2cc0803 chore(tooling): golangci-lint + govulncheck + defensive guards
Phase 6 of the 2026-05-09 review remediation plan. Bundle of small
hygiene fixes — none individually urgent but better folded together
than scattered across follow-ups.

- .golangci.yml: enable errcheck/govet/gosec/staticcheck/unused/
  ineffassign/gocyclo/misspell/revive. Tuned to the codebase style
  (no universal exported-doc requirement, gocyclo cap at 20 to
  accommodate handler dispatch). 0 issues across the tree.
- ci.yml: add golangci-lint job + govulncheck (informational).
- Defensive guards:
  - registry.go: Module.Name mismatch now errors at Build instead of
    silently overwriting (TestBuild_RejectsFactoryNameMismatch).
  - cmd/server/main.go: PORT env validated numerically + 0..65535.
  - firestore_provider.go: For() re-validates module name; invalid
    names return an invalidStore whose every op errors with
    ErrInvalidModuleName.
- Dead code removal:
  - wordle: gameTTLSeconds const + pickDaily/hashDJB2/todayUTC
    helpers + their tests deleted (pickDaily was unused;
    daily.go renamed pick_random.go).
- Dependency: golang.org/x/net v0.52.0 -> v0.54.0 (resolves
  GO-2026-4918 HTTP/2 infinite-loop CVE).
- Deferred from the original phase plan: Docker digest pinning
  (Dependabot handles), per-handler file splits (largest file 279 LOC;
  splits would churn for marginal gain).

go test -race -count=1 ./... clean (15 packages); golangci-lint run
clean (0 issues).
2026-05-09 16:33:21 +07:00

52 lines
1.2 KiB
Go

package wordle
import (
"math/rand"
"sync"
"testing"
)
func TestPickRandom_UsesInjectedRNG(t *testing.T) {
rng := rand.New(rand.NewSource(1))
words := []string{"a", "b", "c", "d", "e"}
first, err := pickRandom(words, rng)
if err != nil {
t.Fatal(err)
}
// Re-seed with same source → same sequence; lock determinism.
rng = rand.New(rand.NewSource(1))
again, _ := pickRandom(words, rng)
if first != again {
t.Errorf("seeded RNG should be deterministic: %s vs %s", first, again)
}
}
func TestPickRandom_EmptyErrors(t *testing.T) {
if _, err := pickRandom(nil, nil); err == nil {
t.Error("expected error for empty list")
}
}
// TestPickRandom_NilRNGIsRaceFree exercises the production path (rng==nil)
// from many goroutines under -race. A regression to a non-thread-safe RNG
// would flag here. Cheap insurance for the hot handler path.
func TestPickRandom_NilRNGIsRaceFree(t *testing.T) {
words := []string{"a", "b", "c", "d", "e"}
const goroutines = 64
const itersEach = 50
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
for j := 0; j < itersEach; j++ {
if _, err := pickRandom(words, nil); err != nil {
t.Errorf("pickRandom: %v", err)
}
}
}()
}
wg.Wait()
}