package service import ( "context" "errors" "testing" "wucher/internal/domain/opc" ) type opcRepoMock struct { createCalled bool updateCalled bool deleteCalled bool getByIDCalled bool listCalled bool lastCreate *opc.Opc lastUpdate *opc.Opc lastDeleteID []byte lastDeleteBy []byte lastGetByID []byte lastListFilter string lastListSort string lastListLimit int lastListOffset int getByIDResult *opc.Opc listResult []opc.Opc listTotal int64 createErr error updateErr error deleteErr error getByIDErr error listErr error } func (m *opcRepoMock) Create(_ context.Context, o *opc.Opc) error { m.createCalled = true m.lastCreate = o return m.createErr } func (m *opcRepoMock) Update(_ context.Context, o *opc.Opc) error { m.updateCalled = true m.lastUpdate = o return m.updateErr } func (m *opcRepoMock) Delete(_ context.Context, id []byte, deletedBy []byte) error { m.deleteCalled = true m.lastDeleteID = id m.lastDeleteBy = deletedBy return m.deleteErr } func (m *opcRepoMock) GetByID(_ context.Context, id []byte) (*opc.Opc, error) { m.getByIDCalled = true m.lastGetByID = id return m.getByIDResult, m.getByIDErr } func (m *opcRepoMock) List(_ context.Context, filter, sort string, limit, offset int) ([]opc.Opc, int64, error) { m.listCalled = true m.lastListFilter = filter m.lastListSort = sort m.lastListLimit = limit m.lastListOffset = offset return m.listResult, m.listTotal, m.listErr } func TestNewOpcService(t *testing.T) { repo := &opcRepoMock{} svc := NewOpcService(repo) if svc == nil || svc.repo == nil { t.Fatalf("expected service and repo initialized") } } func TestOpcServiceCreate(t *testing.T) { repo := &opcRepoMock{} svc := NewOpcService(repo) row := &opc.Opc{Title: "Title"} 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 TestOpcServiceUpdate(t *testing.T) { repo := &opcRepoMock{} svc := NewOpcService(repo) row := &opc.Opc{Title: "Updated"} 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 TestOpcServiceDelete(t *testing.T) { repo := &opcRepoMock{} svc := NewOpcService(repo) id := []byte("id") deletedBy := []byte("by") if err := svc.Delete(context.Background(), id, deletedBy); err != nil { t.Fatalf("unexpected error: %v", err) } if !repo.deleteCalled { t.Fatalf("expected Delete forwarded to repo") } if string(repo.lastDeleteID) != string(id) || string(repo.lastDeleteBy) != string(deletedBy) { t.Fatalf("expected delete params forwarded") } repo.deleteErr = errors.New("delete failed") if err := svc.Delete(context.Background(), id, deletedBy); err == nil { t.Fatalf("expected error from repo") } } func TestOpcServiceGetByID(t *testing.T) { expected := &opc.Opc{Title: "A"} repo := &opcRepoMock{getByIDResult: expected} svc := NewOpcService(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 TestOpcServiceList(t *testing.T) { repo := &opcRepoMock{ listResult: []opc.Opc{{Title: "A"}}, listTotal: 1, } svc := NewOpcService(repo) rows, total, err := svc.List(context.Background(), "find", "title", 10, 20) 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 != "name ASC" || repo.lastListLimit != 10 || repo.lastListOffset != 20 { t.Fatalf("unexpected list args forwarded") } rows, total, err = svc.List(context.Background(), "find", "sortkey", 10, 20) 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, name ASC" { t.Fatalf("unexpected sortkey args forwarded: %q", repo.lastListSort) } repo.listErr = errors.New("list failed") if _, _, err := svc.List(context.Background(), "", "unknown", 1, 0); err == nil { t.Fatalf("expected error from repo") } } func TestNormalizeOpcSort(t *testing.T) { cases := map[string]string{ "title": "name ASC", "-title": "name 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, name 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, name ASC", "note": "note ASC", "-note": "note 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 := normalizeOpcSort(in); got != want { t.Fatalf("normalizeOpcSort(%q) = %q, want %q", in, got, want) } } }