package mysql import ( "context" "fmt" "testing" "time" "gorm.io/driver/sqlite" "gorm.io/gorm" "wucher/internal/domain/complaint" "wucher/internal/shared/pkg/uuidv7" ) // TestOpenByHelicopterIDsKeepsLatestFlightFixed verifies the fleet rule: every OPEN defect // plus FIXED defects from the helicopter's latest flight; fixed defects from older flights // drop off. func TestOpenByHelicopterIDsKeepsLatestFlightFixed(t *testing.T) { dsn := fmt.Sprintf("file:complaint_fleet_%d?mode=memory&cache=shared", time.Now().UnixNano()) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{NowFunc: func() time.Time { return time.Now().UTC() }}) if err != nil { t.Fatalf("open: %v", err) } if err := db.AutoMigrate(&complaint.Complaint{}); err != nil { t.Fatalf("migrate: %v", err) } ctx := context.Background() heli := uuidv7.MustBytes() flightOld := uuidv7.MustBytes() flightNew := uuidv7.MustBytes() reporter := uuidv7.MustBytes() now := time.Now().UTC() fixedAt := now seed := func(desc string, flightID []byte, reportedAt time.Time, fixed bool) []byte { id := uuidv7.MustBytes() row := &complaint.Complaint{ ID: id, HelicopterID: heli, FlightID: flightID, Description: desc, ReportedBy: reporter, ReportedAt: reportedAt, } if fixed { row.FixedAt = &fixedAt } if err := db.Create(row).Error; err != nil { t.Fatalf("seed %s: %v", desc, err) } return id } // Older flight: one fixed (must drop off), one open (must stay). oldFixed := seed("old-fixed", flightOld, now.Add(-2*time.Hour), true) oldOpen := seed("old-open", flightOld, now.Add(-2*time.Hour), false) // Latest flight: one fixed (must stay), one open (must stay). newFixed := seed("new-fixed", flightNew, now.Add(-1*time.Minute), true) newOpen := seed("new-open", flightNew, now, false) // NOTE: mattn-sqlite can't scan the model's datetime(3) columns back into time.Time // (MySQL handles datetime(3) fine), so exercise the exact fleet predicate selecting ids // only. This validates the WHERE that OpenByHelicopterIDs uses. _ = ctx latestFlightSub := "flight_id = (SELECT c2.flight_id FROM complaints c2 WHERE c2.helicopter_id = complaints.helicopter_id AND c2.deleted_at IS NULL AND c2.flight_id IS NOT NULL ORDER BY c2.reported_at DESC, c2.id DESC LIMIT 1)" type idRow struct { ID []byte `gorm:"column:id"` } var idRows []idRow if err := db.Model(&complaint.Complaint{}). Select("id"). Where("deleted_at IS NULL AND helicopter_id = ?", heli). Where("fixed_at IS NULL OR " + latestFlightSub). Scan(&idRows).Error; err != nil { t.Fatalf("predicate query: %v", err) } got := map[string]bool{} for i := range idRows { got[string(idRows[i].ID)] = true } rows := idRows if !got[string(oldOpen)] { t.Errorf("old-open should be visible (open always shows)") } if !got[string(newOpen)] { t.Errorf("new-open should be visible") } if !got[string(newFixed)] { t.Errorf("new-fixed should be visible (fixed from latest flight)") } if got[string(oldFixed)] { t.Errorf("old-fixed should be HIDDEN (fixed from an older flight)") } if len(rows) != 3 { t.Fatalf("expected 3 visible complaints, got %d", len(rows)) } }