626 lines
21 KiB
Go
626 lines
21 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
|
|
"wucher/internal/domain/base"
|
|
filemanager "wucher/internal/domain/file_manager"
|
|
"wucher/internal/domain/flight"
|
|
flightinspection "wucher/internal/domain/flight_inspection"
|
|
helicopterdomain "wucher/internal/domain/helicopter"
|
|
helicopterfile "wucher/internal/domain/helicopter_file"
|
|
reserveac "wucher/internal/domain/reserve_ac"
|
|
takeoverdomain "wucher/internal/domain/takeover"
|
|
mysqlrepo "wucher/internal/repository/mysql"
|
|
"wucher/internal/service"
|
|
"wucher/internal/shared/pkg/userctx"
|
|
"wucher/internal/shared/pkg/uuidv7"
|
|
responsedto "wucher/internal/transport/http/dto/response"
|
|
shareddto "wucher/internal/transport/http/dto/shared"
|
|
)
|
|
|
|
type takeoverTestUserNameGetter struct {
|
|
names map[string]string
|
|
}
|
|
|
|
func (g takeoverTestUserNameGetter) GetLastName(_ context.Context, userID []byte) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (g takeoverTestUserNameGetter) GetShortName(_ context.Context, userID []byte) (string, error) {
|
|
if len(userID) == 0 {
|
|
return "", nil
|
|
}
|
|
if name, ok := g.names[idString(userID)]; ok {
|
|
return name, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_MapsReserveAcRelations(t *testing.T) {
|
|
h := &TakeoverHandler{}
|
|
takeoverID := uuidv7.MustBytes()
|
|
baseID := uuidv7.MustBytes()
|
|
flightID := uuidv7.MustBytes()
|
|
reserveID := uuidv7.MustBytes()
|
|
inspectionID := uuidv7.MustBytes()
|
|
helicopterID := uuidv7.MustBytes()
|
|
notes := " take-over notes "
|
|
inspectionSummary := &shareddto.ReserveAcInspectionSummary{ID: idString(inspectionID), InspectionDate: "2026-04-20"}
|
|
dutyResource := responsedto.DutyRosterResource{
|
|
Attributes: responsedto.DutyRosterAttributes{
|
|
ShiftTime: responsedto.DutyRosterShiftTime{ShiftStart: "06:00", ShiftEnd: "14:00"},
|
|
DutyDate: "2026-04-20",
|
|
Detail: responsedto.DutyRosterDetailsResponse{
|
|
Pilot: []responsedto.DutyRosterPilotCrew{{ID: idString(uuidv7.MustBytes()), Role: "pilot"}},
|
|
},
|
|
},
|
|
}
|
|
takeoverRow := &takeoverdomain.TakeoverAc{ID: takeoverID, BaseID: baseID, Notes: ¬es}
|
|
createdBy := uuidv7.MustBytes()
|
|
updatedBy := uuidv7.MustBytes()
|
|
takeoverRow.CreatedBy = createdBy
|
|
takeoverRow.UpdatedBy = updatedBy
|
|
takeoverRow.CreatedAt = time.Date(2026, 3, 12, 3, 4, 5, 0, time.UTC)
|
|
takeoverRow.UpdatedAt = time.Date(2026, 7, 6, 8, 12, 18, 0, time.UTC)
|
|
flightRow := &flight.Flight{ID: flightID}
|
|
reserveRow := &reserveac.ReserveAc{
|
|
ID: reserveID,
|
|
InspectionID: inspectionID,
|
|
AircraftID: helicopterID,
|
|
Aircraft: &helicopterdomain.Helicopter{ID: helicopterID, Designation: "H145", Identifier: "D-HAAA", Type: "H145"},
|
|
}
|
|
|
|
attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, flightRow, reserveRow, inspectionSummary, dutyResource)
|
|
|
|
if got, want := attrs.ID, idString(takeoverID); got != want {
|
|
t.Fatalf("unexpected takeover id: got %s want %s", got, want)
|
|
}
|
|
if got, want := attrs.FlightID, idString(flightID); got != want {
|
|
t.Fatalf("unexpected flight id: got %s want %s", got, want)
|
|
}
|
|
if got, want := attrs.ReserveAcID, idString(reserveID); got != want {
|
|
t.Fatalf("unexpected reserve_ac_id: got %s want %s", got, want)
|
|
}
|
|
if got, want := attrs.FlightInspectionID, idString(inspectionID); got != want {
|
|
t.Fatalf("unexpected flight_inspection_id: got %s want %s", got, want)
|
|
}
|
|
if got, want := attrs.HelicopterID, idString(helicopterID); got != want {
|
|
t.Fatalf("unexpected helicopter_id: got %s want %s", got, want)
|
|
}
|
|
if attrs.Notes == nil || *attrs.Notes != notes {
|
|
t.Fatalf("unexpected notes: %#v", attrs.Notes)
|
|
}
|
|
if attrs.Inspection.ID != idString(inspectionID) {
|
|
t.Fatalf("unexpected inspection summary: %#v", attrs.Inspection)
|
|
}
|
|
if attrs.Base == nil || attrs.Base.ID != idString(baseID) {
|
|
t.Fatalf("unexpected base: %#v", attrs.Base)
|
|
}
|
|
if attrs.Base.ShiftTime.ShiftStart != "06:00" || attrs.Base.ShiftTime.ShiftEnd != "14:00" {
|
|
t.Fatalf("unexpected base shift time: %#v", attrs.Base.ShiftTime)
|
|
}
|
|
if attrs.CreatedBy != uuidStringOrEmpty(createdBy) {
|
|
t.Fatalf("unexpected created_by: %q", attrs.CreatedBy)
|
|
}
|
|
if attrs.UpdatedBy != uuidStringOrEmpty(updatedBy) {
|
|
t.Fatalf("unexpected updated_by: %q", attrs.UpdatedBy)
|
|
}
|
|
if attrs.CreatedAt != takeoverRow.CreatedAt.UTC().Format(time.RFC3339) {
|
|
t.Fatalf("unexpected created_at: %q", attrs.CreatedAt)
|
|
}
|
|
if attrs.UpdatedAt != takeoverRow.UpdatedAt.UTC().Format(time.RFC3339) {
|
|
t.Fatalf("unexpected updated_at: %q", attrs.UpdatedAt)
|
|
}
|
|
}
|
|
|
|
func TestFlightTakeoverStep_UsesUpdatedByShortName(t *testing.T) {
|
|
prev := userctx.GlobalUserNameGetter
|
|
updatedBy := uuidv7.MustBytes()
|
|
createdBy := uuidv7.MustBytes()
|
|
userctx.WireUserNameGetter(takeoverTestUserNameGetter{
|
|
names: map[string]string{
|
|
idString(updatedBy): "JON",
|
|
idString(createdBy): "GAN",
|
|
},
|
|
})
|
|
t.Cleanup(func() {
|
|
userctx.WireUserNameGetter(prev)
|
|
})
|
|
|
|
pilotID := uuidv7.MustBytes()
|
|
takeoverID := uuidv7.MustBytes()
|
|
baseID := uuidv7.MustBytes()
|
|
reserveID := uuidv7.MustBytes()
|
|
inspectionID := uuidv7.MustBytes()
|
|
helicopterID := uuidv7.MustBytes()
|
|
row := &flight.Flight{
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverAcID: takeoverID,
|
|
CreatedAt: time.Date(2026, 6, 18, 10, 0, 0, 0, time.UTC),
|
|
}
|
|
takeoverRow := &takeoverdomain.TakeoverAc{
|
|
ID: takeoverID,
|
|
BaseID: baseID,
|
|
CreatedBy: createdBy,
|
|
UpdatedBy: updatedBy,
|
|
ReserveAc: &reserveac.ReserveAc{
|
|
ID: reserveID,
|
|
AircraftID: helicopterID,
|
|
InspectionID: inspectionID,
|
|
Aircraft: &helicopterdomain.Helicopter{ID: helicopterID, Designation: "H145", Identifier: "D-HAAA", Type: "H145"},
|
|
Inspection: &flightinspection.FlightInspection{ID: inspectionID},
|
|
},
|
|
RosterCrews: []takeoverdomain.TakeoverRosterCrew{
|
|
{
|
|
RoleCode: "pilot",
|
|
UserID: pilotID,
|
|
NameLabel: "Pilot Full Name",
|
|
},
|
|
},
|
|
}
|
|
|
|
step := flightTakeoverStep(context.Background(), row, &takeoverServiceMock{
|
|
getByIDFn: func(context.Context, []byte) (*takeoverdomain.TakeoverAc, error) {
|
|
return takeoverRow, nil
|
|
},
|
|
})
|
|
|
|
if step.CompletedBy != "JON" {
|
|
t.Fatalf("expected updated_by short name, got %q", step.CompletedBy)
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_MapsFiles(t *testing.T) {
|
|
h := &TakeoverHandler{}
|
|
takeoverID := uuidv7.MustBytes()
|
|
attachmentID := uuidv7.MustBytes()
|
|
fileID := uuidv7.MustBytes()
|
|
takeoverRow := &takeoverdomain.TakeoverAc{
|
|
ID: takeoverID,
|
|
Files: []takeoverdomain.TakeoverFile{
|
|
{
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverID: takeoverID,
|
|
FileAttachmentID: attachmentID,
|
|
FileAttachment: &filemanager.Attachment{
|
|
ID: attachmentID,
|
|
FileID: fileID,
|
|
File: &filemanager.File{
|
|
ID: fileID,
|
|
Name: "takeover-report.pdf",
|
|
ObjectKey: "files/2026/06/23/takeover-report.pdf",
|
|
Bucket: "bucket",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})
|
|
|
|
if len(attrs.Files) != 1 {
|
|
t.Fatalf("expected 1 takeover file, got %d", len(attrs.Files))
|
|
}
|
|
if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
|
|
t.Fatalf("unexpected attachment id: %#v", attrs.Files[0])
|
|
}
|
|
if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
|
|
t.Fatalf("unexpected file name: %#v", attrs.Files[0])
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_HidesTemplateDerivedFiles(t *testing.T) {
|
|
h := &TakeoverHandler{}
|
|
takeoverID := uuidv7.MustBytes()
|
|
regularAttID := uuidv7.MustBytes()
|
|
regularFileID := uuidv7.MustBytes()
|
|
templateAttID := uuidv7.MustBytes()
|
|
templateFileID := uuidv7.MustBytes()
|
|
takeoverRow := &takeoverdomain.TakeoverAc{
|
|
ID: takeoverID,
|
|
Files: []takeoverdomain.TakeoverFile{
|
|
{ // regular attached file — must stay visible
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverID: takeoverID,
|
|
FileAttachmentID: regularAttID,
|
|
FileAttachment: &filemanager.Attachment{
|
|
ID: regularAttID,
|
|
FileID: regularFileID,
|
|
File: &filemanager.File{
|
|
ID: regularFileID,
|
|
Name: "evidence.pdf",
|
|
ObjectKey: "files/evidence.pdf",
|
|
Bucket: "bucket",
|
|
},
|
|
},
|
|
},
|
|
{ // created from a template — must be hidden (kept in DB)
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverID: takeoverID,
|
|
FileAttachmentID: templateAttID,
|
|
FileAttachment: &filemanager.Attachment{
|
|
ID: templateAttID,
|
|
FileID: templateFileID,
|
|
File: &filemanager.File{
|
|
ID: templateFileID,
|
|
Name: "from-template.pdf",
|
|
ObjectKey: "files/from-template.pdf",
|
|
Bucket: "bucket",
|
|
TemplateUUID: uuidv7.MustBytes(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})
|
|
|
|
if len(attrs.Files) != 1 {
|
|
t.Fatalf("expected only the regular file to be visible, got %d: %#v", len(attrs.Files), attrs.Files)
|
|
}
|
|
if attrs.Files[0].Attributes.FileName != "evidence.pdf" {
|
|
t.Fatalf("expected evidence.pdf visible, got %#v", attrs.Files[0])
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_ChecklistBuilderHidesTemplateDerivedFiles(t *testing.T) {
|
|
h := &TakeoverHandler{}
|
|
|
|
visibleID := uuidv7.MustBytes()
|
|
derivedID := uuidv7.MustBytes()
|
|
files := []helicopterfile.HelicopterFile{
|
|
{ID: visibleID},
|
|
{
|
|
ID: derivedID,
|
|
SourceFile: &filemanager.File{
|
|
TemplateUUID: uuidv7.MustBytes(),
|
|
},
|
|
},
|
|
}
|
|
|
|
out := h.buildChecklistSectionSummary(&fiber.Ctx{}, files, map[string]bool{idString(visibleID): true, idString(derivedID): true})
|
|
if out.Total != 1 || out.Completed != 1 || len(out.FileChecklist) != 1 || len(out.DoneFiles) != 1 {
|
|
t.Fatalf("unexpected filtered checklist summary: %#v", out)
|
|
}
|
|
if out.FileChecklist[0].HelicopterFileID != idString(visibleID) {
|
|
t.Fatalf("expected only visible file in checklist, got %#v", out.FileChecklist[0])
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_LoadsFilesFromDBFallback(t *testing.T) {
|
|
db := openTakeoverHandlerTestDB(t)
|
|
if err := db.AutoMigrate(&filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{}, &takeoverdomain.TakeoverAc{}, &takeoverdomain.TakeoverFile{}); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
|
|
rootFolderID := uuidv7.MustBytes()
|
|
if err := db.Create(&filemanager.Folder{
|
|
ID: rootFolderID,
|
|
Name: "takeovers",
|
|
NameNormalized: "takeovers",
|
|
Depth: 0,
|
|
PathCache: "/takeovers",
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create folder: %v", err)
|
|
}
|
|
|
|
takeoverID := uuidv7.MustBytes()
|
|
fileID := uuidv7.MustBytes()
|
|
attachmentID := uuidv7.MustBytes()
|
|
if err := db.Create(&takeoverdomain.TakeoverAc{ID: takeoverID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}).Error; err != nil {
|
|
t.Fatalf("create takeover: %v", err)
|
|
}
|
|
if err := db.Create(&filemanager.File{
|
|
ID: fileID,
|
|
FolderID: rootFolderID,
|
|
Name: "takeover-report.pdf",
|
|
NameNormalized: "takeover-report-pdf",
|
|
SizeBytes: 1,
|
|
MimeType: "application/pdf",
|
|
Bucket: "bucket",
|
|
ObjectKey: "files/2026/06/23/takeover-report.pdf",
|
|
ChecksumSHA256: strings.Repeat("a", 64),
|
|
Status: filemanager.FileStatusReady,
|
|
LifecycleStatus: filemanager.FileLifecycleStatusActive,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create file: %v", err)
|
|
}
|
|
if err := db.Create(&filemanager.Attachment{
|
|
ID: attachmentID,
|
|
FileID: fileID,
|
|
RefType: "takeover",
|
|
RefID: idString(takeoverID),
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create attachment: %v", err)
|
|
}
|
|
if err := db.Create(&takeoverdomain.TakeoverFile{
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverID: takeoverID,
|
|
FileAttachmentID: attachmentID,
|
|
}).Error; err != nil {
|
|
t.Fatalf("create takeover file: %v", err)
|
|
}
|
|
|
|
h := &TakeoverHandler{db: db}
|
|
attrs := h.buildTakeoverResponseAttributes(nil, &takeoverdomain.TakeoverAc{ID: takeoverID}, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})
|
|
|
|
if len(attrs.Files) != 1 {
|
|
t.Fatalf("expected 1 takeover file from db fallback, got %d", len(attrs.Files))
|
|
}
|
|
if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
|
|
t.Fatalf("unexpected file attachment id: %#v", attrs.Files[0])
|
|
}
|
|
if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
|
|
t.Fatalf("unexpected file name from db fallback: %#v", attrs.Files[0])
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_LoadsFilesFromAttachmentFallback(t *testing.T) {
|
|
db := openTakeoverHandlerTestDB(t)
|
|
if err := db.AutoMigrate(&filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{}, &takeoverdomain.TakeoverAc{}); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
|
|
rootFolderID := uuidv7.MustBytes()
|
|
if err := db.Create(&filemanager.Folder{
|
|
ID: rootFolderID,
|
|
Name: "takeovers",
|
|
NameNormalized: "takeovers",
|
|
Depth: 0,
|
|
PathCache: "/takeovers",
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create folder: %v", err)
|
|
}
|
|
|
|
takeoverID := uuidv7.MustBytes()
|
|
fileID := uuidv7.MustBytes()
|
|
attachmentID := uuidv7.MustBytes()
|
|
if err := db.Create(&takeoverdomain.TakeoverAc{ID: takeoverID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}).Error; err != nil {
|
|
t.Fatalf("create takeover: %v", err)
|
|
}
|
|
if err := db.Create(&filemanager.File{
|
|
ID: fileID,
|
|
FolderID: rootFolderID,
|
|
Name: "takeover-report.pdf",
|
|
NameNormalized: "takeover-report-pdf",
|
|
SizeBytes: 1,
|
|
MimeType: "application/pdf",
|
|
Bucket: "bucket",
|
|
ObjectKey: "files/2026/06/23/takeover-report.pdf",
|
|
ChecksumSHA256: strings.Repeat("b", 64),
|
|
Status: filemanager.FileStatusReady,
|
|
LifecycleStatus: filemanager.FileLifecycleStatusActive,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create file: %v", err)
|
|
}
|
|
if err := db.Create(&filemanager.Attachment{
|
|
ID: attachmentID,
|
|
FileID: fileID,
|
|
RefType: "takeover",
|
|
RefID: idString(takeoverID),
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}).Error; err != nil {
|
|
t.Fatalf("create attachment: %v", err)
|
|
}
|
|
|
|
h := &TakeoverHandler{db: db}
|
|
attrs := h.buildTakeoverResponseAttributes(nil, &takeoverdomain.TakeoverAc{ID: takeoverID}, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})
|
|
|
|
if len(attrs.Files) != 1 {
|
|
t.Fatalf("expected 1 takeover file from attachment fallback, got %d", len(attrs.Files))
|
|
}
|
|
if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
|
|
t.Fatalf("unexpected file attachment id: %#v", attrs.Files[0])
|
|
}
|
|
if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
|
|
t.Fatalf("unexpected file name from attachment fallback: %#v", attrs.Files[0])
|
|
}
|
|
}
|
|
|
|
func TestTakeoverEditedTemplateFileBelongsToTakeover(t *testing.T) {
|
|
takeoverA := uuidv7.MustBytes()
|
|
takeoverB := uuidv7.MustBytes()
|
|
|
|
tests := []struct {
|
|
name string
|
|
row *filemanager.File
|
|
want bool
|
|
}{
|
|
{
|
|
name: "allows unattached draft",
|
|
row: &filemanager.File{},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "allows matching takeover draft",
|
|
row: &filemanager.File{TakeoverID: append([]byte(nil), takeoverA...)},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "rejects different takeover draft",
|
|
row: &filemanager.File{TakeoverID: append([]byte(nil), takeoverB...)},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "rejects invalid stored takeover id",
|
|
row: &filemanager.File{TakeoverID: []byte("bad")},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "rejects nil row",
|
|
row: nil,
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := takeoverEditedTemplateFileBelongsToTakeover(tc.row, takeoverA); got != tc.want {
|
|
t.Fatalf("unexpected result: got %v want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_buildTakeoverResponseAttributes_ResolvesShiftTimeFromDB(t *testing.T) {
|
|
db := openTakeoverHandlerTestDB(t)
|
|
now := time.Now().UTC()
|
|
|
|
category := &base.BaseCategory{
|
|
ID: uuidv7.MustBytes(),
|
|
Key: base.CategoryKeyHEMS,
|
|
Name: "HEMS",
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := db.Create(category).Error; err != nil {
|
|
t.Fatalf("create category: %v", err)
|
|
}
|
|
repo := mysqlrepo.NewBaseRepository(db)
|
|
baseID := uuidv7.MustBytes()
|
|
row := &base.Base{
|
|
ID: baseID,
|
|
BaseCategoryID: category.ID,
|
|
BaseName: "Lude",
|
|
BaseAbbreviation: "LOIG",
|
|
DefaultStartTimeType: base.ShiftTimeTypeBMCT,
|
|
DefaultEndTimeType: base.ShiftTimeTypeECET,
|
|
DefaultShiftStart: "06:00:00",
|
|
DefaultShiftEnd: "21:00:00",
|
|
OperationalShiftTimes: []base.BaseOperationalShiftTime{
|
|
{
|
|
DateStart: func() *time.Time { t := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC); return &t }(),
|
|
DateEnd: func() *time.Time { t := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC); return &t }(),
|
|
ShiftStart: "09:00:00",
|
|
ShiftEnd: "18:00:00",
|
|
},
|
|
},
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := repo.CreateBase(context.Background(), row, base.CategoryKeyHEMS); err != nil {
|
|
t.Fatalf("create base: %v", err)
|
|
}
|
|
|
|
h := &TakeoverHandler{
|
|
db: db,
|
|
base: service.NewBaseService(repo),
|
|
}
|
|
flightRow := &flight.Flight{
|
|
ID: uuidv7.MustBytes(),
|
|
Date: time.Date(2026, 6, 12, 0, 0, 0, 0, time.UTC),
|
|
}
|
|
takeoverRow := &takeoverdomain.TakeoverAc{
|
|
ID: uuidv7.MustBytes(),
|
|
BaseID: baseID,
|
|
Base: &base.Base{
|
|
ID: baseID,
|
|
BaseName: "Lude",
|
|
BaseAbbreviation: "LOIG",
|
|
},
|
|
}
|
|
dutyResource := responsedto.DutyRosterResource{
|
|
Attributes: responsedto.DutyRosterAttributes{
|
|
DutyDate: "2026-06-12",
|
|
Detail: responsedto.DutyRosterDetailsResponse{},
|
|
},
|
|
}
|
|
|
|
attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, flightRow, nil, nil, dutyResource)
|
|
resolved := h.resolveTakeoverShiftTime(nil, baseID, flightRow.Date)
|
|
if resolved.ShiftStart == "" || resolved.ShiftEnd == "" {
|
|
t.Fatalf("expected resolved shift time from takeover handler")
|
|
}
|
|
wantStart, wantEnd, err := service.ResolveBaseShiftRangeTx(db, baseID, flightRow.Date)
|
|
if err != nil {
|
|
t.Fatalf("resolve shift window: %v", err)
|
|
}
|
|
|
|
if attrs.Base == nil {
|
|
t.Fatalf("expected base in takeover response")
|
|
}
|
|
if attrs.Base.ShiftTime.ShiftStart != wantStart || attrs.Base.ShiftTime.ShiftEnd != wantEnd {
|
|
t.Fatalf("unexpected base shift time: got=%#v want=%s %s", attrs.Base.ShiftTime, wantStart, wantEnd)
|
|
}
|
|
if attrs.ShiftStart != wantStart || attrs.ShiftEnd != wantEnd {
|
|
t.Fatalf("unexpected top-level shift time: %#v %#v", attrs.ShiftStart, attrs.ShiftEnd)
|
|
}
|
|
}
|
|
|
|
func TestBuildDutyRosterResourceFromTakeoverRows_PreservesCalendarDutyDate(t *testing.T) {
|
|
jakarta := time.FixedZone("UTC+7", 7*60*60)
|
|
flightRow := &flight.Flight{
|
|
ID: uuidv7.MustBytes(),
|
|
TakeoverAcID: uuidv7.MustBytes(),
|
|
Date: time.Date(2026, time.June, 1, 0, 0, 0, 0, jakarta),
|
|
}
|
|
|
|
resource := buildDutyRosterResourceFromTakeoverRows(
|
|
responsedto.DutyRosterResource{
|
|
Attributes: responsedto.DutyRosterAttributes{
|
|
Detail: responsedto.DutyRosterDetailsResponse{},
|
|
},
|
|
},
|
|
nil,
|
|
flightRow,
|
|
)
|
|
|
|
if got, want := resource.Attributes.DutyDate, "2026-06-01"; got != want {
|
|
t.Fatalf("unexpected duty date: got %s want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestCalendarDateString_PreservesCalendarDate(t *testing.T) {
|
|
jakarta := time.FixedZone("UTC+7", 7*60*60)
|
|
value := time.Date(2026, time.June, 1, 0, 0, 0, 0, jakarta)
|
|
|
|
if got, want := calendarDateString(value), "2026-06-01"; got != want {
|
|
t.Fatalf("unexpected calendar date string: got %s want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTakeoverHandler_resolveTakeoverReserveRow_PrefersPreloadedReserveAc(t *testing.T) {
|
|
h := &TakeoverHandler{}
|
|
expected := &reserveac.ReserveAc{ID: uuidv7.MustBytes(), InspectionID: uuidv7.MustBytes()}
|
|
takeoverRow := &takeoverdomain.TakeoverAc{ReserveAc: expected}
|
|
flightRow := &flight.Flight{ReserveAcID: uuidv7.MustBytes()}
|
|
|
|
got := h.resolveTakeoverReserveRow(nil, takeoverRow, flightRow)
|
|
if got != expected {
|
|
t.Fatalf("expected preloaded reserve_ac to win, got %#v", got)
|
|
}
|
|
}
|
|
|
|
func openTakeoverHandlerTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
dsn := fmt.Sprintf("file:takeover_handler_test_%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(&base.BaseCategory{}, &base.Base{}, &base.BaseOperationalShiftTime{}, &base.BaseContactRole{}); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
return db
|
|
}
|