package service import ( "context" "errors" "testing" "wucher/internal/domain/base" ) type baseRepoMock struct { createBaseCalled bool updateBaseCalled bool deleteBaseCalled bool getBaseByIDCalled bool listBasesCalled bool lastCreateBase *base.Base lastCreateCategory string lastUpdateBase *base.Base lastUpdateCategory string lastDeleteBaseID []byte lastDeleteCategory string lastGetBaseByIDID []byte lastGetCategory string lastListFilter string lastListSort string lastListLimit int lastListOffset int lastListCategory string lastListDUL *bool getBaseByIDResult *base.Base listBasesResult []base.Base listBasesTotal int64 createBaseErr error updateBaseErr error deleteBaseErr error getBaseByIDErr error listBasesErr error } func (m *baseRepoMock) CreateBase(_ context.Context, row *base.Base, categoryType string) error { m.createBaseCalled = true m.lastCreateBase = row m.lastCreateCategory = categoryType return m.createBaseErr } func (m *baseRepoMock) UpdateBase(_ context.Context, row *base.Base, categoryType string) error { m.updateBaseCalled = true m.lastUpdateBase = row m.lastUpdateCategory = categoryType return m.updateBaseErr } func (m *baseRepoMock) DeleteBase(_ context.Context, id []byte, categoryType string) error { m.deleteBaseCalled = true m.lastDeleteBaseID = id m.lastDeleteCategory = categoryType return m.deleteBaseErr } func (m *baseRepoMock) GetBaseByID(_ context.Context, id []byte, categoryType string) (*base.Base, error) { m.getBaseByIDCalled = true m.lastGetBaseByIDID = id m.lastGetCategory = categoryType return m.getBaseByIDResult, m.getBaseByIDErr } func (m *baseRepoMock) ListBases(_ context.Context, filter, sort string, limit, offset int, categoryType string, dul *bool) ([]base.Base, int64, error) { m.listBasesCalled = true m.lastListFilter = filter m.lastListSort = sort m.lastListLimit = limit m.lastListOffset = offset m.lastListCategory = categoryType m.lastListDUL = dul return m.listBasesResult, m.listBasesTotal, m.listBasesErr } func TestNewBaseService(t *testing.T) { repo := &baseRepoMock{} svc := NewBaseService(repo) if svc == nil || svc.repo == nil { t.Fatalf("expected service and repo initialized") } } func TestBaseServiceCreateBase(t *testing.T) { repo := &baseRepoMock{} svc := NewBaseService(repo) row := &base.Base{BaseName: "Jakarta", DefaultShiftStart: "06:00:00", DefaultShiftEnd: "21:00:00"} if err := svc.CreateBase(context.Background(), row, "regular"); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.createBaseCalled || repo.lastCreateBase != row || repo.lastCreateCategory != base.CategoryKeyRegular { t.Fatalf("expected CreateBase forwarded with regular category") } if err := svc.CreateBase(context.Background(), row, "hems"); err != nil { t.Fatalf("unexpected hems error: %v", err) } if repo.lastCreateCategory != base.CategoryKeyHEMS { t.Fatalf("expected hems category forwarded") } repo.createBaseErr = errors.New("create base failed") if err := svc.CreateBase(context.Background(), row, "regular"); err == nil { t.Fatalf("expected error from repo") } if err := svc.CreateBase(context.Background(), row, "invalid"); err == nil { t.Fatalf("expected invalid category error") } } func TestBaseServiceUpdateBase(t *testing.T) { repo := &baseRepoMock{} svc := NewBaseService(repo) row := &base.Base{BaseName: "Bandung", DefaultShiftStart: "06:00:00", DefaultShiftEnd: "21:00:00"} if err := svc.UpdateBase(context.Background(), row, "regular"); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.updateBaseCalled || repo.lastUpdateBase != row || repo.lastUpdateCategory != base.CategoryKeyRegular { t.Fatalf("expected UpdateBase forwarded") } repo.updateBaseErr = errors.New("update base failed") if err := svc.UpdateBase(context.Background(), row, "regular"); err == nil { t.Fatalf("expected error from repo") } if err := svc.UpdateBase(context.Background(), row, "x"); err == nil { t.Fatalf("expected invalid category error") } } func TestBaseServiceDeleteBase(t *testing.T) { repo := &baseRepoMock{} svc := NewBaseService(repo) id := []byte("base-id") deleted, err := svc.DeleteBase(context.Background(), id, "hems") if err != nil { t.Fatalf("unexpected error: %v", err) } if deleted != nil { t.Fatalf("expected deleted result to be nil when repo returns nil") } if !repo.deleteBaseCalled || string(repo.lastDeleteBaseID) != string(id) || repo.lastDeleteCategory != base.CategoryKeyHEMS { t.Fatalf("expected DeleteBase forwarded") } repo.deleteBaseErr = errors.New("delete base failed") if _, err := svc.DeleteBase(context.Background(), id, "hems"); err == nil { t.Fatalf("expected error from repo") } if _, err := svc.DeleteBase(context.Background(), id, "unknown"); err == nil { t.Fatalf("expected invalid category error") } } func TestBaseServiceGetBaseByID(t *testing.T) { expected := &base.Base{BaseName: "Surabaya"} repo := &baseRepoMock{getBaseByIDResult: expected} svc := NewBaseService(repo) id := []byte("base-id") got, err := svc.GetBaseByID(context.Background(), id, "hems") if err != nil { t.Fatalf("unexpected error: %v", err) } if got != expected { t.Fatalf("unexpected result") } if !repo.getBaseByIDCalled || string(repo.lastGetBaseByIDID) != string(id) || repo.lastGetCategory != base.CategoryKeyHEMS { t.Fatalf("expected GetBaseByID forwarded") } repo.getBaseByIDErr = errors.New("get base failed") if _, err := svc.GetBaseByID(context.Background(), id, "regular"); err == nil { t.Fatalf("expected error from repo") } if _, err := svc.GetBaseByID(context.Background(), id, "invalid"); err == nil { t.Fatalf("expected invalid category error") } } func TestBaseServiceListBases(t *testing.T) { repo := &baseRepoMock{ listBasesResult: []base.Base{{BaseName: "Bali"}}, listBasesTotal: 1, } svc := NewBaseService(repo) rows, total, err := svc.ListBases(context.Background(), "find", "-base", 10, 20, "regular", nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(rows) != 1 || total != 1 { t.Fatalf("unexpected list result") } if !repo.listBasesCalled { t.Fatalf("expected ListBases forwarded to repo") } if repo.lastListFilter != "find" || repo.lastListSort != "base DESC" || repo.lastListLimit != 10 || repo.lastListOffset != 20 || repo.lastListCategory != base.CategoryKeyRegular { t.Fatalf("unexpected list args forwarded") } rows, total, err = svc.ListBases(context.Background(), "find", "sortkey", 10, 20, "regular", nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(rows) != 1 || total != 1 { t.Fatalf("unexpected list result") } if repo.lastListSort != "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, base ASC" { t.Fatalf("unexpected sortkey args forwarded: %q", repo.lastListSort) } repo.listBasesErr = errors.New("list bases failed") if _, _, err := svc.ListBases(context.Background(), "", "unknown", 1, 0, "hems", nil); err == nil { t.Fatalf("expected error from repo") } if repo.lastListSort != "" { t.Fatalf("expected unknown sort normalized to empty") } if repo.lastListCategory != base.CategoryKeyHEMS { t.Fatalf("expected hems category forwarded") } if _, _, err := svc.ListBases(context.Background(), "", "base", 1, 0, "invalid", nil); err == nil { t.Fatalf("expected invalid category error") } repo.listBasesErr = nil if _, _, err := svc.ListBases(context.Background(), "", "base", 1, 0, "", nil); err != nil { t.Fatalf("expected empty category to list all, got err=%v", err) } if repo.lastListCategory != "" { t.Fatalf("expected empty category forwarded for unfiltered list, got %q", repo.lastListCategory) } } func TestNormalizeBaseSort(t *testing.T) { cases := map[string]string{ "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, base 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, base ASC", "base": "base ASC", "-base": "base DESC", "base_abbreviation": "base_abbreviation ASC", "-base_abbreviation": "base_abbreviation DESC", "note": "notes ASC", "-note": "notes DESC", "notes": "notes ASC", "-notes": "notes DESC", "is_active": "is_active ASC", "-is_active": "is_active DESC", "created_at": "created_at ASC", "-created_at": "created_at DESC", "updated_at": "updated_at ASC", "-updated_at": "updated_at DESC", "unknown": "", "": "", } for in, want := range cases { if got := normalizeBaseSort(in); got != want { t.Fatalf("normalizeBaseSort(%q) = %q, want %q", in, got, want) } } }