package service import ( "context" "errors" "testing" "wucher/internal/domain/helicopter" ) type helicopterRepoMock struct { createCalled bool updateCalled bool deleteCalled bool getByIDCalled bool listCalled bool nextReportNumberCalled bool isInUseCalled bool inUseByIDsCalled bool existsByIdentifier bool existsCalled bool lastCreate *helicopter.Helicopter lastUpdate *helicopter.Helicopter lastDeleteID []byte lastGetByID []byte lastListFilter string lastListStatuses []string lastListSort string lastListLimit int lastListOffset int lastNextReportID []byte lastIsInUseID []byte lastInUseIDs [][]byte getByIDResult *helicopter.Helicopter listResult []helicopter.Helicopter listTotal int64 nextReportNumberResult string nextReportCounter int isInUseResult bool inUseByIDsResult map[string]bool existsByIdentifierID string existsByIdentifierSkip []byte createErr error updateErr error deleteErr error getByIDErr error listErr error nextReportNumberErr error isInUseErr error inUseByIDsErr error existsErr error } func (m *helicopterRepoMock) Create(_ context.Context, row *helicopter.Helicopter) error { m.createCalled = true m.lastCreate = row return m.createErr } func (m *helicopterRepoMock) Update(_ context.Context, row *helicopter.Helicopter) error { m.updateCalled = true m.lastUpdate = row return m.updateErr } func (m *helicopterRepoMock) Delete(_ context.Context, id []byte) error { m.deleteCalled = true m.lastDeleteID = id return m.deleteErr } func (m *helicopterRepoMock) GetByID(_ context.Context, id []byte) (*helicopter.Helicopter, error) { m.getByIDCalled = true m.lastGetByID = id return m.getByIDResult, m.getByIDErr } func (m *helicopterRepoMock) List(_ context.Context, filter string, statuses []string, sort string, limit, offset int, _ [][]byte) ([]helicopter.Helicopter, int64, error) { m.listCalled = true m.lastListFilter = filter m.lastListStatuses = statuses m.lastListSort = sort m.lastListLimit = limit m.lastListOffset = offset return m.listResult, m.listTotal, m.listErr } func (m *helicopterRepoMock) NextReportNumber(_ context.Context, id []byte) (string, int, error) { m.nextReportNumberCalled = true m.lastNextReportID = id return m.nextReportNumberResult, m.nextReportCounter, m.nextReportNumberErr } func (m *helicopterRepoMock) IsInUse(_ context.Context, id []byte) (bool, error) { m.isInUseCalled = true m.lastIsInUseID = append([]byte(nil), id...) return m.isInUseResult, m.isInUseErr } func (m *helicopterRepoMock) InUseByAircraftIDs(_ context.Context, ids [][]byte) (map[string]bool, error) { m.inUseByIDsCalled = true m.lastInUseIDs = m.lastInUseIDs[:0] for i := range ids { m.lastInUseIDs = append(m.lastInUseIDs, append([]byte(nil), ids[i]...)) } if m.inUseByIDsResult == nil { return map[string]bool{}, m.inUseByIDsErr } return m.inUseByIDsResult, m.inUseByIDsErr } func (m *helicopterRepoMock) ActiveFlightIDByAircraftIDs(_ context.Context, _ [][]byte) (map[string][]byte, error) { return map[string][]byte{}, nil } func (m *helicopterRepoMock) ExistsByIdentifier(_ context.Context, identifier string, excludeID []byte) (bool, error) { m.existsCalled = true m.existsByIdentifierID = identifier m.existsByIdentifierSkip = append([]byte(nil), excludeID...) return m.existsByIdentifier, m.existsErr } func TestNewHelicopterService(t *testing.T) { repo := &helicopterRepoMock{} svc := NewHelicopterService(repo) if svc == nil || svc.repo == nil { t.Fatalf("expected service and repo initialized") } } func TestHelicopterServiceCreate(t *testing.T) { repo := &helicopterRepoMock{} svc := NewHelicopterService(repo) row := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"} if err := svc.Create(context.Background(), row); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.createCalled || repo.lastCreate != row { t.Fatalf("expected Create forwarded to repo") } repo.createErr = errors.New("create failed") if err := svc.Create(context.Background(), row); err == nil { t.Fatalf("expected error from repo") } } func TestHelicopterServiceUpdate(t *testing.T) { repo := &helicopterRepoMock{} svc := NewHelicopterService(repo) row := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"} if err := svc.Update(context.Background(), row); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.updateCalled || repo.lastUpdate != row { t.Fatalf("expected Update forwarded to repo") } repo.updateErr = errors.New("update failed") if err := svc.Update(context.Background(), row); err == nil { t.Fatalf("expected error from repo") } } func TestHelicopterServiceDelete(t *testing.T) { repo := &helicopterRepoMock{} svc := NewHelicopterService(repo) id := []byte("id") if err := svc.Delete(context.Background(), id); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.deleteCalled { t.Fatalf("expected Delete forwarded to repo") } if string(repo.lastDeleteID) != string(id) { t.Fatalf("expected delete id forwarded") } repo.deleteErr = errors.New("delete failed") if err := svc.Delete(context.Background(), id); err == nil { t.Fatalf("expected error from repo") } } func TestHelicopterServiceGetByID(t *testing.T) { expected := &helicopter.Helicopter{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"} repo := &helicopterRepoMock{getByIDResult: expected} svc := NewHelicopterService(repo) id := []byte("id") got, err := svc.GetByID(context.Background(), id) if err != nil { t.Fatalf("unexpected error: %v", err) } if got != expected { t.Fatalf("unexpected result") } if !repo.getByIDCalled || string(repo.lastGetByID) != string(id) { t.Fatalf("expected GetByID forwarded") } repo.getByIDErr = errors.New("get failed") if _, err := svc.GetByID(context.Background(), id); err == nil { t.Fatalf("expected error from repo") } } func TestHelicopterServiceList(t *testing.T) { repo := &helicopterRepoMock{ listResult: []helicopter.Helicopter{{Designation: "H145", Identifier: "PK-ABC", Type: "Twin"}}, listTotal: 1, } svc := NewHelicopterService(repo) rows, total, err := svc.List(context.Background(), "find", []string{"available", "booked"}, "-designation", 10, 20, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(rows) != 1 || total != 1 { t.Fatalf("unexpected list result") } if !repo.listCalled { t.Fatalf("expected List forwarded to repo") } if repo.lastListFilter != "find" || repo.lastListSort != "designation DESC" || repo.lastListLimit != 10 || repo.lastListOffset != 20 { t.Fatalf("unexpected list args forwarded") } if len(repo.lastListStatuses) != 2 || repo.lastListStatuses[0] != "available" || repo.lastListStatuses[1] != "booked" { t.Fatalf("unexpected status filter forwarded: %v", repo.lastListStatuses) } repo.listErr = errors.New("list failed") if _, _, err := svc.List(context.Background(), "", nil, "unknown", 1, 0, nil); err == nil { t.Fatalf("expected error from repo") } if repo.lastListSort != "" { t.Fatalf("expected unknown sort normalized to empty") } } func TestHelicopterServiceExistsByIdentifier(t *testing.T) { repo := &helicopterRepoMock{existsByIdentifier: true} svc := NewHelicopterService(repo) excludeID := []byte("id") exists, err := svc.ExistsByIdentifier(context.Background(), "SN-0001", excludeID) if err != nil { t.Fatalf("unexpected error: %v", err) } if !exists { t.Fatalf("expected duplicate exists result") } if !repo.existsCalled || repo.existsByIdentifierID != "SN-0001" || string(repo.existsByIdentifierSkip) != string(excludeID) { t.Fatalf("expected ExistsByIdentifier forwarded") } repo.existsErr = errors.New("exists failed") if _, err := svc.ExistsByIdentifier(context.Background(), "SN-0001", nil); err == nil { t.Fatalf("expected error from repo") } } func TestHelicopterServiceNextReportNumber(t *testing.T) { repo := &helicopterRepoMock{ nextReportNumberResult: "RPT-0001", nextReportCounter: 1, } svc := NewHelicopterService(repo) id := []byte("id") number, counter, err := svc.NextReportNumber(context.Background(), id) if err != nil { t.Fatalf("unexpected error: %v", err) } if number != "RPT-0001" || counter != 1 { t.Fatalf("unexpected next report result") } if !repo.nextReportNumberCalled || string(repo.lastNextReportID) != string(id) { t.Fatalf("expected NextReportNumber forwarded") } repo.nextReportNumberErr = errors.New("next failed") if _, _, err := svc.NextReportNumber(context.Background(), id); err == nil { t.Fatalf("expected error from repo") } } func TestNormalizeHelicopterSort(t *testing.T) { cases := map[string]string{ "designation": "designation ASC", "-designation": "designation DESC", "sortkey": "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END ASC, designation ASC", "-sortkey": "is_active DESC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN is_active = 1 AND sortkey IS NOT NULL AND sortkey >= 0 THEN sortkey END DESC, designation ASC", "identifier": "", "-identifier": "", "type": "", "-type": "", "counter": "", "-counter": "", "created_at": "", "-created_at": "", "updated_at": "", "-updated_at": "", "unknown": "", "": "", } for in, want := range cases { if got := normalizeHelicopterSort(in); got != want { t.Fatalf("normalizeHelicopterSort(%q) = %q, want %q", in, got, want) } } }