init push
This commit is contained in:
468
internal/service/flight_data_service_test.go
Normal file
468
internal/service/flight_data_service_test.go
Normal file
@@ -0,0 +1,468 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
afterflightinspection "wucher/internal/domain/after_flight_inspection"
|
||||
flightdomain "wucher/internal/domain/flight"
|
||||
flightdata "wucher/internal/domain/flight_data"
|
||||
fmreport "wucher/internal/domain/fm_report"
|
||||
missiondomain "wucher/internal/domain/mission"
|
||||
reserveac "wucher/internal/domain/reserve_ac"
|
||||
takeoverdomain "wucher/internal/domain/takeover"
|
||||
sharedconst "wucher/internal/shared/const"
|
||||
"wucher/internal/shared/pkg/uuidv7"
|
||||
)
|
||||
|
||||
type flightDataRepoStub struct {
|
||||
createFn func(context.Context, *flightdata.FlightData) error
|
||||
updateFn func(context.Context, *flightdata.FlightData) error
|
||||
getByIDFn func(context.Context, []byte) (*flightdata.FlightData, error)
|
||||
getByMissionIDFn func(context.Context, []byte) (*flightdata.FlightData, error)
|
||||
getSPODetailsByFlightID func(context.Context, []byte) (*flightdata.SPODetails, error)
|
||||
}
|
||||
|
||||
type missionResolverStub struct {
|
||||
flightID []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (m missionResolverStub) FlightIDByMissionID(context.Context, []byte) ([]byte, error) {
|
||||
return append([]byte(nil), m.flightID...), m.err
|
||||
}
|
||||
|
||||
type fmReportLookupStub struct {
|
||||
report *fmreport.Report
|
||||
err error
|
||||
}
|
||||
|
||||
type flightLookupStub struct {
|
||||
flight *flightdomain.Flight
|
||||
err error
|
||||
}
|
||||
|
||||
type afterFlightInspectionLookupStub struct {
|
||||
row *afterflightinspection.AfterFlightInspection
|
||||
err error
|
||||
}
|
||||
|
||||
func (s fmReportLookupStub) GetByFlightID(context.Context, []byte) (*fmreport.Report, error) {
|
||||
return s.report, s.err
|
||||
}
|
||||
|
||||
func (s flightLookupStub) GetByID(context.Context, []byte) (*flightdomain.Flight, error) {
|
||||
return s.flight, s.err
|
||||
}
|
||||
|
||||
func (s afterFlightInspectionLookupStub) GetByFlightInspectionID(context.Context, []byte) (*afterflightinspection.AfterFlightInspection, error) {
|
||||
return s.row, s.err
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) Create(ctx context.Context, row *flightdata.FlightData) error {
|
||||
if r.createFn != nil {
|
||||
return r.createFn(ctx, row)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) Update(ctx context.Context, row *flightdata.FlightData) error {
|
||||
if r.updateFn != nil {
|
||||
return r.updateFn(ctx, row)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) Delete(context.Context, []byte, []byte) error { return nil }
|
||||
|
||||
func (r *flightDataRepoStub) GetByID(ctx context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
if r.getByIDFn != nil {
|
||||
return r.getByIDFn(ctx, id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) GetByFlightID(context.Context, []byte) (*flightdata.FlightData, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) GetByMissionID(ctx context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
if r.getByMissionIDFn != nil {
|
||||
return r.getByMissionIDFn(ctx, id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) ListByMissionID(context.Context, []byte) ([]flightdata.FlightData, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) List(context.Context, string, []byte, []byte, int, int) ([]flightdata.FlightData, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) GetSPODetailsByFlightDataID(ctx context.Context, id []byte) (*flightdata.SPODetails, error) {
|
||||
if r.getSPODetailsByFlightID != nil {
|
||||
return r.getSPODetailsByFlightID(ctx, id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *flightDataRepoStub) UpsertSPODetails(context.Context, []byte, *flightdata.SPOUpsertData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFlightDataServiceCreateSyncsCompleteStatus(t *testing.T) {
|
||||
var updates []*flightdata.FlightData
|
||||
rowID := []byte("019ecc1111111111")
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(_ context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
loaded := &flightdata.FlightData{
|
||||
ID: id,
|
||||
MissionID: []byte("1234567890123456"),
|
||||
CoPilotID: []byte("2345678901234567"),
|
||||
FromICAOID: []byte("3456789012345678"),
|
||||
ToHospitalID: []byte("4567890123456789"),
|
||||
Mission: &missiondomain.Mission{Type: sharedconst.MissionTypeCAT},
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
Status: flightdata.StatusInProgress,
|
||||
}
|
||||
if got := flightdata.DeriveStatus(loaded, nil); got != flightdata.StatusCompleted {
|
||||
t.Fatalf("expected derived status complete, got %q", got)
|
||||
}
|
||||
return loaded, nil
|
||||
},
|
||||
updateFn: func(_ context.Context, row *flightdata.FlightData) error {
|
||||
updates = append(updates, row)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewFlightDataService(repo)
|
||||
if err := svc.syncStatus(context.Background(), rowID); err != nil {
|
||||
t.Fatalf("syncStatus returned error: %v", err)
|
||||
}
|
||||
if len(updates) != 1 {
|
||||
t.Fatalf("expected one status sync update, got %d", len(updates))
|
||||
}
|
||||
if updates[0].Status != flightdata.StatusCompleted {
|
||||
t.Fatalf("expected sync update to complete status, got %q", updates[0].Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceCreateKeepsSPOIncompleteStatus(t *testing.T) {
|
||||
rowID := []byte("019ecc1122222222")
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(_ context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
loaded := &flightdata.FlightData{
|
||||
ID: id,
|
||||
MissionID: []byte("1234567890123456"),
|
||||
CoPilotID: []byte("2345678901234567"),
|
||||
FromICAOID: []byte("3456789012345678"),
|
||||
ToHospitalID: []byte("4567890123456789"),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
Mission: &missiondomain.Mission{Type: sharedconst.MissionTypeSPO},
|
||||
Status: flightdata.StatusInProgress,
|
||||
}
|
||||
if got := flightdata.DeriveStatus(loaded, nil); got != flightdata.StatusInProgress {
|
||||
t.Fatalf("expected derived status on_progress, got %q", got)
|
||||
}
|
||||
return loaded, nil
|
||||
},
|
||||
getSPODetailsByFlightID: func(context.Context, []byte) (*flightdata.SPODetails, error) {
|
||||
return &flightdata.SPODetails{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewFlightDataService(repo)
|
||||
if err := svc.syncStatus(context.Background(), rowID); err != nil {
|
||||
t.Fatalf("syncStatus returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceCreateSyncsCompleteStatusWithoutRotorBrakeCycleForCATAndNCO(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
typ string
|
||||
}{
|
||||
{name: "cat", typ: sharedconst.MissionTypeCAT},
|
||||
{name: "nco", typ: sharedconst.MissionTypeNCO},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var updates []*flightdata.FlightData
|
||||
rowID := []byte("019ecc1144444444")
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(_ context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
loaded := &flightdata.FlightData{
|
||||
ID: id,
|
||||
MissionID: []byte("1234567890123456"),
|
||||
CoPilotID: []byte("2345678901234567"),
|
||||
FromICAOID: []byte("3456789012345678"),
|
||||
ToHospitalID: []byte("4567890123456789"),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
Mission: &missiondomain.Mission{Type: tc.typ},
|
||||
Status: flightdata.StatusInProgress,
|
||||
}
|
||||
if got := flightdata.DeriveStatus(loaded, nil); got != flightdata.StatusCompleted {
|
||||
t.Fatalf("expected derived status complete, got %q", got)
|
||||
}
|
||||
return loaded, nil
|
||||
},
|
||||
updateFn: func(_ context.Context, row *flightdata.FlightData) error {
|
||||
updates = append(updates, row)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewFlightDataService(repo)
|
||||
if err := svc.syncStatus(context.Background(), rowID); err != nil {
|
||||
t.Fatalf("syncStatus returned error: %v", err)
|
||||
}
|
||||
if len(updates) != 1 {
|
||||
t.Fatalf("expected one status sync update, got %d", len(updates))
|
||||
}
|
||||
if updates[0].Status != flightdata.StatusCompleted {
|
||||
t.Fatalf("expected sync update to complete status, got %q", updates[0].Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceSyncStatusFallsBackToMissionLookup(t *testing.T) {
|
||||
var updates []*flightdata.FlightData
|
||||
rowID := []byte("019ecc1133333333")
|
||||
missionID := []byte("1234567890123456")
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(_ context.Context, id []byte) (*flightdata.FlightData, error) {
|
||||
return &flightdata.FlightData{
|
||||
ID: id,
|
||||
MissionID: missionID,
|
||||
CoPilotID: []byte("2345678901234567"),
|
||||
FromHospitalID: []byte("3456789012345678"),
|
||||
ToHospitalID: []byte("4567890123456789"),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
Status: flightdata.StatusInProgress,
|
||||
}, nil
|
||||
},
|
||||
getByMissionIDFn: func(_ context.Context, _ []byte) (*flightdata.FlightData, error) {
|
||||
return &flightdata.FlightData{
|
||||
ID: rowID,
|
||||
MissionID: missionID,
|
||||
CoPilotID: []byte("2345678901234567"),
|
||||
FromHospitalID: []byte("3456789012345678"),
|
||||
ToHospitalID: []byte("4567890123456789"),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
Mission: &missiondomain.Mission{Type: sharedconst.MissionTypeCAT},
|
||||
Status: flightdata.StatusInProgress,
|
||||
}, nil
|
||||
},
|
||||
updateFn: func(_ context.Context, row *flightdata.FlightData) error {
|
||||
updates = append(updates, row)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
svc := NewFlightDataService(repo)
|
||||
if err := svc.syncStatus(context.Background(), rowID); err != nil {
|
||||
t.Fatalf("syncStatus returned error: %v", err)
|
||||
}
|
||||
if len(updates) != 1 {
|
||||
t.Fatalf("expected one status sync update, got %d", len(updates))
|
||||
}
|
||||
if updates[0].Status != flightdata.StatusCompleted {
|
||||
t.Fatalf("expected sync update to complete status, got %q", updates[0].Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceCreateBlockedWhenFMReportExists(t *testing.T) {
|
||||
missionID := uuidv7.MustBytes()
|
||||
repo := &flightDataRepoStub{
|
||||
createFn: func(context.Context, *flightdata.FlightData) error {
|
||||
t.Fatal("create should not be called when fm report already exists")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := NewFlightDataService(repo).
|
||||
WithMissionResolver(missionResolverStub{flightID: uuidv7.MustBytes()}).
|
||||
WithFMReportLookup(fmReportLookupStub{report: &fmreport.Report{ID: uuidv7.MustBytes(), FlightID: uuidv7.MustBytes()}})
|
||||
|
||||
err := svc.Create(context.Background(), &flightdata.FlightData{
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
})
|
||||
if !errors.Is(err, flightdata.ErrCreateBlocked) {
|
||||
t.Fatalf("expected create blocked, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceUpdateLockedWhenFMReportCompleted(t *testing.T) {
|
||||
missionID := uuidv7.MustBytes()
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(context.Context, []byte) (*flightdata.FlightData, error) {
|
||||
return &flightdata.FlightData{
|
||||
ID: uuidv7.MustBytes(),
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
}, nil
|
||||
},
|
||||
updateFn: func(context.Context, *flightdata.FlightData) error {
|
||||
t.Fatal("update should not be called when fm report is completed")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
completedAt := time.Now().UTC()
|
||||
svc := NewFlightDataService(repo).
|
||||
WithMissionResolver(missionResolverStub{flightID: uuidv7.MustBytes()}).
|
||||
WithFMReportLookup(fmReportLookupStub{report: &fmreport.Report{ID: uuidv7.MustBytes(), FlightID: uuidv7.MustBytes(), CompletedAt: &completedAt}})
|
||||
|
||||
err := svc.Update(context.Background(), &flightdata.FlightData{
|
||||
ID: uuidv7.MustBytes(),
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
})
|
||||
if !errors.Is(err, flightdata.ErrLocked) {
|
||||
t.Fatalf("expected locked error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceCreateBlockedWhenAfterFlightInspectionExists(t *testing.T) {
|
||||
missionID := uuidv7.MustBytes()
|
||||
inspectionID := uuidv7.MustBytes()
|
||||
repo := &flightDataRepoStub{
|
||||
createFn: func(context.Context, *flightdata.FlightData) error {
|
||||
t.Fatal("create should not be called when after flight inspection exists")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := NewFlightDataService(repo).
|
||||
WithMissionResolver(missionResolverStub{flightID: uuidv7.MustBytes()}).
|
||||
WithFlightLookup(flightLookupStub{
|
||||
flight: &flightdomain.Flight{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Takeover: &takeoverdomain.TakeoverAc{
|
||||
ReserveAc: &reserveac.ReserveAc{InspectionID: inspectionID},
|
||||
},
|
||||
},
|
||||
}).
|
||||
WithAfterFlightInspectionLookup(afterFlightInspectionLookupStub{row: &afterflightinspection.AfterFlightInspection{ID: uuidv7.MustBytes(), FlightInspectionID: inspectionID}})
|
||||
|
||||
err := svc.Create(context.Background(), &flightdata.FlightData{
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
})
|
||||
if !errors.Is(err, flightdata.ErrLockedAfterFlightInspection) {
|
||||
t.Fatalf("expected after-flight inspection lock error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceUpdateAllowedWhenAfterFlightInspectionExists(t *testing.T) {
|
||||
missionID := uuidv7.MustBytes()
|
||||
inspectionID := uuidv7.MustBytes()
|
||||
updated := false
|
||||
repo := &flightDataRepoStub{
|
||||
getByIDFn: func(context.Context, []byte) (*flightdata.FlightData, error) {
|
||||
return &flightdata.FlightData{
|
||||
ID: uuidv7.MustBytes(),
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
}, nil
|
||||
},
|
||||
updateFn: func(context.Context, *flightdata.FlightData) error {
|
||||
updated = true
|
||||
return nil
|
||||
},
|
||||
}
|
||||
svc := NewFlightDataService(repo).
|
||||
WithMissionResolver(missionResolverStub{flightID: uuidv7.MustBytes()}).
|
||||
WithFlightLookup(flightLookupStub{
|
||||
flight: &flightdomain.Flight{
|
||||
ID: uuidv7.MustBytes(),
|
||||
Takeover: &takeoverdomain.TakeoverAc{
|
||||
ReserveAc: &reserveac.ReserveAc{InspectionID: inspectionID},
|
||||
},
|
||||
},
|
||||
}).
|
||||
WithAfterFlightInspectionLookup(afterFlightInspectionLookupStub{row: &afterflightinspection.AfterFlightInspection{ID: uuidv7.MustBytes(), FlightInspectionID: inspectionID}})
|
||||
|
||||
err := svc.Update(context.Background(), &flightdata.FlightData{
|
||||
ID: uuidv7.MustBytes(),
|
||||
MissionID: missionID,
|
||||
CoPilotID: uuidv7.MustBytes(),
|
||||
FromHospitalID: uuidv7.MustBytes(),
|
||||
ToICAOID: uuidv7.MustBytes(),
|
||||
FlightTakeOff: time.Now().UTC(),
|
||||
FlightLanding: time.Now().UTC(),
|
||||
FlightDuration: time.Hour,
|
||||
LandingCount: 1,
|
||||
RotorBrakeCycle: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected update to be allowed, got %v", err)
|
||||
}
|
||||
if !updated {
|
||||
t.Fatal("expected update to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlightDataServiceUpdateRequiresRow(t *testing.T) {
|
||||
svc := NewFlightDataService(&flightDataRepoStub{})
|
||||
if err := svc.Update(context.Background(), nil); !errors.Is(err, flightdata.ErrRequired) {
|
||||
t.Fatalf("expected ErrRequired, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user