diff --git a/Makefile b/Makefile index 3ff1554..c5a2825 100644 --- a/Makefile +++ b/Makefile @@ -206,10 +206,6 @@ generate-mocks: install-mockgen clean-mocks @$(MOCKGEN) -source=internal/wtrhandler/deadletter_asynq_handle.go -destination=internal/wtrhandler/deadletter_mock.go -package=wtrhandler DeadLetterStore @echo "$(BLUE)Gerando mock para Fetcher...$(NC)" @$(MOCKGEN) -source=internal/wtrhandler/request_handle_asynq.go -destination=internal/wtrhandler/fetcher_mock.go -package=wtrhandler Fetcher - @echo "$(BLUE)Gerando mock para Cache...$(NC)" - @$(MOCKGEN) -source=pkg/cachemanager/adapter.go -destination=pkg/cachemanager/cache_mock.go -package=cachemanager - @echo "$(BLUE)Gerando mock para Backoffice Repository...$(NC)" - @$(MOCKGEN) -source=internal/backoffice/register_consumer.go -destination=internal/backoffice/repository_mock.go -package=backoffice @echo "$(BLUE)Gerando mock para Publisher...$(NC)" @$(MOCKGEN) -source=pkg/pubadapter/adapter.go -destination=pkg/publisher/publisher_task_mock.go -package=publisher @echo "$(BLUE)Gerando mock para Publisher em pubadapter...$(NC)" @@ -257,11 +253,6 @@ check-mocks: echo "Execute 'make generate-mocks' para gerar os mocks"; \ exit 1; \ fi - @if [ ! -f "internal/backoffice/repository_mock.go" ]; then \ - echo "$(YELLOW)⚠️ Backoffice Repository mock não encontrado!$(NC)"; \ - echo "Execute 'make generate-mocks' para gerar os mocks"; \ - exit 1; \ - fi # TODO: Descomentar quando os mocks de insights forem implementados # @if [ ! -f "internal/wtrhandler/mock_publisher_insights.go" ]; then \ # echo "$(YELLOW)⚠️ PublisherInsights mock não encontrado!$(NC)"; \ @@ -290,8 +281,6 @@ clean-mocks: @rm -f pkg/publisher/publisher_task_mock.go @echo "$(BLUE)Removendo Publisher mock em pubadapter...$(NC)" @rm -f pkg/pubadapter/publisher_task_mock.go - @echo "$(BLUE)Removendo Backoffice Repository mock...$(NC)" - @rm -f internal/backoffice/repository_mock.go @echo "$(BLUE)Removendo PublisherInsights mock...$(NC)" @rm -f internal/wtrhandler/mock_publisher_insights.go @echo "$(BLUE)Removendo ConsumerInsights mock...$(NC)" @@ -335,4 +324,3 @@ help: @echo " $(BLUE)• Cache$(NC) - pkg/cachemanager/cache_mock.go" @echo " $(BLUE)• Publisher$(NC) - pkg/publisher/publisher_task_mock.go" @echo " $(BLUE)• Publisher (pubadapter)$(NC) - pkg/pubadapter/publisher_task_mock.go" - @echo " $(BLUE)• Backoffice Repository$(NC) - internal/backoffice/repository_mock.go" diff --git a/cmd/api/main.go b/cmd/api/main.go index 886c069..fe2d7f1 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -24,12 +24,9 @@ import ( "github.com/IsaacDSC/gqueue/cmd/setup/api" "github.com/IsaacDSC/gqueue/cmd/setup/backoffice" "github.com/IsaacDSC/gqueue/internal/interstore" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" "github.com/redis/go-redis/v9" ) -const appName = "gqueue" - // waitForShutdown waits for SIGINT/SIGTERM and gracefully shuts down the provided servers. func waitForShutdown(apiServer, backofficeServer *http.Server) { quit := make(chan os.Signal, 1) @@ -116,8 +113,6 @@ func main() { panic(err) } - cc := cachemanager.NewStrategy(appName, cacheClient) - service := flag.String("service", "all", "service to run") flag.Parse() @@ -136,7 +131,6 @@ func main() { if *service == "backoffice" { backofficeServer := backoffice.Start( cacheClient, - cc, store, storeInsights, ) @@ -152,7 +146,6 @@ func main() { backofficeServer := backoffice.Start( cacheClient, - cc, store, storeInsights, ) diff --git a/cmd/setup/backoffice/httpserver.go b/cmd/setup/backoffice/httpserver.go index 9511f28..b2476fc 100644 --- a/cmd/setup/backoffice/httpserver.go +++ b/cmd/setup/backoffice/httpserver.go @@ -10,7 +10,6 @@ import ( "github.com/IsaacDSC/gqueue/internal/cfg" "github.com/IsaacDSC/gqueue/internal/domain" "github.com/IsaacDSC/gqueue/internal/interstore" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" "github.com/IsaacDSC/gqueue/pkg/httpadapter" "github.com/redis/go-redis/v9" ) @@ -21,7 +20,6 @@ type InsightsStore interface { func Start( rdsclient *redis.Client, - cache cachemanager.Cache, store interstore.Repository, insightsStore InsightsStore, ) *http.Server { @@ -29,11 +27,11 @@ func Start( routes := []httpadapter.HttpHandle{ backoffice.GetHealthCheckHandler(), - backoffice.PatchConsumer(cache, store), - backoffice.GetEvent(cache, store), - backoffice.GetEvents(cache, store), - backoffice.GetRegisterTaskConsumerArchived(cache, store), - backoffice.RemoveEvent(cache, store), + backoffice.PatchConsumer(store), + backoffice.GetEvent(store), + backoffice.GetEvents(store), + backoffice.GetRegisterTaskConsumerArchived(store), + backoffice.RemoveEvent(store), backoffice.GetInsightsHandle(insightsStore), } diff --git a/example/example.md b/example/example.md index b163776..0355fd6 100644 --- a/example/example.md +++ b/example/example.md @@ -11,7 +11,7 @@ curl -X PATCH \ curl -X GET \ -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ=" \ - http://localhost:8080/api/v1/my-app/events/payment.processed | jq + http://localhost:8080/api/v1/events/{{eventName}} | jq curl -X GET \ -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ=" \ diff --git a/internal/backoffice/event_key.go b/internal/backoffice/event_key.go deleted file mode 100644 index 9400090..0000000 --- a/internal/backoffice/event_key.go +++ /dev/null @@ -1,14 +0,0 @@ -package backoffice - -import ( - "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" -) - -type Cache interface { - Key(params ...string) cachemanager.Key -} - -func eventKey(cache Cache, serviceName, eventName string) cachemanager.Key { - return cache.Key(domain.CacheKeyEventPrefix, serviceName, eventName) -} diff --git a/internal/backoffice/get_event.go b/internal/backoffice/get_event.go index 0c2f7e2..fffb506 100644 --- a/internal/backoffice/get_event.go +++ b/internal/backoffice/get_event.go @@ -1,31 +1,23 @@ package backoffice import ( - "context" "encoding/json" "net/http" "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" "github.com/IsaacDSC/gqueue/pkg/httpadapter" "github.com/IsaacDSC/gqueue/pkg/queryparser" ) -func GetEvent(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func GetEvent(repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ - Path: "GET /api/v1/{service_name}/events/{event_name}", + Path: "GET /api/v1/events/{event_name}", Handler: func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - serviceName := r.PathValue("service_name") eventName := r.PathValue("event_name") - key := eventKey(cc, serviceName, eventName) - defaultTTL := cc.GetDefaultTTL() - - var event domain.Event - if err := cc.Once(ctx, key, &event, defaultTTL, func(ctx context.Context) (any, error) { - return repo.GetInternalEvent(ctx, eventName) - }); err != nil { + event, err := repo.GetInternalEvent(ctx, eventName) + if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -39,7 +31,7 @@ func GetEvent(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { } } -func GetEvents(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func GetEvents(repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ Path: "GET /api/v1/events", Handler: func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/backoffice/register_consumer.go b/internal/backoffice/register_consumer.go index 191c76f..271c55a 100644 --- a/internal/backoffice/register_consumer.go +++ b/internal/backoffice/register_consumer.go @@ -1,14 +1,12 @@ package backoffice import ( - "context" "encoding/json" "fmt" "net/http" "github.com/IsaacDSC/gqueue/internal/cfg" "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" "github.com/IsaacDSC/gqueue/pkg/ctxlogger" "github.com/IsaacDSC/gqueue/pkg/httpadapter" "github.com/google/uuid" @@ -34,7 +32,7 @@ func (e *EventDto) ToDomain() domain.Event { } } -func PatchConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func PatchConsumer(repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ Path: "PATCH /api/v1/event/consumer", Handler: func(w http.ResponseWriter, r *http.Request) { @@ -54,15 +52,7 @@ func PatchConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandl return } - key := eventKey(cc, event.ServiceName, event.Name) - defaultTTL := cc.GetDefaultTTL() - - if err := cc.Hydrate(ctx, key, &payload, defaultTTL, func(ctx context.Context) (any, error) { - if err := repo.Upsert(ctx, event); err != nil { - return domain.Event{}, fmt.Errorf("failed to upsert internal event: %w", err) - } - return payload, nil - }); err != nil { + if err := repo.Upsert(ctx, event); err != nil { l.Error("failed to save consumer", "error", err) http.Error(w, "failed to save consumer", http.StatusInternalServerError) return diff --git a/internal/backoffice/register_event_consumer_archived.go b/internal/backoffice/register_event_consumer_archived.go index f74b220..d690162 100644 --- a/internal/backoffice/register_event_consumer_archived.go +++ b/internal/backoffice/register_event_consumer_archived.go @@ -1,21 +1,20 @@ package backoffice import ( - "context" "encoding/json" - "fmt" "net/http" "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" + "github.com/IsaacDSC/gqueue/pkg/ctxlogger" "github.com/IsaacDSC/gqueue/pkg/httpadapter" ) -func GetRegisterTaskConsumerArchived(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func GetRegisterTaskConsumerArchived(repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ Path: "POST /events/schedule/archived", Handler: func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + l := ctxlogger.GetLogger(ctx) defer r.Body.Close() var payload domain.Event @@ -25,23 +24,12 @@ func GetRegisterTaskConsumerArchived(cc cachemanager.Cache, repo Repository) htt } payload.State = "archived" - typeEvent := "schedule" - key := cc.Key(typeEvent, payload.State, payload.ServiceName, payload.Name) - - if err := cc.Hydrate(ctx, key, &payload, cc.GetDefaultTTL(), func(ctx context.Context) (any, error) { - if err := repo.Upsert(ctx, payload); err != nil { - return domain.Event{}, fmt.Errorf("failed to upsert internal event: %w", err) - } - return payload, nil - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + if err := repo.Upsert(ctx, payload); err != nil { + l.Error("failed to upsert internal event", "error", err) + http.Error(w, "failed to upsert internal event", http.StatusInternalServerError) } - consumersKey := cc.Key("consumers", typeEvent, payload.State) - cc.IncrementValue(ctx, consumersKey, &payload) - w.WriteHeader(http.StatusCreated) }, } diff --git a/internal/backoffice/remove_event_handle.go b/internal/backoffice/remove_event_handle.go index 48b8807..da79d43 100644 --- a/internal/backoffice/remove_event_handle.go +++ b/internal/backoffice/remove_event_handle.go @@ -1,35 +1,27 @@ package backoffice import ( - "context" "net/http" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" + "github.com/IsaacDSC/gqueue/pkg/ctxlogger" httpadapter "github.com/IsaacDSC/gqueue/pkg/httpadapter" "github.com/google/uuid" ) -func RemoveEvent(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func RemoveEvent(repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ Path: "DELETE /api/v1/event/{id}", Handler: func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + l := ctxlogger.GetLogger(ctx) eventID, err := uuid.Parse(r.PathValue("id")) if err != nil { http.Error(w, "Invalid event ID", http.StatusBadRequest) return } - event, err := repo.GetEventByID(ctx, eventID) - if err != nil { - http.Error(w, "Event not found", http.StatusNotFound) - return - } - - key := eventKey(cc, event.ServiceName, event.Name) - if err := cc.RemoveValue(ctx, key, func(ctx context.Context) error { - return repo.DisabledEvent(ctx, eventID) - }); err != nil { + if err := repo.DisabledEvent(ctx, eventID); err != nil { + l.Error("failed to disable event", "error", err) http.Error(w, "Failed to remove event", http.StatusInternalServerError) return } diff --git a/pkg/cachemanager/adapter.go b/pkg/cachemanager/adapter.go deleted file mode 100644 index 63bb4d6..0000000 --- a/pkg/cachemanager/adapter.go +++ /dev/null @@ -1,15 +0,0 @@ -package cachemanager - -import ( - "context" - "time" -) - -type Cache interface { - Key(params ...string) Key - Hydrate(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error - Once(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error - GetDefaultTTL() time.Duration - IncrementValue(ctx context.Context, key Key, value any) error - RemoveValue(ctx context.Context, key Key, fn func(ctx context.Context) error) error -} diff --git a/pkg/cachemanager/cache_mock.go b/pkg/cachemanager/cache_mock.go deleted file mode 100644 index 3fa4ca0..0000000 --- a/pkg/cachemanager/cache_mock.go +++ /dev/null @@ -1,130 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: pkg/cachemanager/adapter.go -// -// Generated by this command: -// -// mockgen -source=pkg/cachemanager/adapter.go -destination=pkg/cachemanager/cache_mock.go -package=cachemanager -// - -// Package cachemanager is a generated GoMock package. -package cachemanager - -import ( - context "context" - reflect "reflect" - time "time" - - gomock "go.uber.org/mock/gomock" -) - -// MockCache is a mock of Cache interface. -type MockCache struct { - ctrl *gomock.Controller - recorder *MockCacheMockRecorder - isgomock struct{} -} - -// MockCacheMockRecorder is the mock recorder for MockCache. -type MockCacheMockRecorder struct { - mock *MockCache -} - -// NewMockCache creates a new mock instance. -func NewMockCache(ctrl *gomock.Controller) *MockCache { - mock := &MockCache{ctrl: ctrl} - mock.recorder = &MockCacheMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockCache) EXPECT() *MockCacheMockRecorder { - return m.recorder -} - -// GetDefaultTTL mocks base method. -func (m *MockCache) GetDefaultTTL() time.Duration { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDefaultTTL") - ret0, _ := ret[0].(time.Duration) - return ret0 -} - -// GetDefaultTTL indicates an expected call of GetDefaultTTL. -func (mr *MockCacheMockRecorder) GetDefaultTTL() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultTTL", reflect.TypeOf((*MockCache)(nil).GetDefaultTTL)) -} - -// Hydrate mocks base method. -func (m *MockCache) Hydrate(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Hydrate", ctx, key, value, ttl, fn) - ret0, _ := ret[0].(error) - return ret0 -} - -// Hydrate indicates an expected call of Hydrate. -func (mr *MockCacheMockRecorder) Hydrate(ctx, key, value, ttl, fn any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hydrate", reflect.TypeOf((*MockCache)(nil).Hydrate), ctx, key, value, ttl, fn) -} - -// IncrementValue mocks base method. -func (m *MockCache) IncrementValue(ctx context.Context, key Key, value any) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IncrementValue", ctx, key, value) - ret0, _ := ret[0].(error) - return ret0 -} - -// IncrementValue indicates an expected call of IncrementValue. -func (mr *MockCacheMockRecorder) IncrementValue(ctx, key, value any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementValue", reflect.TypeOf((*MockCache)(nil).IncrementValue), ctx, key, value) -} - -// Key mocks base method. -func (m *MockCache) Key(params ...string) Key { - m.ctrl.T.Helper() - varargs := []any{} - for _, a := range params { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Key", varargs...) - ret0, _ := ret[0].(Key) - return ret0 -} - -// Key indicates an expected call of Key. -func (mr *MockCacheMockRecorder) Key(params ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockCache)(nil).Key), params...) -} - -// Once mocks base method. -func (m *MockCache) Once(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Once", ctx, key, value, ttl, fn) - ret0, _ := ret[0].(error) - return ret0 -} - -// Once indicates an expected call of Once. -func (mr *MockCacheMockRecorder) Once(ctx, key, value, ttl, fn any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Once", reflect.TypeOf((*MockCache)(nil).Once), ctx, key, value, ttl, fn) -} - -// RemoveValue mocks base method. -func (m *MockCache) RemoveValue(ctx context.Context, key Key, fn func(context.Context) error) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveValue", ctx, key, fn) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveValue indicates an expected call of RemoveValue. -func (mr *MockCacheMockRecorder) RemoveValue(ctx, key, fn any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveValue", reflect.TypeOf((*MockCache)(nil).RemoveValue), ctx, key, fn) -} diff --git a/pkg/cachemanager/cache_strategy.go b/pkg/cachemanager/cache_strategy.go deleted file mode 100644 index b6e94f5..0000000 --- a/pkg/cachemanager/cache_strategy.go +++ /dev/null @@ -1,164 +0,0 @@ -package cachemanager - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strings" - "time" - - "github.com/IsaacDSC/gqueue/internal/cfg" - redis "github.com/redis/go-redis/v9" -) - -// Fn defines a function type that takes a context and returns any value and an error. -type Fn func(ctx context.Context) (any, error) - -// Key represents a cache key as a string. -type Key string - -// String returns the string representation of the Key. -func (k Key) String() string { - return string(k) -} - -// Strategy provides caching methods using a Redis client. -type Strategy struct { - appPrefix string - client *redis.Client -} - -var _ Cache = (*Strategy)(nil) - -// NewStrategy creates a new Strategy with the given Redis client. -func NewStrategy(appPrefix string, client *redis.Client) *Strategy { - return &Strategy{appPrefix: appPrefix, client: client} -} - -// Key constructs a cache key by joining the provided parameters with a separator. -func (s Strategy) Key(params ...string) Key { - params = append([]string{s.appPrefix}, params...) - return Key(strings.Join(params, ":")) -} - -// GetDefaultTTL for cache entries, can be adjusted as needed. -func (s Strategy) GetDefaultTTL() time.Duration { - conf := cfg.Get() - return conf.Cache.DefaultTTL -} - -// Hydrate executes the provided function, stores its result in the cache, and unmarshals it into value. -// It always refreshes the cache with the latest value. -func (s Strategy) Hydrate(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error { - v, err := fn(ctx) - if err != nil { - return fmt.Errorf("error executing function for key %s: %w", key.String(), err) - } - - b, err := json.Marshal(v) - if err != nil { - return fmt.Errorf("error marshalling value for key %s: %w", key.String(), err) - } - - if err := s.client.Set(ctx, key.String(), b, ttl).Err(); err != nil { - return fmt.Errorf("error setting value for key %s: %w", key.String(), err) - } - - if err := json.Unmarshal(b, value); err != nil { - return fmt.Errorf("error unmarshalling value for key %s: %w", key.String(), err) - } - - return nil -} - -// Once retrieves the value from the cache if present, otherwise executes the function, stores, and returns the result. -// It only executes the function if the cache is missing. -func (s Strategy) Once(ctx context.Context, key Key, value any, ttl time.Duration, fn Fn) error { - exist, err := s.client.Exists(ctx, key.String()).Result() - if err != nil { - return fmt.Errorf("error checking existence of key %s: %w", key.String(), err) - } - - if exist > 0 { - v, err := s.client.Get(ctx, key.String()).Bytes() - if err != nil { - return fmt.Errorf("error getting value for key %s: %w", key.String(), err) - } - - if err := json.Unmarshal(v, value); err != nil { - return fmt.Errorf("error unmarshalling value for key %s: %w", key.String(), err) - } - - return nil - } - - res, err := fn(ctx) - if err != nil { - return fmt.Errorf("error executing function for key %s: %w", key.String(), err) - } - - valueBytes, err := json.Marshal(res) - if err != nil { - return fmt.Errorf("error marshalling value for key %s: %w", key.String(), err) - } - - if err := s.client.Set(ctx, key.String(), valueBytes, ttl).Err(); err != nil { - return fmt.Errorf("error setting value for key %s: %w", key.String(), err) - } - - if err := json.Unmarshal(valueBytes, value); err != nil { - return fmt.Errorf("error unmarshalling value after setting key %s: %w", key.String(), err) - } - - return nil -} - -func (s Strategy) IncrementValue(ctx context.Context, key Key, value any) error { - val, err := s.client.Get(ctx, key.String()).Result() - if err != nil && !errors.Is(err, redis.Nil) { - return fmt.Errorf("error getting value for key %s: %w", key.String(), err) - } - - var result []any - if !errors.Is(err, redis.Nil) { - json.Unmarshal([]byte(val), &result) - } - - allreadyExists := false - for i := range result { - if result[i] == value { - allreadyExists = true - result[i] = value - break - } - } - - if !allreadyExists { - result = append(result, value) - } - - b, err := json.Marshal(result) - if err != nil { - return fmt.Errorf("error marshalling value for key %s: %w", key.String(), err) - } - - conf := cfg.Get() - if err := s.client.Set(ctx, key.String(), b, conf.Cache.DefaultTTL).Err(); err != nil { - return fmt.Errorf("error setting value for key %s: %w", key.String(), err) - } - - return nil -} - -func (s Strategy) RemoveValue(ctx context.Context, key Key, fn func(ctx context.Context) error) error { - if err := s.client.Del(ctx, key.String()).Err(); err != nil { - return fmt.Errorf("error removing value for key %s: %w", key.String(), err) - } - - if err := fn(ctx); err != nil { - return fmt.Errorf("error executing function for key %s: %w", key.String(), err) - } - - return nil -} diff --git a/pkg/cachemanager/cache_strategy_test.go b/pkg/cachemanager/cache_strategy_test.go deleted file mode 100644 index 59e844b..0000000 --- a/pkg/cachemanager/cache_strategy_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package cachemanager - -import ( - "context" - "encoding/json" - "errors" - "os" - "testing" - "time" - - "github.com/redis/go-redis/v9" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func init() { - // Set required environment variables for tests - os.Setenv("GO_ENV", "test") - os.Setenv("WQ", "redis") - os.Setenv("WQ_QUEUES", `{"internal.critical": 7, "internal.high": 5, "internal.medium": 3, "internal.low": 1, "external.critical": 7, "external.high": 5, "external.medium": 3, "external.low": 1}`) - os.Setenv("CACHE_ADDR", "localhost:6379") - os.Setenv("CACHE_DEFAULT_TTL", "24h") - os.Setenv("DB_DRIVER", "pg") - os.Setenv("DB_CONNECTION_STRING", "postgresql://test:test@localhost:5432/test?sslmode=disable") - os.Setenv("WQ_CONCURRENCY", "32") -} - -type testPerson struct { - Name string `json:"name"` - Age int `json:"age"` -} - -func TestKey(t *testing.T) { - strategy := Strategy{appPrefix: "testapp"} - - testCases := []struct { - name string - params []string - expected Key - }{ - { - name: "single parameter", - params: []string{"test"}, - expected: Key("testapp:test"), - }, - { - name: "multiple parameters", - params: []string{"user", "123", "profile"}, - expected: Key("testapp:user:123:profile"), - }, - { - name: "empty parameters", - params: []string{}, - expected: Key("testapp"), - }, - { - name: "parameters with special characters", - params: []string{"user:1", "data-set", "item.123"}, - expected: Key("testapp:user:1:data-set:item.123"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - key := strategy.Key(tc.params...) - assert.Equal(t, tc.expected, key) - }) - } -} - -func TestKeyString(t *testing.T) { - key := Key("test:key:123") - assert.Equal(t, "test:key:123", key.String()) -} - -func TestGetDefaultTTL(t *testing.T) { - strategy := Strategy{appPrefix: "testapp"} - assert.Equal(t, 24*time.Hour, strategy.GetDefaultTTL()) -} - -func TestNewStrategy(t *testing.T) { - redisClient := redis.NewClient(&redis.Options{Addr: "localhost:6379"}) - strategy := NewStrategy("testapp", redisClient) - - assert.NotNil(t, strategy) - assert.Equal(t, "testapp", strategy.appPrefix) - assert.NotNil(t, strategy.client) - - // Test that the strategy implements the Cache interface - var _ Cache = strategy -} - -func TestStrategyImplementsCache(t *testing.T) { - // Compile-time check that Strategy implements Cache interface - var _ Cache = (*Strategy)(nil) - - // Runtime check - strategy := NewStrategy("testapp", redis.NewClient(&redis.Options{Addr: "localhost:6379"})) - - var cache Cache = strategy - assert.NotNil(t, cache) - - // Test interface methods - key := cache.Key("test") - assert.Equal(t, Key("testapp:test"), key) - - ttl := cache.GetDefaultTTL() - assert.Equal(t, 24*time.Hour, ttl) -} - -func TestJSONMarshalUnmarshal(t *testing.T) { - // Test JSON marshaling/unmarshaling that is used in the cache methods - testData := testPerson{Name: "John", Age: 30} - - // Marshal - data, err := json.Marshal(testData) - require.NoError(t, err) - assert.NotEmpty(t, data) - - // Unmarshal - var result testPerson - err = json.Unmarshal(data, &result) - require.NoError(t, err) - assert.Equal(t, testData, result) -} - -func TestErrorHandling(t *testing.T) { - t.Run("json marshal error", func(t *testing.T) { - // Create a value that cannot be marshaled to JSON - ch := make(chan int) - _, err := json.Marshal(ch) - assert.Error(t, err) - assert.Contains(t, err.Error(), "json: unsupported type") - }) - - t.Run("json unmarshal error", func(t *testing.T) { - var result testPerson - err := json.Unmarshal([]byte("invalid json"), &result) - assert.Error(t, err) - }) -} - -func TestIncrementValueLogic(t *testing.T) { - // Test the core logic for IncrementValue without Redis - t.Run("new slice creation", func(t *testing.T) { - var result []interface{} - value := "test-value" - - // Simulate what happens when key doesn't exist (empty slice) - alreadyExists := false - for i := range result { - if result[i] == value { - alreadyExists = true - result[i] = value - break - } - } - - if !alreadyExists { - result = append(result, value) - } - - assert.Len(t, result, 1) - assert.Equal(t, value, result[0]) - }) - - t.Run("append to existing slice", func(t *testing.T) { - result := []interface{}{"value1", "value2"} - value := "value3" - - alreadyExists := false - for i := range result { - if result[i] == value { - alreadyExists = true - result[i] = value - break - } - } - - if !alreadyExists { - result = append(result, value) - } - - assert.Len(t, result, 3) - assert.Contains(t, result, "value1") - assert.Contains(t, result, "value2") - assert.Contains(t, result, value) - }) - - t.Run("update existing value", func(t *testing.T) { - result := []interface{}{"value1", "value2"} - value := "value1" // duplicate - - alreadyExists := false - for i := range result { - if result[i] == value { - alreadyExists = true - result[i] = value - break - } - } - - if !alreadyExists { - result = append(result, value) - } - - assert.Len(t, result, 2) // Should still be 2, not 3 - assert.Contains(t, result, "value1") - assert.Contains(t, result, "value2") - }) -} - -func TestContextUsage(t *testing.T) { - // Test that functions properly handle context - ctx := context.Background() - cancelCtx, cancel := context.WithCancel(ctx) - - // Test function that respects context - testFn := func(ctx context.Context) (any, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - return testPerson{Name: "Test", Age: 25}, nil - } - } - - // Test with normal context - result, err := testFn(ctx) - require.NoError(t, err) - assert.Equal(t, testPerson{Name: "Test", Age: 25}, result) - - // Test with cancelled context - cancel() - _, err = testFn(cancelCtx) - assert.Error(t, err) - assert.Equal(t, context.Canceled, err) -} - -func TestErrorWrapping(t *testing.T) { - // Test error wrapping patterns used in the Strategy methods - baseErr := errors.New("base error") - key := Key("test-key") - - wrappedErr := errors.New("error executing function for key " + key.String() + ": " + baseErr.Error()) - assert.Contains(t, wrappedErr.Error(), "error executing function") - assert.Contains(t, wrappedErr.Error(), key.String()) - assert.Contains(t, wrappedErr.Error(), baseErr.Error()) -} - -func TestTimeHandling(t *testing.T) { - // Test TTL handling - strategy := Strategy{appPrefix: "testapp"} - - defaultTTL := strategy.GetDefaultTTL() - assert.Equal(t, 24*time.Hour, defaultTTL) - - // Test different TTL values - testTTLs := []time.Duration{ - time.Minute, - time.Hour, - 24 * time.Hour, - time.Duration(-1), // No expiration in Redis - } - - for _, ttl := range testTTLs { - assert.IsType(t, time.Duration(0), ttl) - } -}