676 lines
26 KiB
Go
676 lines
26 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
afterflightinspection "wucher/internal/domain/after_flight_inspection"
|
|
"wucher/internal/domain/flight"
|
|
flightdata "wucher/internal/domain/flight_data"
|
|
flightinspection "wucher/internal/domain/flight_inspection"
|
|
"wucher/internal/domain/mission"
|
|
reserveac "wucher/internal/domain/reserve_ac"
|
|
takeoverdomain "wucher/internal/domain/takeover"
|
|
"wucher/internal/shared/pkg/uuidv7"
|
|
)
|
|
|
|
type flightServiceMock struct {
|
|
createFn func(context.Context, *flight.Flight) error
|
|
getByIDFn func(context.Context, []byte) (*flight.Flight, error)
|
|
listByUserIDFn func(context.Context, []byte, int, int) ([]flight.Flight, int64, error)
|
|
listFn func(context.Context, string, string, string, int, int) ([]flight.Flight, int64, error)
|
|
}
|
|
|
|
type missionServiceMock struct {
|
|
listByFlightIDFn func(context.Context, []byte) ([]mission.Mission, error)
|
|
listByFlightIDsFn func(context.Context, [][]byte) ([]mission.Mission, error)
|
|
listFn func(context.Context, string, string, []byte, int, int) ([]mission.Mission, int64, error)
|
|
listDTFn func(context.Context, mission.ListFilter, int, int) ([]mission.Mission, int64, int64, error)
|
|
deleteByIDFn func(context.Context, []byte, []byte) error
|
|
}
|
|
|
|
type afterFlightInspectionServiceMock struct {
|
|
getByFlightInspectionIDFn func(context.Context, []byte) (*afterflightinspection.AfterFlightInspection, error)
|
|
}
|
|
|
|
type takeoverServiceMock struct {
|
|
getByIDFn func(context.Context, []byte) (*takeoverdomain.TakeoverAc, error)
|
|
}
|
|
|
|
func (m *flightServiceMock) Create(ctx context.Context, row *flight.Flight) error {
|
|
if m.createFn != nil {
|
|
return m.createFn(ctx, row)
|
|
}
|
|
return nil
|
|
}
|
|
func (m *flightServiceMock) Update(context.Context, *flight.Flight) error { return nil }
|
|
func (m *flightServiceMock) Delete(context.Context, []byte, []byte) error { return nil }
|
|
func (m *flightServiceMock) GetByID(ctx context.Context, id []byte) (*flight.Flight, error) {
|
|
if m.getByIDFn != nil {
|
|
return m.getByIDFn(ctx, id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) ListByUserID(ctx context.Context, userID []byte, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if m.listByUserIDFn != nil {
|
|
return m.listByUserIDFn(ctx, userID, limit, offset)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
func (m *flightServiceMock) ListByCreatedBy(context.Context, []byte, string, string, string, int, int) ([]flight.Flight, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
func (m *flightServiceMock) GetByReserveAcID(context.Context, []byte) (*flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) ListByReserveAcID(context.Context, []byte) ([]flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) GetByTakeoverAcID(context.Context, []byte) (*flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) ListByTakeoverAcIDs(context.Context, [][]byte) ([]flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) GetByDutyRosterID(context.Context, []byte) (*flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) ListByDutyRosterIDs(context.Context, [][]byte) ([]flight.Flight, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *flightServiceMock) List(ctx context.Context, filter string, date string, sort string, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if m.listFn != nil {
|
|
return m.listFn(ctx, filter, date, sort, limit, offset)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (m *missionServiceMock) Create(context.Context, *mission.Mission) error { return nil }
|
|
func (m *missionServiceMock) CreateType(context.Context, *mission.MissionCategory) error {
|
|
return nil
|
|
}
|
|
func (m *missionServiceMock) UpdateByID(context.Context, []byte, string, []byte, string, []byte) error {
|
|
return nil
|
|
}
|
|
func (m *missionServiceMock) DeleteByFlightID(context.Context, []byte, []byte) error { return nil }
|
|
func (m *missionServiceMock) AttachFile(context.Context, []byte, []byte) error { return nil }
|
|
func (m *missionServiceMock) DetachFile(context.Context, []byte, []byte) error { return nil }
|
|
func (m *missionServiceMock) DeleteByID(ctx context.Context, missionID []byte, deletedBy []byte) error {
|
|
if m.deleteByIDFn != nil {
|
|
return m.deleteByIDFn(ctx, missionID, deletedBy)
|
|
}
|
|
return nil
|
|
}
|
|
func (m *missionServiceMock) ListByFlightID(ctx context.Context, flightID []byte) ([]mission.Mission, error) {
|
|
if m.listByFlightIDFn != nil {
|
|
return m.listByFlightIDFn(ctx, flightID)
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *missionServiceMock) ListByFlightIDs(ctx context.Context, flightIDs [][]byte) ([]mission.Mission, error) {
|
|
if m.listByFlightIDsFn != nil {
|
|
return m.listByFlightIDsFn(ctx, flightIDs)
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *missionServiceMock) GetByID(context.Context, []byte) (*mission.Mission, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *missionServiceMock) GetByFlightID(context.Context, []byte) (*mission.Mission, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *missionServiceMock) List(ctx context.Context, filter string, sort string, flightID []byte, flightDataStatus string, limit, offset int) ([]mission.Mission, int64, error) {
|
|
if m.listFn != nil {
|
|
return m.listFn(ctx, filter, sort, flightID, limit, offset)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
func (m *missionServiceMock) ListDatatable(ctx context.Context, filter mission.ListFilter, limit, offset int) ([]mission.Mission, int64, int64, error) {
|
|
if m.listDTFn != nil {
|
|
return m.listDTFn(ctx, filter, limit, offset)
|
|
}
|
|
return nil, 0, 0, nil
|
|
}
|
|
func (m *missionServiceMock) ListTypes(context.Context) ([]mission.MissionCategory, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *afterFlightInspectionServiceMock) Upsert(context.Context, []byte, *afterflightinspection.UpsertRequest) (*afterflightinspection.AfterFlightInspection, error) {
|
|
return nil, nil
|
|
}
|
|
func (m *afterFlightInspectionServiceMock) GetByFlightInspectionID(ctx context.Context, id []byte) (*afterflightinspection.AfterFlightInspection, error) {
|
|
if m.getByFlightInspectionIDFn != nil {
|
|
return m.getByFlightInspectionIDFn(ctx, id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *afterFlightInspectionServiceMock) DeleteByFlightInspectionID(context.Context, []byte) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *takeoverServiceMock) Create(context.Context, *takeoverdomain.TakeoverAc) error { return nil }
|
|
func (m *takeoverServiceMock) Update(context.Context, *takeoverdomain.TakeoverAc) error { return nil }
|
|
func (m *takeoverServiceMock) Delete(context.Context, []byte, []byte) error { return nil }
|
|
func (m *takeoverServiceMock) AttachFile(context.Context, []byte, []byte) error { return nil }
|
|
func (m *takeoverServiceMock) GetByID(ctx context.Context, id []byte) (*takeoverdomain.TakeoverAc, error) {
|
|
if m.getByIDFn != nil {
|
|
return m.getByIDFn(ctx, id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
func (m *takeoverServiceMock) List(context.Context, int, int) ([]takeoverdomain.TakeoverAc, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func newFlightApp(h *FlightHandler, withActor bool) *fiber.App {
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
if withActor {
|
|
c.Locals("user_id", uuidv7.MustBytes())
|
|
}
|
|
return c.Next()
|
|
})
|
|
app.Post("/flights", h.Create)
|
|
app.Get("/flights/me", h.GetByAuthUser)
|
|
app.Get("/flights", h.List)
|
|
return app
|
|
}
|
|
|
|
func doReqFlight(t *testing.T, app *fiber.App, method, path string, body []byte) (int, string) {
|
|
t.Helper()
|
|
var rdr io.Reader
|
|
if body != nil {
|
|
rdr = bytes.NewReader(body)
|
|
}
|
|
req, err := http.NewRequest(method, path, rdr)
|
|
if err != nil {
|
|
t.Fatalf("new req: %v", err)
|
|
}
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
resp, err := app.Test(req)
|
|
if err != nil {
|
|
t.Fatalf("app.Test: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func TestFlightHandler(t *testing.T) {
|
|
t.Run("new and with reserve ac", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
if h == nil || h.validate == nil {
|
|
t.Fatal("expected handler initialized")
|
|
}
|
|
if h.WithReserveAcService(nil) != h {
|
|
t.Fatal("expected same pointer")
|
|
}
|
|
})
|
|
|
|
t.Run("create invalid json", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", []byte(`{"data":`))
|
|
if code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("create validation", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", []byte(`{}`))
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("expected 422 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("create date invalid", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
body := []byte(`{"data":{"type":"flight","attributes":{"date":"x"}}}`)
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", body)
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("expected 422 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("create optional uuid invalid", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
body := []byte(`{"data":{"type":"flight","attributes":{"date":"2026-04-14","duty_roster_id":"bad"}}}`)
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", body)
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("expected 422 got %d", code)
|
|
}
|
|
body = []byte(`{"data":{"type":"flight","attributes":{"date":"2026-04-14","reserve_ac_id":"bad"}}}`)
|
|
code, _ = doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", body)
|
|
if code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("expected 422 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("create service error", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{
|
|
createFn: func(context.Context, *flight.Flight) error { return errors.New("boom") },
|
|
})
|
|
body := []byte(`{"data":{"type":"flight","attributes":{"date":"2026-04-14"}}}`)
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", body)
|
|
if code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("create success with refetch and fallback", func(t *testing.T) {
|
|
var createdID []byte
|
|
h := NewFlightHandler(&flightServiceMock{
|
|
createFn: func(_ context.Context, row *flight.Flight) error {
|
|
row.ID = uuidv7.MustBytes()
|
|
createdID = append([]byte(nil), row.ID...)
|
|
return nil
|
|
},
|
|
getByIDFn: func(_ context.Context, id []byte) (*flight.Flight, error) {
|
|
if string(id) != string(createdID) {
|
|
t.Fatal("unexpected id")
|
|
}
|
|
return &flight.Flight{
|
|
ID: id,
|
|
Date: time.Date(2026, 4, 14, 0, 0, 0, 0, time.UTC),
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}, nil
|
|
},
|
|
})
|
|
body := []byte(`{"data":{"type":"flight","attributes":{"date":"2026-04-14","duty_roster_id":"` + mustUUIDStringHospital(t, uuidv7.MustBytes()) + `","reserve_ac_id":"` + mustUUIDStringHospital(t, uuidv7.MustBytes()) + `"}}}`)
|
|
code, _ := doReqFlight(t, newFlightApp(h, true), http.MethodPost, "/flights", body)
|
|
if code != http.StatusCreated {
|
|
t.Fatalf("expected 201 got %d", code)
|
|
}
|
|
|
|
h2 := NewFlightHandler(&flightServiceMock{
|
|
createFn: func(_ context.Context, row *flight.Flight) error {
|
|
row.ID = uuidv7.MustBytes()
|
|
row.CreatedAt = time.Now().UTC()
|
|
row.UpdatedAt = row.CreatedAt
|
|
return nil
|
|
},
|
|
getByIDFn: func(context.Context, []byte) (*flight.Flight, error) { return nil, errors.New("x") },
|
|
})
|
|
code, _ = doReqFlight(t, newFlightApp(h2, true), http.MethodPost, "/flights", []byte(`{"data":{"type":"flight","attributes":{"date":"2026-04-14"}}}`))
|
|
if code != http.StatusCreated {
|
|
t.Fatalf("expected 201 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("get by auth user branches", func(t *testing.T) {
|
|
h := NewFlightHandler(&flightServiceMock{})
|
|
code, _ := doReqFlight(t, newFlightApp(h, false), http.MethodGet, "/flights/me", nil)
|
|
if code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 got %d", code)
|
|
}
|
|
|
|
hErr := NewFlightHandler(&flightServiceMock{
|
|
listByUserIDFn: func(context.Context, []byte, int, int) ([]flight.Flight, int64, error) {
|
|
return nil, 0, errors.New("err")
|
|
},
|
|
})
|
|
code, _ = doReqFlight(t, newFlightApp(hErr, true), http.MethodGet, "/flights/me?page[number]=0&page[size]=500", nil)
|
|
if code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 got %d", code)
|
|
}
|
|
|
|
row := flight.Flight{
|
|
ID: uuidv7.MustBytes(),
|
|
Date: time.Date(2026, 4, 14, 0, 0, 0, 0, time.UTC),
|
|
DutyRosterID: uuidv7.MustBytes(),
|
|
ReserveAcID: uuidv7.MustBytes(),
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
hOK := NewFlightHandler(&flightServiceMock{
|
|
listByUserIDFn: func(_ context.Context, _ []byte, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if limit != 100 || offset != 0 {
|
|
t.Fatalf("unexpected paging %d %d", limit, offset)
|
|
}
|
|
return []flight.Flight{row}, 1, nil
|
|
},
|
|
})
|
|
code, body := doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights/me?page[number]=0&page[size]=500", nil)
|
|
if code != http.StatusOK || !strings.Contains(body, `"flight"`) {
|
|
t.Fatalf("expected 200 flight body, got %d %s", code, body)
|
|
}
|
|
|
|
hPg := NewFlightHandler(&flightServiceMock{
|
|
listByUserIDFn: func(_ context.Context, _ []byte, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if limit <= 0 || offset != 0 {
|
|
t.Fatalf("unexpected paging")
|
|
}
|
|
return nil, 0, nil
|
|
},
|
|
})
|
|
code, _ = doReqFlight(t, newFlightApp(hPg, true), http.MethodGet, "/flights/me?page=1&limit=5", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hPg, true), http.MethodGet, "/flights/me?page=1&size=7", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hPg, true), http.MethodGet, "/flights/me?page=-1&page[size]=-1", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hPg, true), http.MethodGet, "/flights/me?page=1&page[size]=-1&limit=-1&size=-1", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("list branches", func(t *testing.T) {
|
|
hErr := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(context.Context, string, string, string, int, int) ([]flight.Flight, int64, error) {
|
|
return nil, 0, errors.New("no")
|
|
},
|
|
})
|
|
code, _ := doReqFlight(t, newFlightApp(hErr, true), http.MethodGet, "/flights?search=x", nil)
|
|
if code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 got %d", code)
|
|
}
|
|
|
|
hOK := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(_ context.Context, filter, date, sort string, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if filter != "abc" || sort != "-date" || limit != 0 || offset != 0 {
|
|
t.Fatalf("unexpected args")
|
|
}
|
|
return []flight.Flight{{ID: uuidv7.MustBytes(), Date: time.Now().UTC(), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}}, 1, nil
|
|
},
|
|
}).WithMissionService(&missionServiceMock{
|
|
listByFlightIDFn: func(_ context.Context, _ []byte) ([]mission.Mission, error) {
|
|
return []mission.Mission{{ID: uuidv7.MustBytes(), Type: "CAT", CreatedAt: time.Now().UTC()}}, nil
|
|
},
|
|
})
|
|
code, body := doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?filter[search]=abc&sort=-date&page[number]=0&page[size]=0", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
if !strings.Contains(body, `"mission_id"`) {
|
|
t.Fatalf("expected mission_id in response, got %s", body)
|
|
}
|
|
|
|
hProgress := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(_ context.Context, filter, date, sort string, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if filter != "" || date != "" || sort != "" || limit != 0 || offset != 0 {
|
|
t.Fatalf("unexpected args")
|
|
}
|
|
return []flight.Flight{{
|
|
ID: uuidv7.MustBytes(),
|
|
Date: time.Now().UTC(),
|
|
TakeoverAcID: uuidv7.MustBytes(),
|
|
Takeover: &takeoverdomain.TakeoverAc{
|
|
ReserveAcID: uuidv7.MustBytes(),
|
|
ReserveAc: &reserveac.ReserveAc{Status: "onprogress"},
|
|
},
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}}, 1, nil
|
|
},
|
|
})
|
|
code, body = doReqFlight(t, newFlightApp(hProgress, true), http.MethodGet, "/flights", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
if !strings.Contains(body, `"status":"in_progress"`) {
|
|
t.Fatalf("expected on_progress status, got %s", body)
|
|
}
|
|
if !strings.Contains(body, `"complete":false`) {
|
|
t.Fatalf("expected complete=false in response, got %s", body)
|
|
}
|
|
|
|
inspectionID := uuidv7.MustBytes()
|
|
afterID := uuidv7.MustBytes()
|
|
hAfter := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(_ context.Context, filter, date, sort string, limit, offset int) ([]flight.Flight, int64, error) {
|
|
if filter != "" || date != "" || sort != "" || limit != 0 || offset != 0 {
|
|
t.Fatalf("unexpected args")
|
|
}
|
|
return []flight.Flight{{
|
|
ID: uuidv7.MustBytes(),
|
|
Date: time.Now().UTC(),
|
|
TakeoverAcID: uuidv7.MustBytes(),
|
|
Takeover: &takeoverdomain.TakeoverAc{
|
|
ReserveAcID: uuidv7.MustBytes(),
|
|
ReserveAc: &reserveac.ReserveAc{
|
|
Inspection: &flightinspection.FlightInspection{ID: inspectionID},
|
|
},
|
|
},
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}}, 1, nil
|
|
},
|
|
}).WithMissionService(&missionServiceMock{
|
|
listByFlightIDFn: func(_ context.Context, _ []byte) ([]mission.Mission, error) {
|
|
return []mission.Mission{{ID: uuidv7.MustBytes(), Type: "CAT", CreatedAt: time.Now().UTC()}}, nil
|
|
},
|
|
}).WithAfterFlightInspectionService(&afterFlightInspectionServiceMock{
|
|
getByFlightInspectionIDFn: func(_ context.Context, id []byte) (*afterflightinspection.AfterFlightInspection, error) {
|
|
if string(id) != string(inspectionID) {
|
|
t.Fatalf("unexpected inspection id")
|
|
}
|
|
now := time.Now().UTC()
|
|
return &afterflightinspection.AfterFlightInspection{
|
|
ID: afterID,
|
|
FlightInspectionID: inspectionID,
|
|
CreatedAt: now,
|
|
CreatedBy: uuidv7.MustBytes(),
|
|
UpdatedAt: now,
|
|
UpdatedBy: uuidv7.MustBytes(),
|
|
}, nil
|
|
},
|
|
}).WithTakeoverService(&takeoverServiceMock{
|
|
getByIDFn: func(_ context.Context, id []byte) (*takeoverdomain.TakeoverAc, error) {
|
|
if len(id) != 16 {
|
|
t.Fatalf("unexpected takeover id")
|
|
}
|
|
return &takeoverdomain.TakeoverAc{
|
|
ID: id,
|
|
BaseID: uuidv7.MustBytes(),
|
|
ReserveAc: &reserveac.ReserveAc{
|
|
AircraftID: uuidv7.MustBytes(),
|
|
Inspection: &flightinspection.FlightInspection{ID: inspectionID},
|
|
},
|
|
RosterCrews: []takeoverdomain.TakeoverRosterCrew{{ID: uuidv7.MustBytes()}},
|
|
OtherPeople: []takeoverdomain.TakeoverOtherPerson{{ID: uuidv7.MustBytes()}},
|
|
}, nil
|
|
},
|
|
})
|
|
code, body = doReqFlight(t, newFlightApp(hAfter, true), http.MethodGet, "/flights", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
if !strings.Contains(body, `"phase":"after_flight_inspection"`) {
|
|
t.Fatalf("expected after_flight_inspection step, got %s", body)
|
|
}
|
|
if !strings.Contains(body, `"step":3`) {
|
|
t.Fatalf("expected step 3 in response, got %s", body)
|
|
}
|
|
if !strings.Contains(body, `"complete":true`) {
|
|
t.Fatalf("expected complete=true in response, got %s", body)
|
|
}
|
|
|
|
code, _ = doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?page=1&limit=5&search=abc&sort=-date", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?page=1&size=7&search=abc&sort=-date", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?page=-1&page[size]=-1&search=abc&sort=-date", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?page=1&page[size]=-1&limit=-1&size=-1&search=abc&sort=-date", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
code, _ = doReqFlight(t, newFlightApp(hOK, true), http.MethodGet, "/flights?page=1&page[size]=200&search=abc&sort=-date", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("expected 200 got %d", code)
|
|
}
|
|
})
|
|
|
|
t.Run("helpers", func(t *testing.T) {
|
|
if stringPtrOrEmpty(nil) != "" {
|
|
t.Fatal("expected empty")
|
|
}
|
|
v := " x "
|
|
if stringPtrOrEmpty(&v) != "x" {
|
|
t.Fatal("expected trimmed")
|
|
}
|
|
if timeStringOrEmptyRefTime(nil) != "" || timeStringOrEmptyRefTime(&time.Time{}) != "" {
|
|
t.Fatal("expected empty")
|
|
}
|
|
now := time.Now().UTC()
|
|
if timeStringOrEmptyRefTime(&now) == "" {
|
|
t.Fatal("expected time")
|
|
}
|
|
raw := "bad"
|
|
if _, errObj := parseOptionalUUIDFlight(&raw, "/data/attributes/duty_roster_id"); errObj == nil {
|
|
t.Fatal("expected err")
|
|
}
|
|
raw = " "
|
|
if id, errObj := parseOptionalUUIDFlight(&raw, "/data/attributes/duty_roster_id"); errObj != nil || id != nil {
|
|
t.Fatal("expected nil")
|
|
}
|
|
raw = mustUUIDStringHospital(t, uuidv7.MustBytes())
|
|
if id, errObj := parseOptionalUUIDFlight(&raw, "/data/attributes/duty_roster_id"); errObj != nil || len(id) != 16 {
|
|
t.Fatal("expected id")
|
|
}
|
|
if s := flightMissionStep(nil); s.Status != "unassigned" {
|
|
t.Fatal("expected unassigned")
|
|
}
|
|
if s := flightDutyRosterStep(nil); s.Status != "unassigned" {
|
|
t.Fatal("expected unassigned")
|
|
}
|
|
if s := flightReserveAcStep(nil); s.Status != "unassigned" {
|
|
t.Fatal("expected unassigned")
|
|
}
|
|
if s := flightReserveAcStep(&flight.Flight{Takeover: &takeoverdomain.TakeoverAc{ReserveAcID: uuidv7.MustBytes(), ReserveAc: &reserveac.ReserveAc{Status: "pending"}}}); s.Status != "pending" {
|
|
t.Fatal("expected pending")
|
|
}
|
|
if s := flightReserveAcStep(&flight.Flight{Takeover: &takeoverdomain.TakeoverAc{ReserveAcID: uuidv7.MustBytes(), ReserveAc: &reserveac.ReserveAc{Status: "onprogress"}}}); s.Status != "in_progress" {
|
|
t.Fatal("expected on progress")
|
|
}
|
|
if s := flightReserveAcStep(&flight.Flight{Takeover: &takeoverdomain.TakeoverAc{ReserveAcID: uuidv7.MustBytes(), ReserveAc: &reserveac.ReserveAc{Status: "approved", UpdatedAt: time.Now().UTC(), UpdatedBy: uuidv7.MustBytes()}}}); s.Status != "completed" {
|
|
t.Fatal("expected completed")
|
|
}
|
|
if s := flightMissionStep(&flight.Flight{}); s.Status != "unassigned" {
|
|
t.Fatal("expected mission unassigned")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFlightHandlerList_MissionStepShowsInProgressUntilFlightDataComplete(t *testing.T) {
|
|
flightID := uuidv7.MustBytes()
|
|
missionID := uuidv7.MustBytes()
|
|
|
|
h := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(context.Context, string, string, string, int, int) ([]flight.Flight, int64, error) {
|
|
return []flight.Flight{{
|
|
ID: flightID,
|
|
Date: time.Date(2026, time.June, 26, 0, 0, 0, 0, time.UTC),
|
|
CreatedAt: time.Date(2026, time.June, 24, 4, 31, 37, 0, time.UTC),
|
|
CreatedBy: []byte("creator-id-12345"),
|
|
}}, 1, nil
|
|
},
|
|
}).WithMissionService(&missionServiceMock{
|
|
listByFlightIDFn: func(context.Context, []byte) ([]mission.Mission, error) {
|
|
return []mission.Mission{{
|
|
ID: missionID,
|
|
FlightID: flightID,
|
|
Type: "CAT",
|
|
CreatedAt: time.Date(2026, time.June, 24, 5, 23, 3, 0, time.UTC),
|
|
UpdatedAt: time.Date(2026, time.June, 24, 5, 23, 3, 0, time.UTC),
|
|
}}, nil
|
|
},
|
|
}).WithFlightDataService(&flightDataServiceMock{
|
|
getByMisFn: func(context.Context, []byte) (*flightdata.FlightData, error) {
|
|
return &flightdata.FlightData{Status: flightdata.StatusInProgress}, nil
|
|
},
|
|
})
|
|
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_id", uuidv7.MustBytes())
|
|
return c.Next()
|
|
})
|
|
app.Get("/flights", h.List)
|
|
|
|
code, body := doReqFlight(t, app, http.MethodGet, "/flights?date=2026-06-26&limit=2000", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("unexpected status: %d body=%s", code, body)
|
|
}
|
|
if !strings.Contains(body, `"phase":"mission"`) {
|
|
t.Fatalf("expected mission step in body: %s", body)
|
|
}
|
|
if !strings.Contains(body, `"status":"in_progress"`) {
|
|
t.Fatalf("expected mission step in_progress: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestFlightHandlerList_MissionStepShowsCompletedWhenFlightDataComplete(t *testing.T) {
|
|
flightID := uuidv7.MustBytes()
|
|
missionID := uuidv7.MustBytes()
|
|
|
|
h := NewFlightHandler(&flightServiceMock{
|
|
listFn: func(context.Context, string, string, string, int, int) ([]flight.Flight, int64, error) {
|
|
return []flight.Flight{{
|
|
ID: flightID,
|
|
Date: time.Date(2026, time.June, 26, 0, 0, 0, 0, time.UTC),
|
|
CreatedAt: time.Date(2026, time.June, 24, 4, 31, 37, 0, time.UTC),
|
|
CreatedBy: []byte("creator-id-12345"),
|
|
}}, 1, nil
|
|
},
|
|
}).WithMissionService(&missionServiceMock{
|
|
listByFlightIDFn: func(context.Context, []byte) ([]mission.Mission, error) {
|
|
return []mission.Mission{{
|
|
ID: missionID,
|
|
FlightID: flightID,
|
|
Type: "CAT",
|
|
CreatedAt: time.Date(2026, time.June, 24, 5, 23, 3, 0, time.UTC),
|
|
UpdatedAt: time.Date(2026, time.June, 24, 6, 41, 45, 0, time.UTC),
|
|
UpdatedBy: []byte("test test"),
|
|
}}, nil
|
|
},
|
|
}).WithFlightDataService(&flightDataServiceMock{
|
|
getByMisFn: func(context.Context, []byte) (*flightdata.FlightData, error) {
|
|
return &flightdata.FlightData{Status: flightdata.StatusCompleted}, nil
|
|
},
|
|
})
|
|
|
|
app := fiber.New()
|
|
app.Use(func(c *fiber.Ctx) error {
|
|
c.Locals("user_id", uuidv7.MustBytes())
|
|
return c.Next()
|
|
})
|
|
app.Get("/flights", h.List)
|
|
|
|
code, body := doReqFlight(t, app, http.MethodGet, "/flights?date=2026-06-26&limit=2000", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("unexpected status: %d body=%s", code, body)
|
|
}
|
|
if !strings.Contains(body, `"phase":"mission"`) {
|
|
t.Fatalf("expected mission step in body: %s", body)
|
|
}
|
|
if !strings.Contains(body, `"status":"completed"`) {
|
|
t.Fatalf("expected mission step completed: %s", body)
|
|
}
|
|
}
|