Files
fm_be/internal/service/fleet_status_update_test.go
2026-07-16 22:16:45 +07:00

96 lines
4.1 KiB
Go

package service
import (
"context"
"fmt"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
filemanager "wucher/internal/domain/file_manager"
fleetstatus "wucher/internal/domain/fleet_status"
"wucher/internal/domain/helicopter"
helicopterusage "wucher/internal/domain/helicopter_usage"
"wucher/internal/repository/mysql"
)
// TestFleetStatusUpdateAcceptsNegativeSummaryAndBareSchedule guards the full service path
// behind PATCH /fleet-status/update/{id}: negative summary values + an A/F schedule with
// empty type/due/ext must persist (incl. syncAutomaticAOG, which the repo-level test skips).
func TestFleetStatusUpdateAcceptsNegativeSummaryAndBareSchedule(t *testing.T) {
dsn := fmt.Sprintf("file:fleet_update_repro_%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 sqlite: %v", err)
}
if err := db.AutoMigrate(
&filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{},
&helicopter.Helicopter{},
&fleetstatus.FleetStatus{}, &fleetstatus.MaintenanceSchedule{}, &fleetstatus.FleetStatusFile{},
&helicopterusage.HelicopterUsage{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
ctx := context.Background()
heliRepo := mysql.NewHelicopterRepository(db)
heli := &helicopter.Helicopter{Designation: "H1", Identifier: "PK-H1", Type: "Twin", NR1: true, NR2: true}
if err := heliRepo.Create(ctx, heli); err != nil {
t.Fatalf("create heli: %v", err)
}
fsRepo := mysql.NewFleetStatusRepository(db)
fs := &fleetstatus.FleetStatus{HelicopterID: heli.ID, Status: fleetstatus.StatusActive}
if err := fsRepo.Create(ctx, fs); err != nil {
t.Fatalf("create fs: %v", err)
}
usageSvc := NewHelicopterUsageService(mysql.NewHelicopterUsageRepository(db))
svc := NewFleetStatusService(fsRepo, nil, NewHelicopterService(heliRepo), nil).WithUsage(usageSvc)
// Handler builds this row from the patch: full-replace schedules with one A/F entry.
fs.MaintenanceSchedules = []fleetstatus.MaintenanceSchedule{
{FleetStatusID: fs.ID, InspectionType: "A/F", Type: "", Due: "", Ext: ""},
}
fs.UpdatedBy = heli.ID // any actor
if err := svc.Update(ctx, fs); err != nil {
t.Fatalf("REPRO: FleetStatusService.Update FAILED: %v", err)
}
t.Log("FleetStatusService.Update OK (incl syncAutomaticAOG)")
f := func(v float64) *float64 { return &v }
if err := svc.ReviseSummary(ctx, heli.ID, helicopterusage.ManualSummaryInput{
AirframeHours: f(1234), AirframeCycles: f(-1),
Engine1Hours: f(1234), Engine1GpcNgN1: f(23.23), Engine1PtcNfN2: f(23.59), Engine1Ccc: f(-8),
Engine2Hours: f(234), Engine2GpcNgN1: f(123), Engine2PtcNfN2: f(1230), Engine2Ccc: f(123),
Landing: f(1), FlightReport: f(1), HookRelease: f(1), RotorBrakeCycle: f(1),
}, heli.ID); err != nil {
t.Fatalf("REPRO: ReviseSummary FAILED: %v", err)
}
got, _ := usageSvc.GetByHelicopterID(ctx, heli.ID)
t.Logf("ReviseSummary OK: cycles=%v ccc1=%v ccc2=%v", got.TotalAirframeCycles, got.TotalEngine1Ccc, got.TotalEngine2Ccc)
// The fleet GET reads the summary via GetSummaryByHelicopterID — verify it reflects the patch.
summary, err := svc.GetSummaryByHelicopterID(ctx, heli.ID)
if err != nil {
t.Fatalf("GetSummaryByHelicopterID: %v", err)
}
if summary.Airframe.Hours == nil || *summary.Airframe.Hours != 1234 {
t.Fatalf("summary airframe hours = %v, want 1234 (patch not reflected)", summary.Airframe.Hours)
}
if summary.Engine1.Hours == nil || *summary.Engine1.Hours != 1234 {
t.Fatalf("summary engine1 hours = %v, want 1234", summary.Engine1.Hours)
}
// Second PATCH with a NEW value — the "ga terganti" (value doesn't change) scenario.
if err := svc.ReviseSummary(ctx, heli.ID, helicopterusage.ManualSummaryInput{AirframeHours: f(9999)}, heli.ID); err != nil {
t.Fatalf("second ReviseSummary: %v", err)
}
summary2, _ := svc.GetSummaryByHelicopterID(ctx, heli.ID)
if summary2.Airframe.Hours == nil || *summary2.Airframe.Hours != 9999 {
t.Fatalf("second patch: airframe hours = %v, want 9999 (VALUE DID NOT CHANGE)", summary2.Airframe.Hours)
}
t.Logf("both patches reflected in GET summary: first af.hours=1234, second=%v", *summary2.Airframe.Hours)
}