diff --git a/internal/adapter/redis/provisioner.go b/internal/adapter/redis/provisioner.go index 446195c..5d01685 100644 --- a/internal/adapter/redis/provisioner.go +++ b/internal/adapter/redis/provisioner.go @@ -180,7 +180,8 @@ func (p *Provisioner) GetProjectCache(ctx context.Context, projectID string) (*d // Check if user exists result, err := p.client.Do(ctx, "ACL", "GETUSER", username).Result() if err != nil { - if strings.Contains(err.Error(), "User") { + // redis.Nil means the user does not exist (RESP null bulk string response) + if err == redis.Nil || strings.Contains(err.Error(), "User") { return nil, nil // User doesn't exist } return nil, fmt.Errorf("get ACL user: %w", err) diff --git a/internal/service/component_infra.go b/internal/service/component_infra.go index 1877f83..a0fe5ef 100644 --- a/internal/service/component_infra.go +++ b/internal/service/component_infra.go @@ -88,7 +88,20 @@ func (s *ComponentService) provisionRedis(ctx context.Context, projectID, name s return nil, fmt.Errorf("failed to check existing cache: %w", err) } if existing != nil { - return nil, fmt.Errorf("%w: redis already provisioned for project %s", domain.ErrDuplicateComponent, projectID) + // Redis user exists — check if credentials are stored. If they are, it's a true duplicate. + // If not (credentials were lost), fall through to re-provision (CreateProjectCache resets the password). + if s.credentialStore != nil { + storedURL, storeErr := s.credentialStore.Get(ctx, projectID+":REDIS_URL") + if storeErr == nil && storedURL != "" { + return nil, fmt.Errorf("%w: redis already provisioned for project %s", domain.ErrDuplicateComponent, projectID) + } + // Credentials missing from store — re-provision to recover + log := logging.FromContext(ctx).WithService("component") + log.Warn("redis user exists but REDIS_URL not in credential store, re-provisioning", + logging.FieldProjectID, projectID) + } else { + return nil, fmt.Errorf("%w: redis already provisioned for project %s", domain.ErrDuplicateComponent, projectID) + } } // Provision the cache diff --git a/internal/service/component_test.go b/internal/service/component_test.go index d5712cc..a7747e5 100644 --- a/internal/service/component_test.go +++ b/internal/service/component_test.go @@ -375,7 +375,12 @@ func TestProvisionRedis(t *testing.T) { cacheProvisioner: &mockCacheProvisioner{ existingCache: &domain.CacheCredentials{ProjectID: "test-project"}, }, - credStore: newMockCredentialStore(), + credStore: func() *mockCredentialStore { + // Simulate credentials already stored — this is a true duplicate + cs := newMockCredentialStore() + cs.stored["test-project:REDIS_URL"] = "redis://proj-test-project:pass@localhost:6379" + return cs + }(), wantErr: true, wantErrContains: "already provisioned", },