Refuse deleting a plant that has seed lots
Build image / build-and-push (push) Successful in 4s

seed_lots.plant_id is ON DELETE CASCADE, so deleting a plant would have silently
destroyed the purchase records attached to it — vendor, cost, germination rate,
gone with no warning. Plantings were already guarded with ErrPlantInUse because
their FK is RESTRICT; lots needed the same guard for the opposite reason, since
their FK would have obliged rather than refused.

Refusing lets the owner delete the lots deliberately if that is really what they
meant, which is the same shape as the existing clones-and-edits path.

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 01:29:24 -04:00
co-authored by Claude Opus 4.8
parent 81481ede2f
commit 5eedcaf945
3 changed files with 51 additions and 0 deletions
+26
View File
@@ -343,3 +343,29 @@ func TestSeedLotUnitAndQuantityValidation(t *testing.T) {
}
func intPtr(v int) *int { return &v }
// TestDeletingAPlantWithLotsIsRefused — seed_lots.plant_id is ON DELETE CASCADE,
// so without a guard, deleting a plant would silently destroy the purchase
// records attached to it: vendor, cost, germination rate, gone with no warning.
func TestDeletingAPlantWithLotsIsRefused(t *testing.T) {
s := newTestService(t, openConfig())
owner := seedUser(t, s, "[email protected]")
plant := seedOwnPlant(t, s, owner, 15)
lot := seedLot(t, s, owner, plant.ID, 100, func(in *SeedLotInput) { in.CostCents = intPtr(499) })
ctx := context.Background()
if err := s.DeletePlant(ctx, owner, plant.ID); !errors.Is(err, domain.ErrPlantInUse) {
t.Fatalf("DeletePlant err = %v, want ErrPlantInUse", err)
}
if _, err := s.GetSeedLot(ctx, owner, lot.ID); err != nil {
t.Errorf("the lot was destroyed anyway: %v", err)
}
// Deleting the lot deliberately then frees the plant.
if err := s.DeleteSeedLot(ctx, owner, lot.ID); err != nil {
t.Fatalf("DeleteSeedLot: %v", err)
}
if err := s.DeletePlant(ctx, owner, plant.ID); err != nil {
t.Errorf("DeletePlant after clearing lots: %v", err)
}
}