Address Gadfly findings on #88
Build image / build-and-push (push) Successful in 13s

- Cover the plantId filter's out-of-range branch (0, -1), not just the
  non-numeric one — the handler rejects id < 1.
- Add the documented negative-remaining case: over-planting a lot (57 against
  50 bought) drives remaining to -7 through the HTTP surface. The number is a
  derived truth about what's committed, not a floor at zero, and that's the
  signal a gardener wants.
- Rename objID → oid in the remaining test to match the package convention
  (shares_test etc.).

Not taken: relocating createPlantAPI to plants_test.go — it's shared with #89's
branch and the move is cleanest once both land (consolidating with that branch's
makeFillPlant), noted on both PRs. The 409 "current" assertion style is
deliberate and reads clearly; matching journal_test.go's exact phrasing isn't
worth a divergence churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
2026-07-21 20:39:43 -04:00
co-authored by Claude Opus 4.8
parent 8552f1d152
commit 5bdaf21828
+23 -4
View File
@@ -89,8 +89,12 @@ func TestSeedLotCrudAPI(t *testing.T) {
if n := len(decodeList(t, w.Body.Bytes())); n != 0 {
t.Errorf("filter by a plant with no lots returned %d, want 0", n)
}
if w := doJSON(t, r, http.MethodGet, "/api/v1/seed-lots?plantId=nope", nil, cookie); w.Code != http.StatusBadRequest {
t.Errorf("bad plantId = %d, want 400", w.Code)
// A bad plantId filter is 400, whether non-numeric or out of range — the
// handler rejects id < 1, not just unparseable strings.
for _, bad := range []string{"nope", "0", "-1"} {
if w := doJSON(t, r, http.MethodGet, "/api/v1/seed-lots?plantId="+bad, nil, cookie); w.Code != http.StatusBadRequest {
t.Errorf("plantId=%q filter = %d, want 400", bad, w.Code)
}
}
// PATCH with the current version.
@@ -156,10 +160,10 @@ func TestSeedLotRemainingIsDerivedAPI(t *testing.T) {
if w.Code != http.StatusCreated {
t.Fatalf("create object: status %d, body %s", w.Code, w.Body.String())
}
objID := int64(decodeMap(t, w.Body.Bytes())["id"].(float64))
oid := int64(decodeMap(t, w.Body.Bytes())["id"].(float64))
// Plant 12 of them against the lot.
w = doJSON(t, r, http.MethodPost, objectPath(objID)+"/plantings", map[string]any{
w = doJSON(t, r, http.MethodPost, objectPath(oid)+"/plantings", map[string]any{
"plantId": plantID, "xCm": 0, "yCm": 0, "radiusCm": 20, "count": 12, "seedLotId": lotID,
}, cookie)
if w.Code != http.StatusCreated {
@@ -174,6 +178,21 @@ func TestSeedLotRemainingIsDerivedAPI(t *testing.T) {
if rem, ok := got["remaining"].(float64); !ok || rem != 38 {
t.Errorf("remaining = %v, want 38 (50 bought - 12 planted): %s", got["remaining"], w.Body.String())
}
// Over-plant it: 45 more (57 total against 50 bought) drives remaining
// NEGATIVE. That's deliberate — the number is a derived truth about what
// you've committed, not a floor clamped at zero, and "you've planted more than
// you bought" is exactly the signal a gardener wants rather than a hidden -7.
w = doJSON(t, r, http.MethodPost, objectPath(oid)+"/plantings", map[string]any{
"plantId": plantID, "xCm": 40, "yCm": 40, "radiusCm": 20, "count": 45, "seedLotId": lotID,
}, cookie)
if w.Code != http.StatusCreated {
t.Fatalf("over-plant: status %d, body %s", w.Code, w.Body.String())
}
w = doJSON(t, r, http.MethodGet, seedLotPath(lotID), nil, cookie)
if rem, ok := decodeMap(t, w.Body.Bytes())["remaining"].(float64); !ok || rem != -7 {
t.Errorf("remaining after over-planting = %v, want -7 (50 - 57)", rem)
}
}
// TestSeedLotsArePrivateAPI checks the ACL through the router.