Conversation
|
Important Review skippedToo many files! This PR contains 228 files, which is 78 over the limit of 150. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (45)
📒 Files selected for processing (228)
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughAnálise do Pull RequestWalkthroughEste PR implementa um módulo completo de Pagamentos com suporte a assinaturas Stripe, webhooks, portais de cobrança e processamento assíncrono em background jobs. Inclui também melhorias na infraestrutura de mensagens (Rebus), localização frontend multilíngue (pt/en), e cobertura extensiva de testes integrados. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Cliente
participant CreateSubEp as CreateSubscriptionEndpoint
participant Handler as CreateSubscriptionCommandHandler
participant Gateway as StripePaymentGateway
participant Stripe as Stripe API
participant Repo as SubscriptionRepository
participant DB as PaymentsDB
Client->>CreateSubEp: POST /api/v1/payments/subscriptions<br/>{ProviderId, PlanId}
CreateSubEp->>CreateSubEp: Validar ProviderId, PlanId<br/>Verificar autorização por provider
CreateSubEp->>Handler: HandleAsync(CreateSubscriptionCommand)
Handler->>Handler: Carregar config plano<br/>Validar Amount/Currency
Handler->>Gateway: CreateSubscriptionAsync(...)
Gateway->>Stripe: CreateCheckoutSession(metadata, amount)
Stripe-->>Gateway: checkoutUrl
Gateway-->>Handler: SubscriptionGatewayResult<br/>(Success, externalSubId, url)
Handler->>Repo: AddAsync(subscription)
Repo->>DB: INSERT subscription<br/>(Pending status)
DB-->>Repo: OK
Repo-->>Handler: OK
Handler-->>CreateSubEp: checkoutUrl
CreateSubEp-->>Client: 200 OK {checkoutUrl}
Note over Client,DB: Fluxo posterior (webhook)
Client->>Stripe: Completa checkout session<br/>(POST form)
Stripe->>Stripe: Cria subscription
Stripe->>Stripe: Dispara checkout.session.completed
Stripe-->>Client: Redireciona para successUrl
Note over Stripe: Webhook assíncrono
Stripe->>CreateSubEp: POST /api/payments/webhooks/stripe
CreateSubEp->>CreateSubEp: Validar assinatura Stripe
CreateSubEp->>Repo: SaveToInbox(event)
Repo->>DB: INSERT inbox_message
DB-->>Repo: OK
Repo-->>CreateSubEp: OK
CreateSubEp-->>Stripe: 200 OK
Note over Stripe,DB: Background Job - ProcessInboxJob
DB->>DB: SELECT * FROM inbox_messages<br/>WHERE processed_at IS NULL<br/>AND retry_count < max_retries
activate DB
DB-->>ProcessInbox as ProcessInboxJob: unprocessed events
deactivate DB
ProcessInbox->>ProcessInbox: Parsear evento Stripe<br/>MapToStripeEventData
ProcessInbox->>Repo: GetActiveByProviderIdAsync(providerId)
Repo->>DB: SELECT subscription
DB-->>Repo: subscription
Repo-->>ProcessInbox: subscription
ProcessInbox->>ProcessInbox: Chamar Activate<br/>Emitir SubscriptionActivatedEvent
ProcessInbox->>Repo: UpdateAsync(subscription)
Repo->>DB: UPDATE subscription (Active)
DB-->>Repo: OK
Repo-->>ProcessInbox: OK
ProcessInbox->>DB: UPDATE inbox_message<br/>SET processed_at = NOW()
DB-->>ProcessInbox: OK
Estimativa de Esforço para Revisão🎯 4 (Complexo) | ⏱️ ~60 minutos Este PR é extenso e heterogêneo: introduz um novo módulo completo (Payments) com lógica densa em handlers (compensação de falhas, validação de retorno URLs), background jobs sofisticados (processamento de Stripe events com retry exponencial), múltiplos padrões de teste, reconfiguração da infraestrutura de mensagens (Rebus vs RabbitMQ), e mudanças frontend de internacionalização. Embora muitos testes sejam repetitivos, as implementações de negócio variam em complexidade (gateway Stripe, inbox idempotente, domain events). Requer verificação cuidadosa de lógica de compensação, configuração de Stripe, tratamento de exceções em background job e segurança de autorização por provider. Possíveis PRs Relacionadas
Poema do Coelho 🐰
✨ Finishing Touches🧪 Generate unit tests (beta)
|
…per attribute casing
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (19)
src/Shared/Utilities/UserRoles.cs (1)
84-84: Padronização parcial: vale centralizar também os tiers de provider.Line 84 ficou ótimo ao apontar para
RoleConstants.Customer. Como próximo passo, considere aplicar o mesmo padrão para os papéis de tier (ProviderStandard/Silver/Gold/Platinum, Lines 91-106) para manter uma única fonte de verdade.Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Utilities/UserRoles.cs` at line 84, The provider-tier string constants in the UserRoles class (ProviderStandard, ProviderSilver, ProviderGold, ProviderPlatinum) should be redirected to the central RoleConstants (or the Shared.Contracts enum/constant source) just like Customer was; replace each provider-tier literal with the corresponding RoleConstants.* entry (or the Shared.Contracts symbol) so all role values come from one source of truth and keep Web aligned with backend/shared code.src/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csproj (1)
10-18: Considere reduzirInternalsVisibleTopara assemblies de produção.Expor internals do Domain para
InfrastructureeApplicationenfraquece encapsulamento do módulo. Como melhoria, prefira contratos explícitos públicos no Domain e reserveInternalsVisibleToprincipalmente para testes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csproj` around lines 10 - 18, The project currently declares InternalsVisibleTo for MeAjudaAi.Modules.Payments.Tests, MeAjudaAi.Modules.Payments.Infrastructure and MeAjudaAi.Modules.Payments.Application; remove the two production entries (MeAjudaAi.Modules.Payments.Infrastructure and MeAjudaAi.Modules.Payments.Application) leaving only the Tests visibility, and instead make the required domain API surface explicit by converting internal types/members used by Infrastructure/Application into public types or well-defined public interfaces (or factory/DTO classes) so Consumers use explicit contracts rather than relying on InternalsVisibleTo.src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj (1)
23-24: Remova a dependência do providerNpgsqlda camada Application.A camada Application não utiliza tipos específicos do Npgsql, portanto essa dependência deve ficar na Infrastructure para manter as fronteiras arquiteturais limpas.
♻️ Ajuste sugerido no csproj
<ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore" /> - <PackageReference Include="Npgsql" /> </ItemGroup>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj` around lines 23 - 24, Remova a referência PackageReference Include="Npgsql" do csproj da camada Application (remover a entrada PackageReference Include="Npgsql") e mova essa dependência para o csproj da camada Infrastructure; verifique dentro do projeto Application (classes/serviços) que nenhum tipo específico de Npgsql está sendo usado e, se houver, substitua por abstrações/DTOs ou interfaces da camada de domínio/infrastructure; após a alteração execute restore/build para garantir que a dependência exista apenas na camada Infrastructure.src/Modules/Payments/API/Endpoints/PaymentsEndpoints.cs (1)
12-12: Evite strings mágicas para rota/tag do módulo.Considere extrair
"payments"e"Payments"para constantes compartilhadas (ex.: em Shared.Contracts) para reduzir drift entre camadas e facilitar refactors.Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/API/Endpoints/PaymentsEndpoints.cs` at line 12, Extrair os literais "payments" e "Payments" usados em BaseEndpoint.CreateVersionedGroup(app, "payments", "Payments") para constantes compartilhadas (por exemplo em Shared.Contracts como PaymentsRoute e PaymentsTag ou um enum/const class ModuleRoutes/ModuleTags), importar esse módulo e substituir a chamada por BaseEndpoint.CreateVersionedGroup(app, PaymentsRoute, PaymentsTag); atualizar quaisquer usos semelhantes no projeto para referenciar as mesmas constantes para evitar drift entre camadas e facilitar refactors.src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs (1)
39-40: Considere índice composto para fila pendente (otimização futura).Inferência: se o
ProcessInboxJobfiltra porProcessedAte ordena porCreatedAt, um índice composto (ProcessedAt,CreatedAt) tende a reduzir custo de varredura em volume alto.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs` around lines 39 - 40, The current configuration creates separate indexes for ProcessedAt and CreatedAt; add a composite index on (ProcessedAt, CreatedAt) in InboxMessageConfiguration by replacing or augmenting the existing builder.HasIndex calls to use builder.HasIndex(m => new { m.ProcessedAt, m.CreatedAt }) so that queries from ProcessInboxJob that filter by ProcessedAt and order by CreatedAt can use the composite index; also ensure you add a corresponding EF migration to apply the new composite index to the database.tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs (1)
185-185: Evite múltiplas implementações deIPaymentGatewayno container de testes.Hoje o mock é adicionado sem remover o registro real anterior. Para resolução de
IEnumerable<IPaymentGateway>, isso pode gerar comportamento inesperado em testes.💡 Ajuste sugerido
services.AddSingleton<IBackgroundJobService, MockBackgroundJobService>(); - services.AddScoped<IPaymentGateway, MockPaymentGateway>(); + var paymentGatewayDescriptors = services + .Where(d => d.ServiceType == typeof(IPaymentGateway)) + .ToList(); + foreach (var descriptor in paymentGatewayDescriptors) + services.Remove(descriptor); + services.AddScoped<IPaymentGateway, MockPaymentGateway>(); services.AddHttpContextAccessor();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs` at line 185, O teste está registrando MockPaymentGateway sem remover a implementação real de IPaymentGateway, o que deixa múltiplas implementações no container; antes de chamar services.AddScoped<IPaymentGateway, MockPaymentGateway>() remova quaisquer ServiceDescriptor existentes para ServiceType == typeof(IPaymentGateway) (ou use services.Replace com um ServiceDescriptor scoped) para garantir que apenas MockPaymentGateway seja resolvido em testes; altere o bloco que registra MockPaymentGateway para primeiro limpar/ substituir o registro existente de IPaymentGateway e então adicionar o mock.src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs (1)
31-33: Fortaleça a verificação dos argumentos enviados ao gateway e da moeda persistida.Com
It.IsAny, o teste passa mesmo se o handler mapearPlanId/Amount/Currencyincorretamente para o gateway.✅ Ajuste sugerido nos asserts
- _gatewayMock.Setup(g => g.CreateSubscriptionAsync( - It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<Money>(), It.IsAny<CancellationToken>())) + _gatewayMock.Setup(g => g.CreateSubscriptionAsync( + command.ProviderId, + command.PlanId, + It.Is<Money>(m => m.Amount == command.Amount && m.Currency == command.Currency), + It.IsAny<CancellationToken>())) .ReturnsAsync(gatewayResult); @@ _repositoryMock.Verify(r => r.AddAsync(It.Is<Subscription>(s => s.ProviderId == command.ProviderId && s.PlanId == command.PlanId && - s.Amount.Amount == command.Amount), It.IsAny<CancellationToken>()), Times.Once); + s.Amount.Amount == command.Amount && + s.Amount.Currency == command.Currency), It.IsAny<CancellationToken>()), Times.Once); + + _gatewayMock.Verify(g => g.CreateSubscriptionAsync( + command.ProviderId, + command.PlanId, + It.Is<Money>(m => m.Amount == command.Amount && m.Currency == command.Currency), + It.IsAny<CancellationToken>()), Times.Once);Also applies to: 40-43
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs` around lines 31 - 33, O teste está usando It.IsAny for CreateSubscriptionAsync, deixando passar mapeamentos errados; update the assertions to verify the exact arguments passed to the gateway and the persisted currency by replacing It.IsAny with argument matchers (e.g. It.Is<Guid>(id => id == expectedPlanId), It.Is<Money>(m => m.Amount == expectedAmount && m.Currency == expectedCurrency)) when setting up/Verifying _gatewayMock.CreateSubscriptionAsync in CreateSubscriptionCommandHandlerTests and the other similar setup at lines 40-43, and add/verifiy an assertion against the repository/entity save to ensure the stored subscription.Currency equals expectedCurrency.src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs (1)
25-28: EviteExceptiongenérica no fluxo de aplicação.Use exceções de domínio/aplicação específicas para facilitar tratamento consistente (ex.: mapeamento para 4xx vs 5xx).
♻️ Proposta de ajuste
- if (!result.Success) - { - throw new Exception($"Failed to create subscription: {result.ErrorMessage}"); - } + if (!result.Success) + { + throw new InvalidOperationException($"Failed to create subscription: {result.ErrorMessage}"); + } @@ - throw new InvalidOperationException("Checkout URL is missing."); + throw new InvalidOperationException("Checkout URL is missing.");Also applies to: 36-36
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs` around lines 25 - 28, Replace the generic Exception thrown in CreateSubscriptionCommandHandler when result.Success is false with a specific application/domain exception (e.g., SubscriptionCreationException or a BadRequest/ApplicationOperationException) so callers can map it to 4xx/5xx consistently; change both occurrences where you currently throw new Exception($"Failed to create subscription: {result.ErrorMessage}") to throw the new specific exception type and pass the result.ErrorMessage (and optionally an inner exception or error code) so the handler and error middleware can distinguish domain validation/expected failures from unexpected server errors.src/Modules/Payments/Domain/Entities/Subscription.cs (2)
11-19: Atribuição redundante deId.Mesmo caso de
PaymentTransaction:AggregateRoot<Guid>(viaBaseEntity) já inicializaId. A atribuição explícita comGuid.NewGuid()sobrescreve o valor gerado porUuidGenerator.NewId().♻️ Sugestão de refatoração
public Subscription(Guid providerId, string planId, Money amount) { - Id = Guid.NewGuid(); ProviderId = providerId; PlanId = planId; Amount = amount; Status = ESubscriptionStatus.Pending; // CreatedAt is in BaseEntity }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/Subscription.cs` around lines 11 - 19, O construtor Subscription está atribuindo Id = Guid.NewGuid(), o que sobrescreve o Id já inicializado pelo AggregateRoot<Guid>/BaseEntity (via UuidGenerator.NewId()); remova a atribuição explícita de Id dentro do construtor Subscription para deixar a geração do identificador ao BaseEntity/AggregateRoot (mesma correção aplicada em PaymentTransaction), mantendo apenas a inicialização das outras propriedades (ProviderId, PlanId, Amount, Status).
50-54: Verificar seUpdateStatusé necessário.O método
UpdateStatus(ESubscriptionStatus)permite qualquer transição de estado diretamente, ignorando a lógica encapsulada emActivate(),Cancel()eExpire(). Se for intencional (ex: operações administrativas), está OK. Caso contrário, considere removê-lo para forçar o uso dos métodos específicos.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/Subscription.cs` around lines 50 - 54, The UpdateStatus(ESubscriptionStatus) method on Subscription bypasses the specialized transition methods (Activate, Cancel, Expire) and should either be removed or constrained; to fix, either delete UpdateStatus to force callers to use Activate/Cancel/Expire, or make UpdateStatus private/internal and implement explicit allowed-transition checks (or dispatch to the corresponding methods) so state transitions go through Activate(), Cancel(), or Expire() and still call MarkAsUpdated() as needed; locate the Subscription class and the UpdateStatus, Activate, Cancel, Expire, and MarkAsUpdated members to apply the chosen change.src/Modules/Payments/Domain/Entities/PaymentTransaction.cs (2)
26-39: Considerar guardas de transição de estado.Os métodos
Settle()eFail()não verificam o estado atual antes da transição. Isso permite transições inválidas (ex:Fail()em uma transação jáSucceeded). Se o domínio exigir, adicione validações.♻️ Exemplo com guardas de estado
public void Settle(string externalTransactionId) { + if (Status != EPaymentStatus.Pending) + throw new InvalidOperationException($"Cannot settle transaction in {Status} status."); + + if (string.IsNullOrWhiteSpace(externalTransactionId)) + throw new ArgumentException("External transaction ID is required.", nameof(externalTransactionId)); + ExternalTransactionId = externalTransactionId; Status = EPaymentStatus.Succeeded; ProcessedAt = DateTime.UtcNow; MarkAsUpdated(); } public void Fail() { + if (Status != EPaymentStatus.Pending) + throw new InvalidOperationException($"Cannot fail transaction in {Status} status."); + Status = EPaymentStatus.Failed; ProcessedAt = DateTime.UtcNow; MarkAsUpdated(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs` around lines 26 - 39, Os métodos Settle(...) e Fail() permitem transições de estado inválidas porque não verificam o estado atual (EPaymentStatus); adicione guardas de transição em Settle and Fail que validem o estado atual antes de alterar Status, por exemplo rejeitando Settle se Status já for Succeeded/Cancelled and rejeitando Fail se Status já for Failed/Succeeded, lançando uma exceção de domínio (ou retornando um resultado de erro) quando a transição for inválida; preserve a atribuição de ExternalTransactionId, ProcessedAt e chamada MarkAsUpdated() somente após a validação passar.
11-18: Atribuição redundante deId.
BaseEntityjá inicializaIdcomUuidGenerator.NewId(). A atribuiçãoId = Guid.NewGuid()sobrescreve esse valor, potencialmente gerando IDs com algoritmo diferente do padrão do projeto.♻️ Sugestão de refatoração
public PaymentTransaction(Guid subscriptionId, Money amount) { - Id = Guid.NewGuid(); SubscriptionId = subscriptionId; Amount = amount; Status = EPaymentStatus.Pending; // CreatedAt is already in BaseEntity }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs` around lines 11 - 18, The PaymentTransaction constructor is overwriting the Id set by BaseEntity (which uses UuidGenerator.NewId()); remove the redundant assignment "Id = Guid.NewGuid()" from the PaymentTransaction(Guid subscriptionId, Money amount) constructor so the entity inherits the project's standard UUID generation, leaving SubscriptionId, Amount, and Status initialization intact.src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs (1)
11-36: ConsiderarAsNoTracking()para consultas read-only.Os métodos de consulta (
GetByIdAsync,GetActiveByProviderIdAsync, etc.) podem se beneficiar deAsNoTracking()quando a entidade não será modificada no mesmo contexto, melhorando performance.♻️ Exemplo com AsNoTracking
public async Task<Subscription?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default) { - return await context.Subscriptions.FirstOrDefaultAsync(s => s.Id == id, cancellationToken); + return await context.Subscriptions + .AsNoTracking() + .FirstOrDefaultAsync(s => s.Id == id, cancellationToken); }Nota: Se a entidade for modificada após a consulta (como em
ProcessInboxJob), o tracking é necessário, então avalie caso a caso.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs` around lines 11 - 36, The repository query methods (GetByIdAsync, GetActiveByProviderIdAsync, GetLatestByProviderIdAsync, GetByExternalIdAsync) should use AsNoTracking() for read-only access to avoid change-tracking overhead: update each LINQ chain to call AsNoTracking() before FirstOrDefaultAsync/OrderByDescending when the returned Subscription won't be updated in the same DbContext; keep tracking for any code paths that later modify the entity (e.g., referenced in ProcessInboxJob) and do not change those usages.src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs (2)
53-65:CancelSubscriptionAsyncsilencia erros sem logging.Quando
StripeExceptioné capturada, o método retornafalsesem registrar o erro. Isso dificulta diagnóstico em produção. Considere adicionar logging.📝 Sugestão com logging
+ private readonly ILogger<StripePaymentGateway> _logger; + - public StripePaymentGateway(IConfiguration configuration) + public StripePaymentGateway(IConfiguration configuration, ILogger<StripePaymentGateway> logger) { + _logger = logger; // ... existing code } public async Task<bool> CancelSubscriptionAsync(string externalSubscriptionId, CancellationToken cancellationToken) { try { var service = new SubscriptionService(); await service.CancelAsync(externalSubscriptionId, cancellationToken: cancellationToken); return true; } catch (StripeException ex) { + _logger.LogError(ex, "Failed to cancel Stripe subscription {SubscriptionId}", externalSubscriptionId); return false; } }Based on learnings: "Enforce English log messages across all C# files."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs` around lines 53 - 65, The CancelSubscriptionAsync method swallows StripeException without logging; update CancelSubscriptionAsync to catch StripeException (and optionally Exception) and log the error in English before returning false—use the existing logger (or inject an ILogger if none) and include context such as externalSubscriptionId and exception details; keep the SubscriptionService usage and return behavior but ensure the log message is in English and contains the exception message/stack to aid debugging.
34-35: URLs de callback hardcoded.
SuccessUrleCancelUrlestão fixos no código. Isso dificulta testes e ambientes diferentes (staging, produção). Considere extrair para configuração.♻️ Sugestão de configuração
+ private readonly string _successUrl; + private readonly string _cancelUrl; + public StripePaymentGateway(IConfiguration configuration) { _apiKey = configuration["Stripe:ApiKey"] ?? throw new ArgumentNullException("Stripe:ApiKey is missing"); + _successUrl = configuration["Stripe:SuccessUrl"] ?? "https://meajudaai.com.br/payments/success?session_id={CHECKOUT_SESSION_ID}"; + _cancelUrl = configuration["Stripe:CancelUrl"] ?? "https://meajudaai.com.br/payments/cancel"; StripeConfiguration.ApiKey = _apiKey; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs` around lines 34 - 35, Os URLs de callback (SuccessUrl e CancelUrl) estão hardcoded na classe StripePaymentGateway; extraia-os para configuração. Atualize StripePaymentGateway para receber os valores via dependência (IConfiguration ou um Options/Settings POCO, ex. PaymentsOptions com SuccessUrl e CancelUrl) no construtor, use essas propriedades ao montar a sessão de checkout (onde SuccessUrl/CancelUrl são atribuídos) e forneça valores padrão ou lance erro claro se não configurados; também atualize a configuração/DI para mapear as chaves (ex. "Payments:SuccessUrl"/"Payments:CancelUrl") por ambiente (dev/staging/prod).src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs (2)
83-87: Comentário deve estar em português.Conforme a política de linguagem do repositório, comentários no código devem ser escritos em português.
📝 Correção sugerida
if (subscription.Status == ESubscriptionStatus.Active && subscription.ExternalSubscriptionId == session.SubscriptionId) { - // Already processed + // Já processado anteriormente break; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs` around lines 83 - 87, O comentário em inglês "// Already processed" na condição que verifica subscription.Status == ESubscriptionStatus.Active && subscription.ExternalSubscriptionId == session.SubscriptionId (no ProcessInboxJob.cs, local onde as variáveis subscription e session são usadas) deve ser alterado para português; substitua pelo comentário equivalente em PT-BR, por exemplo "// Já processado" ou outra formulação em português alinhada ao padrão do repositório, mantendo a lógica e posição do comentário junto à verificação de ESubscriptionStatus.Active e ExternalSubscriptionId == session.SubscriptionId.
99-111: Considere adicionar log quando a assinatura não for encontrada.Diferente do caso
checkout.session.completed(linha 79), aqui não há log de warning quando a assinatura não é encontrada. Isso pode dificultar a investigação de problemas.📝 Sugestão
if (stripeSubscription != null) { var subscription = await repository.GetByExternalIdAsync(stripeSubscription.Id, ct); if (subscription != null) { subscription.Cancel(); await repository.UpdateAsync(subscription, ct); logger.LogInformation("Subscription {Id} canceled (external ID: {ExternalId})", subscription.Id, stripeSubscription.Id); } + else + { + logger.LogWarning("Subscription not found for external ID {ExternalId}", stripeSubscription.Id); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs` around lines 99 - 111, No bloco do case "customer.subscription.deleted", quando repository.GetByExternalIdAsync(stripeSubscription.Id, ct) retorna null não há log; adicione um logger.LogWarning (ou similar) imediatamente no caminho em que subscription == null para registrar que a assinatura não foi encontrada, incluindo o external id (stripeSubscription.Id) e contexto ("customer.subscription.deleted") para facilitar investigação; mantenha o restante (Cancel/Update/LogInformation) inalterado e use os mesmos identificadores stripeSubscription, repository.GetByExternalIdAsync e logger para localizar o ponto a ser alterado.src/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cs (1)
59-67: EntidadeInboxMessagedeveria estar em arquivo separado.A classe
InboxMessageestá definida no mesmo arquivo quePaymentsDbContext, violando o princípio de responsabilidade única. Recomenda-se mover paraDomain/Entities/InboxMessage.csouInfrastructure/Persistence/Entities/InboxMessage.cspara manter consistência com outras entidades do módulo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cs` around lines 59 - 67, A classe InboxMessage está definida inline no arquivo que contém PaymentsDbContext; mova a entidade para um arquivo separado (por exemplo Domain/Entities/InboxMessage.cs ou Infrastructure/Persistence/Entities/InboxMessage.cs), mantendo o mesmo namespace usado por outras entidades do módulo; atualize quaisquer usings/referências para que PaymentsDbContext continue a usar InboxMessage e remova a definição antiga do arquivo do DbContext; garanta que a classe mantenha public, propriedades atuais (Id, Type, Content, CreatedAt, ProcessedAt, Error) e execute build/refs para ajustar o projeto se necessário.tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs (1)
117-118: Delay fixo pode causar testes flaky.
Task.Delay(2000)é uma abordagem frágil - pode ser insuficiente em CI sobrecarregado ou desnecessariamente lento em ambiente local. Considere usar polling com timeout.🔧 Sugestão com polling
- // 3. Give the background worker a moment to process the Inbox - await Task.Delay(2000); + // 3. Aguarda o processamento do Inbox com polling + var maxWait = TimeSpan.FromSeconds(10); + var pollInterval = TimeSpan.FromMilliseconds(200); + var sw = System.Diagnostics.Stopwatch.StartNew(); + + Domain.Entities.Subscription? subscription = null; + while (sw.Elapsed < maxWait) + { + await _fixture.WithServiceScopeAsync(async services => + { + var dbContext = services.GetRequiredService<PaymentsDbContext>(); + subscription = await dbContext.Subscriptions + .FirstOrDefaultAsync(s => s.ProviderId == providerId && s.Status == ESubscriptionStatus.Active); + }); + + if (subscription != null) break; + await Task.Delay(pollInterval); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs` around lines 117 - 118, No use of fixed Task.Delay(2000) in PaymentsEndToEndTests; replace it with a polling wait-with-timeout that repeatedly checks the real completion condition (e.g., database flag, inbox queue empty, or an endpoint indicating processing finished) instead of sleeping. Implement or reuse a helper like WaitForConditionAsync/WaitUntilAsync in the PaymentsEndToEndTests class to poll at short intervals (e.g., 100–500ms) up to a configurable timeout and assert failure if the timeout elapses; update the test to call this helper where Task.Delay(2000) is now used so CI flakiness is avoided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cs`:
- Around line 185-204: Update the two tests
ProcessPendingMessagesAsync_WhenSmsSuccess_ShouldMarkAsSentAndLog and the
corresponding Push test to assert that a success log entry was written by
verifying _logRepositoryMock.AddAsync(...) was called once with an Outbox log
(or expected log DTO); specifically add _logRepositoryMock.Verify(x =>
x.AddAsync(It.IsAny<LogEntryType>(), It.IsAny<CancellationToken>()), Times.Once)
(replace LogEntryType with the actual log entity type used by the service) after
the existing assertions so the tests validate both status change and that the
success log was recorded.
In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs`:
- Around line 20-24: The current creation of CreateSubscriptionCommand uses
request.Currency ?? "BRL" which only handles null and will allow empty or
whitespace currency values; update the endpoint to normalize request.Currency
using string.IsNullOrWhiteSpace (e.g., use
string.IsNullOrWhiteSpace(request.Currency) ? "BRL" : request.Currency) before
constructing CreateSubscriptionCommand so CreateSubscriptionEndpoint passes a
valid currency to CreateSubscriptionCommand.
In `@src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs`:
- Around line 46-50: Before calling EventUtility.ConstructEvent, validate the
webhookSecret string (the value used when building stripeEvent) to ensure it is
not null/empty; if it is missing, log a clear error mentioning
"Stripe:WebhookSecret" and short-circuit the request (return an appropriate HTTP
error or throw a descriptive exception) instead of passing a null/empty secret
into EventUtility.ConstructEvent; update the StripeWebhookEndpoint handler to
check webhookSecret, log details, and return early so ConstructEvent is only
called with a valid secret.
- Around line 60-63: No handler should return internal stack traces; in
StripeWebhookEndpoint's catch block remove e.StackTrace from the public response
and return a generic error payload (e.g., { error = "Internal server error" })
while injecting ILogger<StripeWebhookEndpoint> via constructor/DI and logging
the full exception (e) internally with logger.LogError(e, "...") so the stack
trace is retained only in logs; update the StripeWebhookEndpoint class to accept
ILogger and replace the Results.InternalServerError(...) to return a
non-sensitive message.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 34-36: No método CreateSubscriptionCommandHandler: atualmente você
persiste a entidade chamando subscriptionRepository.AddAsync(subscription,
cancellationToken) antes de validar result.CheckoutUrl, o que pode gravar
assinaturas com estado inválido; altere a ordem para primeiro verificar
result.CheckoutUrl (por exemplo: if
(string.IsNullOrWhiteSpace(result.CheckoutUrl)) throw new Exception(...)), só
então chamar subscriptionRepository.AddAsync(...) e retornar result.CheckoutUrl;
referencie explicitamente result.CheckoutUrl, subscriptionRepository.AddAsync e
a própria CreateSubscriptionCommandHandler ao aplicar a mudança.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 41-55: The loop in ProcessInboxJob that processes inbox messages
(the foreach over messages calling ProcessStripeEventAsync) currently sets
message.Error but never increments a retry counter or marks messages as
processed, allowing perpetual failures; add retry control to the InboxMessage
entity (e.g., RetryCount and MaxRetries or NextAttemptAt) and update the
processing logic: on success set ProcessedAt, on failure increment RetryCount,
set NextAttemptAt to now + backoff (or mark as poisoned when RetryCount >=
MaxRetries) and persist these fields; also update the query that loads messages
in ProcessInboxJob to ignore messages with RetryCount >= MaxRetries or where
NextAttemptAt is in the future so failed messages are skipped or retried with
backoff instead of looping forever.
In `@src/Modules/Payments/Infrastructure/Extensions.cs`:
- Around line 31-35: O trecho atual só chama options.UseNpgsql quando connStr
não é nulo, o que faz o erro só ocorrer em runtime; altere a extensão que
configura o DbContext para validar connStr antecipadamente (variável connStr
usada na chamada options.UseNpgsql) e lançar uma InvalidOperationException ou
ArgumentException com mensagem clara quando estiver nula/whitespace, garantindo
fail-fast ao registrar o provedor de EF (refira-se ao bloco que contém
options.UseNpgsql(...) no método de extensão em Extensions.cs).
In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs`:
- Around line 12-16: StripeConfiguration.ApiKey is a static property and setting
it in the StripePaymentGateway constructor (_apiKey /
StripeConfiguration.ApiKey) can cause race conditions when multiple instances or
parallel tests run; instead remove global assignment and switch to per-request
authentication by creating and storing a Stripe.RequestOptions instance (e.g.,
_requestOptions) initialized with the API key and pass that RequestOptions into
each Stripe client call (e.g., service.CreateAsync(options, _requestOptions,
...)) or alternatively ensure StripePaymentGateway is registered as a singleton
so the static assignment is only done once.
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs`:
- Around line 49-54: A configuração EF Core está faltando para a propriedade
UpdatedAt da entidade PaymentTransaction (herdada de BaseEntity), então
alterações feitas por MarkAsUpdated() em Settle()/Fail() não serão persistidas;
abra a classe PaymentTransactionConfiguration e adicione a configuração para
builder.Property(t => t.UpdatedAt).HasColumnName("updated_at") (seguir o mesmo
mapeamento usado em SubscriptionConfiguration para UpdatedAt), ajustando
IsRequired()/nullable conforme o padrão usado lá.
In `@src/Shared/Domain/ValueObjects/Money.cs`:
- Around line 3-8: Money currently allows invalid state (empty/null Currency and
negative Amount); add invariant checks so Currency is not null/empty and Amount
is non-negative. Enforce this by adding a validating constructor or validation
logic inside the record (e.g., a custom ctor or validation method) and update
the factory helpers Money.Zero and Money.FromDecimal to use that validated path;
throw ArgumentNullException/ArgumentException when invariants fail and ensure
all creation flows (including the primary record instantiation) go through the
validation.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs`:
- Around line 80-83: A requisição de criação de assinatura (variável
createRequest) está faltando o campo Currency; ajuste o objeto anônimo usado em
PaymentsEndToEndTests (createRequest) para incluir Currency = "BRL" juntamente
com ProviderId, PlanId e Amount para que a payload coincida com o primeiro teste
e com a validação do endpoint.
- Around line 120-128: The test currently only asserts the subscription exists;
update the assertions in the Should_ActivateSubscription test (inside the Using
scope where you fetch the PaymentsDbContext and the subscription variable) to
also verify that subscription.Status equals SubscriptionStatus.Active and that
subscription.ExternalSubscriptionId is not null or empty (or non-whitespace).
Use the existing subscription variable and PaymentsDbContext to add these checks
so the test validates activation and external id population in addition to
existence.
In `@tests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs`:
- Around line 41-75: The test StripeWebhook_ShouldReturnOk is asserting that the
response can be OK or BadRequest which hides contract regressions; update the
test so its expectation matches its name: after calling
Client.PostAsJsonAsync("/api/v1/payments/webhooks/stripe", webhookPayload)
assert that response.StatusCode equals HttpStatusCode.OK only (or alternatively
rename the test to reflect allowing BadRequest if that behavior is intended) and
remove the BeOneOf(HttpStatusCode.OK, HttpStatusCode.BadRequest) check so the
test fails on unexpected BadRequest responses.
---
Nitpick comments:
In `@src/Modules/Payments/API/Endpoints/PaymentsEndpoints.cs`:
- Line 12: Extrair os literais "payments" e "Payments" usados em
BaseEndpoint.CreateVersionedGroup(app, "payments", "Payments") para constantes
compartilhadas (por exemplo em Shared.Contracts como PaymentsRoute e PaymentsTag
ou um enum/const class ModuleRoutes/ModuleTags), importar esse módulo e
substituir a chamada por BaseEndpoint.CreateVersionedGroup(app, PaymentsRoute,
PaymentsTag); atualizar quaisquer usos semelhantes no projeto para referenciar
as mesmas constantes para evitar drift entre camadas e facilitar refactors.
In
`@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj`:
- Around line 23-24: Remova a referência PackageReference Include="Npgsql" do
csproj da camada Application (remover a entrada PackageReference
Include="Npgsql") e mova essa dependência para o csproj da camada
Infrastructure; verifique dentro do projeto Application (classes/serviços) que
nenhum tipo específico de Npgsql está sendo usado e, se houver, substitua por
abstrações/DTOs ou interfaces da camada de domínio/infrastructure; após a
alteração execute restore/build para garantir que a dependência exista apenas na
camada Infrastructure.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 25-28: Replace the generic Exception thrown in
CreateSubscriptionCommandHandler when result.Success is false with a specific
application/domain exception (e.g., SubscriptionCreationException or a
BadRequest/ApplicationOperationException) so callers can map it to 4xx/5xx
consistently; change both occurrences where you currently throw new
Exception($"Failed to create subscription: {result.ErrorMessage}") to throw the
new specific exception type and pass the result.ErrorMessage (and optionally an
inner exception or error code) so the handler and error middleware can
distinguish domain validation/expected failures from unexpected server errors.
In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs`:
- Around line 26-39: Os métodos Settle(...) e Fail() permitem transições de
estado inválidas porque não verificam o estado atual (EPaymentStatus); adicione
guardas de transição em Settle and Fail que validem o estado atual antes de
alterar Status, por exemplo rejeitando Settle se Status já for
Succeeded/Cancelled and rejeitando Fail se Status já for Failed/Succeeded,
lançando uma exceção de domínio (ou retornando um resultado de erro) quando a
transição for inválida; preserve a atribuição de ExternalTransactionId,
ProcessedAt e chamada MarkAsUpdated() somente após a validação passar.
- Around line 11-18: The PaymentTransaction constructor is overwriting the Id
set by BaseEntity (which uses UuidGenerator.NewId()); remove the redundant
assignment "Id = Guid.NewGuid()" from the PaymentTransaction(Guid
subscriptionId, Money amount) constructor so the entity inherits the project's
standard UUID generation, leaving SubscriptionId, Amount, and Status
initialization intact.
In `@src/Modules/Payments/Domain/Entities/Subscription.cs`:
- Around line 11-19: O construtor Subscription está atribuindo Id =
Guid.NewGuid(), o que sobrescreve o Id já inicializado pelo
AggregateRoot<Guid>/BaseEntity (via UuidGenerator.NewId()); remova a atribuição
explícita de Id dentro do construtor Subscription para deixar a geração do
identificador ao BaseEntity/AggregateRoot (mesma correção aplicada em
PaymentTransaction), mantendo apenas a inicialização das outras propriedades
(ProviderId, PlanId, Amount, Status).
- Around line 50-54: The UpdateStatus(ESubscriptionStatus) method on
Subscription bypasses the specialized transition methods (Activate, Cancel,
Expire) and should either be removed or constrained; to fix, either delete
UpdateStatus to force callers to use Activate/Cancel/Expire, or make
UpdateStatus private/internal and implement explicit allowed-transition checks
(or dispatch to the corresponding methods) so state transitions go through
Activate(), Cancel(), or Expire() and still call MarkAsUpdated() as needed;
locate the Subscription class and the UpdateStatus, Activate, Cancel, Expire,
and MarkAsUpdated members to apply the chosen change.
In `@src/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csproj`:
- Around line 10-18: The project currently declares InternalsVisibleTo for
MeAjudaAi.Modules.Payments.Tests, MeAjudaAi.Modules.Payments.Infrastructure and
MeAjudaAi.Modules.Payments.Application; remove the two production entries
(MeAjudaAi.Modules.Payments.Infrastructure and
MeAjudaAi.Modules.Payments.Application) leaving only the Tests visibility, and
instead make the required domain API surface explicit by converting internal
types/members used by Infrastructure/Application into public types or
well-defined public interfaces (or factory/DTO classes) so Consumers use
explicit contracts rather than relying on InternalsVisibleTo.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 83-87: O comentário em inglês "// Already processed" na condição
que verifica subscription.Status == ESubscriptionStatus.Active &&
subscription.ExternalSubscriptionId == session.SubscriptionId (no
ProcessInboxJob.cs, local onde as variáveis subscription e session são usadas)
deve ser alterado para português; substitua pelo comentário equivalente em
PT-BR, por exemplo "// Já processado" ou outra formulação em português alinhada
ao padrão do repositório, mantendo a lógica e posição do comentário junto à
verificação de ESubscriptionStatus.Active e ExternalSubscriptionId ==
session.SubscriptionId.
- Around line 99-111: No bloco do case "customer.subscription.deleted", quando
repository.GetByExternalIdAsync(stripeSubscription.Id, ct) retorna null não há
log; adicione um logger.LogWarning (ou similar) imediatamente no caminho em que
subscription == null para registrar que a assinatura não foi encontrada,
incluindo o external id (stripeSubscription.Id) e contexto
("customer.subscription.deleted") para facilitar investigação; mantenha o
restante (Cancel/Update/LogInformation) inalterado e use os mesmos
identificadores stripeSubscription, repository.GetByExternalIdAsync e logger
para localizar o ponto a ser alterado.
In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs`:
- Around line 53-65: The CancelSubscriptionAsync method swallows StripeException
without logging; update CancelSubscriptionAsync to catch StripeException (and
optionally Exception) and log the error in English before returning false—use
the existing logger (or inject an ILogger if none) and include context such as
externalSubscriptionId and exception details; keep the SubscriptionService usage
and return behavior but ensure the log message is in English and contains the
exception message/stack to aid debugging.
- Around line 34-35: Os URLs de callback (SuccessUrl e CancelUrl) estão
hardcoded na classe StripePaymentGateway; extraia-os para configuração. Atualize
StripePaymentGateway para receber os valores via dependência (IConfiguration ou
um Options/Settings POCO, ex. PaymentsOptions com SuccessUrl e CancelUrl) no
construtor, use essas propriedades ao montar a sessão de checkout (onde
SuccessUrl/CancelUrl são atribuídos) e forneça valores padrão ou lance erro
claro se não configurados; também atualize a configuração/DI para mapear as
chaves (ex. "Payments:SuccessUrl"/"Payments:CancelUrl") por ambiente
(dev/staging/prod).
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs`:
- Around line 39-40: The current configuration creates separate indexes for
ProcessedAt and CreatedAt; add a composite index on (ProcessedAt, CreatedAt) in
InboxMessageConfiguration by replacing or augmenting the existing
builder.HasIndex calls to use builder.HasIndex(m => new { m.ProcessedAt,
m.CreatedAt }) so that queries from ProcessInboxJob that filter by ProcessedAt
and order by CreatedAt can use the composite index; also ensure you add a
corresponding EF migration to apply the new composite index to the database.
In `@src/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cs`:
- Around line 59-67: A classe InboxMessage está definida inline no arquivo que
contém PaymentsDbContext; mova a entidade para um arquivo separado (por exemplo
Domain/Entities/InboxMessage.cs ou
Infrastructure/Persistence/Entities/InboxMessage.cs), mantendo o mesmo namespace
usado por outras entidades do módulo; atualize quaisquer usings/referências para
que PaymentsDbContext continue a usar InboxMessage e remova a definição antiga
do arquivo do DbContext; garanta que a classe mantenha public, propriedades
atuais (Id, Type, Content, CreatedAt, ProcessedAt, Error) e execute build/refs
para ajustar o projeto se necessário.
In `@src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs`:
- Around line 11-36: The repository query methods (GetByIdAsync,
GetActiveByProviderIdAsync, GetLatestByProviderIdAsync, GetByExternalIdAsync)
should use AsNoTracking() for read-only access to avoid change-tracking
overhead: update each LINQ chain to call AsNoTracking() before
FirstOrDefaultAsync/OrderByDescending when the returned Subscription won't be
updated in the same DbContext; keep tracking for any code paths that later
modify the entity (e.g., referenced in ProcessInboxJob) and do not change those
usages.
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs`:
- Around line 31-33: O teste está usando It.IsAny for CreateSubscriptionAsync,
deixando passar mapeamentos errados; update the assertions to verify the exact
arguments passed to the gateway and the persisted currency by replacing It.IsAny
with argument matchers (e.g. It.Is<Guid>(id => id == expectedPlanId),
It.Is<Money>(m => m.Amount == expectedAmount && m.Currency == expectedCurrency))
when setting up/Verifying _gatewayMock.CreateSubscriptionAsync in
CreateSubscriptionCommandHandlerTests and the other similar setup at lines
40-43, and add/verifiy an assertion against the repository/entity save to ensure
the stored subscription.Currency equals expectedCurrency.
In `@src/Shared/Utilities/UserRoles.cs`:
- Line 84: The provider-tier string constants in the UserRoles class
(ProviderStandard, ProviderSilver, ProviderGold, ProviderPlatinum) should be
redirected to the central RoleConstants (or the Shared.Contracts enum/constant
source) just like Customer was; replace each provider-tier literal with the
corresponding RoleConstants.* entry (or the Shared.Contracts symbol) so all role
values come from one source of truth and keep Web aligned with backend/shared
code.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs`:
- Around line 117-118: No use of fixed Task.Delay(2000) in
PaymentsEndToEndTests; replace it with a polling wait-with-timeout that
repeatedly checks the real completion condition (e.g., database flag, inbox
queue empty, or an endpoint indicating processing finished) instead of sleeping.
Implement or reuse a helper like WaitForConditionAsync/WaitUntilAsync in the
PaymentsEndToEndTests class to poll at short intervals (e.g., 100–500ms) up to a
configurable timeout and assert failure if the timeout elapses; update the test
to call this helper where Task.Delay(2000) is now used so CI flakiness is
avoided.
In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Line 185: O teste está registrando MockPaymentGateway sem remover a
implementação real de IPaymentGateway, o que deixa múltiplas implementações no
container; antes de chamar services.AddScoped<IPaymentGateway,
MockPaymentGateway>() remova quaisquer ServiceDescriptor existentes para
ServiceType == typeof(IPaymentGateway) (ou use services.Replace com um
ServiceDescriptor scoped) para garantir que apenas MockPaymentGateway seja
resolvido em testes; altere o bloco que registra MockPaymentGateway para
primeiro limpar/ substituir o registro existente de IPaymentGateway e então
adicionar o mock.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3834a257-a1bb-47d9-abaa-ae2ec822e4bf
⛔ Files ignored due to path filters (22)
src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/PaymentsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.ApiService.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Architecture.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.E2E.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Integration.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Shared.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (51)
Directory.Packages.propsMeAjudaAi.slnxinfrastructure/keycloak/realms/meajudaai-realm.dev.jsonsrc/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csprojsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cssrc/Modules/Communications/Application/Services/Email/StubEmailService.cssrc/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cssrc/Modules/Payments/API/Endpoints/PaymentsEndpoints.cssrc/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/API/Extensions.cssrc/Modules/Payments/API/MeAjudaAi.Modules.Payments.API.csprojsrc/Modules/Payments/Application/Extensions.cssrc/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csprojsrc/Modules/Payments/Application/Subscriptions/Commands/CreateSubscriptionCommand.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Domain/Abstractions/IPaymentGateway.cssrc/Modules/Payments/Domain/Entities/PaymentTransaction.cssrc/Modules/Payments/Domain/Entities/Subscription.cssrc/Modules/Payments/Domain/Enums/EPaymentStatus.cssrc/Modules/Payments/Domain/Enums/ESubscriptionStatus.cssrc/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csprojsrc/Modules/Payments/Domain/Repositories/ISubscriptionRepository.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Infrastructure/Extensions.cssrc/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cssrc/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csprojsrc/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/SubscriptionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cssrc/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cssrc/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csprojsrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cssrc/Modules/Ratings/Infrastructure/Extensions.cssrc/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cssrc/Shared/Domain/ValueObjects/Money.cssrc/Shared/Utilities/Constants/RoleConstants.cssrc/Shared/Utilities/UserRoles.cstests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csprojtests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cstests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cstests/MeAjudaAi.E2E.Tests/MeAjudaAi.E2E.Tests.csprojtests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csprojtests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
💤 Files with no reviewable changes (2)
- src/Modules/Communications/Application/Services/Email/StubEmailService.cs
- src/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cs
…ground job for subscription management
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs (1)
132-135:⚠️ Potential issue | 🟡 MinorFortaleça a asserção do
ExternalSubscriptionId.Validar apenas “não vazio” pode deixar passar mapeamento incorreto; aqui dá para validar exatamente contra
externalSubId.✅ Ajuste sugerido
- subscription.ExternalSubscriptionId.Should().NotBeNullOrWhiteSpace(); + subscription.ExternalSubscriptionId.Should().Be(externalSubId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs` around lines 132 - 135, Aserte que o ExternalSubscriptionId corresponde exatamente ao valor esperado em vez de apenas não ser vazio: substituir a asserção subscription.ExternalSubscriptionId.Should().NotBeNullOrWhiteSpace() por uma asserção de igualdade contra a variável esperada (externalSubId) no teste PaymentsEndToEndTests (onde a variável subscription e o enum ESubscriptionStatus.Active são verificados) para garantir mapeamento correto do ID externo.
🧹 Nitpick comments (4)
src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cs (1)
60-77: O teste “BeforePublishing” não garante a ordem das chamadasHoje ele valida quantidade de chamadas, mas não prova que
GetAverageRatingForProviderAsyncocorreu antes dePublishAsync, apesar do nome do teste indicar isso.♻️ Ajuste sugerido com
MockSequence[Fact] public async Task HandleAsync_ShouldCalculateAverageRating_BeforePublishing() { // Arrange var providerId = Guid.NewGuid(); var domainEvent = new ReviewApprovedDomainEvent(Guid.NewGuid(), 0, providerId, 3, null); - _repositoryMock.Setup(r => r.GetAverageRatingForProviderAsync(providerId, It.IsAny<CancellationToken>())) - .ReturnsAsync((3.75m, 4)); + var sequence = new MockSequence(); + _repositoryMock.InSequence(sequence) + .Setup(r => r.GetAverageRatingForProviderAsync(providerId, It.IsAny<CancellationToken>())) + .ReturnsAsync((3.75m, 4)); + _messageBusMock.InSequence(sequence) + .Setup(m => m.PublishAsync(It.IsAny<ReviewApprovedIntegrationEvent>(), It.IsAny<string?>(), It.IsAny<CancellationToken>())) + .Returns(Task.CompletedTask); // Act await _handler.HandleAsync(domainEvent); // Assert _repositoryMock.Verify(r => r.GetAverageRatingForProviderAsync(providerId, It.IsAny<CancellationToken>()), Times.Once); _messageBusMock.Verify(m => m.PublishAsync( It.Is<ReviewApprovedIntegrationEvent>(e => e.NewAverageRating == 3.75m && e.TotalReviews == 4), It.IsAny<string?>(), It.IsAny<CancellationToken>()), Times.Once); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cs` around lines 60 - 77, The test must assert call order: wrap the repository and message bus setups in a MockSequence and configure _repositoryMock to expect GetAverageRatingForProviderAsync(providerId, It.IsAny<CancellationToken>()) first and _messageBusMock to expect PublishAsync(It.Is<ReviewApprovedIntegrationEvent>(...), It.IsAny<string?>(), It.IsAny<CancellationToken>()) after; then run _handler.HandleAsync(domainEvent) and verify the sequence-based expectations so the test ensures GetAverageRatingForProviderAsync runs before PublishAsync (use MockSequence and SetupSequence/Setup in order on _repositoryMock and _messageBusMock to enforce this).src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs (1)
56-56: Ajuste de idioma no comentário inline.Troque o comentário para português para manter o padrão do repositório.
Based on learnings: "In all C# files, keep logging messages in English, but ensure comments and XML documentation ... are written in Portuguese across the codebase."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs` at line 56, Altere o comentário inline que acompanha a atribuição de message.NextAttemptAt dentro de ProcessInboxJob (a linha com "message.NextAttemptAt = DateTime.UtcNow.AddMinutes(Math.Pow(2, message.RetryCount));") substituindo o texto em inglês "Exponential backoff" por português, por exemplo "Retardo exponencial" ou "Backoff exponencial", mantendo o restante do código inalterado.src/Modules/Payments/Infrastructure/Extensions.cs (1)
24-29: Evite credencial hardcoded no fallback de conexão.Mesmo em ambiente de bypass, manter usuário/senha no código aumenta risco de exposição e dificulta rotação.
🔧 Refactor sugerido
- if (string.IsNullOrWhiteSpace(connStr) && MeAjudaAi.Shared.Utilities.EnvironmentHelpers.IsSecurityBypassEnvironment(environment)) - { -#pragma warning disable S2068 - connStr = "Host=localhost;Port=5432;Database=meajudaai_test;Username=postgres;Password=test"; -#pragma warning restore S2068 - } + if (string.IsNullOrWhiteSpace(connStr) && MeAjudaAi.Shared.Utilities.EnvironmentHelpers.IsSecurityBypassEnvironment(environment)) + { + connStr = resolvedConfig.GetConnectionString("PaymentsBypass") + ?? resolvedConfig["Payments:BypassConnectionString"]; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Extensions.cs` around lines 24 - 29, O trecho que define uma connection string com credenciais hardcoded (connStr assignment inside the IsSecurityBypassEnvironment check) deve ser removido; em vez disso, readicione uma fallback que obtém usuário/senha/host/port/database de variáveis de ambiente ou de um secret provider (use EnvironmentHelpers.IsSecurityBypassEnvironment to gate the fallback), ou lance uma exceção clara se as variáveis obrigatórias estiverem ausentes; atualize o método/logic onde connStr é montada (referência: connStr variable and MeAjudaAi.Shared.Utilities.EnvironmentHelpers.IsSecurityBypassEnvironment) para construir a connection string dinamicamente a partir das variáveis de ambiente/secrets e não incluir valores literais no código.tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs (1)
62-127: Padronize comentários não-AAA em português neste teste.Os cabeçalhos AAA podem ficar em inglês, mas os demais comentários deste arquivo podem seguir português para manter consistência do repositório.
Based on learnings: "Adopt a language policy across the codebase: ... Comments and XML summaries ... are written in Portuguese" e "em código de testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs` around lines 62 - 127, Padronize os comentários não-AAA neste teste para português mantendo os cabeçalhos AAA em inglês: edite o método StripeWebhook_CheckoutSessionCompleted_Should_ActivateSubscription e substitua comentários como "// 1. Create a pending subscription first", "// 2. Prepare mock Stripe Webhook payload with required Stripe.net fields", "// Act - Send webhook" (mantenha "Act" se desejar) e "// 3. Poll until the background worker processes the Inbox (max 10s)" por equivalentes em português (ex.: "// 1. Criar uma subscription pendente", "// 2. Preparar payload do webhook Stripe com campos necessários do Stripe.net", "// Act - Enviar webhook" ou "// Ação - Enviar webhook" se preferir manter AAA, "// 3. Pollar até o worker processar o Inbox (máx. 10s)"). Verifique também comentários finais antes de Asserts e atualize para português mantendo nomes de variáveis e símbolos (providerId, webhookPayload, WaitForConditionAsync, PaymentsDbContext) inalterados.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 21-25: O código chama paymentGateway.CreateSubscriptionAsync antes
de persistir via repository.AddAsync, o que pode deixar uma subscrição externa
ativa se a persistência falhar; altere para primeiro criar um registro local em
estado "pending"/"creating" (por exemplo na lógica de
CreateSubscriptionCommandHandler usando o repositório e AddAsync) dentro de uma
transação, depois chamar paymentGateway.CreateSubscriptionAsync e então
atualizar o registro para "active" com os dados retornados; alternativamente, se
não puder reordenar, envolva a chamada a repository.AddAsync em try/catch e no
catch chame um método de compensação como
paymentGateway.CancelSubscriptionAsync(result.Id) para cancelar a subscrição
externa; aplique a mesma correção ao segundo caso mencionado (linhas 41-43) para
evitar inconsistência entre gateway e banco.
- Around line 37-39: Os comentários em inglês no arquivo relacionados ao handler
CreateSubscriptionCommandHandler (ex.: comentário acima do método Handle/na
classe CreateSubscriptionCommandHandler) devem ser convertidos para português
mantendo a semântica original; localize as linhas com texto explicativo sobre
"checkout session", "ExternalSubscriptionId" e "Webhook" e traduza-os para
português (por exemplo: explicar que a sessão de checkout não cria a assinatura
no Stripe ainda, portanto normalmente não há ExternalSubscriptionId e que ele
será definido via webhook quando o pagamento for concluído). Garanta que
comentários XML summary/remarks em C# também fiquem em português e deixe
quaisquer mensagens de log (processLogger, logger, etc.) em inglês conforme a
convenção indicada.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 51-57: In ProcessInboxJob's catch block, don't treat
OperationCanceledException as a processing error: add a specific catch
(OperationCanceledException) (or check ex is OperationCanceledException) before
the generic catch so you do not increment message.RetryCount, set Error, or
schedule NextAttemptAt; instead log cancellation (e.g., logger.LogInformation)
and exit/return (or rethrow) to avoid rescheduling the message. Update the
exception handling around the existing logger.LogError/Retry logic to only run
for non-cancellation exceptions and keep references to message, logger and the
existing retry/backoff behavior intact.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cs`:
- Around line 115-116: O comentário acima da criação de domainEvent deve ser
traduzido para português mantendo o cabeçalho AAA em inglês; substitua o
comentário atual "Arrange - Reason is nullable based on the handler code using
?.Length" por uma versão em português mantendo "Arrange" como header (por
exemplo: "Arrange - Reason é anulável com base no código do handler que usa
?.Length"), atualize próximo ao uso de ReviewRejectedDomainEvent e da variável
domainEvent para refletir a mudança.
- Around line 57-75: The test
HandleAsync_ShouldHandleLongReason_TruncatingInDebugLog currently only verifies
LogLevel.Debug and not the actual truncated message; update the
_loggerMock.Verify call to inspect the logged state payload by replacing the
It.Is<It.IsAnyType>((v, t) => true) predicate with a predicate that converts the
state to string and asserts it contains the expected truncated preview (e.g.,
longReason.Substring(0, 100) + "..."); keep the rest of the Verify signature
(LogLevel.Debug, EventId, exception, formatter) and Times.Once, and invoke
_handler.HandleAsync(domainEvent) as before.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryTests.cs`:
- Around line 14-203: Os testes atuais em ReviewRepositoryTests só exercitam o
Mock<IReviewRepository> em vez da implementação real; substitua-os por testes de
integração do SUT ou renomeie a classe para deixar claro que são apenas testes
de contrato. Para corrigir, instancie um RatingsDbContext de teste (InMemory ou
SQLite in-memory), construa o ReviewRepository com esse contexto (classe
ReviewRepository) e refatore cada Fact para chamar os métodos reais (AddAsync,
GetByIdAsync, GetByProviderIdAsync, UpdateAsync, GetByProviderAndCustomerAsync,
GetAverageRatingForProviderAsync) verificando resultados no contexto em vez de
usar _repositoryMock.Setup/Verify; alternativamente, se quiser apenas validar o
contrato do interface, renomeie ReviewRepositoryTests para
ReviewRepositoryContractTests e mantenha os mocks.
---
Duplicate comments:
In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs`:
- Around line 132-135: Aserte que o ExternalSubscriptionId corresponde
exatamente ao valor esperado em vez de apenas não ser vazio: substituir a
asserção subscription.ExternalSubscriptionId.Should().NotBeNullOrWhiteSpace()
por uma asserção de igualdade contra a variável esperada (externalSubId) no
teste PaymentsEndToEndTests (onde a variável subscription e o enum
ESubscriptionStatus.Active são verificados) para garantir mapeamento correto do
ID externo.
---
Nitpick comments:
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Line 56: Altere o comentário inline que acompanha a atribuição de
message.NextAttemptAt dentro de ProcessInboxJob (a linha com
"message.NextAttemptAt = DateTime.UtcNow.AddMinutes(Math.Pow(2,
message.RetryCount));") substituindo o texto em inglês "Exponential backoff" por
português, por exemplo "Retardo exponencial" ou "Backoff exponencial", mantendo
o restante do código inalterado.
In `@src/Modules/Payments/Infrastructure/Extensions.cs`:
- Around line 24-29: O trecho que define uma connection string com credenciais
hardcoded (connStr assignment inside the IsSecurityBypassEnvironment check) deve
ser removido; em vez disso, readicione uma fallback que obtém
usuário/senha/host/port/database de variáveis de ambiente ou de um secret
provider (use EnvironmentHelpers.IsSecurityBypassEnvironment to gate the
fallback), ou lance uma exceção clara se as variáveis obrigatórias estiverem
ausentes; atualize o método/logic onde connStr é montada (referência: connStr
variable and
MeAjudaAi.Shared.Utilities.EnvironmentHelpers.IsSecurityBypassEnvironment) para
construir a connection string dinamicamente a partir das variáveis de
ambiente/secrets e não incluir valores literais no código.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cs`:
- Around line 60-77: The test must assert call order: wrap the repository and
message bus setups in a MockSequence and configure _repositoryMock to expect
GetAverageRatingForProviderAsync(providerId, It.IsAny<CancellationToken>())
first and _messageBusMock to expect
PublishAsync(It.Is<ReviewApprovedIntegrationEvent>(...), It.IsAny<string?>(),
It.IsAny<CancellationToken>()) after; then run _handler.HandleAsync(domainEvent)
and verify the sequence-based expectations so the test ensures
GetAverageRatingForProviderAsync runs before PublishAsync (use MockSequence and
SetupSequence/Setup in order on _repositoryMock and _messageBusMock to enforce
this).
In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs`:
- Around line 62-127: Padronize os comentários não-AAA neste teste para
português mantendo os cabeçalhos AAA em inglês: edite o método
StripeWebhook_CheckoutSessionCompleted_Should_ActivateSubscription e substitua
comentários como "// 1. Create a pending subscription first", "// 2. Prepare
mock Stripe Webhook payload with required Stripe.net fields", "// Act - Send
webhook" (mantenha "Act" se desejar) e "// 3. Poll until the background worker
processes the Inbox (max 10s)" por equivalentes em português (ex.: "// 1. Criar
uma subscription pendente", "// 2. Preparar payload do webhook Stripe com campos
necessários do Stripe.net", "// Act - Enviar webhook" ou "// Ação - Enviar
webhook" se preferir manter AAA, "// 3. Pollar até o worker processar o Inbox
(máx. 10s)"). Verifique também comentários finais antes de Asserts e atualize
para português mantendo nomes de variáveis e símbolos (providerId,
webhookPayload, WaitForConditionAsync, PaymentsDbContext) inalterados.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: be4e732b-76de-49bd-97d4-2249248a7e58
📒 Files selected for processing (31)
src/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cssrc/Modules/Payments/API/Endpoints/PaymentsEndpoints.cssrc/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csprojsrc/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Domain/Entities/InboxMessage.cssrc/Modules/Payments/Domain/Entities/PaymentTransaction.cssrc/Modules/Payments/Domain/Entities/Subscription.cssrc/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csprojsrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Infrastructure/Extensions.cssrc/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cssrc/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cssrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryTests.cssrc/Shared/Domain/ValueObjects/Money.cssrc/Shared/Utilities/Constants/RoleConstants.cssrc/Shared/Utilities/UserRoles.cstests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
✅ Files skipped from review due to trivial changes (8)
- src/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cs
- src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj
- src/Shared/Utilities/Constants/RoleConstants.cs
- src/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cs
- src/Shared/Domain/ValueObjects/Money.cs
- src/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cs
- src/Modules/Payments/Domain/Entities/PaymentTransaction.cs
- src/Shared/Utilities/UserRoles.cs
🚧 Files skipped from review as they are similar to previous changes (13)
- src/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csproj
- src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs
- tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs
- src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs
- src/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs
- tests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
- src/Modules/Payments/Domain/Entities/Subscription.cs
- src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs
- src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs
- src/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cs
- src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs
- src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs
- src/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cs
…ing via background job
…and add E2E/integration tests
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs (1)
180-180: Padronize literal decimal para evitar ambiguidade visual.Aqui vale usar
0mem vez de0para manter consistência com o tipo deaverage.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs` at line 180, No assert de média use literal decimal consistente com o tipo: substituir a chamada average.Should().Be(0) por average.Should().Be(0m) para evitar ambiguidade visual e casar com o tipo decimal; localize a asserção usando average.Should().Be e atualize o literal para 0m.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 67-71: No loop-level shutdowns should be logged as errors: in
ProcessInboxJob (the try/catch around the inbox processor loop) add an explicit
catch for OperationCanceledException/TaskCanceledException (or check
stoppingToken.IsCancellationRequested) and exit/return without calling
logger.LogError; keep the general catch (Exception ex) to log real errors, and
only await Task.Delay when not canceled. Ensure you reference the existing
logger.LogError call so you replace its invocation for cancellation cases with a
silent return/clean shutdown.
- Around line 81-84: The code silently skips activating subscriptions when
session?.Metadata lacks a valid "provider_id" (inside ProcessInboxJob's handler
for checkout.session.completed), causing messages to be marked processed without
error; update the handler so that if Metadata.TryGetValue("provider_id") is
false or Guid.TryParse fails you log a clear warning/error including identifying
info (e.g., session.Id or message id) and surface a failure (either throw a
specific exception or call the job/message retry/fail API instead of continuing)
so the message is not marked processed and can be retried or investigated;
ensure repository.GetLatestByProviderIdAsync(providerId, ct) is only called when
providerId is valid.
---
Nitpick comments:
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs`:
- Line 180: No assert de média use literal decimal consistente com o tipo:
substituir a chamada average.Should().Be(0) por average.Should().Be(0m) para
evitar ambiguidade visual e casar com o tipo decimal; localize a asserção usando
average.Should().Be e atualize o literal para 0m.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: edec0586-13ed-47f0-80d3-559234f0ae5b
⛔ Files ignored due to path filters (21)
src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/PaymentsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.ApiService.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Architecture.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.E2E.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Integration.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Shared.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (9)
src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cstests/MeAjudaAi.E2E.Tests/Base/Helpers/DbContextSchemaHelper.cstests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
✅ Files skipped from review due to trivial changes (2)
- tests/MeAjudaAi.E2E.Tests/Base/Helpers/DbContextSchemaHelper.cs
- src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs
🚧 Files skipped from review as they are similar to previous changes (5)
- src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cs
- src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs
- src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs
- tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs
- tests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
…view repository contract
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 11
♻️ Duplicate comments (2)
src/Shared/Domain/ValueObjects/Money.cs (1)
5-18:⚠️ Potential issue | 🟠 MajorAs invariantes de
Moneyainda podem ser burladas porinitpúblico.Mesmo com validação no construtor,
Amount/Currencypodem ser alterados em object initializer ouwith, reintroduzindo estado inválido.🔒 Ajuste sugerido
public record Money { - public decimal Amount { get; init; } - public string Currency { get; init; } + public decimal Amount { get; } + public string Currency { get; } public Money(decimal amount, string currency) { if (amount < 0) throw new ArgumentOutOfRangeException(nameof(amount), "Amount cannot be negative."); if (string.IsNullOrWhiteSpace(currency)) throw new ArgumentNullException(nameof(currency), "Currency cannot be null or empty."); Amount = amount; - Currency = currency; + Currency = currency.ToUpperInvariant(); }#!/bin/bash # Verifica usos que podem burlar invariantes de Money no estado atual rg -nP --type cs 'new\s+Money\s*\([^)]*\)\s*\{[^}]*\b(Amount|Currency)\b' rg -nP --type cs '\bwith\s*\{[^}]*\b(Amount|Currency)\b'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Domain/ValueObjects/Money.cs` around lines 5 - 18, The Money invariants are bypassable because Amount and Currency use public init; change Money to use read-only properties (remove init, e.g., public decimal Amount { get; } and public string Currency { get; }) and keep the constructor validation in the Money(decimal amount, string currency) to assign the fields, preventing object-initializer or with-expression mutation; update any call sites that rely on object-initializers/with to use the constructor or explicit factory methods instead.tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs (1)
74-75:⚠️ Potential issue | 🟡 MinorO nome do teste promete ativação, mas ele só valida a inbox.
Do jeito atual, o teste passa mesmo sem a assinatura ficar
Active. Se o job de background não for determinístico no E2E, o mais seguro é renomear o teste para refletir persistência na inbox; se for, vale fazer polling e então afirmar ativação/external id.Also applies to: 121-135
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs` around lines 74 - 75, O nome do teste StripeWebhook_CheckoutSessionCompleted_Should_ActivateSubscription promete que a assinatura ficará Active, mas o corpo só verifica que a mensagem foi persistida na inbox; atualize o teste para corresponder ao nome: ou (A) renomeie o método para algo como StripeWebhook_CheckoutSessionCompleted_Should_PersistToInbox se você só pretende validar a inbox, ou (B) torne-o determinístico adicionando polling/retentativas após a persistência da inbox para aguardar o job em background completar e então afirmar que a assinatura foi ativada (verificar Status == Active e ExternalId preenchido) — ajuste também o outro caso similar nas linhas 121-135 da mesma forma; localize pelos nomes dos testes/fixtures usados e implemente uma espera com timeout razoável e checagens repetidas antes dasserções finais.
🧹 Nitpick comments (14)
src/Shared/Utilities/Constants/RoleConstants.cs (1)
46-47: Migrar usages hardcoded de"customer"paraRoleConstants.Customer.A constante foi criada corretamente, mas ainda existem pontos em produção usando literal (
src/Modules/Users/Infrastructure/Services/LocalDevelopment/LocalDevelopmentAuthenticationDomainService.cs:1-100esrc/Modules/Users/Infrastructure/Events/Handlers/UserRegisteredDomainEventHandler.cs:1-80). Vale fechar essa migração agora para garantir consistência completa.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Utilities/Constants/RoleConstants.cs` around lines 46 - 47, Replace all hardcoded "customer" string literals with the new constant RoleConstants.Customer; specifically update usages inside LocalDevelopmentAuthenticationDomainService and UserRegisteredDomainEventHandler to reference RoleConstants.Customer, add the appropriate using/import for the namespace that contains RoleConstants if missing, and recompile to ensure no remaining literal occurrences remain.src/Modules/Payments/Domain/Entities/InboxMessage.cs (1)
6-7: Evite estado inválido inicial emTypeeContent.Com
null!+ setter público, a entidade pode circular sem dados obrigatórios e falhar só tardiamente (persistência/processamento). Prefira forçar invariantes na criação.💡 Sugestão de ajuste
public class InboxMessage { - public Guid Id { get; set; } = Guid.NewGuid(); - public string Type { get; set; } = null!; - public string Content { get; set; } = null!; + private InboxMessage() { } // EF + + public InboxMessage(string type, string content) + { + Type = string.IsNullOrWhiteSpace(type) + ? throw new ArgumentException("Type is required.", nameof(type)) + : type; + Content = string.IsNullOrWhiteSpace(content) + ? throw new ArgumentException("Content is required.", nameof(content)) + : content; + } + + public Guid Id { get; private set; } = Guid.NewGuid(); + public string Type { get; private set; } = string.Empty; + public string Content { get; private set; } = string.Empty;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/InboxMessage.cs` around lines 6 - 7, A entidade InboxMessage permite um estado inválido ao inicializar Type e Content com null! e setters públicos; force as invariantes na construção: remova o uso de null!, adicione um construtor que aceita string type e string content, valide não nulo/não vazio (lançando ArgumentNullException/ArgumentException) e atribua aos campos, e torne os setters de Type e Content privados ou readonly para impedir mutação externa após criação; referencie a classe InboxMessage e as propriedades Type e Content ao aplicar estas alterações.src/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cs (1)
3-3: Considere adicionar overload cominnerException.Isso preserva causa original em falhas de gateway e melhora troubleshooting.
♻️ Ajuste sugerido
-public class SubscriptionCreationException(string message) : Exception(message); +public class SubscriptionCreationException : Exception +{ + public SubscriptionCreationException(string message) : base(message) + { + } + + public SubscriptionCreationException(string message, Exception innerException) + : base(message, innerException) + { + } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cs` at line 3, A classe SubscriptionCreationException só tem um construtor que recebe message; adicione um overload que também aceite uma Exception innerException ande preserve a causa original; implemente um segundo construtor em SubscriptionCreationException(string message, Exception innerException) : base(message, innerException) para encaminhar o innerException à base, mantendo o construtor atual compatível e garantindo que lugares que lançam new SubscriptionCreationException possam repassar a exceção original para melhor troubleshooting.tests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cs (1)
251-252: Evite registro duplicado deIPaymentGatewayno container de testes.O mock é adicionado diretamente sem remover registro prévio; isso pode gerar resolução ambígua em cenários que consumem
IEnumerable<IPaymentGateway>.♻️ Ajuste sugerido
- services.AddScoped<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway, MockPaymentGateway>(); + var paymentGatewayDescriptors = services + .Where(d => d.ServiceType == typeof(MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway)) + .ToList(); + foreach (var d in paymentGatewayDescriptors) services.Remove(d); + services.AddScoped<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway, MockPaymentGateway>();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cs` around lines 251 - 252, O registro duplicado de IPaymentGateway (interface) adicionando MockPaymentGateway diretamente causa ambiguidade ao resolver IEnumerable<IPaymentGateway>; antes de chamar services.AddScoped<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway, MockPaymentGateway>() remova ou substitua registros anteriores para essa interface (por exemplo chamar services.RemoveAll<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway>() ou usar ServiceDescriptor.Replace/ServiceCollectionExtensions.Replace com ServiceDescriptor.Scoped(typeof(IPaymentGateway), typeof(MockPaymentGateway))) para garantir que apenas o mock seja registrado no TestContainerFixture.tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs (1)
185-187: Considere condicionar o mock de gateway ao módulo Payments.Hoje a substituição de
IPaymentGatewayé global; limitar ao caso em queRequiredModulesincluiPaymentsreduz acoplamento entre testes de módulos não relacionados.♻️ Ajuste sugerido
- var paymentGatewayDescriptors = services.Where(d => d.ServiceType == typeof(IPaymentGateway)).ToList(); - foreach (var descriptor in paymentGatewayDescriptors) services.Remove(descriptor); - services.AddScoped<IPaymentGateway, MockPaymentGateway>(); + if (RequiredModules.HasFlag(TestModule.Payments)) + { + var paymentGatewayDescriptors = services.Where(d => d.ServiceType == typeof(IPaymentGateway)).ToList(); + foreach (var descriptor in paymentGatewayDescriptors) services.Remove(descriptor); + services.AddScoped<IPaymentGateway, MockPaymentGateway>(); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs` around lines 185 - 187, The current setup always replaces IPaymentGateway with MockPaymentGateway globally; change it so the replacement runs only when the test's RequiredModules includes the Payments module. In the BaseApiTest configuration code (where IPaymentGateway and MockPaymentGateway are referenced), wrap the descriptor removal and services.AddScoped<IPaymentGateway, MockPaymentGateway>() calls in a conditional that checks RequiredModules contains Payments (or the equivalent module identifier your test uses), so tests that don't require Payments keep the original registration.src/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csproj (1)
21-21: AdicionePrivateAssetsao pacoteMicrosoft.EntityFrameworkCore.Design.Este é um pacote de tooling que não deve ser propagado aos consumidores. Siga o padrão já utilizado em outros módulos (Communications, Documents, Locations, etc.).
♻️ Ajuste sugerido
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csproj` at line 21, O PackageReference para Microsoft.EntityFrameworkCore.Design deve marcar esse pacote como tooling para não propagar aos consumidores; edite o elemento PackageReference que contém Include="Microsoft.EntityFrameworkCore.Design" e adicione o atributo/elemento PrivateAssets="all" (seguindo o padrão dos módulos Communications/Documents/Locations) para garantir que o pacote seja privado ao projeto de infraestrutura.src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj (2)
13-15:InternalsVisibleTopara Infrastructure enfraquece o boundary do módulo.Dar visibilidade de internals da Application para
MeAjudaAi.Modules.Payments.Infrastructuretende a criar acoplamento bidirecional. Prefira contratos explícitos/public API entre camadas.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj` around lines 13 - 15, The AssemblyAttribute granting InternalsVisibleTo from MeAjudaAi.Modules.Payments.Application to MeAjudaAi.Modules.Payments.Infrastructure creates a module boundary leak; remove the <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> entry (the InternalsVisibleTo for MeAjudaAi.Modules.Payments.Infrastructure) from MeAjudaAi.Modules.Payments.Application.csproj, make the required types/members in the Application layer public or define explicit contracts (interfaces/DTOs) exported by the Application layer, or move shared contracts into a dedicated Contracts/Shared project that both Application and Infrastructure reference, and update usages to consume those public contracts instead of relying on internals.
22-24: Evite dependência de EF Core na camada Application.A referência
Microsoft.EntityFrameworkCorena Application acopla regra de aplicação à infraestrutura. Mova esse pacote para Infrastructure e mantenha a Application dependente apenas de contratos/domínio.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj` around lines 22 - 24, The Application project file MeAjudaAi.Modules.Payments.Application.csproj currently references the concrete package Microsoft.EntityFrameworkCore; remove that PackageReference from the Application csproj and add it to the Infrastructure project instead, then refactor any usages in the Application layer to depend on domain contracts/abstractions (interfaces, DTOs, repository interfaces) rather than EF Core types so no EF namespaces remain in Application; finally run a restore/build and update any unit tests or DI registrations in Infrastructure to expose EF-backed implementations for the Application interfaces.src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs (1)
50-50: Ajuste de índice para fluxo de retry do inbox.O índice atual ajuda ordenação, mas o processamento filtra também
NextAttemptAt/retries. Vale considerar índice focado em mensagens pendentes para reduzir scan em produção.💡 Sugestão de ajuste
- builder.HasIndex(m => new { m.ProcessedAt, m.CreatedAt }); + builder.HasIndex(m => new { m.ProcessedAt, m.NextAttemptAt, m.CreatedAt });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs` at line 50, O índice atual definido em InboxMessageConfiguration via builder.HasIndex(m => new { m.ProcessedAt, m.CreatedAt }) não favorece consultas de retry que filtram por NextAttemptAt; atualize o índice para priorizar mensagens pendentes incluindo NextAttemptAt (por exemplo builder.HasIndex(m => new { m.ProcessedAt, m.NextAttemptAt, m.CreatedAt }) ou a ordem que melhor reflita o plano de consulta) de modo a reduzir full scans nas queries de retry. Altere a definição em InboxMessageConfiguration, execute migração/upgrade do esquema e valide as consultas de retry para confirmar melhoria de desempenho.tests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cs (1)
10-14: Melhore a rastreabilidade do mock usando um único identificador.Hoje ID externo e URL usam GUIDs diferentes; usar o mesmo token facilita debug e asserts em testes.
✏️ Ajuste sugerido
- return Task.FromResult(new SubscriptionGatewayResult( + var token = Guid.NewGuid().ToString("n"); + return Task.FromResult(new SubscriptionGatewayResult( true, - "sub_mock_" + Guid.NewGuid().ToString("n"), - "https://checkout.stripe.com/mock_" + Guid.NewGuid().ToString("n"), + "sub_mock_" + token, + "https://checkout.stripe.com/mock_" + token, null));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cs` around lines 10 - 14, The mock creates two different GUIDs for the subscription id and the checkout URL; change MockPaymentGateway so it generates a single token once (e.g., var token = Guid.NewGuid().ToString("n")) and reuse it when constructing the SubscriptionGatewayResult values ("sub_mock_" + token and "https://checkout.stripe.com/mock_" + token) so both id and URL share the same identifier for easier tracing in tests.src/Modules/Payments/Infrastructure/Extensions.cs (1)
26-28: String de conexão de teste inconsistente com a constante compartilhada.O módulo Payments usa uma string de conexão hardcoded que difere de
DatabaseConstants.DefaultTestConnectionString(usada pelo módulo Documents). Isso pode causar problemas em ambientes de teste.♻️ Proposta de correção para usar a constante compartilhada
if (string.IsNullOrWhiteSpace(connStr) && MeAjudaAi.Shared.Utilities.EnvironmentHelpers.IsSecurityBypassEnvironment(environment)) { `#pragma` warning disable S2068 - connStr = "Host=localhost;Port=5432;Database=meajudaai_test;Username=postgres;Password=test"; + connStr = MeAjudaAi.Shared.Database.DatabaseConstants.DefaultTestConnectionString; `#pragma` warning restore S2068 }Conforme o snippet de
src/Modules/Documents/Infrastructure/Extensions.cs, o padrão esperado é usarDatabaseConstants.DefaultTestConnectionStringpara manter consistência entre módulos.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Extensions.cs` around lines 26 - 28, Replace the hardcoded test connection string in the Payments module's Extensions.cs by using the shared constant DatabaseConstants.DefaultTestConnectionString: locate where connStr is assigned (the hardcoded "Host=localhost;Port=5432;Database=meajudaai_test;Username=postgres;Password=test") and set it to DatabaseConstants.DefaultTestConnectionString instead, keeping the surrounding pragma warning directives (S2068) intact and ensuring any using/import for DatabaseConstants is present or added.src/Modules/Payments/Domain/Entities/Subscription.cs (1)
7-18: Falta documentação XML em português para a classe e construtor.Conforme a política de linguagem do repositório, classes e métodos públicos devem ter documentação XML em português (///
, /// , etc.).
Além disso, o comentário na linha 17 está em inglês — deve ser traduzido para português.
📝 Sugestão de documentação
+/// <summary> +/// Representa uma assinatura de um prestador no sistema de pagamentos. +/// </summary> public class Subscription : AggregateRoot<Guid> { private Subscription() { } + /// <summary> + /// Cria uma nova assinatura para um prestador. + /// </summary> + /// <param name="providerId">Identificador único do prestador.</param> + /// <param name="planId">Identificador do plano de assinatura.</param> + /// <param name="amount">Valor da assinatura.</param> public Subscription(Guid providerId, string planId, Money amount) { ProviderId = providerId; PlanId = planId; Amount = amount; Status = ESubscriptionStatus.Pending; - // CreatedAt is in BaseEntity + // CreatedAt está na BaseEntity }Based on learnings: "Comments and XML summaries must be in Portuguese."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/Subscription.cs` around lines 7 - 18, Adicione documentação XML em português para a classe Subscription e seu construtor público Subscription(Guid providerId, string planId, Money amount): inclua /// <summary> descrevendo a entidade de assinatura em português, e /// <param> para providerId, planId e amount explicando cada parâmetro; substitua o comentário em inglês "// CreatedAt is in BaseEntity" por um comentário XML em português referenciando CreatedAt (por exemplo "/// <remarks>CreatedAt está definido em BaseEntity</remarks>" ou similar) para cumprir a política de linguagem do repositório.src/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cs (1)
51-65: Teste de idempotência não valida queUpdatedAtpermanece inalterado.O teste
Activate_ShouldBeIdempotent_WhenAlreadyActivecapturaoriginalUpdatedAtmas não o utiliza na asserção. Se a idempotência deve garantir que nenhum campo seja alterado, adicione a verificação.✅ Proposta de asserção adicional
// Assert - Should be idempotent, not change subscription.Status.Should().Be(ESubscriptionStatus.Active); subscription.ExternalSubscriptionId.Should().Be("sub_123"); // Original value kept + subscription.UpdatedAt.Should().Be(originalUpdatedAt); // UpdatedAt não deve mudar🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cs` around lines 51 - 65, The test Activate_ShouldBeIdempotent_WhenAlreadyActive records originalUpdatedAt but never asserts it; update the test to assert that subscription.UpdatedAt remains equal to originalUpdatedAt after the second call to Subscription.Activate (in addition to the existing assertions on subscription.Status and subscription.ExternalSubscriptionId) to validate true idempotence of Activate.src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs (1)
27-38: Padronize consultas somente-leitura comAsNoTracking().Os outros métodos de leitura já seguem esse padrão. Aqui a inconsistência adiciona tracking sem necessidade.
♻️ Ajuste sugerido
public async Task<Subscription?> GetLatestByProviderIdAsync(Guid providerId, CancellationToken cancellationToken = default) { return await context.Subscriptions + .AsNoTracking() .Where(s => s.ProviderId == providerId) .OrderByDescending(s => s.CreatedAt) .FirstOrDefaultAsync(cancellationToken); } public async Task<Subscription?> GetByExternalIdAsync(string externalSubscriptionId, CancellationToken cancellationToken = default) { return await context.Subscriptions + .AsNoTracking() .FirstOrDefaultAsync(s => s.ExternalSubscriptionId == externalSubscriptionId, cancellationToken); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs` around lines 27 - 38, As consultas de leitura GetLatestByProviderIdAsync e GetByExternalIdAsync estão criando tracking inconsistentes; update ambas para usar AsNoTracking() on the IQueryable (i.e., call AsNoTracking() on context.Subscriptions before Where/FirstOrDefaultAsync) so the queries are no-tracking like the other read methods, preserving behavior and performance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs`:
- Around line 15-29: Add explicit request validation in the subscriptions
MapPost handler before creating CreateSubscriptionCommand: check
CreateSubscriptionRequest.ProviderId and PlanId are not null/empty/whitespace
and that Amount is > 0 (and optionally that Currency is non-empty or default to
"BRL"); if any check fails, return Results.BadRequest with a Portuguese error
message describing the invalid field(s) (e.g., "ProviderId inválido", "PlanId
inválido", "Amount deve ser maior que zero"). Keep the existing creation of
CreateSubscriptionCommand and dispatcher.SendAsync only for valid input and
ensure the error responses are user-facing and in Portuguese.
In `@src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs`:
- Around line 39-42: The inline comment near the Testing bypass in
StripeWebhookEndpoint.cs is written in English; change it to Portuguese to match
the project's C# comment convention (keep logs in English). Update the comment
that currently reads "// throwOnApiVersionMismatch: false para evitar erros em
testes com payloads manuais" and any surrounding English lines to Portuguese
near the block that checks environment.IsEnvironment("Testing") &&
string.IsNullOrEmpty(stripeSignature") so all inline comments in the method
(StripeWebhookEndpoint handling the signature bypass) are in Portuguese while
leaving log messages (if any) in English.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 34-39: As mensagens lançadas em CreateSubscriptionCommandHandler
estão em inglês; atualize as mensagens do SubscriptionCreationException para
português — substitua $"Failed to create subscription: {result.ErrorMessage}"
por uma mensagem em português que preserve result.ErrorMessage (por exemplo
"Falha ao criar assinatura: {result.ErrorMessage}") e troque "Checkout URL is
missing from gateway result." por algo como "URL de checkout ausente no
resultado do gateway."; altere as duas instâncias onde
SubscriptionCreationException é lançado (a que usa result.ErrorMessage e a que
verifica result.CheckoutUrl) garantindo texto em português consistente com
outras mensagens da API.
- Around line 26-35: A chamada a paymentGateway.CreateSubscriptionAsync dentro
de CreateSubscriptionCommandHandler deve ser envolvida em um try/catch para
padronizar falhas técnicas em SubscriptionCreationException; capture quaisquer
exceções (ex.: timeout, network) lançadas pela chamada, e relance como new
SubscriptionCreationException com uma mensagem clara e a exceção original como
innerException, preservando ainda o atual tratamento do result.Success para
erros retornados pelo gateway.
In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs`:
- Line 16: O comentário inline em PaymentTransaction.cs ("// CreatedAt is
already in BaseEntity") está em inglês; altere-o para português para seguir o
padrão do repositório (por exemplo "// CreatedAt já está em BaseEntity") próximo
à definição da entidade PaymentTransaction para manter comentários/XML em
português enquanto mantém logs em inglês.
- Around line 25-33: A função Settle precisa validar o parâmetro
externalTransactionId antes de marcar a transação como Succeeded; no método
Settle(string externalTransactionId) (na classe PaymentTransaction) verifique se
externalTransactionId não é null/empty/whitespace (use uma validação equivalente
a string.IsNullOrWhiteSpace) e, se for inválido, lance um
ArgumentException/ArgumentNullException com mensagem clara; somente após a
validação atribua ExternalTransactionId, atualize Status para
EPaymentStatus.Succeeded, ajuste ProcessedAt e chame MarkAsUpdated() para evitar
gravação de valores vazios que quebram reconciliação e violam a unicidade de
ExternalTransactionId.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 124-140: In the "customer.subscription.deleted" case the cast to
Stripe.Subscription (stripeSubscription) can be null and is currently ignored;
make this consistent with the checkout.session.completed handling by failing
fast when stripeSubscription is null: check stripeSubscription immediately after
the cast and throw an InvalidOperationException (or a specific exception used
elsewhere) with a clear message including the event type, or alternatively log
an error and then throw; keep the rest of the flow
(repository.GetByExternalIdAsync, existingSubscription.Cancel,
repository.UpdateAsync, logger messages) unchanged so only the null-cast branch
behavior is updated.
- Around line 29-33: The query in ProcessInboxJob.cs that reads
dbContext.InboxMessages can race across multiple instances causing duplicate
processing; either (A) acquire a DB-level row lock when selecting pending rows
(use a pessimistic lock / SKIP LOCKED variant appropriate to your DB by issuing
a locked query for InboxMessages within the job so only one instance reads each
row — e.g., SELECT ... FOR UPDATE SKIP LOCKED / UPDLOCK, READPAST via raw SQL or
EF Core's ExecuteSql/FromSql methods around the InboxMessages selection), or (B)
add an optimistic concurrency token (e.g., RowVersion) to the InboxMessage
entity and its InboxMessageConfiguration and handle DbUpdateConcurrencyException
in ProcessInboxJob when SaveChangesAsync on BaseDbContext fails; implement one
of these approaches and update the selection/SaveChangesAsync logic (referencing
InboxMessages, InboxMessage, InboxMessageConfiguration.cs, ProcessInboxJob.cs,
and BaseDbContext.SaveChangesAsync) so messages cannot be processed twice.
In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs`:
- Line 37: O comentário inline na atribuição "Price = planId" está em inglês;
altere-o para português alinhado ao padrão C# do repositório mantendo termos
técnicos em inglês quando necessário; por exemplo, substitua "// In Stripe,
planId is often the Price ID" por um comentário em português como "// No Stripe,
planId geralmente é o ID do Price" dentro da classe StripePaymentGateway para
padronização (observe que logs continuam em inglês e comentários/XML devem ser
em português).
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs`:
- Around line 76-77: Os testes atuais em CreateSubscriptionCommandHandlerTests
(asserções na linha que espera SubscriptionCreationException) esperam que
_repositoryMock.AddAsync seja chamado mesmo quando o gateway falha ou não
retorna CheckoutUrl; altere essas especificações para não persistir assinaturas
em cenários de erro: ao assertar ThrowAsync<SubscriptionCreationException> para
o handler (CreateSubscriptionCommandHandler), verifique que
_repositoryMock.Verify(r => r.AddAsync(It.IsAny<Subscription>(),
It.IsAny<CancellationToken>()), Times.Never) e ajuste a mesma mudança nas
asserções correspondentes nas linhas 95-96 para garantir que AddAsync não seja
invocado quando não houver checkout utilizável.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cs`:
- Around line 136-150: O teste atual só verifica o CancellationToken repassado a
GetAverageRatingForProviderAsync; adicione uma verificação equivalente para
IMessageBus.PublishAsync: configure ou verifique o mock _messageBusMock para
esperar uma chamada a PublishAsync com o mesmo cts.Token (por exemplo usando
It.IsAny<MessageType>() para o payload e It.Is<CancellationToken>(t => t ==
cts.Token) para o token) e assegure que _handler.HandleAsync(domainEvent,
cts.Token) invoque PublishAsync com esse token.
---
Duplicate comments:
In `@src/Shared/Domain/ValueObjects/Money.cs`:
- Around line 5-18: The Money invariants are bypassable because Amount and
Currency use public init; change Money to use read-only properties (remove init,
e.g., public decimal Amount { get; } and public string Currency { get; }) and
keep the constructor validation in the Money(decimal amount, string currency) to
assign the fields, preventing object-initializer or with-expression mutation;
update any call sites that rely on object-initializers/with to use the
constructor or explicit factory methods instead.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cs`:
- Around line 74-75: O nome do teste
StripeWebhook_CheckoutSessionCompleted_Should_ActivateSubscription promete que a
assinatura ficará Active, mas o corpo só verifica que a mensagem foi persistida
na inbox; atualize o teste para corresponder ao nome: ou (A) renomeie o método
para algo como StripeWebhook_CheckoutSessionCompleted_Should_PersistToInbox se
você só pretende validar a inbox, ou (B) torne-o determinístico adicionando
polling/retentativas após a persistência da inbox para aguardar o job em
background completar e então afirmar que a assinatura foi ativada (verificar
Status == Active e ExternalId preenchido) — ajuste também o outro caso similar
nas linhas 121-135 da mesma forma; localize pelos nomes dos testes/fixtures
usados e implemente uma espera com timeout razoável e checagens repetidas antes
dasserções finais.
---
Nitpick comments:
In
`@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj`:
- Around line 13-15: The AssemblyAttribute granting InternalsVisibleTo from
MeAjudaAi.Modules.Payments.Application to
MeAjudaAi.Modules.Payments.Infrastructure creates a module boundary leak; remove
the <AssemblyAttribute
Include="System.Runtime.CompilerServices.InternalsVisibleTo"> entry (the
InternalsVisibleTo for MeAjudaAi.Modules.Payments.Infrastructure) from
MeAjudaAi.Modules.Payments.Application.csproj, make the required types/members
in the Application layer public or define explicit contracts (interfaces/DTOs)
exported by the Application layer, or move shared contracts into a dedicated
Contracts/Shared project that both Application and Infrastructure reference, and
update usages to consume those public contracts instead of relying on internals.
- Around line 22-24: The Application project file
MeAjudaAi.Modules.Payments.Application.csproj currently references the concrete
package Microsoft.EntityFrameworkCore; remove that PackageReference from the
Application csproj and add it to the Infrastructure project instead, then
refactor any usages in the Application layer to depend on domain
contracts/abstractions (interfaces, DTOs, repository interfaces) rather than EF
Core types so no EF namespaces remain in Application; finally run a
restore/build and update any unit tests or DI registrations in Infrastructure to
expose EF-backed implementations for the Application interfaces.
In
`@src/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cs`:
- Line 3: A classe SubscriptionCreationException só tem um construtor que recebe
message; adicione um overload que também aceite uma Exception innerException
ande preserve a causa original; implemente um segundo construtor em
SubscriptionCreationException(string message, Exception innerException) :
base(message, innerException) para encaminhar o innerException à base, mantendo
o construtor atual compatível e garantindo que lugares que lançam new
SubscriptionCreationException possam repassar a exceção original para melhor
troubleshooting.
In `@src/Modules/Payments/Domain/Entities/InboxMessage.cs`:
- Around line 6-7: A entidade InboxMessage permite um estado inválido ao
inicializar Type e Content com null! e setters públicos; force as invariantes na
construção: remova o uso de null!, adicione um construtor que aceita string type
e string content, valide não nulo/não vazio (lançando
ArgumentNullException/ArgumentException) e atribua aos campos, e torne os
setters de Type e Content privados ou readonly para impedir mutação externa após
criação; referencie a classe InboxMessage e as propriedades Type e Content ao
aplicar estas alterações.
In `@src/Modules/Payments/Domain/Entities/Subscription.cs`:
- Around line 7-18: Adicione documentação XML em português para a classe
Subscription e seu construtor público Subscription(Guid providerId, string
planId, Money amount): inclua /// <summary> descrevendo a entidade de assinatura
em português, e /// <param> para providerId, planId e amount explicando cada
parâmetro; substitua o comentário em inglês "// CreatedAt is in BaseEntity" por
um comentário XML em português referenciando CreatedAt (por exemplo "///
<remarks>CreatedAt está definido em BaseEntity</remarks>" ou similar) para
cumprir a política de linguagem do repositório.
In `@src/Modules/Payments/Infrastructure/Extensions.cs`:
- Around line 26-28: Replace the hardcoded test connection string in the
Payments module's Extensions.cs by using the shared constant
DatabaseConstants.DefaultTestConnectionString: locate where connStr is assigned
(the hardcoded
"Host=localhost;Port=5432;Database=meajudaai_test;Username=postgres;Password=test")
and set it to DatabaseConstants.DefaultTestConnectionString instead, keeping the
surrounding pragma warning directives (S2068) intact and ensuring any
using/import for DatabaseConstants is present or added.
In
`@src/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csproj`:
- Line 21: O PackageReference para Microsoft.EntityFrameworkCore.Design deve
marcar esse pacote como tooling para não propagar aos consumidores; edite o
elemento PackageReference que contém
Include="Microsoft.EntityFrameworkCore.Design" e adicione o atributo/elemento
PrivateAssets="all" (seguindo o padrão dos módulos
Communications/Documents/Locations) para garantir que o pacote seja privado ao
projeto de infraestrutura.
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs`:
- Line 50: O índice atual definido em InboxMessageConfiguration via
builder.HasIndex(m => new { m.ProcessedAt, m.CreatedAt }) não favorece consultas
de retry que filtram por NextAttemptAt; atualize o índice para priorizar
mensagens pendentes incluindo NextAttemptAt (por exemplo builder.HasIndex(m =>
new { m.ProcessedAt, m.NextAttemptAt, m.CreatedAt }) ou a ordem que melhor
reflita o plano de consulta) de modo a reduzir full scans nas queries de retry.
Altere a definição em InboxMessageConfiguration, execute migração/upgrade do
esquema e valide as consultas de retry para confirmar melhoria de desempenho.
In `@src/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cs`:
- Around line 27-38: As consultas de leitura GetLatestByProviderIdAsync e
GetByExternalIdAsync estão criando tracking inconsistentes; update ambas para
usar AsNoTracking() on the IQueryable (i.e., call AsNoTracking() on
context.Subscriptions before Where/FirstOrDefaultAsync) so the queries are
no-tracking like the other read methods, preserving behavior and performance.
In `@src/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cs`:
- Around line 51-65: The test Activate_ShouldBeIdempotent_WhenAlreadyActive
records originalUpdatedAt but never asserts it; update the test to assert that
subscription.UpdatedAt remains equal to originalUpdatedAt after the second call
to Subscription.Activate (in addition to the existing assertions on
subscription.Status and subscription.ExternalSubscriptionId) to validate true
idempotence of Activate.
In `@src/Shared/Utilities/Constants/RoleConstants.cs`:
- Around line 46-47: Replace all hardcoded "customer" string literals with the
new constant RoleConstants.Customer; specifically update usages inside
LocalDevelopmentAuthenticationDomainService and UserRegisteredDomainEventHandler
to reference RoleConstants.Customer, add the appropriate using/import for the
namespace that contains RoleConstants if missing, and recompile to ensure no
remaining literal occurrences remain.
In `@tests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cs`:
- Around line 251-252: O registro duplicado de IPaymentGateway (interface)
adicionando MockPaymentGateway diretamente causa ambiguidade ao resolver
IEnumerable<IPaymentGateway>; antes de chamar
services.AddScoped<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway,
MockPaymentGateway>() remova ou substitua registros anteriores para essa
interface (por exemplo chamar
services.RemoveAll<MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway>()
ou usar ServiceDescriptor.Replace/ServiceCollectionExtensions.Replace com
ServiceDescriptor.Scoped(typeof(IPaymentGateway), typeof(MockPaymentGateway)))
para garantir que apenas o mock seja registrado no TestContainerFixture.
In `@tests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cs`:
- Around line 10-14: The mock creates two different GUIDs for the subscription
id and the checkout URL; change MockPaymentGateway so it generates a single
token once (e.g., var token = Guid.NewGuid().ToString("n")) and reuse it when
constructing the SubscriptionGatewayResult values ("sub_mock_" + token and
"https://checkout.stripe.com/mock_" + token) so both id and URL share the same
identifier for easier tracing in tests.
In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Around line 185-187: The current setup always replaces IPaymentGateway with
MockPaymentGateway globally; change it so the replacement runs only when the
test's RequiredModules includes the Payments module. In the BaseApiTest
configuration code (where IPaymentGateway and MockPaymentGateway are
referenced), wrap the descriptor removal and services.AddScoped<IPaymentGateway,
MockPaymentGateway>() calls in a conditional that checks RequiredModules
contains Payments (or the equivalent module identifier your test uses), so tests
that don't require Payments keep the original registration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 60d8090f-043b-485e-8110-1f9476eb4315
⛔ Files ignored due to path filters (24)
src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/PaymentsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.ApiService.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Architecture.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.E2E.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Integration.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Shared.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (58)
Directory.Packages.propsMeAjudaAi.slnxinfrastructure/keycloak/realms/meajudaai-realm.dev.jsonsrc/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csprojsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cssrc/Modules/Communications/Application/Services/Email/StubEmailService.cssrc/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cssrc/Modules/Payments/API/Endpoints/PaymentsEndpoints.cssrc/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/API/Extensions.cssrc/Modules/Payments/API/MeAjudaAi.Modules.Payments.API.csprojsrc/Modules/Payments/Application/Extensions.cssrc/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csprojsrc/Modules/Payments/Application/Subscriptions/Commands/CreateSubscriptionCommand.cssrc/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Domain/Abstractions/IPaymentGateway.cssrc/Modules/Payments/Domain/Entities/InboxMessage.cssrc/Modules/Payments/Domain/Entities/PaymentTransaction.cssrc/Modules/Payments/Domain/Entities/Subscription.cssrc/Modules/Payments/Domain/Enums/EPaymentStatus.cssrc/Modules/Payments/Domain/Enums/ESubscriptionStatus.cssrc/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csprojsrc/Modules/Payments/Domain/Repositories/ISubscriptionRepository.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Infrastructure/Extensions.cssrc/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cssrc/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csprojsrc/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/SubscriptionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cssrc/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cssrc/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csprojsrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cssrc/Modules/Ratings/Infrastructure/Extensions.cssrc/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cssrc/Shared/Domain/ValueObjects/Money.cssrc/Shared/Utilities/Constants/RoleConstants.cssrc/Shared/Utilities/UserRoles.cstests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csprojtests/MeAjudaAi.E2E.Tests/Base/Helpers/DbContextSchemaHelper.cstests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cstests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cstests/MeAjudaAi.E2E.Tests/MeAjudaAi.E2E.Tests.csprojtests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csprojtests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
💤 Files with no reviewable changes (2)
- src/Modules/Communications/Application/Services/Email/StubEmailService.cs
- src/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cs
…management, and domain event handling
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs (1)
172-181:⚠️ Potential issue | 🟠 MajorO harness deixa Payments parcialmente ativo quando a flag não está habilitada.
O
PaymentsDbContexté sempre registrado, mas o mock doIPaymentGatewaye as migrations de Payments só entram quandoRequiredModulesincluiPayments. Como o app de teste sobe o módulo inteiro peloProgram.cs, esrc/Modules/Payments/Infrastructure/Extensions.csainda registraProcessInboxJob, os testes dos outros módulos podem ficar com infraestrutura de Payments ativa apontando para schema não migrado ou para o gateway real.♻️ Uma forma simples de alinhar o setup
- if (RequiredModules.HasFlag(TestModule.Payments)) - { - var paymentGatewayDescriptors = services.Where(d => d.ServiceType == typeof(IPaymentGateway)).ToList(); - foreach (var descriptor in paymentGatewayDescriptors) services.Remove(descriptor); - services.AddScoped<IPaymentGateway, MockPaymentGateway>(); - } + var paymentGatewayDescriptors = services.Where(d => d.ServiceType == typeof(IPaymentGateway)).ToList(); + foreach (var descriptor in paymentGatewayDescriptors) services.Remove(descriptor); + services.AddScoped<IPaymentGateway, MockPaymentGateway>();- if (modules.HasFlag(TestModule.Payments)) await ApplyMigrationForContextAsync(serviceProvider.GetRequiredService<PaymentsDbContext>(), "Payments", logger); + await ApplyMigrationForContextAsync(serviceProvider.GetRequiredService<PaymentsDbContext>(), "Payments", logger);Se a intenção for realmente desligar Payments em alguns testes, aí o ajuste precisa ser o inverso: remover também o hosted service/registrations do módulo quando a flag não estiver presente.
Also applies to: 186-191, 276-276
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs` around lines 172 - 181, Tests currently always register PaymentsDbContext via AddTestDbContext<PaymentsDbContext> which leaves Payments partially active when RequiredModules doesn't include Payments; update the test harness to conditionally register Payments infrastructure the same way other modules are gated: remove the unconditional AddTestDbContext<PaymentsDbContext> and instead only call AddTestDbContext<PaymentsDbContext> and any Payments registrations when the RequiredModules list contains "Payments", and ensure the module's hosted services/registrations (e.g., ProcessInboxJob in src/Modules/Payments/Infrastructure/Extensions.cs and any IPaymentGateway registration) are also skipped or replaced with test doubles when Payments is not enabled so the test Program.cs boots without partial Payments wiring.
♻️ Duplicate comments (2)
src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs (1)
14-203:⚠️ Potential issue | 🟠 MajorA suíte ainda não testa o
ReviewRepositoryreal (só valida comportamento do mock).Hoje os testes exercitam
Setup/VerifydoMock<IReviewRepository>, então regressões reais da implementação emsrc/Modules/Ratings/Infrastructure/Persistence/Repositories/ReviewRepository.cs(filtro porEReviewStatus.Approved, paginaçãoSkip/Take, cálculo/média arredondada, etc.) podem passar sem falhar.🔧 Direção de ajuste (usar SUT real com DbContext de teste)
-public class ReviewRepositoryContractTests +public class ReviewRepositoryTests { - private readonly Mock<IReviewRepository> _repositoryMock; + private readonly RatingsDbContext _context; + private readonly ReviewRepository _repository; - public ReviewRepositoryContractTests() + public ReviewRepositoryTests() { - _repositoryMock = new Mock<IReviewRepository>(); + var options = new DbContextOptionsBuilder<RatingsDbContext>() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + _context = new RatingsDbContext(options); + _repository = new ReviewRepository(_context); } }Depois, cada teste deve chamar
_repository(SUT real) e validar estado/resultado no_context, em vez deSetup/Verifyno mock.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs` around lines 14 - 203, Tests currently only assert behavior on the Mock<IReviewRepository> (_repositoryMock) so the real ReviewRepository implementation (ReviewRepository) isn't exercised; replace the mock-based approach by creating a test DbContext (e.g., in-memory or SQLite transient) and instantiate the real SUT ReviewRepository with that context (store it in a field like _repository), seed necessary data into the test _context for each test, call repository methods on _repository (e.g., AddAsync, GetByIdAsync, GetByProviderIdAsync, GetByProviderAndCustomerAsync, GetAverageRatingForProviderAsync, UpdateAsync) and assert results/state against the _context (or returned values) instead of using _repositoryMock.Setup/Verify so filters (EReviewStatus.Approved), pagination (Skip/Take) and averaging logic are actually validated.src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs (1)
23-41:⚠️ Potential issue | 🟠 MajorAinda existe risco de inconsistência entre gateway e banco quando
AddAsyncfalha.Se a criação externa ocorrer e a persistência falhar, você pode deixar recurso externo ativo sem estado local.
🛠️ Mitigação mínima por compensação
- await subscriptionRepository.AddAsync(subscription, cancellationToken); + try + { + await subscriptionRepository.AddAsync(subscription, cancellationToken); + } + catch + { + if (!string.IsNullOrWhiteSpace(result.ExternalSubscriptionId)) + { + await paymentGateway.CancelSubscriptionAsync(result.ExternalSubscriptionId, cancellationToken); + } + throw; + } return result.CheckoutUrl;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs` around lines 23 - 41, The current flow calls paymentGateway.CreateSubscriptionAsync and then persists with subscriptionRepository.AddAsync, risking external subscription remaining if AddAsync fails; update the handler to perform compensating cleanup on persistence failure by catching exceptions around subscriptionRepository.AddAsync and invoking the payment gateway's cancellation method (e.g., paymentGateway.CancelSubscriptionAsync or DeleteSubscriptionAsync) using the subscription identifier returned by CreateSubscriptionAsync (ensure the CreateSubscriptionAsync result exposes a subscriptionId/remoteId), then rethrow or wrap the original error (keep existing SubscriptionCreationException behavior). Also, if CreateSubscriptionAsync doesn't return a remote id, modify its contract to include one so the compensation can target the created external resource; ensure existing checks for result.Success and result.CheckoutUrl remain.
🧹 Nitpick comments (9)
src/Modules/Ratings/Infrastructure/Extensions.cs (1)
8-12: Não aplicar[ExcludeFromCodeCoverage]na classe de infraestrutura (Line 12).
Extensions.AddInfrastructuretem fluxo condicional importante; manter cobertura aqui ajuda a detectar quebras de configuração cedo.💡 Ajuste sugerido
-using System.Diagnostics.CodeAnalysis; @@ -[ExcludeFromCodeCoverage] public static class Extensions🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Infrastructure/Extensions.cs` around lines 8 - 12, Remova o atributo [ExcludeFromCodeCoverage] aplicado no escopo de infraestrutura para garantir cobertura do fluxo condicional em Extensions.AddInfrastructure; especificamente retire a anotação da declaração atual (ou restrinja-a apenas a classes utilitárias sem lógica) para que a classe/namespace que contém o método Extensions.AddInfrastructure mantenha cobertura de testes e possíveis quebras de configuração sejam detectadas.src/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cs (1)
2-12: Evite excluir oRatingsDbContextinteiro da cobertura (Line 12).Essa classe tem lógica relevante (
GetDomainEventsAsync/ClearDomainEvents), então a exclusão global tende a esconder regressões de comportamento.💡 Ajuste sugerido
-using System.Diagnostics.CodeAnalysis; @@ -[ExcludeFromCodeCoverage] public class RatingsDbContext : BaseDbContext🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cs` around lines 2 - 12, Remova a exclusão global de cobertura aplicada à classe (atributo [ExcludeFromCodeCoverage]) para que a lógica de RatingsDbContext seja testada; especificamente, retire o atributo acima da declaração da classe RatingsDbContext e, se for necessário manter exclusões, aplique-os somente a membros gerados/irrelevantes, nunca aos métodos GetDomainEventsAsync e ClearDomainEvents, garantindo que esses métodos permaneçam cobertos por testes.src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cs (1)
35-55: Consolidar testes de warning para reduzir duplicidade.
HandleAsync_ShouldLogWarningeHandleAsync_ShouldHandleShortReasonvalidam essencialmente o mesmo comportamento (Warning uma vez). Dá para unificar em um[Theory]com casos curtos.♻️ Exemplo de consolidação
-[Fact] -public async Task HandleAsync_ShouldLogWarning() -{ - // Arrange - var reviewId = Guid.NewGuid(); - var providerId = Guid.NewGuid(); - var domainEvent = new ReviewRejectedDomainEvent(reviewId, 0, providerId, "Spam"); - - // Act - await _handler.HandleAsync(domainEvent); - - // Assert - _loggerMock.Verify( - x => x.Log( - LogLevel.Warning, - It.IsAny<EventId>(), - It.Is<It.IsAnyType>((v, t) => true), - It.IsAny<Exception?>(), - It.IsAny<Func<It.IsAnyType, Exception?, string>>()), - Times.Once); -} - -[Fact] -public async Task HandleAsync_ShouldHandleShortReason() +[Theory] +[InlineData("Spam")] +[InlineData("Bad")] +public async Task HandleAsync_ShouldLogWarning_ForShortReason(string reason) { // Arrange - var domainEvent = new ReviewRejectedDomainEvent(Guid.NewGuid(), 0, Guid.NewGuid(), "Bad"); + var domainEvent = new ReviewRejectedDomainEvent(Guid.NewGuid(), 0, Guid.NewGuid(), reason); // Act await _handler.HandleAsync(domainEvent); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Warning, It.IsAny<EventId>(), It.Is<It.IsAnyType>((v, t) => true), It.IsAny<Exception?>(), It.IsAny<Func<It.IsAnyType, Exception?, string>>()), Times.Once); }Also applies to: 79-96
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cs` around lines 35 - 55, Consolide os dois testes HandleAsync_ShouldLogWarning e HandleAsync_ShouldHandleShortReason dentro da classe ReviewRejectedDomainEventHandlerTests usando um único [Theory] com [InlineData] para cobrir os vários valores de reason (ex.: curto e normal); refatore para chamar _handler.HandleAsync(domainEvent) dentro do teste parametrizado e manter a mesma asserção existente que usa _loggerMock.Verify(...) para validar um único LogLevel.Warning; atualize os nomes de teste para algo como HandleAsync_LogsWarning_ForVariousReasons e remova os testes duplicados originais (_handler.HandleAsync, _loggerMock.Verify, e a criação de ReviewRejectedDomainEvent permanecem iguais, só parametrizados).src/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csproj (1)
9-9: Evite suprimirCA2201globalmente no projeto de testes.A supressão ampla pode esconder usos inadequados de exceções reservadas em testes futuros; prefira suprimir localmente só onde houver necessidade real.
💡 Ajuste sugerido
- <NoWarn>$(NoWarn);CA2201</NoWarn>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csproj` at line 9, Remove the global suppression of CA2201 from the project file (the <NoWarn> entry referencing CA2201 in MeAjudaAi.Modules.Payments.Tests.csproj) and instead suppress the warning only where necessary: locate specific test methods or classes that legitimately throw/handle System.Exception and apply local suppression via [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA2201:Do not raise reserved exception types", Justification = "...")] on the method/class or use a narrow `#pragma` warning disable CA2201 / `#pragma` warning restore CA2201 block around the offending code; keep the project-level <NoWarn> free of CA2201 so future misuse is not hidden.src/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cs (1)
37-122: Os cenários deShouldRetrypodem ser parametrizados com[Theory].Isso reduz duplicação e facilita manutenção sem mudar comportamento.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cs` around lines 37 - 122, Replace the duplicated [Fact] tests for InboxMessage.ShouldRetry with a single parameterized [Theory] that uses InlineData (or MemberData) to supply scenarios; identify the tests by the class/property InboxMessage and the boolean under test ShouldRetry, and provide rows covering: processed (ProcessedAt set -> expected false), max retries reached (RetryCount == MaxRetries -> false), next attempt in future (NextAttemptAt > now -> false), next attempt in past (NextAttemptAt < now -> true), retry count less than max and no next attempt (RetryCount < MaxRetries, NextAttemptAt null -> true), and edge case RetryCount == MaxRetries with null NextAttemptAt -> false; ensure the test method converts inputs to DateTime? as needed and asserts message.ShouldRetry == expected.tests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cs (1)
10-14: Padronize o token mock para ID externo e URL de checkout.Hoje são gerados dois GUIDs distintos no mesmo retorno; isso torna o mock menos previsível para testes.
♻️ Proposta de ajuste
public Task<SubscriptionGatewayResult> CreateSubscriptionAsync(Guid providerId, string planId, Money amount, CancellationToken cancellationToken) { + var token = Guid.NewGuid().ToString("n"); return Task.FromResult(new SubscriptionGatewayResult( true, - "sub_mock_" + Guid.NewGuid().ToString("n"), - "https://checkout.stripe.com/mock_" + Guid.NewGuid().ToString("n"), + "sub_mock_" + token, + "https://checkout.stripe.com/mock_" + token, null)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cs` around lines 10 - 14, O retorno do mock em MockPaymentGateway (cria um SubscriptionGatewayResult) usa dois GUIDs distintos para o ID externo e a URL de checkout tornando os testes instáveis; gere um único GUID (por exemplo guid = Guid.NewGuid().ToString("n")) e reutilize esse valor tanto em "sub_mock_{guid}" quanto em "https://checkout.stripe.com/mock_{guid}" para padronizar o token mock e tornar o comportamento previsível.src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs (1)
39-39: Evite literal de moeda hardcoded no endpoint.Considere centralizar
"BRL"em constante compartilhada para reduzir divergência entre módulos e testes.Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs` at line 39, Replace the hardcoded "BRL" literal used when computing the currency fallback in the CreateSubscriptionEndpoint (the expression string.IsNullOrWhiteSpace(request.Currency) ? "BRL" : request.Currency) with the shared constant/enum from Shared.Contracts (e.g., Shared.Contracts.Constants.DefaultCurrency or Shared.Contracts.Enums.Currency.BRL) and import the appropriate namespace; update any callers/tests that assumed the literal if needed so all modules reference the single shared symbol instead of duplicating the "BRL" string.src/Modules/Payments/Domain/Entities/PaymentTransaction.cs (1)
30-31: Mensagem de exceção inconsistente - deveria estar em inglês.A mensagem do
ArgumentExceptionestá em português, mas as outras exceções (InvalidOperationExceptionnas linhas 28 e 42) estão em inglês. Exceções são mensagens de runtime e devem seguir o padrão de logs (inglês).♻️ Sugestão de correção
if (string.IsNullOrWhiteSpace(externalTransactionId)) - throw new ArgumentException("ExternalTransactionId não pode ser vazio.", nameof(externalTransactionId)); + throw new ArgumentException("ExternalTransactionId cannot be empty.", nameof(externalTransactionId));Based on learnings: Em todos os arquivos C#, logs e mensagens de runtime devem estar em inglês; comentários e XML docs em português; mensagens visíveis ao usuário (validações/APIs) em português.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs` around lines 30 - 31, The ArgumentException in the PaymentTransaction constructor uses a Portuguese message; update the exception message for the check on externalTransactionId to English (e.g., "ExternalTransactionId cannot be empty.") so it matches the other InvalidOperationException messages and runtime/logging standards; locate the check that throws new ArgumentException(...) for externalTransactionId inside the PaymentTransaction class and replace only the message text while keeping the nameof(externalTransactionId) parameter intact.src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs (1)
29-38: Nota: Duplicação da lógicaShouldRetryé intencional.A query raw SQL replica a lógica de
InboxMessage.ShouldRetry(definida emsrc/Modules/Payments/Domain/Entities/InboxMessage.cs:31), mas isso é necessário para usarFOR UPDATE SKIP LOCKEDque não é suportado diretamente pelo EF Core LINQ.Se a lógica de retry mudar, lembre-se de atualizar ambos os locais.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs` around lines 29 - 38, Resumo: A condição de retry está duplicada entre a query raw em ProcessInboxJob (SELECT ... WHERE processed_at IS NULL AND retry_count < max_retries AND (next_attempt_at IS NULL OR next_attempt_at <= NOW())) e a lógica em InboxMessage.ShouldRetry; mantenha-as em sincronia. Instruções: deixe a duplicação intencional, mas documente claramente no método ProcessInboxJob (na consulta raw) que essa expressão deve espelhar InboxMessage.ShouldRetry (src/Modules/Payments/Domain/Entities/InboxMessage.cs: método ShouldRetry), adicione um comentário com referência ao nome do método e caminho do arquivo, e cubra essa regra com um teste de integração que valida que a query raw retorna exatamente os mesmos resultados que filtrando via ShouldRetry para garantir que mudanças futuras em ShouldRetry acionem atualização da query SQL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Directory.Packages.props`:
- Line 109: O PR atual atualiza a dependência Stripe.net para 51.0.0; antes de
aceitar, verifique compatibilidade com breaking changes: atualizar referências
do pacote "Stripe.net" e confirmar que não há uso de Newtonsoft.Json (procure
por Newtonsoft, JsonConvert, JsonSerializerSettings) substituindo por
System.Text.Json ou ajustar conversores; revisar o tratamento de
opcionalidade/SetTracker vs Emptyable onde a biblioteca é usada (funções/métodos
que setam campos StripeOptions/CustomerCreateOptions etc.) e garantir que null
explícito está correto; auditar parsing/serialização de decimais em modelos da
V2 API (onde campos eram string e agora podem ser decimal) e adaptar
mapeamentos/conversores; testar parsing de webhooks com as funções
Stripe.Webhook.* para capturar e tratar exceções lançadas por parsing incorreto;
e confirmar suporte de target frameworks no Directory.Packages.props (não usar
.NET 5/7) e executar suíte de testes de integração/fluxo de pagamento para
validar que tudo funciona com 51.0.0.
In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs`:
- Around line 35-41: Antes de construir e despachar CreateSubscriptionCommand em
CreateSubscriptionEndpoint (usando dispatcher.SendAsync), valide que o provider
identificado por request.ProviderId existe: injete/consuma o repositório/serviço
de providers (por exemplo IProviderRepository/IProviderService) e faça uma
consulta assíncrona para obter o provider; se não existir, retorne o erro
apropriado (404/BadRequest) sem criar o comando; somente ao confirmar a
existência do provider construa o CreateSubscriptionCommand e chame
dispatcher.SendAsync<CreateSubscriptionCommand, string>(..., cancellationToken).
In
`@src/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csproj`:
- Around line 13-15: Remova declarações desnecessárias no .csproj: exclua o
AssemblyAttribute que define InternalsVisibleTo com valor
"MeAjudaAi.Modules.Payments.Infrastructure" (não há tipos internal na
Application) e remova a PackageReference para "Microsoft.EntityFrameworkCore" já
que não é usada; depois de remover, restaure/compile o projeto para garantir que
não haja referências restantes a InternalsVisibleTo ou a EF Core e elimine
quaisquer using/refs órfãos se surgirem.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 43-45: O bloco de captura em CreateSubscriptionCommandHandler
atualmente encapsula OperationCanceledException em
SubscriptionCreationException; altere a captura para preservar cancelamentos:
add a specific catch for OperationCanceledException that rethrows (e.g., catch
(OperationCanceledException) { throw; }) before the general catch, or change the
when-filter to exclude OperationCanceledException (e.g., catch (Exception ex)
when (ex is not SubscriptionCreationException && ex is not
OperationCanceledException) ). Ensure the general handler still wraps other
exceptions into SubscriptionCreationException with the original exception as
InnerException.
In `@src/Modules/Payments/Domain/Entities/InboxMessage.cs`:
- Line 15: Remova o construtor público vazio InboxMessage() e substitua-o por um
construtor com parâmetros obrigatórios (por exemplo inbox message Type e
Content) ou torne o construtor vazio privado/protected e exponha um
método/fábrica estática (ex: Create) que valide e inicialize as propriedades
obrigatórias; garanta que as propriedades/fields Type e Content sejam
inicializadas e validadas dentro do construtor/factory para preservar as
invariantes de domínio antes de persistir.
In `@src/Modules/Payments/Domain/Entities/Subscription.cs`:
- Around line 23-29: O construtor Subscription permite criar instâncias com
ProviderId vazio (Guid.Empty) e PlanId inválido; adicione validações no
construtor Subscription para: checar ProviderId != Guid.Empty e lançar
ArgumentException se for inválido; checar PlanId não nulo/não vazio/não
whitespace e lançar ArgumentNullException/ArgumentException apropriada se
inválido; também valide Amount (não nulo e valor positivo) antes de atribuir;
mantenha as atribuições de propriedade (ProviderId, PlanId, Amount, Status =
ESubscriptionStatus.Pending) somente após as validações.
- Around line 39-63: Os métodos de transição (Activate, Cancel, Expire) deixam
passar mudanças inválidas entre estados terminais; atualize cada método para
checar e bloquear transições a partir de estados terminais (Canceled e Expired)
— por exemplo, em Activate não apenas ignore quando já for Active, mas também
retorne sem alterar se Status for Canceled ou Expired; em Cancel retorne sem
alterar se Status for Canceled ou Expired; em Expire retorne sem alterar se
Status for Expired ou Canceled — mantendo MarkAsUpdated() somente quando a
transição válida ocorrer.
In `@src/Shared/Domain/ValueObjects/Money.cs`:
- Around line 13-30: The Currency handling in Money is too permissive; ensure
the constructor of Money validates and normalizes currency to a 3-char uppercase
ISO code (throw ArgumentException if null/whitespace or length != 3) so EF
mapping won't fail at persist time and comparisons in operator+ use the
normalized value; update the Money constructor (and any factory methods like
Zero and FromDecimal that delegate to it) to normalize/canonicalize Currency to
Currency = currency.Trim().ToUpperInvariant() after validation so all operations
(e.g., operator+ which checks a.Currency != b.Currency) compare consistent
values.
In `@src/Shared/Utilities/Constants/RoleConstants.cs`:
- Line 26: O comentário "Provider Tiers" no arquivo RoleConstants.cs quebra a
convenção de comentários em pt-BR; substitua esse comentário pelo equivalente em
português (por exemplo "Níveis de Provedor" ou "Camadas de Provedor") mantendo o
mesmo formato de comentário/XML usado no arquivo e garantindo que a alteração
ocorra na declaração relacionada a RoleConstants para consistência com o
restante do código.
In `@tests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cs`:
- Around line 248-252: O container ainda contém a implementação real de
IPaymentGateway (ex.: StripePaymentGateway) porque você só removeu
IDocumentIntelligenceService; antes de registrar MockPaymentGateway you must
remove existing registrations for
MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway (e.g. filter
services.Where(d => d.ServiceType ==
typeof(MeAjudaAi.Modules.Payments.Domain.Abstractions.IPaymentGateway)) and
Remove each) then AddScoped<IPaymentGateway, MockPaymentGateway>(); this ensures
no real gateway remains (and prevents IEnumerable<IPaymentGateway> returning
both).
---
Outside diff comments:
In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Around line 172-181: Tests currently always register PaymentsDbContext via
AddTestDbContext<PaymentsDbContext> which leaves Payments partially active when
RequiredModules doesn't include Payments; update the test harness to
conditionally register Payments infrastructure the same way other modules are
gated: remove the unconditional AddTestDbContext<PaymentsDbContext> and instead
only call AddTestDbContext<PaymentsDbContext> and any Payments registrations
when the RequiredModules list contains "Payments", and ensure the module's
hosted services/registrations (e.g., ProcessInboxJob in
src/Modules/Payments/Infrastructure/Extensions.cs and any IPaymentGateway
registration) are also skipped or replaced with test doubles when Payments is
not enabled so the test Program.cs boots without partial Payments wiring.
---
Duplicate comments:
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 23-41: The current flow calls
paymentGateway.CreateSubscriptionAsync and then persists with
subscriptionRepository.AddAsync, risking external subscription remaining if
AddAsync fails; update the handler to perform compensating cleanup on
persistence failure by catching exceptions around
subscriptionRepository.AddAsync and invoking the payment gateway's cancellation
method (e.g., paymentGateway.CancelSubscriptionAsync or DeleteSubscriptionAsync)
using the subscription identifier returned by CreateSubscriptionAsync (ensure
the CreateSubscriptionAsync result exposes a subscriptionId/remoteId), then
rethrow or wrap the original error (keep existing SubscriptionCreationException
behavior). Also, if CreateSubscriptionAsync doesn't return a remote id, modify
its contract to include one so the compensation can target the created external
resource; ensure existing checks for result.Success and result.CheckoutUrl
remain.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cs`:
- Around line 14-203: Tests currently only assert behavior on the
Mock<IReviewRepository> (_repositoryMock) so the real ReviewRepository
implementation (ReviewRepository) isn't exercised; replace the mock-based
approach by creating a test DbContext (e.g., in-memory or SQLite transient) and
instantiate the real SUT ReviewRepository with that context (store it in a field
like _repository), seed necessary data into the test _context for each test,
call repository methods on _repository (e.g., AddAsync, GetByIdAsync,
GetByProviderIdAsync, GetByProviderAndCustomerAsync,
GetAverageRatingForProviderAsync, UpdateAsync) and assert results/state against
the _context (or returned values) instead of using _repositoryMock.Setup/Verify
so filters (EReviewStatus.Approved), pagination (Skip/Take) and averaging logic
are actually validated.
---
Nitpick comments:
In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs`:
- Line 39: Replace the hardcoded "BRL" literal used when computing the currency
fallback in the CreateSubscriptionEndpoint (the expression
string.IsNullOrWhiteSpace(request.Currency) ? "BRL" : request.Currency) with the
shared constant/enum from Shared.Contracts (e.g.,
Shared.Contracts.Constants.DefaultCurrency or
Shared.Contracts.Enums.Currency.BRL) and import the appropriate namespace;
update any callers/tests that assumed the literal if needed so all modules
reference the single shared symbol instead of duplicating the "BRL" string.
In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs`:
- Around line 30-31: The ArgumentException in the PaymentTransaction constructor
uses a Portuguese message; update the exception message for the check on
externalTransactionId to English (e.g., "ExternalTransactionId cannot be
empty.") so it matches the other InvalidOperationException messages and
runtime/logging standards; locate the check that throws new
ArgumentException(...) for externalTransactionId inside the PaymentTransaction
class and replace only the message text while keeping the
nameof(externalTransactionId) parameter intact.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 29-38: Resumo: A condição de retry está duplicada entre a query
raw em ProcessInboxJob (SELECT ... WHERE processed_at IS NULL AND retry_count <
max_retries AND (next_attempt_at IS NULL OR next_attempt_at <= NOW())) e a
lógica em InboxMessage.ShouldRetry; mantenha-as em sincronia. Instruções: deixe
a duplicação intencional, mas documente claramente no método ProcessInboxJob (na
consulta raw) que essa expressão deve espelhar InboxMessage.ShouldRetry
(src/Modules/Payments/Domain/Entities/InboxMessage.cs: método ShouldRetry),
adicione um comentário com referência ao nome do método e caminho do arquivo, e
cubra essa regra com um teste de integração que valida que a query raw retorna
exatamente os mesmos resultados que filtrando via ShouldRetry para garantir que
mudanças futuras em ShouldRetry acionem atualização da query SQL.
In `@src/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csproj`:
- Line 9: Remove the global suppression of CA2201 from the project file (the
<NoWarn> entry referencing CA2201 in MeAjudaAi.Modules.Payments.Tests.csproj)
and instead suppress the warning only where necessary: locate specific test
methods or classes that legitimately throw/handle System.Exception and apply
local suppression via [System.Diagnostics.CodeAnalysis.SuppressMessage("Design",
"CA2201:Do not raise reserved exception types", Justification = "...")] on the
method/class or use a narrow `#pragma` warning disable CA2201 / `#pragma` warning
restore CA2201 block around the offending code; keep the project-level <NoWarn>
free of CA2201 so future misuse is not hidden.
In `@src/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cs`:
- Around line 37-122: Replace the duplicated [Fact] tests for
InboxMessage.ShouldRetry with a single parameterized [Theory] that uses
InlineData (or MemberData) to supply scenarios; identify the tests by the
class/property InboxMessage and the boolean under test ShouldRetry, and provide
rows covering: processed (ProcessedAt set -> expected false), max retries
reached (RetryCount == MaxRetries -> false), next attempt in future
(NextAttemptAt > now -> false), next attempt in past (NextAttemptAt < now ->
true), retry count less than max and no next attempt (RetryCount < MaxRetries,
NextAttemptAt null -> true), and edge case RetryCount == MaxRetries with null
NextAttemptAt -> false; ensure the test method converts inputs to DateTime? as
needed and asserts message.ShouldRetry == expected.
In `@src/Modules/Ratings/Infrastructure/Extensions.cs`:
- Around line 8-12: Remova o atributo [ExcludeFromCodeCoverage] aplicado no
escopo de infraestrutura para garantir cobertura do fluxo condicional em
Extensions.AddInfrastructure; especificamente retire a anotação da declaração
atual (ou restrinja-a apenas a classes utilitárias sem lógica) para que a
classe/namespace que contém o método Extensions.AddInfrastructure mantenha
cobertura de testes e possíveis quebras de configuração sejam detectadas.
In `@src/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cs`:
- Around line 2-12: Remova a exclusão global de cobertura aplicada à classe
(atributo [ExcludeFromCodeCoverage]) para que a lógica de RatingsDbContext seja
testada; especificamente, retire o atributo acima da declaração da classe
RatingsDbContext e, se for necessário manter exclusões, aplique-os somente a
membros gerados/irrelevantes, nunca aos métodos GetDomainEventsAsync e
ClearDomainEvents, garantindo que esses métodos permaneçam cobertos por testes.
In
`@src/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cs`:
- Around line 35-55: Consolide os dois testes HandleAsync_ShouldLogWarning e
HandleAsync_ShouldHandleShortReason dentro da classe
ReviewRejectedDomainEventHandlerTests usando um único [Theory] com [InlineData]
para cobrir os vários valores de reason (ex.: curto e normal); refatore para
chamar _handler.HandleAsync(domainEvent) dentro do teste parametrizado e manter
a mesma asserção existente que usa _loggerMock.Verify(...) para validar um único
LogLevel.Warning; atualize os nomes de teste para algo como
HandleAsync_LogsWarning_ForVariousReasons e remova os testes duplicados
originais (_handler.HandleAsync, _loggerMock.Verify, e a criação de
ReviewRejectedDomainEvent permanecem iguais, só parametrizados).
In `@tests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cs`:
- Around line 10-14: O retorno do mock em MockPaymentGateway (cria um
SubscriptionGatewayResult) usa dois GUIDs distintos para o ID externo e a URL de
checkout tornando os testes instáveis; gere um único GUID (por exemplo guid =
Guid.NewGuid().ToString("n")) e reutilize esse valor tanto em "sub_mock_{guid}"
quanto em "https://checkout.stripe.com/mock_{guid}" para padronizar o token mock
e tornar o comportamento previsível.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f35730e4-1587-4695-b317-156eb50060cd
⛔ Files ignored due to path filters (24)
src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/PaymentsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.ApiService.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Architecture.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.E2E.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Integration.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Shared.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (60)
Directory.Packages.propsMeAjudaAi.slnxinfrastructure/keycloak/realms/meajudaai-realm.dev.jsonsrc/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csprojsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cssrc/Modules/Communications/Application/Services/Email/StubEmailService.cssrc/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cssrc/Modules/Payments/API/Endpoints/PaymentsEndpoints.cssrc/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/API/Extensions.cssrc/Modules/Payments/API/MeAjudaAi.Modules.Payments.API.csprojsrc/Modules/Payments/Application/Extensions.cssrc/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csprojsrc/Modules/Payments/Application/Subscriptions/Commands/CreateSubscriptionCommand.cssrc/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Domain/Abstractions/IPaymentGateway.cssrc/Modules/Payments/Domain/Entities/InboxMessage.cssrc/Modules/Payments/Domain/Entities/PaymentTransaction.cssrc/Modules/Payments/Domain/Entities/Subscription.cssrc/Modules/Payments/Domain/Enums/EPaymentStatus.cssrc/Modules/Payments/Domain/Enums/ESubscriptionStatus.cssrc/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csprojsrc/Modules/Payments/Domain/Repositories/ISubscriptionRepository.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Infrastructure/Extensions.cssrc/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cssrc/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csprojsrc/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/SubscriptionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cssrc/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cssrc/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csprojsrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cssrc/Modules/Ratings/Infrastructure/Extensions.cssrc/Modules/Ratings/Infrastructure/Persistence/RatingsDbContext.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Persistence/Repositories/ReviewRepositoryContractTests.cssrc/Modules/Users/Infrastructure/Events/Handlers/UserRegisteredDomainEventHandler.cssrc/Modules/Users/Infrastructure/Services/LocalDevelopment/LocalDevelopmentAuthenticationDomainService.cssrc/Shared/Domain/ValueObjects/Money.cssrc/Shared/Utilities/Constants/RoleConstants.cssrc/Shared/Utilities/UserRoles.cstests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csprojtests/MeAjudaAi.E2E.Tests/Base/Helpers/DbContextSchemaHelper.cstests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cstests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cstests/MeAjudaAi.E2E.Tests/MeAjudaAi.E2E.Tests.csprojtests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csprojtests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs
💤 Files with no reviewable changes (2)
- src/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cs
- src/Modules/Communications/Application/Services/Email/StubEmailService.cs
…ties for payments module
… inbox processing, and Ratings module infrastructure setup
|
@coderabbitai full review |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 17
♻️ Duplicate comments (4)
src/Modules/Payments/Application/Subscriptions/Commands/GetBillingPortalCommand.cs (1)
8-10:⚠️ Potential issue | 🟠 MajorValide
ReturnUrlpara evitar open redirect.A propriedade
ReturnUrlé aceita sem validação; o handler deve garantir URI absoluta HTTPS e host em allowlist antes de criar a sessão no Stripe, caso contrário o fluxo pode redirecionar o usuário para domínio malicioso ao sair do Billing Portal.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/Subscriptions/Commands/GetBillingPortalCommand.cs` around lines 8 - 10, GetBillingPortalCommand accepts ReturnUrl without validation which allows open-redirects; update the handler that processes GetBillingPortalCommand (e.g., GetBillingPortalCommandHandler.Handle) to validate ReturnUrl is an absolute HTTPS URI and that its Host is contained in a configured allowlist (e.g., a Billing/AllowedReturnHosts setting), and if validation fails either throw a validation exception or fall back to a safe default return URL before creating the Stripe billing session; reference the ReturnUrl property on GetBillingPortalCommand and ensure the check runs prior to any call that creates the Stripe session.src/Bootstrapper/MeAjudaAi.ApiService/appsettings.json (1)
124-134:⚠️ Potential issue | 🔴 CriticalNão use chaves de domínio como Price IDs do Stripe.
price_premium_monthlyeprice_gold_monthlyainda parecem identificadores internos; se qualquer fluxo os enviar diretamente comoPrice/GetPriceAsync, o checkout falha. Prefira um campo explícito com o Price ID real do Stripe ou resolva vialookup_keyantes de chamar a API. A referência oficial de Price do Stripe usa IDs reais no formato de objetoprice_*: https://docs.stripe.com/api/prices/retrieve#!/bin/bash # Verifica se planId/chaves de Payments são enviados diretamente para APIs Stripe. rg -nP --type=cs -C4 'Payments:Plans|GetPriceAsync|SessionCreateOptions|Price\s*=|planId|lookup_key|LookupKey' src/Modules/Payments src/Bootstrapper🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Bootstrapper/MeAjudaAi.ApiService/appsettings.json` around lines 124 - 134, Assegure que os valores em Payments:Plans não sejam usados como IDs de Price do Stripe; substitua as chaves legadas price_premium_monthly e price_gold_monthly por um campo explícito como StripePriceId (ex.: "StripePriceId": "price_...") ou altere o fluxo que chama GetPriceAsync/SessionCreateOptions para resolver via lookup_key antes de passar para a API Stripe; localize e atualizar o JSON em Payments:Plans e ajustar o código que usa GetPriceAsync, SessionCreateOptions, planId ou LookupKey para usar o novo StripePriceId ou a resolução por lookup_key para evitar enviar chaves de domínio internas diretamente.src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs (1)
46-56:⚠️ Potential issue | 🟡 MinorNão propague a mensagem bruta do gateway para a exceção de domínio.
result.ErrorMessagepode vir em inglês ou conter detalhe de infraestrutura; mantenha o detalhe no log e lance uma mensagem de API consistente em português.🛠️ Ajuste sugerido
logger.LogWarning("Gateway failed to create subscription for Provider {ProviderId}: {ErrorMessage}", command.ProviderId, result.ErrorMessage); @@ - throw new SubscriptionCreationException($"Falha ao criar assinatura no gateway: {result.ErrorMessage}"); + throw new SubscriptionCreationException("Falha ao criar assinatura no gateway de pagamento.");Based on learnings: mensagens ao usuário/validações/API devem estar em português; logs devem permanecer em inglês.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs` around lines 46 - 56, O código em CreateSubscriptionCommandHandler atualmente lança SubscriptionCreationException contendo result.ErrorMessage bruto; mantenha o detalhe técnico apenas no log (já em logger.LogWarning) e lance uma mensagem de exceção/API consistente em português sem propagar o texto do gateway. Altere o throw para usar uma mensagem amigável em português (por exemplo: "Falha ao criar assinatura no gateway.") ou uma versão com contexto controlado, mantendo a chamada a TryCompensateAsync e o LogWarning que registra result.ErrorMessage para diagnóstico; não inclua result.ErrorMessage direto na mensagem da SubscriptionCreationException.src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs (1)
108-119:⚠️ Potential issue | 🟠 MajorVerifique a superfície pública do Stripe.NET para extração do subscription id.
O código depende de dois acessos sensíveis à versão do SDK:
invoice.Parent?.SubscriptionDetails?.Subscription?.Id— presume queSubscriptionseja o objeto expandido (com.Id), e não o string id simples. Em Stripe.NET v48+ (APIbasil2025-03-31),Invoice.Parent.SubscriptionDetailsexiste, mas o tipo do campoSubscriptionfoi alterado entre versões da SDK.invoice.Lines?.Data?.FirstOrDefault()?.SubscriptionId— a propriedade diretaInvoiceLineItem.SubscriptionIdpode ter sido removida/renomeada em versões recentes (movida paraParent.SubscriptionItemDetails.Subscription).Se o payload não vier expandido (caso padrão em webhooks), o primeiro acesso retornará
nulle o código cairá no fallback — que também pode sernullse a SDK em uso não tiver a propriedade. Resultado:data.SubscriptionId = nulle o branchinvoice.paidacaba silencioso (linha 182logger.LogInformation("Invoice ... has no subscription, ignoring")+break), marcando a inbox como processada sem renovar a assinatura.Confirme a versão do Stripe.NET usada pelo projeto e a forma atual dessas propriedades:
#!/bin/bash # 1) Versão referenciada de Stripe.net fd -e csproj | xargs rg -nP 'Stripe(\.net)?"\s+Version' -C0 # 2) Ver outras referências a Parent/SubscriptionDetails no código para sanity-check de tipo rg -nP '(Parent\s*\.\s*SubscriptionDetails|\.SubscriptionId|Lines\s*\.\s*Data)' --type=cs -C1 # 3) Confere se o tipo em uso compila como objeto (tem .Id) ou string rg -nP 'invoice\.Parent\?\.\s*SubscriptionDetails' --type=cs -C2Adicionalmente, pesquise: "Stripe.net v48 Invoice Parent SubscriptionDetails Subscription property type" para confirmar a forma atual. Se o campo for
string, a expressão precisa ser.Subscription(sem.Id). SeInvoiceLineItem.SubscriptionIdfoi removido, useParent?.SubscriptionItemDetails?.Subscriptioncomo fallback.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs` around lines 108 - 119, Resumo: o acesso ao subscription id em ProcessInboxJob.cs usa propriedades da surface do Stripe.NET que variam por versão (invoice.Parent?.SubscriptionDetails?.Subscription?.Id e InvoiceLineItem.SubscriptionId) e pode produzir null silenciosamente; verifique a versão do Stripe.NET referenciada e ajuste a extração para suportar ambas as formas. Instruções: confirme a versão do Stripe.NET no csproj (ou via comandos sugeridos) e, em ProcessInboxJob.cs, altere a lógica que monta StripeEventData no branch "invoice.paid" para detectar se invoice.Parent?.SubscriptionDetails?.Subscription é uma string ou um objeto e extrair o id respeitando ambos os formatos; adicione um fallback que procura o id em invoice.Lines?.Data?.FirstOrDefault()? -> Parent?.SubscriptionItemDetails?.Subscription (ou o campo equivalente na sua versão) quando InvoiceLineItem.SubscriptionId não existir; e registre um log de warning quando não for possível obter subscription id (em vez de ignorar silenciosamente) para facilitar debug.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/Program.cs`:
- Line 141: O endpoint de webhook Stripe está sendo afetado pelo rate limiter
global (UseRateLimiter em Program.cs) e precisa desabilitar rate limiting; edit
the endpoint configuration in StripeWebhookEndpoint (the endpoint builder where
.AllowAnonymous() is called, around the configuration at
StripeWebhookEndpoint.cs:96) and append .DisableRateLimiting() immediately after
.AllowAnonymous(), keeping the subsequent .WithTags(...) call intact so the
webhook endpoint is excluded from the global limiter.
In `@src/Modules/Payments/API/Endpoints/PaymentsEndpoints.cs`:
- Line 21: Altere o comentário que atualmente lê "Stripe webhooks must be
outside versioned group for stability" para Português no arquivo
PaymentsEndpoints.cs (onde o mapeamento de webhooks do Stripe é declarado), por
exemplo substituindo essa linha por um comentário em Português que preserve o
sentido técnico; mantenha as mensagens de log em inglês conforme a política, só
traduza o comentário/XML docs para Português.
In `@src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs`:
- Around line 117-121: The catch block in StripeWebhookEndpoint.cs currently
inspects ex.InnerException as Npgsql.PostgresException, coupling the endpoint to
Postgres; replace this with a provider-agnostic approach by either (A) after
catching DbUpdateException in the handler for webhook processing (where the code
currently checks for uniqueness), perform an existence check against the
repository/DbContext for the Stripe event id or processed-event marker (e.g.,
call the same lookup used when initially deciding to process the event) and
return early if the record already exists, or (B) introduce an abstraction like
IUniqueConstraintViolationDetector injected into the endpoint and use its
IsUniqueConstraintViolation(DbUpdateException) method to decide whether to
swallow the exception; update the catch to use that abstraction or the existence
check and add unit tests covering the idempotency path.
In
`@src/Modules/Payments/Application/Subscriptions/Commands/CreateSubscriptionCommand.cs`:
- Around line 8-15: A idempotencyKey não está sendo composta com o ProviderId
antes de ser enviada ao Stripe, permitindo colisões entre providers; em
StripePaymentGateway (procure pelo método que monta RequestOptions e atribui
requestOptions.IdempotencyKey) concatene o ProviderId com a chave no formato
"{providerId}:{idempotencyKey}" antes de setar requestOptions.IdempotencyKey, e
no CreateSubscriptionCommandHandler valide previamente o IdempotencyKey
(presença, formato e comprimento máximo) no
CreateSubscriptionCommand.IdempotencyKey antes de chamar o gateway para garantir
que chaves inválidas ou demasiado longas sejam rejeitadas.
In `@src/Modules/Payments/Domain/Entities/Subscription.cs`:
- Around line 70-109: Activate currently overwrites ExpiresAt with null when
called without expiresAt, losing a previously set expiration from Renew; update
Activate (method Activate in Subscription) so it only assigns ExpiresAt when a
non-null expiresAt is provided (expiresAt.HasValue), and in the idempotency
branch compare/assign using HasValue to avoid setting ExpiresAt to null; ensure
MarkAsUpdated and AddDomainEvent (SubscriptionActivatedDomainEvent) are still
invoked only when something actually changed (e.g., status/ids changed or
expiresAt was provided and differs).
In `@src/Modules/Payments/Domain/Events/SubscriptionExpiredDomainEvent.cs`:
- Around line 8-11: Os eventos de subscription (SubscriptionExpiredDomainEvent,
SubscriptionActivatedDomainEvent, SubscriptionCanceledDomainEvent,
SubscriptionRenewedDomainEvent) estão usando DomainConstants.InitialVersion em
vez da versão real do agregado; atualize-os para aceitar/propagar a versão do
agregado (ex.: adicione um parâmetro version aos construtores dos eventos ou aos
métodos que os disparam) e altere as chamadas em Subscription.Expire(),
Subscription.Activate(), Subscription.Cancel() e Subscription.Renew() para
passar this.Version ao criar os eventos, garantindo que o DomainEvent receba a
versão correta em vez de DomainConstants.InitialVersion.
In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs`:
- Around line 29-52: The configuration validation currently allows
empty-or-whitespace values to pass and produces wrong redirect URLs; update the
StripePaymentGateway constructor to use string.IsNullOrWhiteSpace instead of
IsNullOrEmpty for apiKey and clientBaseUrl, and ensure Payments:SuccessUrl and
Payments:CancelUrl are checked with IsNullOrWhiteSpace (and trimmed) before
building _successUrl and _cancelUrl; specifically, validate and trim successPath
and cancelPath variables, reject whitespace-only values with ArgumentException,
and then apply the existing Uri.TryCreate logic to decide whether to use the
absolute value or combine with clientBaseUrl so _successUrl and _cancelUrl never
become just the base URL due to blank inputs.
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cs`:
- Around line 58-60: O índice parcial definido em InboxMessageConfiguration
(builder.HasIndex(...) com HasFilter("processed_at IS NULL")) não reflete o
predicado do job (ProcessInboxJob) que também filtra retry_count < max_retries;
altere o filtro SQL do índice para incluir ambas as condições, por exemplo
HasFilter("processed_at IS NULL AND retry_count < max_retries"), para que
mensagens com retry_count >= max_retries não sejam mantidas no índice; após
atualizar o HasFilter, gere/migre o script de alteração de índice para recriar o
índice com o novo filtro.
In
`@src/Modules/Payments/Infrastructure/Repositories/PaymentTransactionRepository.cs`:
- Around line 27-29: No tratamento de idempotência em
PaymentTransactionRepository, o código assume que
UniqueConstraintException.ConstraintName não é nulo e chama .Contains(...), o
que pode lançar NullReferenceException; altere a verificação para proteger
ConstraintName nulo (por exemplo, verificar explicitamente se
uniqueEx.ConstraintName é nulo antes de chamar Contains, ou usar coalescência
nula como (uniqueEx.ConstraintName ?? string.Empty).Contains(...)) garantindo
que a lógica que detecta "external_transaction_id" continue funcionando sem
lançar.
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs`:
- Line 36: O comentário em inglês "Valid plan config setup" dentro da classe
CreateSubscriptionCommandHandlerTests deve ser traduzido para português seguindo
a política do repositório; substitua por algo como "Configuração de plano
válida" (mantendo cabeçalhos AAA e logs em inglês conforme regra) para alinhar o
idioma dos comentários C# ao padrão do projeto.
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/GetBillingPortalCommandHandlerTests.cs`:
- Line 94: Resumo: Comentário em inglês viola a política de idioma do
repositório; padronizar para pt-BR. Substitua o comentário em inglês "// Note:
sub is not activated, so ExternalCustomerId is null" por uma versão em
português, por exemplo "// Observação: o sub não está ativado, portanto
ExternalCustomerId é nulo", no arquivo de testes da classe
GetBillingPortalCommandHandlerTests para manter comentários/XML em pt-BR (logs
continuam em inglês).
In `@src/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cs`:
- Around line 73-89: The test's strict UpdatedAt assertion can race with clock
resolution; before calling subscription.Activate("sub_123", "cus_123", expires2)
capture a preAct timestamp (e.g. var before = DateTime.UtcNow) and then change
the assertion to allow equality by using UpdatedAt.Should().BeOnOrAfter(before)
(or UpdatedAt.Should().BeGreaterOrEqualTo(before) if BeOnOrAfter is
unavailable); update the same pattern for the other tests referenced (lines
~437-451) that assert UpdatedAt to use a captured before timestamp or a
non-strict matcher, and keep the ExpiresAt assertion as-is.
In
`@src/Modules/Payments/Tests/Unit/Infrastructure/BackgroundJobs/ProcessInboxJobExecutionTests.cs`:
- Around line 71-76: Remova o código morto no teste
ProcessInboxJobExecutionTests: apague as variáveis locais content e message que
são atribuídas mas nunca usadas (as identificadores content e message) e deixe
apenas as variáveis realmente exercitadas (por exemplo validContent e
failMessage); garanta que nenhuma asserção ou fluxo do teste dependa de
content/message antes de commitar para evitar alterar o comportamento do teste.
In `@src/Shared/MeAjudaAi.Shared.csproj`:
- Around line 59-63: O projeto removeu a dependência RabbitMQ.Client mas há usos
diretos que quebrarão a build; either re-add the RabbitMQ.Client
PackageReference into the csproj or refactor the classes
RabbitMqInfrastructureManager and RabbitMqDeadLetterService to use Rebus
abstractions instead of RabbitMQ.Client types. If restoring the package, add a
<PackageReference Include="RabbitMQ.Client" /> back into
MeAjudaAi.Shared.csproj; if refactoring, replace usages of RabbitMQ.Client types
(IChannel, ConnectionFactory and any using RabbitMQ.Client) with Rebus APIs
(IBus/transport-specific Rebus services or IMessageContext/transport
configuration) and update the constructors/methods in
RabbitMqInfrastructureManager and RabbitMqDeadLetterService to obtain
connections via Rebus configuration rather than direct RabbitMQ types.
In `@src/Shared/Messaging/MessagingExtensions.cs`:
- Around line 137-146: O bloco que chama manager.EnsureInfrastructureAsync()
está capturando e suprimindo exceções (logger.LogError(...)) fazendo a aplicação
continuar mesmo que filas/exchanges não tenham sido criadas; para comportamento
fail-fast remova o catch ou propague a exceção após logar para que o startup
falhe. Concretamente, localize a chamada a manager.EnsureInfrastructureAsync()
dentro do método em MessagingExtensions.cs e elimine o bloco catch (ou troque o
catch por logger.LogError(ex, ...) seguido de throw;) para que a exceção seja
repassada ao bootstrap.
In `@src/Web/MeAjudaAi.Web.Customer/__tests__/setup.ts`:
- Around line 26-43: The pluralization branch in the t() lookup (in the setup.ts
block handling options.count and building pluralKey) short-circuits the
defaultValue fallback due to an else-if; change the logic in the function that
builds result (the block that computes parentTranslation, pluralKey and assigns
result) so that after attempting to read parentTranslation[pluralKey] you still
check if the translation was not found and options.defaultValue is provided, and
return options.defaultValue when result === key (or parentTranslation[pluralKey]
is undefined); in other words, remove the else-if dependency and add a
post-pluralization check for options.defaultValue (refer to the
variables/options and keys, baseKey, parentKeys, parentTranslation, pluralKey
used in this block) so plural lookups fall back to defaultValue when missing.
In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/CurrencyUtilsTests.cs`:
- Line 35: Translate the InlineData comment on the test case in
CurrencyUtilsTests (the InlineData entry with 1000.001, "KWD", 1000001) from
English to Portuguese: replace "Three-decimal (Kuwaiti Dinar)" with a Portuguese
equivalent such as "Três casas decimais (Dinar kuwaitiano)"; keep existing AAA
headers in the test file in English and only change this explanatory inline
comment text.
---
Duplicate comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/appsettings.json`:
- Around line 124-134: Assegure que os valores em Payments:Plans não sejam
usados como IDs de Price do Stripe; substitua as chaves legadas
price_premium_monthly e price_gold_monthly por um campo explícito como
StripePriceId (ex.: "StripePriceId": "price_...") ou altere o fluxo que chama
GetPriceAsync/SessionCreateOptions para resolver via lookup_key antes de passar
para a API Stripe; localize e atualizar o JSON em Payments:Plans e ajustar o
código que usa GetPriceAsync, SessionCreateOptions, planId ou LookupKey para
usar o novo StripePriceId ou a resolução por lookup_key para evitar enviar
chaves de domínio internas diretamente.
In
`@src/Modules/Payments/Application/Subscriptions/Commands/GetBillingPortalCommand.cs`:
- Around line 8-10: GetBillingPortalCommand accepts ReturnUrl without validation
which allows open-redirects; update the handler that processes
GetBillingPortalCommand (e.g., GetBillingPortalCommandHandler.Handle) to
validate ReturnUrl is an absolute HTTPS URI and that its Host is contained in a
configured allowlist (e.g., a Billing/AllowedReturnHosts setting), and if
validation fails either throw a validation exception or fall back to a safe
default return URL before creating the Stripe billing session; reference the
ReturnUrl property on GetBillingPortalCommand and ensure the check runs prior to
any call that creates the Stripe session.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cs`:
- Around line 46-56: O código em CreateSubscriptionCommandHandler atualmente
lança SubscriptionCreationException contendo result.ErrorMessage bruto; mantenha
o detalhe técnico apenas no log (já em logger.LogWarning) e lance uma mensagem
de exceção/API consistente em português sem propagar o texto do gateway. Altere
o throw para usar uma mensagem amigável em português (por exemplo: "Falha ao
criar assinatura no gateway.") ou uma versão com contexto controlado, mantendo a
chamada a TryCompensateAsync e o LogWarning que registra result.ErrorMessage
para diagnóstico; não inclua result.ErrorMessage direto na mensagem da
SubscriptionCreationException.
In `@src/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cs`:
- Around line 108-119: Resumo: o acesso ao subscription id em ProcessInboxJob.cs
usa propriedades da surface do Stripe.NET que variam por versão
(invoice.Parent?.SubscriptionDetails?.Subscription?.Id e
InvoiceLineItem.SubscriptionId) e pode produzir null silenciosamente; verifique
a versão do Stripe.NET referenciada e ajuste a extração para suportar ambas as
formas. Instruções: confirme a versão do Stripe.NET no csproj (ou via comandos
sugeridos) e, em ProcessInboxJob.cs, altere a lógica que monta StripeEventData
no branch "invoice.paid" para detectar se
invoice.Parent?.SubscriptionDetails?.Subscription é uma string ou um objeto e
extrair o id respeitando ambos os formatos; adicione um fallback que procura o
id em invoice.Lines?.Data?.FirstOrDefault()? ->
Parent?.SubscriptionItemDetails?.Subscription (ou o campo equivalente na sua
versão) quando InvoiceLineItem.SubscriptionId não existir; e registre um log de
warning quando não for possível obter subscription id (em vez de ignorar
silenciosamente) para facilitar debug.
---
Nitpick comments:
In `@docs/messaging.md`:
- Line 5: Replace the introductory phrase "focada exclusivamente no
**RabbitMQ**" with wording that reflects the actual implementation, e.g. "focada
no **RabbitMQ via Rebus**", so the intro matches the rest of the document; keep
the rest of the sentence about NoOp/Mocks for test environments unchanged and
ensure the exact phrase "focada exclusivamente no **RabbitMQ**" is updated
wherever it appears.
In `@docs/technical-debt.md`:
- Line 14: Padronize os itens de backlog nessa seção substituindo qualquer texto
em inglês por português — por exemplo altere "Memory profiling in production"
para "Perfilagem de memória em produção" e traduza os outros bullets
referenciados (itens que aparecem após, atualmente em inglês nas linhas
equivalentes) para manter todo o documento em português; revise os textos dos
bullets e comentários relacionados (procure a string "Memory profiling in
production" e bullets semelhantes) e aplique traduções consistentes, preservando
exceção apenas para mensagens de log que devam permanecer em inglês.
In `@infrastructure/keycloak/realms/meajudaai-realm.prod.json`:
- Around line 197-199: Ajuste o valor de userAttribute para ficar consistente
com o realm de dev e a PR 200: substituir "userID" por "userId" no mapper que
define userAttribute (localize o campo userAttribute dentro do objeto config do
mapper correspondente) e remova a linha em branco extra logo após
"userAttribute" para manter a formatação consistente com os demais mappers do
arquivo.
In `@src/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cs`:
- Around line 67-75: Replace the current multi-line extraction/reassignment of
Idempotency-Key with a single expression that reads the header and yields null
for missing/whitespace or too-long values (Stripe max 255). For example, set
string? idempotencyKey =
httpContext.Request.Headers.TryGetValue("Idempotency-Key", out var v) ?
(string.IsNullOrWhiteSpace(v.ToString()) || v.ToString().Length > 255 ? null :
v.ToString()) : null; so the variable is assigned once and any value >255 chars
is treated as null before passing to downstream code (locate usage around the
Idempotency-Key extraction in CreateSubscriptionEndpoint.cs / idempotencyKey).
In `@src/Modules/Payments/API/Endpoints/Public/GetBillingPortalEndpoint.cs`:
- Around line 70-81: Add an observable log when the ReturnUrl falls into the
fallback branch: in GetBillingPortalEndpoint (around the
finalReturnUrl/returnUrl logic) detect the else case where finalReturnUrl is set
to clientBaseUrl and emit a LogDebug or LogInformation including the incoming
returnUrl value and context (e.g., customer id or request id if available) so
developers can see unexpected keys (like "/account" or "dashboard") without
changing behavior; use the existing logger instance in that class and keep the
log level low (Debug/Information).
In `@src/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cs`:
- Around line 19-27: Adicionar proteção de tamanho e limitação de taxa ao
endpoint público de webhook: na função StripeWebhookEndpoint.Map ao chamar
app.MapPost("stripe", ...) mantenha .AllowAnonymous() mas encadeie
.WithMetadata(new RequestSizeLimitAttribute(256_000)) (ou equivalente de policy)
para limitar o payload antes da leitura em MemoryStream e aplique uma política
de rate limiting (por exemplo .RequireRateLimiting("StripeWebhookPolicy") ou
equivalente) configurada por IP/assinatura; garanta que a política seja
registrada no pipeline de middleware e que validação de assinatura continue
ocorrendo antes de processamento pesado no método que consome o MemoryStream.
In
`@src/Modules/Payments/Application/Subscriptions/Handlers/GetBillingPortalCommandHandler.cs`:
- Around line 59-67: The current GetBillingPortalCommandHandler reads
configuration per-request via
configuration.GetSection("Payments:AllowedReturnHosts").Get<string[]>() and
configuration["ClientBaseUrl"] and builds trustedHosts on each call; change this
to inject an options object (IOptions<PaymentsOptions> or
IOptionsMonitor<PaymentsOptions>) that contains AllowedReturnHosts and
ClientBaseUrl and precompute the HashSet<string> trustedHosts once at startup
(or in the PaymentsOptions factory) so GetBillingPortalCommandHandler just
injects the prepared trustedHosts (or the options and uses a cached field)
instead of calling configuration inside the handler.
- Around line 56-57: The current check in GetBillingPortalCommandHandler that
only allows HTTP for host "localhost" will reject valid dev/CI loopback
addresses; update the validation so HTTP is allowed when uri.Host is "localhost"
OR when uri.Host parses to a loopback IP (e.g., "127.0.0.1" or "::1") — for
example, replace the literal host equality check in the if that throws
BusinessRuleException ("INVALID_RETURN_URL_SCHEME") with logic that uses
IPAddress.TryParse(uri.Host, out var ip) && IPAddress.IsLoopback(ip) or
otherwise checks uri.Host equals "localhost" (case-insensitive), so loopback IPs
are accepted as well.
In `@src/Modules/Payments/Domain/Entities/PaymentTransaction.cs`:
- Around line 33-45: The Settle method validates transaction state before input
arguments causing callers with bad input to get InvalidOperationException
instead of ArgumentException; in the Settle(string externalTransactionId)
method, move the guard clause that checks
string.IsNullOrWhiteSpace(externalTransactionId) to run before the Status check
(EPaymentStatus.Pending) so invalid externalTransactionId throws
ArgumentException first, then proceed to set ExternalTransactionId, Status,
ProcessedAt and call MarkAsUpdated.
- Around line 57-64: The Refund method doesn't record a timestamp; add a
nullable DateTime property RefundedAt to the PaymentTransaction entity and set
RefundedAt = DateTime.UtcNow inside Refund (alongside Status =
EPaymentStatus.Refunded and MarkAsUpdated()); also update any ORM mapping/DB
schema and relevant tests to persist/validate RefundedAt so refunds are
auditable (references: Refund, Settle, Fail, ProcessedAt, MarkAsUpdated,
EPaymentStatus).
In `@src/Modules/Payments/Domain/Entities/Subscription.cs`:
- Around line 133-149: The Renew method contains a redundant assignment of
Status = ESubscriptionStatus.Active after you already guard that Status ==
ESubscriptionStatus.Active; remove that no-op assignment in the Renew method
(keep the existing guard, MarkAsUpdated() call, and AddDomainEvent invocation)
so the method simply updates ExpiresAt, marks the entity updated, and raises
SubscriptionRenewedDomainEvent without reassigning Status.
In `@src/Modules/Payments/Domain/Events/SubscriptionActivatedDomainEvent.cs`:
- Around line 5-9: O evento público SubscriptionActivatedDomainEvent carece de
documentação XML; adicione um comentário <summary> em português acima da
declaração do record SubscriptionActivatedDomainEvent (seguindo o mesmo estilo
usado no evento de renovação) para descrever o propósito do evento e seus
parâmetros públicos (SubscriptionId, ProviderId, ExternalSubscriptionId),
mantendo a consistência dos contratos públicos do domínio.
In `@src/Modules/Payments/Infrastructure/Extensions.cs`:
- Around line 18-37: Replace the unused overload
AddDbContext<PaymentsDbContext>((serviceProvider, options) => { ... }) with the
simpler AddDbContext<PaymentsDbContext>(options => { ... }) by removing the
unused serviceProvider parameter, and add a using MeAjudaAi.Shared.Utilities; at
the top so you can call
EnvironmentHelpers.IsSecurityBypassEnvironment(environment) unqualified; keep
the same connection-string resolution, exception throw and
options.UseNpgsql(connStr, m =>
m.MigrationsHistoryTable("__EFMigrationsHistory", "payments")) logic unchanged.
In `@src/Modules/Payments/Infrastructure/Gateways/IStripeService.cs`:
- Around line 13-18: The StripeService currently constructs SDK service
instances with their parameterless ctors (e.g., PriceService,
Stripe.Checkout.SessionService, Stripe.BillingPortal.SessionService,
Stripe.SubscriptionService) which rely on the global StripeConfiguration.ApiKey;
change StripeService to accept an IStripeClient via constructor injection and
instantiate those services with that client (e.g., new
PriceService(stripeClient), new SessionService(stripeClient), etc.), remove
direct reliance on StripeConfiguration.ApiKey, and ensure IStripeClient is
registered in DI (or use Stripe.Extensions.DependencyInjection) so HttpClient
lifetimes and testability are handled correctly.
In `@src/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cs`:
- Around line 70-71: Substitua o comentário inline que precede a resolução do
planId pelo mapeamento de configuração para Português mantendo o estilo dos
arquivos C# do repositório; localize a linha que atribui stripePriceId (var
stripePriceId = _configuration[$"Payments:Plans:{planId}:StripePriceId"] ??
planId;) e troque o comentário atual por uma frase em português explicando que o
planId do domínio é resolvido para o Stripe Price ID via mapeamento de
configuração (mantenha apenas um comentário de linha simples, sem transformar em
cabeçalho AAA ou alterar logs).
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cs`:
- Line 61: The current unique index declaration builder.HasIndex(t =>
t.ExternalTransactionId).IsUnique() should be changed to a unique partial index
that only applies when ExternalTransactionId is not null to express intent and
improve performance; update the index configuration in
PaymentTransactionConfiguration (the builder.HasIndex(...) call) to use the EF
Core method overload or raw SQL/annotation that creates a filtered/partial index
with the predicate "WHERE external_transaction_id IS NOT NULL" so the unique
constraint only applies to non-null values.
In
`@src/Modules/Payments/Infrastructure/Persistence/Configurations/SubscriptionConfiguration.cs`:
- Line 70: The unique index on ExternalSubscriptionId should be partial so null
values are allowed; update the builder.HasIndex(s =>
s.ExternalSubscriptionId).IsUnique() call in SubscriptionConfiguration to add a
filter for non-null values (e.g. .HasFilter("external_subscription_id IS NOT
NULL") or the appropriate quoted identifier for your provider) so the index
becomes UNIQUE WHERE external_subscription_id IS NOT NULL, matching the optional
ExternalSubscriptionId property semantics.
In
`@src/Modules/Payments/Infrastructure/Repositories/PaymentTransactionRepository.cs`:
- Around line 40-45: Add the same input guard present in
SubscriptionRepository.GetByExternalIdAsync to
PaymentTransactionRepository.GetByExternalIdAsync: check that the
externalTransactionId parameter is not null or whitespace at the start of the
method and throw an ArgumentException (or the same exception type/message used
by SubscriptionRepository) if it's invalid, so the query is never executed with
an empty value that could match unintended records.
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cs`:
- Around line 63-89: Mantenha o teste HandleAsync_ShouldThrow_WhenPlanIsInvalid
como está; para HandleAsync_ShouldThrow_WhenConfigIsIncomplete, transforme-o em
um [Theory] com [InlineData("Payments:Plans:price_premium_monthly:Amount")] e
[InlineData("Payments:Plans:price_premium_monthly:Currency")] (ou duas entradas
equivalentes) para validar tanto Amount quanto Currency vazios, e no corpo do
teste use _configurationMock.Setup(x => x[It.Is<string>(s => s ==
key)]).Returns("") where key é o parâmetro da teoria, crie o mesmo
CreateSubscriptionCommand(...) e invoque _handler.HandleAsync(command) esperando
SubscriptionCreationException com a mensagem "Configuração incompleta"; preserve
o uso de _configurationMock e os nomes dos testes/command para fácil
localização.
In
`@src/Modules/Payments/Tests/Unit/Application/Handlers/GetBillingPortalCommandHandlerTests.cs`:
- Around line 36-42: Replace the fragile mocked IConfiguration setup
(references: _configurationMock, GetSection, GetChildren) with a real
IConfiguration built via ConfigurationBuilder.AddInMemoryCollection containing
the key "Payments:AllowedReturnHosts:0" -> "localhost"; then use that
IConfiguration instance in the test setup (or inject it into the handler under
test) instead of the mocked _configurationMock so calls like
GetSection/GetChildren/indexer behave naturally and you avoid brittle mock
plumbing.
In `@src/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cs`:
- Around line 58-68: O teste atual só verifica que o segundo backoff é maior que
60s; antes de chamar msg.RecordError("err2") capture DateTime.UtcNow as
beforeSecondError, then compute expectedDelay = TimeSpan.FromSeconds(120) (2^2 *
30) and assert que msg.NextAttemptAt.Value is close to beforeSecondError +
expectedDelay (por exemplo usando Should().BeCloseTo or comparing absolute
difference menor que 1-2s) para validar a proximidade do atraso esperado;
atualize asserções referenciando os membros InboxMessage.RecordError and
InboxMessage.NextAttemptAt.
In `@src/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cs`:
- Around line 100-124: Add unit tests to cover the missing edge cases on
PaymentTransaction: create tests that call Refund() when the transaction is
already in Refunded state and when it's in Failed state and assert
InvalidOperationException; add tests that call Settle(null!) and Settle(" ")
and assert the appropriate exception (ArgumentNullException or ArgumentException
/ ArgumentException respectively) to ensure null and whitespace externalId
validation; reference the PaymentTransaction class and its Refund() and
Settle(string) methods when adding these tests.
- Line 55: The test currently asserts the exact exception message via
WithMessage("*status*"), which is fragile across translations; update the
assertion in PaymentTransactionTests (the act variable assertion) to only assert
the exception type (act.Should().Throw<InvalidOperationException>()) or, if you
need to check message content, compare against a shared production constant
(e.g., PaymentTransaction.InvalidStatusMessage) instead of a hard-coded string
so translations won't break the test.
In
`@src/Modules/Payments/Tests/Unit/Infrastructure/BackgroundJobs/ProcessInboxJobExecutionTests.cs`:
- Around line 103-141: The test wrapper duplicates the ExecuteAsync loop (in
ProcessInboxJobWrapper.DoExecuteStepAsync), risking drift; refactor the
production class ProcessInboxJob by extracting the batch-processing loop into a
protected method (e.g., protected virtual Task
ProcessSingleBatchAsync(CancellationToken ct) or protected for tests) that
contains the claim → parse (MapToStripeEventData) → ProcessStripeEventAsync →
MarkAsProcessed/RecordError logic, have ExecuteAsync call that new method, and
update ProcessInboxJobWrapper to call the protected ProcessSingleBatchAsync
instead of reimplementing the loop so tests exercise the real logic.
In
`@src/Modules/Payments/Tests/Unit/Infrastructure/Persistence/PaymentsDbContextTests.cs`:
- Around line 22-48: The test currently only verifies ProcessDomainEventsAsync
was called once but not that non-empty events were passed; update the assertion
to verify the dispatched events themselves by expecting a non-empty
IEnumerable<IDomainEvent> (or capture the argument via a Callback) when
verifying domainEventProcessorMock.ProcessDomainEventsAsync in
PaymentsDbContextTests (look for domainEventProcessorMock and
ProcessDomainEventsAsync), then assert the captured/verified collection contains
the expected domain event types (or at least Any()) after calling
context.SaveChangesAsync and before/after checking sub.DomainEvents.
In `@src/Modules/Payments/Tests/Unit/Infrastructure/ProcessInboxJobTests.cs`:
- Around line 1-294: The test
ProcessStripeEventAsync_InvoicePaid_WithCurrencyDivergence_ShouldLogWarningAndSucceed
does not assert the logger warning; add a verification on the _loggerMock to
ensure a LogLevel.Warning call containing the expected message (e.g., "Currency
divergence") occurred once after calling ProcessStripeEventAsync, using
_loggerMock.Verify(...) with LogLevel.Warning, It.IsAny<EventId>(), and an
object predicate that checks o.ToString().Contains("Currency divergence") so the
intent is enforced and prevents silent regressions.
In
`@src/Modules/Ratings/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandler.cs`:
- Around line 15-23: The current truncation in ReviewRejectedDomainEventHandler
uses domainEvent.Reason[..100], which can split UTF-16 surrogate pairs (e.g.,
emojis); replace the naive slice with a safe truncation that preserves grapheme
clusters or at least surrogate pairs: implement or call a helper (e.g.,
TruncateUnicode or a StringInfo-based method) to produce reasonPreview from
domainEvent.Reason, then pass that safe reasonPreview into
logger.LogDebug("Rejection reason for Review {ReviewId}: {Reason}",
domainEvent.AggregateId, reasonPreview); ensure the guard
(string.IsNullOrWhiteSpace) remains.
In
`@src/Modules/Ratings/Tests/Integration/Persistence/Repositories/ReviewRepositoryTests.cs`:
- Around line 200-218: The test AddAsync_ShouldThrowDbUpdateException currently
asserts a DbUpdateException which is fine given the unique index configured in
ReviewConfiguration, but refine the assertion to ensure the DbUpdateException is
due to SQLite unique constraint: invoke the act against
_repository.AddAsync(review2) and assert the thrown DbUpdateException has an
InnerException of type SqliteException (System.Data.Sqlite.SqliteException or
Microsoft.Data.Sqlite.SqliteException depending on project) and that the
SqliteErrorCode (or the inner exception message) indicates a constraint/unique
violation (or contains "UNIQUE"); this makes the test explicitly validate the
uniqueness violation rather than any other DB update error.
In `@src/Shared/Domain/ValueObjects/Money.cs`:
- Line 46: The current Money.ToString() uses the culture-dependent format
"{Amount:N2}" which yields different results across environments; update
Money.ToString() to format Amount with a fixed culture (e.g.,
CultureInfo.InvariantCulture) so serialization/logs are stable—use
Amount.ToString with an explicit CultureInfo (and keep Currency unchanged) to
produce a consistent string representation across locales.
In `@src/Shared/Messaging/Options/RabbitMqOptions.cs`:
- Around line 20-31: A propriedade RabbitMqOptions.DomainQueues usa literais de
string para chaves (ex.: "Users", "Payments") que podem divergir
silenciosamente; extracte essas chaves para um tipo fortemente tipado (por
exemplo uma classe estática ModuleNames com constantes string ou um enum
DomainName com mapeamento canônico) e substitua as literais no inicializador de
DomainQueues por essas constantes; depois assegure que publishers e a função
GetQueueForDomain (e qualquer uso de DefaultQueueName) utilizem as mesmas
constantes/enum e um formato canônico (ex.: ToString()/ToLowerInvariant() ou um
método helper) para evitar diferenças de caixa/typos e garantir lookup
determinístico.
In `@src/Web/MeAjudaAi.Web.Customer/lib/i18n.ts`:
- Around line 10-28: Translation keys can flicker because i18n.init() is async
while useSuspense is false and I18nProvider does not wait for readiness; update
I18nProvider to track i18n readiness (subscribe to i18n.on('initialized') or
check i18n.isInitialized) and render a neutral fallback until ready, or
alternatively embed critical translations via the i18n.init({ resources: { ... }
}) option instead of relying on the http backend; ensure components using
useTranslation({ useSuspense: false }) also check the i18n ready flag before
rendering translated strings to avoid showing raw keys.
In `@src/Web/MeAjudaAi.Web.Customer/public/locales/pt/translation.json`:
- Around line 10-21: The translation keys mix camelCase and snake_case (e.g.,
view_profile vs loadingProfile); rename snake_case keys to camelCase for
consistency—change "view_profile" → "viewProfile", "contact" etc. remain same if
already camelCase—and keep plural keys "review_one" and "review_other" as-is
(i18next plural suffixes). After renaming entries in the provider block, update
all code references to those keys (e.g., replace t('provider.view_profile') with
t('provider.viewProfile')) so runtime lookups match the new keys.
In `@tests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cs`:
- Around line 6-25: The MockPaymentGateway currently ignores inputs and always
returns success; update MockPaymentGateway to record incoming calls by adding
thread-safe storage (e.g., counters and lists) and expose read-only properties
so tests can assert call count and parameters; specifically record parameters
passed into CreateSubscriptionAsync (providerId, planId, amount,
idempotencyKey), CancelSubscriptionAsync (externalSubscriptionId) and
CreateBillingPortalSessionAsync (externalCustomerId, returnUrl), increment call
counters (or enqueue a call-info object) on each invocation, and ensure storage
is safe for concurrent test runs (use ConcurrentQueue/Interlocked or similar) so
E2E tests can validate propagation and invocation counts without changing return
behavior.
In `@tests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cs`:
- Around line 6-27: O MockPaymentGateway sempre retorna sucesso e não permite
inspeção ou simulação de falhas; altere a classe MockPaymentGateway para expor
propriedades configuráveis e histórico de chamadas (por exemplo bool
ShouldFailNextCall, Dictionary<string,int> CallCounts, List<InvocationRecord>
RecordedCalls) e/ou aceitar delegate/Func substituíveis para cada operação
(CreateSubscriptionAsync, CancelSubscriptionAsync,
CreateBillingPortalSessionAsync) para que testes possam forçar retorno de erro
(ex.: retornar false ou null), respeitar idempotencyKey e verificar parâmetros
usados; implemente lógica nas methods CreateSubscriptionAsync,
CancelSubscriptionAsync e CreateBillingPortalSessionAsync que primeiro registra
a invocação em RecordedCalls, incrementa CallCounts, e então usa o
delegate/configuração para decidir se retorna sucesso aleatório, um valor
nulo/false ou lança exceção.
In `@tests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cs`:
- Around line 240-328: The test exposes ProcessInboxJob's public helpers only
for testing; change MapToStripeEventData and ProcessStripeEventAsync (and any
other test-only public members on
MeAjudaAi.Modules.Payments.Infrastructure.BackgroundJobs.ProcessInboxJob) from
public to internal and add an InternalsVisibleTo entry for the test assembly so
tests can access them without making the job's API public.
- Around line 208-224: O uso de $$$""" em
StripeWebhook_CheckoutSessionMissingProviderId_ShouldEnqueueInboxMessage
habilita interpolação desnecessária porque não há placeholders; troque essas
strings para raw triple-quoted literals ("""...""") nas declarações dentro desse
teste para deixar a intenção clara e evitar confusão; revise também as outras
ocorrências mencionadas (payloads nas faixas correspondentes onde não existem
{{{...}}} placeholders) e mantenha $$$""" apenas onde há substituições reais
(por exemplo onde {{{externalSubId}}} é usado).
In
`@tests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/ConfigurableTestAuthenticationHandler.cs`:
- Around line 229-240: Rename the ConfigureProvider method parameter userName to
username to match ConfigureRegularUser; update the method signature
(ConfigureProvider(Guid providerId, string username = "provider", ...)) and
replace all uses of userName inside ConfigureProvider (including the UserConfig
constructor call) so the code compiles and named-parameter calls remain
consistent with ConfigureRegularUser.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a50469a1-8f93-454e-ba71-70547e92abfa
⛔ Files ignored due to path filters (65)
src/Aspire/MeAjudaAi.AppHost/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Aspire/MeAjudaAi.ServiceDefaults/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Communications/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Documents/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Locations/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260414205201_InitialPayments.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260415181442_AddInboxMessageRetryColumns.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/20260416000000_AddExternalCustomerIdToSubscriptions.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260416000000_AddExternalCustomerIdToSubscriptions.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/20260416192522_AddExternalEventIdToInbox.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Payments/Infrastructure/Persistence/Migrations/20260416192522_AddExternalEventIdToInbox.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/Persistence/Migrations/PaymentsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Payments/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Payments/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Providers/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Ratings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/SearchProviders/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/ServiceCatalogs/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Users/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Shared/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Web/MeAjudaAi.Web.Provider/app/configuracoes/page.tsxis excluded by!**/MeAjudaAi.Web.Provider/**src/Web/package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.jsontests/MeAjudaAi.ApiService.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Architecture.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.E2E.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Integration.Tests/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Shared.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (123)
Directory.Packages.propsMeAjudaAi.slnxdocs/messaging.mddocs/roadmap.mddocs/technical-debt.mdinfrastructure/keycloak/realms/meajudaai-realm.dev.jsoninfrastructure/keycloak/realms/meajudaai-realm.prod.jsonsrc/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csprojsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Bootstrapper/MeAjudaAi.ApiService/appsettings.jsonsrc/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cssrc/Modules/Communications/Application/Services/Email/StubEmailService.cssrc/Modules/Communications/Tests/Unit/Application/Handlers/UserRegisteredIntegrationEventHandlerTests.cssrc/Modules/Communications/Tests/Unit/Application/Services/OutboxProcessorServiceTests.cssrc/Modules/Payments/API/Endpoints/PaymentsEndpoints.cssrc/Modules/Payments/API/Endpoints/Public/CreateSubscriptionEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/GetBillingPortalEndpoint.cssrc/Modules/Payments/API/Endpoints/Public/StripeWebhookEndpoint.cssrc/Modules/Payments/API/Extensions.cssrc/Modules/Payments/API/MeAjudaAi.Modules.Payments.API.csprojsrc/Modules/Payments/Application/Extensions.cssrc/Modules/Payments/Application/MeAjudaAi.Modules.Payments.Application.csprojsrc/Modules/Payments/Application/Subscriptions/Commands/CreateSubscriptionCommand.cssrc/Modules/Payments/Application/Subscriptions/Commands/GetBillingPortalCommand.cssrc/Modules/Payments/Application/Subscriptions/Exceptions/SubscriptionCreationException.cssrc/Modules/Payments/Application/Subscriptions/Handlers/CreateSubscriptionCommandHandler.cssrc/Modules/Payments/Application/Subscriptions/Handlers/GetBillingPortalCommandHandler.cssrc/Modules/Payments/Domain/Abstractions/IPaymentGateway.cssrc/Modules/Payments/Domain/DomainConstants.cssrc/Modules/Payments/Domain/Entities/InboxMessage.cssrc/Modules/Payments/Domain/Entities/PaymentTransaction.cssrc/Modules/Payments/Domain/Entities/Subscription.cssrc/Modules/Payments/Domain/Enums/EPaymentStatus.cssrc/Modules/Payments/Domain/Enums/ESubscriptionStatus.cssrc/Modules/Payments/Domain/Events/SubscriptionActivatedDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionCanceledDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionExpiredDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionRenewedDomainEvent.cssrc/Modules/Payments/Domain/MeAjudaAi.Modules.Payments.Domain.csprojsrc/Modules/Payments/Domain/Repositories/IPaymentTransactionRepository.cssrc/Modules/Payments/Domain/Repositories/ISubscriptionRepository.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/ProcessInboxJob.cssrc/Modules/Payments/Infrastructure/BackgroundJobs/StripeEventData.cssrc/Modules/Payments/Infrastructure/Extensions.cssrc/Modules/Payments/Infrastructure/Gateways/IStripeService.cssrc/Modules/Payments/Infrastructure/Gateways/StripePaymentGateway.cssrc/Modules/Payments/Infrastructure/MeAjudaAi.Modules.Payments.Infrastructure.csprojsrc/Modules/Payments/Infrastructure/Persistence/Configurations/InboxMessageConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/PaymentTransactionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/Configurations/SubscriptionConfiguration.cssrc/Modules/Payments/Infrastructure/Persistence/PaymentsDbContext.cssrc/Modules/Payments/Infrastructure/Repositories/PaymentTransactionRepository.cssrc/Modules/Payments/Infrastructure/Repositories/SubscriptionRepository.cssrc/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csprojsrc/Modules/Payments/Tests/Unit/Application/Handlers/CreateSubscriptionCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Application/Handlers/GetBillingPortalCommandHandlerTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/InboxMessageTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/PaymentTransactionTests.cssrc/Modules/Payments/Tests/Unit/Domain/Entities/SubscriptionTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/BackgroundJobs/ProcessInboxJobExecutionTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/Gateways/StripePaymentGatewayTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/Persistence/PaymentsDbContextTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/ProcessInboxJobTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/Repositories/PaymentTransactionRepositoryTests.cssrc/Modules/Payments/Tests/Unit/Infrastructure/Repositories/SubscriptionRepositoryTests.cssrc/Modules/Ratings/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandler.cssrc/Modules/Ratings/Tests/Integration/Persistence/Repositories/ReviewRepositoryTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewApprovedDomainEventHandlerTests.cssrc/Modules/Ratings/Tests/Unit/Infrastructure/Events/Handlers/ReviewRejectedDomainEventHandlerTests.cssrc/Modules/Users/Infrastructure/Events/Handlers/UserRegisteredDomainEventHandler.cssrc/Modules/Users/Infrastructure/Services/LocalDevelopment/LocalDevelopmentAuthenticationDomainService.cssrc/Shared/Domain/ValueObjects/Money.cssrc/Shared/MeAjudaAi.Shared.csprojsrc/Shared/Messaging/Factories/MessageBusFactory.cssrc/Shared/Messaging/IMessageBus.cssrc/Shared/Messaging/MessagingExtensions.cssrc/Shared/Messaging/NoOp/NoOpMessageBus.cssrc/Shared/Messaging/Options/RabbitMqOptions.cssrc/Shared/Messaging/RabbitMq/RabbitMqMessageBus.cssrc/Shared/Messaging/Rebus/RebusMessageBus.cssrc/Shared/Utilities/Constants/AuthConstants.cssrc/Shared/Utilities/Constants/RoleConstants.cssrc/Shared/Utilities/CurrencyUtils.cssrc/Shared/Utilities/UserRoles.cssrc/Web/MeAjudaAi.Web.Customer/__tests__/app/(main)/prestador/[id]/loading.test.tsxsrc/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/provider-card.test.tsxsrc/Web/MeAjudaAi.Web.Customer/__tests__/components/ui/skeleton.test.tsxsrc/Web/MeAjudaAi.Web.Customer/__tests__/setup.tssrc/Web/MeAjudaAi.Web.Customer/app/(main)/buscar/loading.tsxsrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/loading.tsxsrc/Web/MeAjudaAi.Web.Customer/components/providers/I18nProvider.tsxsrc/Web/MeAjudaAi.Web.Customer/components/providers/app-providers.tsxsrc/Web/MeAjudaAi.Web.Customer/components/providers/provider-card.tsxsrc/Web/MeAjudaAi.Web.Customer/components/ui/skeleton.tsxsrc/Web/MeAjudaAi.Web.Customer/lib/i18n.tssrc/Web/MeAjudaAi.Web.Customer/package.jsonsrc/Web/MeAjudaAi.Web.Customer/public/locales/en/translation.jsonsrc/Web/MeAjudaAi.Web.Customer/public/locales/pt/translation.jsontests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csprojtests/MeAjudaAi.E2E.Tests/Base/Helpers/DbContextSchemaHelper.cstests/MeAjudaAi.E2E.Tests/Base/Mocks.cstests/MeAjudaAi.E2E.Tests/Base/TestContainerFixture.cstests/MeAjudaAi.E2E.Tests/Infrastructure/Mocks/MockPaymentGateway.cstests/MeAjudaAi.E2E.Tests/Infrastructure/SynchronousInMemoryMessageBus.cstests/MeAjudaAi.E2E.Tests/MeAjudaAi.E2E.Tests.csprojtests/MeAjudaAi.E2E.Tests/Modules/Payments/PaymentsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csprojtests/MeAjudaAi.Integration.Tests/Mocks/MockPaymentGateway.cstests/MeAjudaAi.Integration.Tests/Modules/Payments/PaymentsApiTests.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Extensions/TestBaseAuthExtensions.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Extensions/TestInfrastructureExtensions.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/ConfigurableTestAuthenticationHandler.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/ITestAuthenticationConfiguration.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/InstanceTestAuthenticationHandler.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/TestAuthenticationConfiguration.cstests/MeAjudaAi.Shared.Tests/TestInfrastructure/Mocks/Messaging/MockRabbitMqMessageBus.cstests/MeAjudaAi.Shared.Tests/Unit/Domain/ValueObjects/MoneyTests.cstests/MeAjudaAi.Shared.Tests/Unit/Messaging/MessagingExtensionsTests.cstests/MeAjudaAi.Shared.Tests/Unit/Messaging/RabbitMqMessageBusTests.cstests/MeAjudaAi.Shared.Tests/Unit/Messaging/RebusMessageBusTests.cstests/MeAjudaAi.Shared.Tests/Unit/Monitoring/MetricsCollectorServiceTests.cstests/MeAjudaAi.Shared.Tests/Unit/Utilities/CurrencyUtilsTests.cs
💤 Files with no reviewable changes (4)
- src/Modules/Communications/Application/Services/CommunicationsOutboxWorker.cs
- src/Modules/Communications/Application/Services/Email/StubEmailService.cs
- tests/MeAjudaAi.Shared.Tests/Unit/Messaging/RabbitMqMessageBusTests.cs
- src/Shared/Messaging/RabbitMq/RabbitMqMessageBus.cs
…mple data classes
…DTOs across all modules
- Disable rate limiting for Stripe webhooks and add request size limits - Implement provider-agnostic idempotency check in StripeWebhookEndpoint - Enhance domain events with aggregate versioning and fix Subscription.Activate logic - Improve StripePaymentGateway configuration validation and idempotency key composition - Refactor ProcessInboxJob with better batch processing and corrected SDK property paths - Update test infrastructure with recording mocks and improved assertions - Standardize translations and naming conventions across backend and frontend - Propagate infrastructure errors in messaging setup for fail-fast behavior
… dummy IStripeClient
|
@coderabbitai por favor, revise as mudanças nos commits abd597f, d7be99c, 7ed9c0f e 8706c6d, de preferência criando um prompt para agente ai. Também analise por que o módulo payments, mais especificamente as camadas de Domain e Applicaion, não tem uma boa cobertura, onde está o problema neste módulo em específico. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 89416 🏁 Script executed: Length of output: 36250 🏁 Script executed: Length of output: 163423
Review (commits: abd597f, d7be99c, 7ed9c0f, 8706c6d)
Risks/notes
Por que a cobertura de Payments (Domain e Application) está baixa Evidence from the repo:
Likely causes (actionable):
Quick verification you can run locally
AI agent prompt (to automate a deep pass on these commits + coverage) Use this prompt with your internal agent to perform a commit-scoped review and pinpoint coverage gaps: You are reviewing MeAjudaAi PR
Tasks:
Next steps I recommend
If you want, I can propose exact dotnet test globs for your workflows and draft 6–8 new xUnit tests targeting the uncovered branches you care about. 🧠 Learnings used |
@coderabbitai eu quero que crie uma lista de atividades baseadas neste relatório, assim como isso aqui: I can propose exact dotnet test globs for your workflows and draft 6–8 new xUnit tests targeting the uncovered branches you care about. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 24104 🏁 Script executed: Length of output: 4570
Lista de Atividades🔧 Atividade 1 — CI: Adicionar Payments ao array MODULESProblema confirmado: Fix — MODULES=(
"src/Modules/Users/Tests/MeAjudaAi.Modules.Users.Tests.csproj"
"src/Modules/Providers/Tests/MeAjudaAi.Modules.Providers.Tests.csproj"
"src/Modules/Documents/Tests/MeAjudaAi.Modules.Documents.Tests.csproj"
"src/Modules/ServiceCatalogs/Tests/MeAjudaAi.Modules.ServiceCatalogs.Tests.csproj"
"src/Modules/Locations/Tests/MeAjudaAi.Modules.Locations.Tests.csproj"
"src/Modules/Communications/Tests/MeAjudaAi.Modules.Communications.Tests.csproj"
"src/Modules/Ratings/Tests/MeAjudaAi.Modules.Ratings.Tests.csproj"
"src/Modules/SearchProviders/Tests/MeAjudaAi.Modules.SearchProviders.Tests.csproj"
+ "src/Modules/Payments/Tests/MeAjudaAi.Modules.Payments.Tests.csproj"
"tests/MeAjudaAi.Shared.Tests/MeAjudaAi.Shared.Tests.csproj"
"tests/MeAjudaAi.ApiService.Tests/MeAjudaAi.ApiService.Tests.csproj"
"tests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csproj"
)Adicionar também a env var de connection string para o job de unit tests (o ConnectionStrings__meajudaai-db: ${{ steps.db.outputs.connection-string }}
+ ConnectionStrings__Payments: ${{ steps.db.outputs.connection-string }}🧪 Atividade 2 — 8 novos testes xUnit para branches não cobertasAdicionar ao arquivo [Fact]
public async Task HandleAsync_ShouldThrow_WhenIdempotencyKeyExceeds255Chars()
{
// Arrange
var longKey = new string('x', 256);
var command = new CreateSubscriptionCommand(Guid.NewGuid(), "price_premium_monthly", longKey);
// Act
Func<Task> act = () => _handler.HandleAsync(command);
// Assert
var ex = await act.Should().ThrowAsync<SubscriptionCreationException>();
ex.Which.Message.Should().Contain("255");
}Adicionar ao arquivo [Fact]
public void Refund_ShouldBeIdempotent_WhenAlreadyRefunded()
{
// Arrange
var transaction = new PaymentTransaction(Guid.NewGuid(), new Money(100m, "BRL"));
transaction.Settle("tx_ext_001");
transaction.Refund(); // primeira chamada
// Act
Action act = () => transaction.Refund(); // segunda chamada
// Assert
act.Should().NotThrow();
transaction.Status.Should().Be(EPaymentStatus.Refunded);
}
[Fact]
public void Refund_ShouldPreserveRefundedAt_OnSecondCall()
{
// Arrange
var transaction = new PaymentTransaction(Guid.NewGuid(), new Money(50m, "BRL"));
transaction.Settle("tx_ext_002");
transaction.Refund();
var firstRefundedAt = transaction.RefundedAt;
Thread.Sleep(10); // garante delta de tempo
// Act
transaction.Refund(); // segunda chamada — no-op
// Assert
transaction.RefundedAt.Should().Be(firstRefundedAt);
}Adicionar ao arquivo [Fact]
public void Cancel_ShouldBeNoOp_WhenAlreadyExpired()
{
// Arrange
var sub = new Subscription(Guid.NewGuid(), "plan_a", new Money(99.90m, "BRL"));
sub.Activate("sub_ext", "cus_ext");
sub.Expire();
sub.ClearDomainEvents();
// Act
sub.Cancel(); // deve ser no-op
// Assert
sub.Status.Should().Be(ESubscriptionStatus.Expired);
sub.DomainEvents.Should().BeEmpty();
}
[Fact]
public void Expire_ShouldBeNoOp_WhenAlreadyCanceled()
{
// Arrange
var sub = new Subscription(Guid.NewGuid(), "plan_a", new Money(99.90m, "BRL"));
sub.Activate("sub_ext", "cus_ext");
sub.Cancel();
sub.ClearDomainEvents();
// Act
sub.Expire(); // deve ser no-op
// Assert
sub.Status.Should().Be(ESubscriptionStatus.Canceled);
sub.DomainEvents.Should().BeEmpty();
}
[Fact]
public void Renew_ShouldThrow_WhenNewExpiresAtEqualsCurrentExpiresAt()
{
// Arrange
var expiresAt = DateTime.UtcNow.AddDays(30);
var sub = new Subscription(Guid.NewGuid(), "plan_a", new Money(99.90m, "BRL"));
sub.Activate("sub_ext", "cus_ext", expiresAt);
// Act
Action act = () => sub.Renew(expiresAt); // mesma data — não é posterior
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*after current expiration*");
}
[Fact]
public void Activate_ShouldIncrementVersion_OnSuccessfulActivation()
{
// Arrange
var sub = new Subscription(Guid.NewGuid(), "plan_a", new Money(99.90m, "BRL"));
var initialVersion = sub.Version;
// Act
sub.Activate("sub_ext", "cus_ext");
// Assert
sub.Version.Should().BeGreaterThan(initialVersion);
}Adicionar ao arquivo [Fact]
public async Task HandleAsync_ShouldAllowLoopbackIp_WithHttp()
{
// Arrange
var providerId = Guid.NewGuid();
var sub = new Subscription(Guid.NewGuid(), "plan_a", new Money(99.90m, "BRL"));
sub.Activate("sub_ext", "cus_ext_loopback");
_repositoryMock
.Setup(r => r.GetActiveByProviderIdAsync(providerId, It.IsAny<CancellationToken>()))
.ReturnsAsync(sub);
_gatewayMock
.Setup(g => g.CreateBillingPortalSessionAsync("cus_ext_loopback", It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync("https://billing.stripe.com/portal_session");
var command = new GetBillingPortalCommand(providerId, "http://127.0.0.1/return");
// Act
var result = await _handler.HandleAsync(command);
// Assert
result.Should().Be("https://billing.stripe.com/portal_session");
}✅ Checklist de Execução
🧠 Learnings used |
- Include Payments module tests in CI pipeline - Add 8 new xUnit tests for Payments domain and application edge cases - Remove ExcludeFromCodeCoverage from Command records across all modules - Configure necessary connection strings for unit test jobs
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai apliquei as ultimas mudanças sugeridas, a quantidade de linhas na conta aumentou drasticamente (+7000), com isso a cobertura global caiu para 87%. É possível chegar aos 90% de cobertura global ou é algo intangível? Poderia propor soluçÕes (desde abaixar o theshold até sugerir testes ou exclusões). |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 17251
Diagnóstico rápidoO threshold atual no CI (linha 288 de thresholds: "90 80" # line branch
fail_below_min: trueVocê está em 87% de linhas — faltam ~3 pontos percentuais. As opções não são excludentes: você pode combinar todas. 🅐 Excluir boilerplate do relatório (impacto estimado: +1,5 a 2 pp)Expanda o - classfilters: "-*.Tests;-*.Tests.*;-*Test*;-testhost;-*.Migrations.*;-*Program*;-*.Seeding.*;-*.Monitoring.*;-MeAjudaAi.Shared.Jobs.*;-MeAjudaAi.Shared.Mediator.*;-MeAjudaAi.Shared.API.*"
+ classfilters: "-*.Tests;-*.Tests.*;-*Test*;-testhost;-*.Migrations.*;-*Program*;-*.Seeding.*;-*.Monitoring.*;-MeAjudaAi.Shared.Jobs.*;-MeAjudaAi.Shared.Mediator.*;-MeAjudaAi.Shared.API.*;-*Configuration;-*Extensions;-*Endpoint;-MeAjudaAi.Modules.Payments.Infrastructure.Gateways.StripeService"O que isso exclui e por quê:
🅑 Adicionar
|
| # | Ação | Impacto | Esforço |
|---|---|---|---|
| A | Expandir classfilters no CI |
~+1,5–2 pp | 1 linha no YAML |
| B | [ExcludeFromCodeCoverage] nos 2 command records |
~+0,5 pp | 2 linhas de código |
| C1 | Teste zero-decimal em StripePaymentGatewayTests |
~+0,3 pp | ~15 linhas |
| C2 | Teste Activate com ExpiresAt = now |
~+0,1 pp | ~10 linhas |
| C3 | Teste unknown event em ProcessInboxJobTests |
~+0,1 pp | ~10 linhas |
| D | Baixar threshold para 87% temporariamente | bridge | 1 linha no YAML |
Recomendação: aplicar A + B agora (impacto ~2+ pp, chega a ≥89%) e os testes C para cruzar 90% — sem precisar baixar o threshold.
🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.
…argeted unit tests
@coderabbitai no ultimo relatorio de cobertura, alguns namespaces aparecem com cobertura bem baixa: MeAjudaAi.Modules.Communications.Infrastructure, MeAjudaAi.Modules.Ratings.API, |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 52789
Resumo das exclusões já ativas
Ajustes rápidos (baixo esforço, alto impacto)
Por namespace — o que testar
Checklist rápido
Quer que eu já abra um plano com os arquivos-alvo e esboços de testes por projeto? Posso também ajustar o ci-backend.yml com os novos classfilters e mandar os trechos prontos para commit. 🧠 Learnings used |
@coderabbitai Quer que eu já abra um plano com os arquivos-alvo e esboços de testes por projeto? Posso também ajustar o ci-backend.yml com os novos classfilters e mandar os trechos prontos para commit. EU quero este plano sim, sobre o ajuste do yaml, pode fazer, ma verifique o commit [da891a1], talvez já foi feito. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 53124
A seguir, o plano completo por projeto, com um pequeno ajuste opcional no YAML e esboços de testes. Plano objetivo para elevar cobertura por namespace (sem inflar boilerplate)
CI — ajuste fino (opcional, 1 linha)
Communications.Infrastructure — repositórios e stubs
Ratings.API — manter excluído; smoke opcional
Users.Infrastructure — Keycloak e mapeamentos
Shared — mensageria e métricas
Estimativa de impacto
Cronograma sugerido
Quer que eu já gere os arquivos de teste com esqueleto inicial (Arrange/Act/Assert) e o diff do YAML para commit? Posso preparar em blocos por pasta para facilitar o review incremental. 🧠 Learnings usedFailed to handle agent chat message. Please try again. |
…dules ≥90% - Updated OutboxMessageRepositoryTests and EmailTemplateRepositoryTests - Added KeycloakService concurrency and error path tests - Added UsersDbContextMappingTests and UsersTestDb - Added MessageBusFactoryTests and DatabaseMetricsInterceptorErrorTests - Added CachingExtensionsRegistrationTests - Excluded stubs and boilerplate from coverage - Updated roadmap with messaging attributes strategy
Minimum allowed line rate is |
Code Coverage Report
|
Summary by CodeRabbit
Release Notes
Novas Funcionalidades
Melhorias
Testes