Conversation
…gs module structure
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdiciona o módulo Bookings (Domain, Application, Infrastructure, API, Tests), endpoints e clientes HTTP para agendamento, repositórios EF Core com inserção atômica sem sobreposição, handlers CQRS, convenções de mensageria por atributo, ajustes de bootstrap/migrations, frontend BookingModal e ampla suíte de testes (unit/integration/E2E). Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Cliente/Frontend
participant API as API Minimal
participant Handler as CreateBookingHandler
participant Providers as Providers Module API
participant ScheduleRepo as ProviderScheduleRepo
participant BookingRepo as BookingRepository
participant DB as PostgreSQL
Client->>API: POST /api/v1/bookings {providerId, serviceId, start, end}
API->>Handler: Dispatch CreateBookingCommand
Handler->>Providers: ProviderExistsAsync(providerId)
Providers-->>Handler: true
Handler->>ScheduleRepo: GetByProviderIdReadOnlyAsync(providerId)
ScheduleRepo->>DB: SELECT ProviderSchedule + Availabilities
DB-->>ScheduleRepo: ProviderSchedule
Handler->>Handler: Converter UTC→local e validar disponibilidade
Handler->>BookingRepo: AddIfNoOverlapAsync(booking)
BookingRepo->>DB: BEGIN TRANSACTION (SERIALIZABLE) / SELECT overlap
DB-->>BookingRepo: no overlap
BookingRepo->>DB: INSERT Booking / COMMIT
DB-->>BookingRepo: success
BookingRepo-->>Handler: Result.Success(BookingDto)
Handler-->>API: Result<BookingDto>
API-->>Client: 201 Created {BookingDto}
Estimated code review effort🎯 4 (Complexo) | ⏱️ ~60 minutos Possibly related PRs
Poema
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/roadmap.md (1)
46-48:⚠️ Potential issue | 🟡 MinorInconsistência: Bookings aparece em Sprint 12 e também em Fase 4.
A “Fase 4: Experiência e Engajamento” ainda lista Módulo de Agendamentos (Bookings): Calendário de disponibilidade como item futuro, mas o escopo já foi antecipado para o Sprint 12. Considere remover/ajustar esta entrada para evitar ambiguidade no roadmap.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap.md` around lines 46 - 48, The roadmap contains a duplication where "Módulo de Agendamentos (Bookings): Calendário de disponibilidade" appears under "Fase 4: Experiência e Engajamento" despite being scheduled in Sprint 12; update the "Fase 4: Experiência e Engajamento" section to either remove that Bookings line or mark it as already moved to Sprint 12 (e.g., change or annotate the "Módulo de Agendamentos (Bookings)" entry) so the roadmap is unambiguous.
🧹 Nitpick comments (6)
docs/roadmap-history.md (1)
14-14: Nit: estrangeirismo sinalizado pelo LanguageTool.Opcional: substituir “performance” por “desempenho” para manter consistência de idioma no documento.
✏️ Sugestão
-- ✅ **UX Polish**: Implementação de skeleton loaders animados para melhor percepção de performance. +- ✅ **UX Polish**: Implementação de skeleton loaders animados para melhor percepção de desempenho.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap-history.md` at line 14, Replace the English word "performance" with the Portuguese "desempenho" in the markdown line containing "✅ **UX Polish**: Implementação de skeleton loaders animados para melhor percepção de performance." so the document remains consistently in Portuguese; update only that phrase and keep the rest of the sentence intact.docs/roadmap.md (1)
9-13: Nit: espaçamento/quebra entre Sprint/Status.Falta uma quebra de linha ou dois espaços ao final da linha 9 para que
Sprint AtualeStatusnão fiquem na mesma linha renderizada em Markdown. O LanguageTool também sinalizou “pontuação duplicada” na linha 13; verifique se o Markdown foi renderizado como esperado.✏️ Sugestão
-**Sprint Atual**: 12 (Bookings & Messaging Excellence) -**Status**: 🚀 Em Início +**Sprint Atual**: 12 (Bookings & Messaging Excellence) +**Status**: 🚀 Em Início🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap.md` around lines 9 - 13, Add a Markdown line break between the "Sprint Atual" and "Status" headings and fix the duplicate punctuation on the "Stack Principal" line: edit the block containing "Sprint Atual: 12 (Bookings & Messaging Excellence)" and "Status: 🚀 Em Início" to either place them on separate lines with a blank line between or add two trailing spaces at the end of the "Sprint Atual" line so they render on different lines, and remove any accidental duplicated punctuation (e.g., extra periods) from the "Stack Principal: .NET 10 LTS + Aspire 13 + PostgreSQL + NX Monorepo + React 19 + Next.js 15 + Tailwind v4" line.src/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csproj (1)
1-13: ConsiderarInternalsVisibleTotambém nas camadas Application/Infrastructure.O projeto Domain expõe internals para
MeAjudaAi.Modules.Bookings.Tests, mas Application e Infrastructure não. Em outros módulos do repo, handlers/command validators costumam ser testados com visibilidadeinternal. Avaliar incluir o mesmoAssemblyAttributeaqui para manter consistência e permitir testes de handlers internos sem promover parapublic.✏️ Sugestão
<ItemGroup> + <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> + <_Parameter1>MeAjudaAi.Modules.Bookings.Tests</_Parameter1> + </AssemblyAttribute> + </ItemGroup> + + <ItemGroup> <ProjectReference Include="..\Domain\MeAjudaAi.Modules.Bookings.Domain.csproj" /> </ItemGroup>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csproj` around lines 1 - 13, Add the InternalsVisibleTo assembly attribute to the Application and Infrastructure projects so test assembly can access internal types: in MeAjudaAi.Modules.Bookings.Application.csproj (and the corresponding Infrastructure csproj) add an AssemblyAttribute for System.Runtime.CompilerServices.InternalsVisibleTo with the test assembly name "MeAjudaAi.Modules.Bookings.Tests" (or the exact test assembly name used in the repo) so internal handlers/validators remain internal but are visible to the test project; alternatively add the same [assembly: InternalsVisibleTo("MeAjudaAi.Modules.Bookings.Tests")] to each project’s AssemblyInfo source if you prefer source attributes.src/Modules/Bookings/Domain/Enums/BookingStatus.cs (1)
3-10: LGTM — enum simples e explícito.Valores inteiros explícitos são adequados para persistência estável. Considere, em um PR futuro, avaliar se faz sentido expor este enum via
Shared.Contractscaso haja consumo futuro pelo Web/Frontend, alinhado à política de reuso de contratos compartilhados.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/Enums/BookingStatus.cs` around lines 3 - 10, No code changes required now; consider extracting the BookingStatus enum into the shared contracts library for reuse by frontend: create a matching enum named BookingStatus in Shared.Contracts, keep the explicit integer values (Pending=0, Confirmed=1, Cancelled=2, Completed=3, Rejected=4) to preserve persistence, update server-side references to use the shared enum (replace usages of src/Modules/Bookings/Domain/Enums/BookingStatus with the Shared.Contracts type), and add any necessary serialization attributes or mappings to maintain backward compatibility.src/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csproj (1)
1-13: Mesma observação da camada Application sobreInternalsVisibleTo.Para consistência com Domain e com o padrão usado em outros módulos (repositórios/
DbContext/configurations marcados comointernal), considere adicionarInternalsVisibleToparaMeAjudaAi.Modules.Bookings.Tests. Também, assim que Infrastructure começar a hospedar EF Core/migrations para o schemabookings, será necessário adicionar asPackageReferencecorrespondentes (Microsoft.EntityFrameworkCore.*,Npgsql.EntityFrameworkCore.PostgreSQL) e oProjectReferenceparaMeAjudaAi.Shared.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csproj` around lines 1 - 13, Add an InternalsVisibleTo entry for the test assembly in MeAjudaAi.Modules.Bookings.Infrastructure.csproj so internal types (e.g., internal repositories/DbContext/configuration classes) are visible to MeAjudaAi.Modules.Bookings.Tests; when you start hosting EF Core/migrations in this Infrastructure project also add PackageReference entries for Microsoft.EntityFrameworkCore (and any provider packages like Npgsql.EntityFrameworkCore.PostgreSQL) and add a ProjectReference to MeAjudaAi.Shared so migrations and shared types compile and tests can access internal members.src/Modules/Bookings/Domain/MeAjudaAi.Modules.Bookings.Domain.csproj (1)
1-19: LGTM — estrutura consistente com os demais módulos.
InternalsVisibleToapontando paraMeAjudaAi.Modules.Bookings.Testsestá alinhado com a convenção do repositório. Apenas lembre de criar o projeto de testes correspondente antes de mergear código de domínio real, para garantir a meta global de cobertura ≥90%.Posso gerar o esqueleto do projeto
MeAjudaAi.Modules.Bookings.Tests(csproj + estrutura Unit/Integration) quando começarem a entrar as entidades/VOs?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/MeAjudaAi.Modules.Bookings.Domain.csproj` around lines 1 - 19, O .csproj já expõe internals via AssemblyAttribute InternalsVisibleTo para MeAjudaAi.Modules.Bookings.Tests; antes de dar merge, crie o projeto de testes MeAjudaAi.Modules.Bookings.Tests (csproj) e a estrutura inicial de pastas Unit e Integration, garantindo que o nome do assembly coincida com o valor passado em InternalsVisibleTo do arquivo MeAjudaAi.Modules.Bookings.Domain.csproj; adicione referência ao projeto de domínio e configure frameworks e pacotes de teste padrão para que a cobertura mínima possa ser alcançada quando as entidades/VOs forem adicionadas.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/technical-debt.md`:
- Around line 19-23: Update the checklist entry that currently marks the Rebus
migration as fully complete to indicate it’s partial: change the item text to
something like “Implementação do RebusMessageBus concluída; infra RabbitMQ
direta (stubs) a ser removida” so it accurately reflects that
RabbitMqInfrastructureManager still imports RabbitMQ.Client and contains stub
methods (CreateQueueAsync, CreateExchangeAsync, BindQueueToExchangeAsync) which
must be removed/merged into IMessageBus; ensure the new wording clarifies that
consolidation (removing direct RabbitMQ usage) and validation under .NET 10
remain open tasks.
In `@src/Modules/Bookings/API/MeAjudaAi.Modules.Bookings.API.csproj`:
- Around line 1-13: Crie o arquivo Extensions.cs em src/Modules/Bookings/API
contendo a composição do módulo: defina a classe estática Extensions com os
métodos públicos AddBookingsModule(this IServiceCollection services,
IConfiguration configuration) — que chama services.AddBookingsApplication() e
services.AddBookingsInfrastructure(configuration) e retorna services;
UseBookingsModule(this IApplicationBuilder app) — que registra middlewares
específicos do módulo (mesmo que apenas retorne app por enquanto); e
MapBookingsEndpoints(this IEndpointRouteBuilder endpoints) — que retorna
endpoints (placeholder até os endpoints serem implementados); importe os
namespaces MeAjudaAi.Modules.Bookings.Application,
MeAjudaAi.Modules.Bookings.Infrastructure, Microsoft.AspNetCore.Builder,
Microsoft.AspNetCore.Routing, Microsoft.Extensions.Configuration e
Microsoft.Extensions.DependencyInjection para manter o mesmo padrão dos outros
módulos.
---
Outside diff comments:
In `@docs/roadmap.md`:
- Around line 46-48: The roadmap contains a duplication where "Módulo de
Agendamentos (Bookings): Calendário de disponibilidade" appears under "Fase 4:
Experiência e Engajamento" despite being scheduled in Sprint 12; update the
"Fase 4: Experiência e Engajamento" section to either remove that Bookings line
or mark it as already moved to Sprint 12 (e.g., change or annotate the "Módulo
de Agendamentos (Bookings)" entry) so the roadmap is unambiguous.
---
Nitpick comments:
In `@docs/roadmap-history.md`:
- Line 14: Replace the English word "performance" with the Portuguese
"desempenho" in the markdown line containing "✅ **UX Polish**: Implementação de
skeleton loaders animados para melhor percepção de performance." so the document
remains consistently in Portuguese; update only that phrase and keep the rest of
the sentence intact.
In `@docs/roadmap.md`:
- Around line 9-13: Add a Markdown line break between the "Sprint Atual" and
"Status" headings and fix the duplicate punctuation on the "Stack Principal"
line: edit the block containing "Sprint Atual: 12 (Bookings & Messaging
Excellence)" and "Status: 🚀 Em Início" to either place them on separate lines
with a blank line between or add two trailing spaces at the end of the "Sprint
Atual" line so they render on different lines, and remove any accidental
duplicated punctuation (e.g., extra periods) from the "Stack Principal: .NET 10
LTS + Aspire 13 + PostgreSQL + NX Monorepo + React 19 + Next.js 15 + Tailwind
v4" line.
In
`@src/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csproj`:
- Around line 1-13: Add the InternalsVisibleTo assembly attribute to the
Application and Infrastructure projects so test assembly can access internal
types: in MeAjudaAi.Modules.Bookings.Application.csproj (and the corresponding
Infrastructure csproj) add an AssemblyAttribute for
System.Runtime.CompilerServices.InternalsVisibleTo with the test assembly name
"MeAjudaAi.Modules.Bookings.Tests" (or the exact test assembly name used in the
repo) so internal handlers/validators remain internal but are visible to the
test project; alternatively add the same [assembly:
InternalsVisibleTo("MeAjudaAi.Modules.Bookings.Tests")] to each project’s
AssemblyInfo source if you prefer source attributes.
In `@src/Modules/Bookings/Domain/Enums/BookingStatus.cs`:
- Around line 3-10: No code changes required now; consider extracting the
BookingStatus enum into the shared contracts library for reuse by frontend:
create a matching enum named BookingStatus in Shared.Contracts, keep the
explicit integer values (Pending=0, Confirmed=1, Cancelled=2, Completed=3,
Rejected=4) to preserve persistence, update server-side references to use the
shared enum (replace usages of src/Modules/Bookings/Domain/Enums/BookingStatus
with the Shared.Contracts type), and add any necessary serialization attributes
or mappings to maintain backward compatibility.
In `@src/Modules/Bookings/Domain/MeAjudaAi.Modules.Bookings.Domain.csproj`:
- Around line 1-19: O .csproj já expõe internals via AssemblyAttribute
InternalsVisibleTo para MeAjudaAi.Modules.Bookings.Tests; antes de dar merge,
crie o projeto de testes MeAjudaAi.Modules.Bookings.Tests (csproj) e a estrutura
inicial de pastas Unit e Integration, garantindo que o nome do assembly coincida
com o valor passado em InternalsVisibleTo do arquivo
MeAjudaAi.Modules.Bookings.Domain.csproj; adicione referência ao projeto de
domínio e configure frameworks e pacotes de teste padrão para que a cobertura
mínima possa ser alcançada quando as entidades/VOs forem adicionadas.
In
`@src/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csproj`:
- Around line 1-13: Add an InternalsVisibleTo entry for the test assembly in
MeAjudaAi.Modules.Bookings.Infrastructure.csproj so internal types (e.g.,
internal repositories/DbContext/configuration classes) are visible to
MeAjudaAi.Modules.Bookings.Tests; when you start hosting EF Core/migrations in
this Infrastructure project also add PackageReference entries for
Microsoft.EntityFrameworkCore (and any provider packages like
Npgsql.EntityFrameworkCore.PostgreSQL) and add a ProjectReference to
MeAjudaAi.Shared so migrations and shared types compile and tests can access
internal members.
🪄 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: 03fbbb61-dd4a-4bb9-8d68-6d15ffa3c3db
⛔ Files ignored due to path filters (56)
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/Client/MeAjudaAi.Client.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Contracts/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/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.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)
Directory.Packages.propsdocs/roadmap-history.mddocs/roadmap.mddocs/technical-debt.mdsrc/Modules/Bookings/API/MeAjudaAi.Modules.Bookings.API.csprojsrc/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csprojsrc/Modules/Bookings/Domain/Enums/BookingStatus.cssrc/Modules/Bookings/Domain/MeAjudaAi.Modules.Bookings.Domain.csprojsrc/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csproj
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (10)
src/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cs-7-13 (1)
7-13:⚠️ Potential issue | 🟡 Minor
Start/EndcomoDateTimesem garantia deDateTimeKind.Como o comando atravessa API → handler → domínio (
TimeSlot) e persistência, umDateTimecomKind=UnspecifiedouLocalpode gerar comparações incorretas e resultados inconsistentes entre overlap check, persistência em UTC no Postgres (timestamptz) e serialização. Recomendo normalizar para UTC na fronteira (endpoint) ou trocar porDateTimeOffset. Vale reaproveitar a observação deCorrelationId = defaultdo comentário emSetProviderScheduleCommand.cs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cs` around lines 7 - 13, CreateBookingCommand currently accepts Start and End as DateTime without guaranteed DateTimeKind which can produce incorrect overlap checks in TimeSlot, inconsistent storage with Postgres timestamptz, and serialization bugs; change the command to carry UTC-normalized timestamps or, preferably, switch Start and End to DateTimeOffset (update CreateBookingCommand signature and all handlers/validators that consume it, including the TimeSlot construction and overlap logic) and normalize incoming values at the API boundary (endpoint) to ensure UTC/offset consistency; also apply the same CorrelationId default pattern used in SetProviderScheduleCommand.cs to keep signatures consistent.src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs-5-37 (1)
5-37:⚠️ Potential issue | 🟡 MinorRisco com
DateTimeKindinconsistente emStart/End.
TimeSlotaceita qualquerDateTimesem normalizar/validarKind. Comparaçõesstart >= end,Overlapse a persistência (Postgrestimestamptzconverte assumindo UTC) podem divergir silenciosamente se umDateTimecomKind=LocalouUnspecifiedentrar. Sugestões (em ordem de preferência):
- Exigir
Kind == DateTimeKind.UtcemCreate(...)e lançarArgumentExceptioncaso contrário, ou- Converter explicitamente para UTC (
DateTime.SpecifyKind/ToUniversalTime) na fábrica, ou- Migrar Start/End para
DateTimeOffset.🛡️ Exemplo de validação defensiva
private TimeSlot(DateTime start, DateTime end) { + if (start.Kind != DateTimeKind.Utc || end.Kind != DateTimeKind.Utc) + { + throw new ArgumentException("TimeSlot requires UTC DateTime values."); + } if (start >= end) { throw new ArgumentException("Start time must be before end time."); }Também aplicável à validação de
CreateBookingCommand.Start/EndeGetProviderAvailabilityQuery.Date.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs` around lines 5 - 37, The TimeSlot value object accepts DateTime values with inconsistent DateTimeKind which can break comparisons and DB persistence; update TimeSlot.Create (and the private constructor) to validate that both start and end have DateTimeKind.Utc and throw an ArgumentException if not (or alternatively explicitly convert them to UTC in the factory), ensuring Start and End are always UTC before performing the start>=end check and using Overlaps/Duration; apply the same UTC validation/conversion pattern to CreateBookingCommand.Start/End and GetProviderAvailabilityQuery.Date to keep inputs consistent with timestamptz storage.src/Modules/Bookings/Domain/Entities/Booking.cs-37-37 (1)
37-37:⚠️ Potential issue | 🟡 MinorTraduzir mensagens de exceção de
InvalidOperationExceptionque chegam à API.Os handlers
ConfirmBookingCommandHandler(linha 37) eCancelBookingCommandHandler(linha 60) capturamInvalidOperationExceptione propagamex.Messagecomo resposta de erro para o frontend. As mensagens em inglês ("Only pending bookings can be confirmed." e "Completed or already cancelled bookings cannot be cancelled.") devem ser mapeadas para português no application layer ou centralizadas em validações localizadas.Os métodos
Reject()(linha 48) eComplete()(linha 72) ainda não possuem handlers, portanto não propagam mensagens ao frontend no momento.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/Entities/Booking.cs` at line 37, As mensagens de InvalidOperationException lançadas nas entidades (por exemplo em Booking.Throw: "Only pending bookings can be confirmed." e "Completed or already cancelled bookings cannot be cancelled.") não devem ser encaminhadas em inglês ao frontend; atualize os handlers ConfirmBookingCommandHandler e CancelBookingCommandHandler para interceptar essas exceções e mapear as mensagens para português antes de propagar (ou, preferencialmente, introduza uma camada/serviço de localização centralizada para mapear chaves/erros de domínio em mensagens localizadas); também adapte o mapeamento para futuras exceções de Reject() e Complete() quando esses métodos tiverem handlers, usando chaves de erro constantes no domínio em vez de strings literais para facilitar a tradução e testes.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx-111-119 (1)
111-119:⚠️ Potential issue | 🟡 MinorAdicione
statusdouseSession()para evitar flash de login durante carregamento da sessão.Com
!!session?.user, usuários autenticados veem "Entrar para Agendar" enquanto o NextAuth carrega a sessão. Usestatus === "loading"para renderizar um estado de carregamento antes de decidir entreBookingModale login, seguindo o padrão já usado emuser-menu.tsxeperfil/page.tsx.💡 Ajuste sugerido
- const { data: session } = useSession(); - const isAuthenticated = !!session?.user; + const { data: session, status } = useSession(); + const isSessionLoading = status === "loading"; + const isAuthenticated = status === "authenticated" && !!session?.user;- {isAuthenticated ? ( + {isSessionLoading ? ( + <Button disabled className="w-full py-6 text-lg"> + <Loader2 className="mr-2 h-4 w-4 animate-spin" /> + Carregando... + </Button> + ) : isAuthenticated ? ( <BookingModal providerId={id} providerName={displayName}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 111 - 119, The rendering uses only !!session?.user (isAuthenticated) and causes a login flash while NextAuth is loading; update the component to destructure status from useSession() alongside session and treat status === "loading" as a distinct state: when loading render a placeholder/loading button or disabled Button, when status === "authenticated" render BookingModal (providerId/id, providerName/displayName), otherwise render the login Button/Link. Locate the useSession call and the JSX that renders BookingModal vs Button to implement this change.src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs-17-33 (1)
17-33:⚠️ Potential issue | 🟡 MinorMesmos pontos do
CancelBookingCommandHandler: escopo dotrye idioma da mensagem.
"Booking not found."é uma mensagem voltada ao cliente (viaResults.Problem) e deveria estar em pt-BR; logs permanecem em inglês (ok).- O
tryenvolveUpdateAsync. Caso o EF lanceInvalidOperationException(tracker/transação), o erro será convertido emBadRequestcom texto técnico. Envolva apenasbooking.Confirm().♻️ Sugestão
- return Result.Failure(Error.NotFound("Booking not found.")); + return Result.Failure(Error.NotFound("Agendamento não encontrado.")); } - try - { - booking.Confirm(); - await bookingRepository.UpdateAsync(booking, cancellationToken); - } - catch (InvalidOperationException ex) - { - return Result.Failure(Error.BadRequest(ex.Message)); - } + try + { + booking.Confirm(); + } + catch (InvalidOperationException ex) + { + return Result.Failure(Error.BadRequest(ex.Message)); + } + + await bookingRepository.UpdateAsync(booking, cancellationToken);Com base nas learnings: logs em inglês, mensagens ao usuário em português.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs` around lines 17 - 33, The NotFound message should be user-facing in pt-BR and the try/catch should only cover domain logic (booking.Confirm()) so EF exceptions from bookingRepository.UpdateAsync are not transformed into BadRequest with technical text; in ConfirmBookingCommandHandler change the Error.NotFound text to Portuguese (e.g. "Reserva não encontrada."), move the try block to wrap only booking.Confirm() and catch InvalidOperationException there to return Result.Failure(Error.BadRequest(ex.Message)), then call await bookingRepository.UpdateAsync(booking, cancellationToken) outside that try/catch; keep the logger.LogInformation message in English.src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs-16-37 (1)
16-37:⚠️ Potential issue | 🟡 MinorFalta validação do
Reasone metadados de resposta.
CancelBookingRequest.Reasonnão tem validação — nem de obrigatoriedade, nem de comprimento. A colunacancellation_reasonestá limitada a 500 caracteres emBookingConfiguration, então um payload maior provocaráDbUpdateExceptionnão tratada. Também não háProduces/ProducesProblempara documentar as respostas.♻️ Sugestão
}) .RequireAuthorization() .WithTags(BookingsEndpoints.Tag) .WithName("CancelBooking") -.WithSummary("Cancela um agendamento."); +.WithSummary("Cancela um agendamento.") +.Produces(StatusCodes.Status204NoContent) +.ProducesProblem(StatusCodes.Status400BadRequest) +.ProducesProblem(StatusCodes.Status404NotFound);Considere adicionar um validador (FluentValidation ou similar) para
CancelBookingRequestexigindoReasonnão vazio e com no máximo 500 caracteres, alinhado ao schema da tabela.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs` around lines 16 - 37, Add validation for CancelBookingRequest.Reason and API response metadata: create a validator (e.g., FluentValidation) for CancelBookingRequest enforcing Reason is required/not empty and MaxLength(500) to match BookingConfiguration, and invoke/validate it before constructing CancelBookingCommand in the MapPut handler (idempotently rejecting invalid requests with a 400). Also add endpoint metadata (.Produces(StatusCodes.Status204NoContent), .ProducesProblem for error responses like 400 and 500) to the MapPut chain so OpenAPI documents the possible responses. Ensure the CancelBookingRequest record name, the MapPut handler that constructs CancelBookingCommand, and the Result error handling remain unchanged while adding validation and .Produces/.ProducesProblem calls.src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs-17-35 (1)
17-35:⚠️ Potential issue | 🟡 MinorTry/catch amplo demais e mensagem de erro em idioma não conforme.
A cláusula
tryenvolvebooking.Cancel()EbookingRepository.UpdateAsync(). QualquerInvalidOperationExceptionlançada pelo EF/repositório (p.ex. estado inconsistente, violação de restrição) será silenciosamente convertida emBadRequestcom mensagem técnica exposta ao cliente. Restrinja otryapenas abooking.Cancel(), deixandoUpdateAsyncfora.
"Booking not found."é devolvida viaResults.Problem. Conforme política do repositório, mensagens voltadas ao usuário devem estar em pt-BR; apenas logs permanecem em inglês.Não há verificação explícita de autorização/ownership no handler — só autenticação no endpoint via
.RequireAuthorization(). Confirme se políticas de propriedade (ex.: cliente só cancela seus bookings) estão implementadas em outra camada ou se precisam ser adicionadas.Sugestão de ajuste
var booking = await bookingRepository.GetByIdAsync(command.BookingId, cancellationToken); if (booking == null) { - return Result.Failure(Error.NotFound("Booking not found.")); + return Result.Failure(Error.NotFound("Agendamento não encontrado.")); } try { booking.Cancel(command.Reason); } catch (InvalidOperationException ex) { return Result.Failure(Error.BadRequest(ex.Message)); } + + await bookingRepository.UpdateAsync(booking, cancellationToken);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs` around lines 17 - 35, No handler should catch repository exceptions or leak English user-facing messages: in CancelBookingCommandHandler restrict the try/catch to only booking.Cancel(command.Reason) so that InvalidOperationException from booking domain is handled as BadRequest while allowing bookingRepository.UpdateAsync(booking, cancellationToken) to propagate repository/EF errors; change the user-facing NotFound message returned by Result.Failure(Error.NotFound(...)) from "Booking not found." to a pt-BR message (e.g., "Reserva não encontrada."); and verify or add an ownership/authorization check (e.g., ensure the current user owns the booking) in this handler before calling booking.Cancel() if such policy isn't enforced elsewhere.src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs-24-27 (1)
24-27:⚠️ Potential issue | 🟡 MinorLocalize as mensagens retornadas pela API e evite expor
ex.Message.Esses
Errorchegam ao frontend; hoje há texto em inglês e detalhe bruto da exceção. Prefira mensagens estáveis em pt-BR.💬 Ajuste sugerido
- return Result.Failure(Error.NotFound("Provider not found.")); + return Result.Failure(Error.NotFound("Prestador não encontrado.")); ... - return Result.Failure(Error.BadRequest(ex.Message)); + return Result.Failure(Error.BadRequest("Dados de disponibilidade inválidos."));Based on learnings: User-facing validation messages and API responses shown to the frontend must be in Portuguese.
Also applies to: 49-52
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs` around lines 24 - 27, Atualize as mensagens de erro retornadas pelo handler SetProviderScheduleCommandHandler para usar textos estáveis em pt-BR e evitar expor detalhes de exceção; por exemplo, substitua Error.NotFound("Provider not found.") (variável providerExists) por uma mensagem em português (ex.: Error.NotFound("Prestador não encontrado.")) e revise os retornos de erro nas linhas equivalentes 49-52 para não propagar ex.Message — em vez disso, mapeie exceções internas para mensagens genéricas em pt-BR e registre o detalhe do erro apenas nos logs internos.src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs-26-53 (1)
26-53:⚠️ Potential issue | 🟡 MinorLocalize as respostas de erro retornadas ao frontend.
As mensagens de
Error.*aqui são API-facing e devem ficar em pt-BR para manter consistência com o Web.💬 Ajuste sugerido
- return Result<BookingDto>.Failure(Error.NotFound("Provider not found.")); + return Result<BookingDto>.Failure(Error.NotFound("Prestador não encontrado.")); ... - return Result<BookingDto>.Failure(Error.BadRequest("Provider has no defined schedule.")); + return Result<BookingDto>.Failure(Error.BadRequest("Prestador não possui agenda definida.")); ... - return Result<BookingDto>.Failure(Error.BadRequest("Provider is not available at the requested time.")); + return Result<BookingDto>.Failure(Error.BadRequest("Prestador não está disponível no horário solicitado.")); ... - return Result<BookingDto>.Failure(Error.Conflict("There is already a booking for this provider in the requested time.")); + return Result<BookingDto>.Failure(Error.Conflict("Já existe um agendamento para este prestador no horário solicitado."));Based on learnings: User-facing validation messages and API responses shown to the frontend must be in Portuguese.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 26 - 53, Atualize as mensagens de erro API-facing neste handler para pt-BR: substitua as chamadas atuais a Error.NotFound("Provider not found."), Error.BadRequest("Provider has no defined schedule."), Error.BadRequest("Provider is not available at the requested time."), e Error.Conflict("There is already a booking for this provider in the requested time.") por textos em português (por exemplo: "Prestador não encontrado.", "Prestador não possui horário definido.", "Prestador indisponível no horário solicitado." e "Já existe um agendamento para este prestador no período solicitado."). Altere as strings diretamente no CreateBookingCommandHandler onde são retornadas após verificar providerExists, schedule (scheduleRepository.GetByProviderIdAsync / schedule.IsAvailable) e sobreposição (bookingRepository.HasOverlapAsync); se a aplicação usar um sistema de localização, mova essas mensagens para as chaves de recursos correspondentes e referencie-as aqui.src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs-23-23 (1)
23-23:⚠️ Potential issue | 🟡 MinorLocalize a mensagem retornada pela API.
Provider schedule not found.pode chegar ao frontend viaProblem(...); use uma mensagem em português, por exemplo:Agenda do prestador não encontrada.Based on learnings: user-facing validation messages and API responses shown to the frontend must be in Portuguese.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs` at line 23, The failure message returned from GetProviderAvailabilityQueryHandler currently uses English; update the call to Result<AvailabilityDto>.Failure(Error.NotFound(...)) to use a Portuguese user-facing message such as "Agenda do prestador não encontrada." Replace the string inside Error.NotFound in the GetProviderAvailabilityQueryHandler method so API responses routed to Problem(...) will be localized in Portuguese.
🧹 Nitpick comments (6)
src/Modules/Bookings/Domain/ValueObjects/Availability.cs (1)
13-33: Validar entrada e considerar semântica de slots adjacentes tocando.Dois pontos:
slotsnão é null-guardado;slots.OrderBy(...)lançariaArgumentNullExceptionde dentro do LINQ com stack pouco expressivo. AdicionarArgumentNullException.ThrowIfNull(slots)no início torna o contrato explícito.TimeSlot.Overlapsusa limites exclusivos (Start < other.End && other.Start < End), portanto dois slots "tocando" (ex.: 09:00–10:00 e 10:00–11:00) são considerados NÃO sobrepostos — o que é a definição canônica e parece correto. Vale confirmar que esse é o comportamento desejado paraAvailabilitye documentar no XML, já que o handler e a persistência dependem disso.🛡️ Proposta
private Availability(DayOfWeek dayOfWeek, IEnumerable<TimeSlot> slots) { + ArgumentNullException.ThrowIfNull(slots); DayOfWeek = dayOfWeek; _slots.AddRange(slots.OrderBy(s => s.Start)); ValidateNoOverlaps(); }Observação: o loop de
ValidateNoOverlaps(check adjacente após ordenar porStart) é suficiente para detectar qualquer par sobreposto — correto.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/ValueObjects/Availability.cs` around lines 13 - 33, Add an explicit null guard for the incoming slots (e.g., call ArgumentNullException.ThrowIfNull(slots)) at the start of the Availability constructor (or in Availability.Create) so callers get a clear contract/exception instead of an internal LINQ ArgumentNullException, and add/update XML doc comments on the Availability class/Create method to state that TimeSlot.Overlaps uses exclusive bounds so adjacent touching slots (e.g., 09:00–10:00 and 10:00–11:00) are allowed and not considered overlaps; keep ValidateNoOverlaps and its adjacent-only check as-is since ordering by Start correctly detects true overlaps.src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs (1)
7-10:CorrelationId = defaultresulta emGuid.Empty, inutilizando a correlação quando o chamador não informar.Um
CorrelationIdcom valorGuid.Emptynão é útil para rastreamento e pode poluir logs/telemetria. Considere uma das opções:
- Tornar o parâmetro obrigatório e gerar um novo
Guidno endpoint quando o cliente não enviar;- Ou usar
Guid?e tratarnullexplicitamente no handler, gerando um novo quando ausente.Aplica-se também a
GetProviderAvailabilityQueryeCreateBookingCommand.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs` around lines 7 - 10, O parâmetro CorrelationId nos records SetProviderScheduleCommand (e igualmente em GetProviderAvailabilityQuery e CreateBookingCommand) está definido como Guid = default, o que produz Guid.Empty quando omitido; altere a modelagem para ou tornar CorrelationId obrigatório (remover o default) e garantir que o endpoint gere e passe um new Guid() quando o cliente não informar, ou tornar o tipo Guid? e no handler correspondente (ex.: SetProviderScheduleCommand handler) detectar null e gerar um novo Guid para correlação antes de prosseguir; atualize assinaturas dos records e a lógica nos handlers/endpoints que constroem/consomem esses comandos/queries para garantir um CorrelationId válido (não Guid.Empty) sempre presente.src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs (1)
58-103: Cobertura incompleta para caminhos negativos deReject,CanceleComplete.Há teste de erro apenas para
Confirmem estado não-pendente. Os demais métodos de transição provavelmente também têm guard-clauses (seguindo o mesmo padrão doConfirmvisto emBooking.cs), mas não há testes validando que essas exceções são lançadas nos estados inválidos (ex.:Completeem booking cancelado,Rejectem booking confirmado,Cancelem booking já cancelado/completo). Vale acrescentar esses casos para prevenir regressões nas invariantes de domínio.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs` around lines 58 - 103, Add unit tests that assert invalid state transitions throw the expected exceptions: create fixtures using CreatePendingBooking, CreateConfirmedBooking and CreateCancelledBooking (or create bookings then call Confirm/Cancel as needed) and verify calling Booking.Reject(reason) on a confirmed booking, Booking.Cancel(reason) on an already cancelled or completed booking, and Booking.Complete() on a cancelled booking each throw the domain exception (same type thrown by Booking.Confirm invalid-path guard). Also assert that state and reason fields are unchanged (Status remains original and RejectionReason/CancellationReason not set) after the failed call to ensure invariants are preserved.src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj (1)
1-57: LGTM.Estrutura padrão de projeto de teste do monorepo, com referências às camadas do módulo Bookings e às infraestruturas compartilhadas de teste. Pequeno ponto opcional:
NoWarn CA2201suprime "Do not raise reserved exception types" em todo o projeto; se possível, suprima localmente onde for realmente necessário para não mascarar usos incorretos de exceções em novos testes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj` around lines 1 - 57, The project-wide suppression of CA2201 is risky; remove "CA2201" from the <NoWarn> entry in MeAjudaAi.Modules.Bookings.Tests.csproj and instead apply local suppressions where needed (e.g., add "#pragma warning disable CA2201" / "#pragma warning restore CA2201" around the specific test methods or files that intentionally throw System.Exception), or use [SuppressMessage] on the specific test methods/classes; this keeps the project file (<NoWarn> removal) clean while limiting CA2201 suppression to the exact code locations that require it.src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs (1)
17-34: Adicione metadados OpenAPI e considere usarDateOnlypara o parâmetrodate.O endpoint não declara
Produces<AvailabilityDto>()eProducesProblem(...), o que reduz a qualidade da documentação Swagger e dos contratos tipados. Além disso, emboraDateTimevia query string funcione,DateOnlyseria mais apropriado semanticamente: o cálculo de disponibilidade usa apenasDayOfWeeke não precisa da informação de horário, evitando potencial ambiguidade de fuso (DateTimeKind.Unspecified).♻️ Sugestão
app.MapGet("/availability/{providerId}", async ( Guid providerId, - [FromQuery] DateTime date, + [FromQuery] DateOnly date, [FromServices] IQueryDispatcher dispatcher, CancellationToken cancellationToken) => { var query = new GetProviderAvailabilityQuery(providerId, date); var result = await dispatcher.QueryAsync<GetProviderAvailabilityQuery, Result<AvailabilityDto>>(query, cancellationToken); return result.Match( onSuccess: availability => Results.Ok(availability), onFailure: error => Results.Problem(error.Message, statusCode: error.StatusCode) ); }) .RequireAuthorization() .WithTags(BookingsEndpoints.Tag) .WithName("GetProviderAvailability") .WithSummary("Consulta a disponibilidade de um prestador em uma data específica.") + .Produces<AvailabilityDto>() + .ProducesProblem(StatusCodes.Status404NotFound);A mudança para
DateOnlyrequer atualização correspondente na assinatura deGetProviderAvailabilityQuerye no handler, mas o repositório já suporta esse tipo sem impacto nas operações de data realizadas.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs` around lines 17 - 34, Change the endpoint to declare OpenAPI response metadata and switch the query date type to DateOnly: add Produces<AvailabilityDto>() and ProducesProblem(...) to the MapGet chain for proper Swagger contracts, change the MapGet parameter from [FromQuery] DateTime date to [FromQuery] DateOnly date, and update the GetProviderAvailabilityQuery constructor and its handler to accept DateOnly instead of DateTime (so the dispatcher.QueryAsync call uses GetProviderAvailabilityQuery(providerId, date) with DateOnly). Ensure any DateOnly model binding is supported and that the handler logic that uses DayOfWeek continues to work with DateOnly.src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs (1)
65-113: Cubra os limites do contrato de sobreposição.Os testes atuais validam overlap e não-overlap com intervalo distante, mas não protegem os casos mais frágeis: intervalos adjacentes e bookings
Cancelled/Rejected. Adicionar esses cenários evita regressões como trocar<por<=ou considerar reservas canceladas como bloqueio.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 65 - 113, Add tests to cover edge overlap cases: extend BookingRepositoryTests to include (1) an adjacent-interval test calling HasOverlapAsync with start equal to existing booking's end and with end equal to existing booking's start to ensure these return false (guards against using <= instead of <), and (2) tests where the existing Booking has status Cancelled and Rejected (create via Booking.Create then set status accordingly or use factory) to assert HasOverlapAsync ignores those and returns false; reference HasOverlapAsync, Booking.Create and TimeSlot.Create to locate where to add the new test methods and ensure you persist each booking via _repository.AddAsync before calling HasOverlapAsync.
🤖 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/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cs`:
- Around line 16-29: The endpoint currently only uses .RequireAuthorization()
but does not ensure the authenticated user is allowed to confirm the booking;
update ConfirmBookingCommandHandler (the handler for ConfirmBookingCommand which
calls booking.Confirm()) to validate the resource-level authorization: retrieve
the current user id/claims (e.g., via injected IHttpContextAccessor or a user
principal provider inside ConfirmBookingCommandHandler), verify
booking.ProviderId equals the current user id and check the user's
role/permission for confirming appointments, and return a failed Result with an
appropriate StatusCode (e.g., 403 Forbidden) if validation fails; alternatively
implement and apply a resource authorization policy that enforces the same
checks before dispatching ConfirmBookingCommand, but do not rely solely on
.RequireAuthorization().
In `@src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs`:
- Around line 17-27: O endpoint CreateBookingEndpoint está aceitando
CreateBookingRequest.ClientId do body sem confirmar que pertence ao usuário
autenticado; atualize a rota que cria o CreateBookingCommand para obter e usar o
ClientId autenticado dos claims (por exemplo via HttpContext.User) ou,
alternativamente, adicione validação no CreateBookingCommandHandler que compare
command.ClientId com o authenticated user id e retorne um erro/forbidden se
houver mismatch; assegure que a verificação ocorra antes de executar a lógica de
criação/disparo via ICommandDispatcher para evitar permitir reservas em nome de
outro cliente.
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 17-42: The endpoint SetProviderScheduleEndpoint currently takes
ProviderId from SetProviderScheduleRequest and calls dispatcher.SendAsync with
SetProviderScheduleCommand without checking ownership; instead derive the
ProviderId from the authenticated user (ClaimsPrincipal) or call a
service/handler method to verify the caller is owner/admin of the provider
before constructing SetProviderScheduleCommand (do not trust the request body
for ProviderId), and add explicit validation for Availabilities (e.g., a
dedicated validator used before creating TimeSlot instances) so malformed time
ranges produce controlled validation errors rather than runtime exceptions.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 44-64: The overlap check and insert are not atomic: calls to
bookingRepository.HasOverlapAsync followed by bookingRepository.AddAsync can
race and allow double bookings; update the implementation so the conflict check
and creation occur atomically—either implement a repository method like
bookingRepository.AddIfNoOverlapAsync(command.ProviderId, timeSlot, ...) that
performs the overlap check and insert inside a single transaction with
appropriate isolation (or uses a DB-level exclusion constraint/index) and use
that instead of separate HasOverlapAsync + AddAsync, or wrap the existing logic
in a transaction scope with Serializable/RepeatableRead isolation and re-check
before AddAsync to ensure no gap between check and persist.
- Around line 38-42: Verifique e rejeite intervalos inválidos antes de consultar
disponibilidade: em CreateBookingCommandHandler compute `duration = command.End
- command.Start` and if `command.End <= command.Start` return
`Result<BookingDto>.Failure(Error.BadRequest("End must be after Start."))`; only
then call `schedule.IsAvailable(command.Start, duration)` so `IsAvailable` and
downstream `TimeSlot.Create` never receive zero/negative durations.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs`:
- Around line 32-47: Calcule e remova os slots que conflitam com agendamentos
existentes antes de montar o AvailabilityDto: usar o dayBookings (resultado de
bookingRepository.GetByProviderIdAsync) para filtrar daySchedule.Slots e excluir
qualquer slot cujo intervalo sobreponha-se a qualquer b.TimeSlot (por exemplo
checando overlap entre s.Start/s.End e b.TimeSlot.Start/b.TimeSlot.End), ou
dividir slots maiores ao redor dos bookings se necessário; então projetar os
TimeSlotDto a partir dos slots filtrados e retornar new
AvailabilityDto(query.Date.DayOfWeek, slotsFiltrados).
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 42-47: O handler atual (no SetProviderScheduleCommandHandler ao
iterar command.Availabilities usando availabilityDto, TimeSlot.Create,
Availability.Create e schedule.SetAvailability) apenas substitui os dias
enviados, mantendo dias omitidos; chame schedule.ClearAvailabilities() antes do
loop para remover todas as disponibilidades existentes e então recriar apenas as
recebidas, garantindo que a semântica seja realmente "definir" a agenda.
In `@src/Modules/Bookings/Domain/Entities/Booking.cs`:
- Around line 56-66: O método Cancel na entidade Booking atualmente só bloqueia
os estados EBookingStatus.Completed e EBookingStatus.Cancelled, permitindo que
reservas em EBookingStatus.Rejected sejam convertidas para Cancelled e deixando
ambos RejectionReason e CancellationReason; atualize a verificação em
Booking.Cancel para permitir cancelamento somente quando Status for um estado
cancelável explícito (por exemplo EBookingStatus.Pending ou
EBookingStatus.Confirmed), lançando InvalidOperationException para quaisquer
outros estados (incluindo EBookingStatus.Rejected), e assegure que ao definir
Status = EBookingStatus.Cancelled você também trate ou limpe
RejectionReason/CancellationReason conforme a regra de domínio antes de chamar
MarkAsUpdated().
In `@src/Modules/Bookings/Domain/Entities/ProviderSchedule.cs`:
- Around line 38-45: Reject non-positive durations and any intervals that cross
midnight before checking slots: in the method using requestStart, requestEnd and
duration (in ProviderSchedule.cs), first verify duration > TimeSpan.Zero and
that requestEnd.Date == requestStart.Date (or equivalently requestEnd <=
requestStart.Date.AddDays(1) and requestEnd.Date == requestStart.Date) and
return false if either fails; then perform the slot check
(dayAvailability.Slots.Any with slot.Start/slot.End) using the existing
TimeOfDay comparisons knowing the interval does not wrap to the next day.
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs`:
- Around line 32-67: Update BookingConfiguration to: 1) add a composite index
that includes the booking time to speed overlap checks (e.g., an index over
ProviderId, Status and the owned TimeSlot Start column used in queries) or
create a filtered index excluding Cancelled/Rejected to limit rows scanned by
BookingRepository.HasOverlapAsync; 2) remove the redundant HasIndex(b =>
b.ProviderId) since ProviderId is a prefix of the composite index; and 3) change
the timestamp column mapping for Start, End, CreatedAt and UpdatedAt to use
timestamptz (HasColumnType("timestamptz")) and ensure the domain uses
DateTimeOffset or enforces UTC to avoid timezone issues (locate these in
BookingConfiguration, the builder.OwnsOne(timeSlot => timeSlot.Property(ts =>
ts.Start/End)), and the builder.Property calls for CreatedAt/UpdatedAt).
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 56-68: O problema é que HasOverlapAsync seguido de AddAsync no
CreateBookingCommandHandler não é atômico e permite race conditions; corrija
encapsulando a verificação e a inserção numa transação com isolamento adequado
(preferencialmente Serializable ou RepeatableRead) ou usando lock
pessimista/DB-level lock (ex.: sp_getapplock) antes de chamar HasOverlapAsync,
ou então introduza uma constraint única no banco que impeça bookings sobrepostos
para o mesmo ProviderId/TimeSlot (tratando Status cancelado/rejeitado conforme
regra) e adapte CreateBookingCommandHandler para usar essa
transação/lock/constraint ao executar AddAsync para garantir atomicidade.
In `@src/Shared/Messaging/Attributes/MessagingAttributes.cs`:
- Around line 1-28: There are duplicate attribute types
(DedicatedTopicAttribute, HighVolumeEventAttribute, CriticalEventAttribute)
defined in two namespaces which causes AttributeTopicNameConvention to miss the
old ones; consolidate by keeping a single canonical definition in
MeAjudaAi.Shared.Messaging.Attributes (the versions shown here) and remove the
older definitions in MeAjudaAi.Shared.Events.Attributes or mark those old
classes with [Obsolete("Use
MeAjudaAi.Shared.Messaging.Attributes.DedicatedTopicAttribute/HighVolumeEventAttribute/CriticalEventAttribute
instead")] to surface warnings; also either wire
HighVolumeEventAttribute.MaxParallelism into MessagingExtensions (remove the
hardcoded SetMaxParallelism(20)) or document/remove the unused property so
behavior is consistent with the attribute.
In `@src/Shared/Messaging/MessagingExtensions.cs`:
- Around line 103-110: Replace the call to o.Register<ITopicNameConvention>(...)
with o.Decorate<ITopicNameConvention>(...) so you safely override the default
Rebus topic naming convention without clashing with transport-registered
implementations; locate the Options configuration where o.SetMaxParallelism and
o.SetNumberOfWorkers are set and change the registration of ITopicNameConvention
to use Decorate and return a new AttributeTopicNameConvention() (a single
stateless instance via the closure is fine).
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 51-58: The request is being sent with missing or placeholder IDs
(session?.accessToken, session?.user?.id, serviceId =
"00000000-0000-0000-0000-000000000000"), so update the booking submission flow
(the confirm handler that uses selectedSlot, providerId, clientId, serviceId and
builds headers/body) to validate required values before calling fetch: ensure
session?.accessToken and session?.user?.id are present and non-empty, ensure
serviceId is a real selection (not the zero-GUID), and if any are missing,
prevent the request, surface a user-facing error/toast and disable the confirm
button; only include the Authorization header when accessToken exists and
replace the placeholder serviceId with the selected serviceId from the UI (also
apply the same validation/fix for the other occurrence around the second block
at lines 146-148).
- Around line 98-105: O handler onChange no componente booking-modal usa new
Date(e.target.value) (referenciado junto a selectedDate, setSelectedDate e
setSelectedSlot), o que interpreta a string "yyyy-MM-dd" como UTC e causa dia
errado em fusos negativos; troque esse parsing por um parsing explícito para
data local: pegue a string do input, separe ano/mês/dia e construa uma Date
local usando ano, mês-1 e dia antes de chamar setSelectedDate, mantendo a
limpeza de selectedSlot; altere apenas o parsing no onChange para essa criação
de Date local (não usar new Date(string) nem parse UTC).
---
Minor comments:
In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs`:
- Around line 16-37: Add validation for CancelBookingRequest.Reason and API
response metadata: create a validator (e.g., FluentValidation) for
CancelBookingRequest enforcing Reason is required/not empty and MaxLength(500)
to match BookingConfiguration, and invoke/validate it before constructing
CancelBookingCommand in the MapPut handler (idempotently rejecting invalid
requests with a 400). Also add endpoint metadata
(.Produces(StatusCodes.Status204NoContent), .ProducesProblem for error responses
like 400 and 500) to the MapPut chain so OpenAPI documents the possible
responses. Ensure the CancelBookingRequest record name, the MapPut handler that
constructs CancelBookingCommand, and the Result error handling remain unchanged
while adding validation and .Produces/.ProducesProblem calls.
In `@src/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cs`:
- Around line 7-13: CreateBookingCommand currently accepts Start and End as
DateTime without guaranteed DateTimeKind which can produce incorrect overlap
checks in TimeSlot, inconsistent storage with Postgres timestamptz, and
serialization bugs; change the command to carry UTC-normalized timestamps or,
preferably, switch Start and End to DateTimeOffset (update CreateBookingCommand
signature and all handlers/validators that consume it, including the TimeSlot
construction and overlap logic) and normalize incoming values at the API
boundary (endpoint) to ensure UTC/offset consistency; also apply the same
CorrelationId default pattern used in SetProviderScheduleCommand.cs to keep
signatures consistent.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs`:
- Around line 17-35: No handler should catch repository exceptions or leak
English user-facing messages: in CancelBookingCommandHandler restrict the
try/catch to only booking.Cancel(command.Reason) so that
InvalidOperationException from booking domain is handled as BadRequest while
allowing bookingRepository.UpdateAsync(booking, cancellationToken) to propagate
repository/EF errors; change the user-facing NotFound message returned by
Result.Failure(Error.NotFound(...)) from "Booking not found." to a pt-BR message
(e.g., "Reserva não encontrada."); and verify or add an ownership/authorization
check (e.g., ensure the current user owns the booking) in this handler before
calling booking.Cancel() if such policy isn't enforced elsewhere.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs`:
- Around line 17-33: The NotFound message should be user-facing in pt-BR and the
try/catch should only cover domain logic (booking.Confirm()) so EF exceptions
from bookingRepository.UpdateAsync are not transformed into BadRequest with
technical text; in ConfirmBookingCommandHandler change the Error.NotFound text
to Portuguese (e.g. "Reserva não encontrada."), move the try block to wrap only
booking.Confirm() and catch InvalidOperationException there to return
Result.Failure(Error.BadRequest(ex.Message)), then call await
bookingRepository.UpdateAsync(booking, cancellationToken) outside that
try/catch; keep the logger.LogInformation message in English.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 26-53: Atualize as mensagens de erro API-facing neste handler para
pt-BR: substitua as chamadas atuais a Error.NotFound("Provider not found."),
Error.BadRequest("Provider has no defined schedule."),
Error.BadRequest("Provider is not available at the requested time."), e
Error.Conflict("There is already a booking for this provider in the requested
time.") por textos em português (por exemplo: "Prestador não encontrado.",
"Prestador não possui horário definido.", "Prestador indisponível no horário
solicitado." e "Já existe um agendamento para este prestador no período
solicitado."). Altere as strings diretamente no CreateBookingCommandHandler onde
são retornadas após verificar providerExists, schedule
(scheduleRepository.GetByProviderIdAsync / schedule.IsAvailable) e sobreposição
(bookingRepository.HasOverlapAsync); se a aplicação usar um sistema de
localização, mova essas mensagens para as chaves de recursos correspondentes e
referencie-as aqui.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs`:
- Line 23: The failure message returned from GetProviderAvailabilityQueryHandler
currently uses English; update the call to
Result<AvailabilityDto>.Failure(Error.NotFound(...)) to use a Portuguese
user-facing message such as "Agenda do prestador não encontrada." Replace the
string inside Error.NotFound in the GetProviderAvailabilityQueryHandler method
so API responses routed to Problem(...) will be localized in Portuguese.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 24-27: Atualize as mensagens de erro retornadas pelo handler
SetProviderScheduleCommandHandler para usar textos estáveis em pt-BR e evitar
expor detalhes de exceção; por exemplo, substitua Error.NotFound("Provider not
found.") (variável providerExists) por uma mensagem em português (ex.:
Error.NotFound("Prestador não encontrado.")) e revise os retornos de erro nas
linhas equivalentes 49-52 para não propagar ex.Message — em vez disso, mapeie
exceções internas para mensagens genéricas em pt-BR e registre o detalhe do erro
apenas nos logs internos.
In `@src/Modules/Bookings/Domain/Entities/Booking.cs`:
- Line 37: As mensagens de InvalidOperationException lançadas nas entidades (por
exemplo em Booking.Throw: "Only pending bookings can be confirmed." e "Completed
or already cancelled bookings cannot be cancelled.") não devem ser encaminhadas
em inglês ao frontend; atualize os handlers ConfirmBookingCommandHandler e
CancelBookingCommandHandler para interceptar essas exceções e mapear as
mensagens para português antes de propagar (ou, preferencialmente, introduza uma
camada/serviço de localização centralizada para mapear chaves/erros de domínio
em mensagens localizadas); também adapte o mapeamento para futuras exceções de
Reject() e Complete() quando esses métodos tiverem handlers, usando chaves de
erro constantes no domínio em vez de strings literais para facilitar a tradução
e testes.
In `@src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs`:
- Around line 5-37: The TimeSlot value object accepts DateTime values with
inconsistent DateTimeKind which can break comparisons and DB persistence; update
TimeSlot.Create (and the private constructor) to validate that both start and
end have DateTimeKind.Utc and throw an ArgumentException if not (or
alternatively explicitly convert them to UTC in the factory), ensuring Start and
End are always UTC before performing the start>=end check and using
Overlaps/Duration; apply the same UTC validation/conversion pattern to
CreateBookingCommand.Start/End and GetProviderAvailabilityQuery.Date to keep
inputs consistent with timestamptz storage.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 111-119: The rendering uses only !!session?.user (isAuthenticated)
and causes a login flash while NextAuth is loading; update the component to
destructure status from useSession() alongside session and treat status ===
"loading" as a distinct state: when loading render a placeholder/loading button
or disabled Button, when status === "authenticated" render BookingModal
(providerId/id, providerName/displayName), otherwise render the login
Button/Link. Locate the useSession call and the JSX that renders BookingModal vs
Button to implement this change.
---
Nitpick comments:
In
`@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs`:
- Around line 17-34: Change the endpoint to declare OpenAPI response metadata
and switch the query date type to DateOnly: add Produces<AvailabilityDto>() and
ProducesProblem(...) to the MapGet chain for proper Swagger contracts, change
the MapGet parameter from [FromQuery] DateTime date to [FromQuery] DateOnly
date, and update the GetProviderAvailabilityQuery constructor and its handler to
accept DateOnly instead of DateTime (so the dispatcher.QueryAsync call uses
GetProviderAvailabilityQuery(providerId, date) with DateOnly). Ensure any
DateOnly model binding is supported and that the handler logic that uses
DayOfWeek continues to work with DateOnly.
In
`@src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs`:
- Around line 7-10: O parâmetro CorrelationId nos records
SetProviderScheduleCommand (e igualmente em GetProviderAvailabilityQuery e
CreateBookingCommand) está definido como Guid = default, o que produz Guid.Empty
quando omitido; altere a modelagem para ou tornar CorrelationId obrigatório
(remover o default) e garantir que o endpoint gere e passe um new Guid() quando
o cliente não informar, ou tornar o tipo Guid? e no handler correspondente (ex.:
SetProviderScheduleCommand handler) detectar null e gerar um novo Guid para
correlação antes de prosseguir; atualize assinaturas dos records e a lógica nos
handlers/endpoints que constroem/consomem esses comandos/queries para garantir
um CorrelationId válido (não Guid.Empty) sempre presente.
In `@src/Modules/Bookings/Domain/ValueObjects/Availability.cs`:
- Around line 13-33: Add an explicit null guard for the incoming slots (e.g.,
call ArgumentNullException.ThrowIfNull(slots)) at the start of the Availability
constructor (or in Availability.Create) so callers get a clear
contract/exception instead of an internal LINQ ArgumentNullException, and
add/update XML doc comments on the Availability class/Create method to state
that TimeSlot.Overlaps uses exclusive bounds so adjacent touching slots (e.g.,
09:00–10:00 and 10:00–11:00) are allowed and not considered overlaps; keep
ValidateNoOverlaps and its adjacent-only check as-is since ordering by Start
correctly detects true overlaps.
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 65-113: Add tests to cover edge overlap cases: extend
BookingRepositoryTests to include (1) an adjacent-interval test calling
HasOverlapAsync with start equal to existing booking's end and with end equal to
existing booking's start to ensure these return false (guards against using <=
instead of <), and (2) tests where the existing Booking has status Cancelled and
Rejected (create via Booking.Create then set status accordingly or use factory)
to assert HasOverlapAsync ignores those and returns false; reference
HasOverlapAsync, Booking.Create and TimeSlot.Create to locate where to add the
new test methods and ensure you persist each booking via _repository.AddAsync
before calling HasOverlapAsync.
In `@src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj`:
- Around line 1-57: The project-wide suppression of CA2201 is risky; remove
"CA2201" from the <NoWarn> entry in MeAjudaAi.Modules.Bookings.Tests.csproj and
instead apply local suppressions where needed (e.g., add "#pragma warning
disable CA2201" / "#pragma warning restore CA2201" around the specific test
methods or files that intentionally throw System.Exception), or use
[SuppressMessage] on the specific test methods/classes; this keeps the project
file (<NoWarn> removal) clean while limiting CA2201 suppression to the exact
code locations that require it.
In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs`:
- Around line 58-103: Add unit tests that assert invalid state transitions throw
the expected exceptions: create fixtures using CreatePendingBooking,
CreateConfirmedBooking and CreateCancelledBooking (or create bookings then call
Confirm/Cancel as needed) and verify calling Booking.Reject(reason) on a
confirmed booking, Booking.Cancel(reason) on an already cancelled or completed
booking, and Booking.Complete() on a cancelled booking each throw the domain
exception (same type thrown by Booking.Confirm invalid-path guard). Also assert
that state and reason fields are unchanged (Status remains original and
RejectionReason/CancellationReason not set) after the failed call to ensure
invariants are preserved.
🪄 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: b0c4f0e2-3925-4489-b63f-b64373439b0e
⛔ Files ignored due to path filters (28)
src/Bootstrapper/MeAjudaAi.ApiService/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421131811_Initial_Bookings.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421131811_Initial_Bookings.csis excluded by!**/Migrations/**src/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421132527_Add_ProviderSchedule.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421132527_Add_ProviderSchedule.csis excluded by!**/Migrations/**src/Modules/Bookings/Infrastructure/Persistence/Migrations/BookingsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Bookings/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Tests/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/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.jsonsrc/Web/MeAjudaAi.Web.Provider/app/agenda/page.tsxis excluded by!**/MeAjudaAi.Web.Provider/**src/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**src/Web/MeAjudaAi.Web.Provider/components/layout/header.tsxis excluded by!**/MeAjudaAi.Web.Provider/**tests/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 (53)
docs/roadmap-history.mddocs/roadmap.mddocs/technical-debt.mdsrc/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csprojsrc/Bootstrapper/MeAjudaAi.ApiService/MigrationExtensions.cssrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Contracts/Bookings/Enums/EBookingStatus.cssrc/Modules/Bookings/API/Endpoints/BookingsEndpoints.cssrc/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/API/Extensions.cssrc/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/ConfirmBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cssrc/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cssrc/Modules/Bookings/Application/Bookings/DTOs/BookingDto.cssrc/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Queries/GetProviderAvailabilityQuery.cssrc/Modules/Bookings/Application/Extensions.cssrc/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csprojsrc/Modules/Bookings/Domain/Entities/Booking.cssrc/Modules/Bookings/Domain/Entities/ProviderSchedule.cssrc/Modules/Bookings/Domain/Repositories/IBookingRepository.cssrc/Modules/Bookings/Domain/Repositories/IProviderScheduleRepository.cssrc/Modules/Bookings/Domain/ValueObjects/Availability.cssrc/Modules/Bookings/Domain/ValueObjects/TimeSlot.cssrc/Modules/Bookings/Infrastructure/Extensions.cssrc/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csprojsrc/Modules/Bookings/Infrastructure/Persistence/BookingsDbContext.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cssrc/Modules/Bookings/Tests/BaseUnitTest.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csprojsrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cssrc/Modules/Bookings/Tests/Unit/Domain/ValueObjects/AvailabilityTests.cssrc/Shared/Messaging/Attributes/MessagingAttributes.cssrc/Shared/Messaging/MessagingExtensions.cssrc/Shared/Messaging/Rebus/Conventions/AttributeTopicNameConvention.cssrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.Architecture.Tests/MeAjudaAi.Architecture.Tests.csproj
✅ Files skipped from review due to trivial changes (7)
- src/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csproj
- src/Contracts/Bookings/Enums/EBookingStatus.cs
- docs/roadmap-history.md
- src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs
- src/Modules/Bookings/Application/Bookings/DTOs/BookingDto.cs
- src/Modules/Bookings/Tests/BaseUnitTest.cs
- src/Modules/Bookings/Application/MeAjudaAi.Modules.Bookings.Application.csproj
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/technical-debt.md
- src/Modules/Bookings/Infrastructure/MeAjudaAi.Modules.Bookings.Infrastructure.csproj
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (2)
src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs (1)
70-73:⚠️ Potential issue | 🟠 MajorInclua a coluna de tempo usada no overlap check no índice filtrado.
O índice filtrado ainda usa
{ ProviderId, Status, Id }; para a consulta de conflito por intervalo,Idnão ajuda a reduzir o scan dos bookings ativos do prestador. Prefira um índice filtrado que inclua pelo menosstart_time— e, se fizer sentido para o plano,end_time— junto deprovider_id/status.Verifique a query real e a migration gerada antes de ajustar:
#!/bin/bash # Verifica a query de overlap e o índice/migration atual sem alterar o repositório. rg -n -C4 'HasOverlapAsync|start_time|end_time|ix_bookings_provider_active_status' --type cs fd -e cs -p 'Migrations' -x rg -n -C3 'ix_bookings_provider_active_status|start_time|end_time|status NOT IN' {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs` around lines 70 - 73, O índice filtrado atual definido em builder.HasIndex(...) usa { b.ProviderId, b.Status, b.Id } e não inclui as colunas de tempo usadas no overlap check; altere a definição do índice (referência: builder.HasIndex e o nome ix_bookings_provider_active_status) para usar pelo menos b.StartTime como parte da chave do índice (por exemplo { b.ProviderId, b.Status, b.StartTime }) e, se fizer sentido, inclua b.EndTime via key ou IncludeProperties para acelerar a verificação de overlap; depois gere/inspecione a migration e verifique a query real para overlap conforme sugerido pelo comentário usando as ferramentas indicadas antes de commitar.src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (1)
70-76:⚠️ Potential issue | 🟠 Major
serviceIdplaceholder ainda é enviado ao backend.O payload continua fixando
"00000000-0000-0000-0000-000000000000"com TODO de seleção de serviço. Além de gerar reservas sem serviço real vinculado (corrompendo dados), qualquer validação/integração que dependa deServiceId(relatórios, pagamentos, notificações) ficará inconsistente. ExponhaserviceIdvia props e desabilite o confirmar quando ausente, ou adicione a UI de seleção antes de ativar o fluxo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 70 - 76, The component is still sending a hardcoded serviceId "00000000-0000-0000-0000-000000000000" in the booking payload which creates invalid bookings; update the booking flow in booking-modal.tsx so that serviceId is provided externally (add a prop like serviceId or onSelectService) and used in the JSON.stringify body instead of the placeholder, and prevent/disable the confirm/submit action (the handler that uses providerId and selectedSlot) when serviceId is null/undefined or present a UI selection step to choose a valid service before allowing the submit.
🧹 Nitpick comments (7)
docs/roadmap.md (1)
31-33: Considere unificar a terminologia em português.No título e bullets (Line 31–33), “Messaging” e “Performance” podem ser trocados por “Mensageria” e “Desempenho” para consistência com o restante do documento.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap.md` around lines 31 - 33, Update the Portuguese terminology for consistency: rename the section title "🚀 Arquitetura Evolutiva e Messaging (Evolução)" to use "Mensageria" instead of "Messaging", and change the bullet "Performance do Service Bus" to "Desempenho do Service Bus"; keep the existing event tags `[HighVolumeEvent]` and `[CriticalEvent]` unchanged and ensure the rest of the paragraph wording remains Portuguese-consistent.src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs (1)
48-50: Valide também o intervalo retornado.
HaveCount(1)deixa passar regressões em data/hora do slot; assertarStarteEndprende melhor o contrato do handler.💚 Sugestão de assert adicional
// Assert result.IsSuccess.Should().BeTrue(); result.Value.Slots.Should().HaveCount(1); + result.Value.Slots[0].Start.Should().Be(baseTime.AddHours(8)); + result.Value.Slots[0].End.Should().Be(baseTime.AddHours(10));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs` around lines 48 - 50, O teste em GetProviderAvailabilityQueryHandlerTests apenas verifica o número de slots retornados (result.Value.Slots.Should().HaveCount(1)), permitindo regressões de data/hora; adicione asserts que validem os campos Start e End do slot (por exemplo, inspecionar result.Value.Slots[0].Start e .End contra as DateTime esperadas) para garantir o contrato do handler permaneça correto; mantenha a verificação de contagem e acrescente comparações explícitas de Start e End usando as mesmas constantes/valores esperados usados para construir o cenário de teste.src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs (1)
51-70: Garanta que o fluxo 403 não persiste alterações.O teste cobre o status code, mas deveria travar também que
UpdateAsyncnão é chamado quando o provider não é dono da reserva.💚 Sugestão de assert adicional
// Assert result.IsFailure.Should().BeTrue(); result.Error!.StatusCode.Should().Be(403); + booking.Status.Should().Be(Contracts.Bookings.Enums.EBookingStatus.Pending); + _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs` around lines 51 - 70, O teste HandleAsync_Should_Fail_When_UserIsNotOwner verifica apenas o status 403; adicione uma asserção para garantir que nenhuma persistência ocorreu: verifique que o mock de repositório (_bookingRepoMock) nunca chama UpdateAsync quando o usuário não é o owner. Ou seja, após chamar _sut.HandleAsync(new ConfirmBookingCommand(booking.Id)), inclua uma verificação do tipo _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never()) para o método UpdateAsync do repositório.src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs (1)
59-87: Fortaleça os asserts dos cenários de cancelamento.Os testes validam o status, mas não cobrem todos os efeitos da transição. Vale assertar
CancellationReasoneUpdatedAttambém para evitar regressões silenciosas.💚 Sugestão de ajuste nos asserts
// Assert booking.Status.Should().Be(EBookingStatus.Cancelled); booking.CancellationReason.Should().Be(reason); + booking.UpdatedAt.Should().NotBeNull(); @@ // Assert booking.Status.Should().Be(EBookingStatus.Cancelled); + booking.CancellationReason.Should().Be(reason); + booking.UpdatedAt.Should().NotBeNull();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs` around lines 59 - 87, Ambos os testes de cancelamento devem validar não só o Status mas também CancellationReason e UpdatedAt para evitar regressões; para isso, in CreatePendingBooking test (Cancel_Should_ChangeStatusToCancelled_When_Pending) keep o assert de CancellationReason e adicione um assert que UpdatedAt foi atualizado (por exemplo capturando um previousUpdatedAt antes de Cancel e verificando booking.UpdatedAt > previousUpdatedAt ou que não é default), e no Confirmed test (Cancel_Should_ChangeStatusToCancelled_When_Confirmed) adicione asserts para booking.CancellationReason igual ao reason passado e para booking.UpdatedAt ter sido atualizado da mesma forma; use referências Booking.Cancel, Booking.Confirm, CancellationReason e UpdatedAt ao implementar os asserts.src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (1)
102-102: IncluaaccessTokenno cálculo deisConfirmDisabled.A
mutationFnjá validaaccessToken, mas o botão fica habilitado quando háuser.idsemaccessToken, permitindo clique que apenas dispara erro viatoast. Manter o botão consistente com as precondições da mutação evita UX confusa.🛠️ Sugestão
- const isConfirmDisabled = !selectedSlot || createBooking.isPending || !session?.user?.id; + const isConfirmDisabled = + !selectedSlot || + createBooking.isPending || + !session?.user?.id || + !session?.accessToken;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` at line 102, A condição que define isConfirmDisabled deve também checar a presença do accessToken para refletir as precondições da mutation; atualize a expressão em isConfirmDisabled (onde atualmente verifica selectedSlot, createBooking.isPending e session?.user?.id) para incluir uma verificação de session?.accessToken (p.ex. !session?.accessToken) para desabilitar o botão quando faltar o token e evitar que mutationFn seja disparada sem accessToken.src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs (1)
13-92: Cobertura incompleta para caminhos de falha do handler.Faltam cenários importantes já implementados em
HandleAsync: (a)schedule == null→ BadRequest com "Prestador não possui agenda configurada", (b)schedule.IsAvailableretornafalse→ BadRequest, (c)AddIfNoOverlapAsyncretorna conflito (409). Adicionar esses casos previne regressões silenciosas na validação/persistência.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs` around lines 13 - 92, Add unit tests covering three missing failure paths in CreateBookingCommandHandler.HandleAsync: (1) when _scheduleRepoMock.GetByProviderIdAsync returns null assert failure with StatusCode 400 and error message containing "Prestador não possui agenda configurada"; (2) when schedule.IsAvailable returns false (set up a ProviderSchedule for providerId with no matching Availability/TimeSlot) assert failure with StatusCode 400; (3) when _bookingRepoMock.AddIfNoOverlapAsync returns Result.Failure indicating overlap assert failure with StatusCode 409. Use the existing _providersApiMock to return provider exists true, use ProviderSchedule.Create(providerId) to build schedules, and arrange _scheduleRepoMock and _bookingRepoMock behaviors accordingly, then call _sut.HandleAsync(command) and assert the expected IsFailure, StatusCode and (where required) error message.src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs (1)
121-134: Nome do teste promete atomicidade, mas apenas valida sucesso.
AddIfNoOverlapAsync_ShouldBeAtomicAndSucceed_WhenNoOverlapnão exercita concorrência — apenas valida o caminho feliz sequencial. Ou renomeie para refletir o que é testado (ex.:AddIfNoOverlapAsync_ShouldPersist_WhenNoOverlap) ou adicione um teste real de concorrência disparando duas chamadas paralelas deAddIfNoOverlapAsyncpara o mesmo provider/slot e assertando que apenas uma tem sucesso (aproveitando a transaçãoSerializabledo repositório).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 121 - 134, The test named AddIfNoOverlapAsync_ShouldBeAtomicAndSucceed_WhenNoOverlap currently only asserts the happy path; either rename it to AddIfNoOverlapAsync_ShouldPersist_WhenNoOverlap to match behavior, or replace/extend it with a real concurrency check: create two bookings with the same provider/slot, start two parallel tasks that call _repository.AddIfNoOverlapAsync(booking) concurrently (e.g., Task.WhenAll), then assert that exactly one result.IsSuccess is true and the other failed, and finally verify only one entity exists via _repository.GetByIdAsync or a query; use the repository's AddIfNoOverlapAsync entry point and rely on the repository’s Serializable transaction to enforce atomicity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/roadmap.md`:
- Line 13: Replace the date string "**Meta MVP**: 12 - 16 de Maio de 2026" with
the localized pt-BR format "12 a 16 de maio de 2026" (remove the hyphen, use "a"
for the range, and lowercase the month) so the Line containing the exact text
"**Meta MVP**: 12 - 16 de Maio de 2026" follows the editorial date standard.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs`:
- Around line 27-40: O handler está assumindo que HttpContext.User implica
autenticação; antes de acessar claims (uso de httpContextAccessor.HttpContext,
User e FindFirst on AuthConstants.Claims.*) verifique
User?.Identity?.IsAuthenticated e, se falso, retorne
Result.Failure(Error.Unauthorized("Usuário não autenticado.")); só depois
proceda a extrair userIdClaim/providerIdClaim, avaliar isOwner/isProvider e
aplicar a lógica de autorização.
- Around line 47-49: No repasse a exceção de domínio em inglês para a API: na
classe/método CancelBookingCommandHandler (bloco catch
(InvalidOperationException ex)) substitua
Result.Failure(Error.BadRequest(ex.Message)) por uma mensagem user-facing em
Português (ex.: Result.Failure(Error.BadRequest("Não foi possível cancelar a
reserva." ou mensagem localizada via recurso de idioma)), mantendo o detalhe do
erro apenas em logs internos (registre ex no logger) e não no payload de erro
enviado ao frontend; não retorne ex.Message diretamente.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 44-48: The availability check is using UTC DateTime which
misaligns DayOfWeek with the provider's local schedule; update
CreateBookingCommandHandler to resolve the provider timezone from the
schedule/provider and convert command.Start into the provider's local date/time
before calling schedule.IsAvailable(duration), and change
SetProviderScheduleCommandHandler/TimeSlot.Create (and the related
AvailabilityDto/TimeSlotDto) to persist the provider's timezone (e.g., IANA ID
or TimeZoneInfo) or ensure slots are normalized into the provider local zone at
creation so comparisons inside schedule.IsAvailable use the provider-local
DayOfWeek and times rather than UTC.
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs`:
- Around line 32-35: Adicionar uma constraint de índice único na tabela da
entidade owned para garantir que não existam duas disponibilidades com o mesmo
dia para um mesmo agendamento: no ProviderScheduleConfiguration, onde a
propriedade owned "availability" é configurada (referencie o builder do owned
entity configurado via ProviderScheduleConfiguration e a propriedade DayOfWeek),
acrescente uma chamada para criar um índice único sobre as colunas
provider_schedule_id e day_of_week (a tabela owned provider_availabilities) para
reforçar a regra usada por SetAvailability no domínio.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs`:
- Around line 34-58: The test is creating DateTime values with unspecified Kind
which get implicitly converted to DateTimeOffset using the machine local offset,
causing flaky day-of-week/UTC calculations; fix by constructing explicit
DateTimeOffset start/end values (e.g. var start = new
DateTimeOffset(DateTime.UtcNow.Date.AddDays(1).AddHours(10), TimeSpan.Zero) and
similarly for end) and use those when creating CreateBookingCommand and when
building the ProviderSchedule/Availability/TimeSlot (replace usages of
command.Start.Date.AddHours(...) with the explicit DateTimeOffset-based times or
use command.Start.UtcDateTime wrapped into a DateTimeOffset with TimeSpan.Zero)
so all date/time values use a deterministic UTC offset.
In `@src/Shared/Messaging/MessagingExtensions.cs`:
- Line 108: The current decoration of ITopicNameConvention in
MessagingExtensions.cs replaces the default convention; change the decorator to
pass the existing convention into AttributeTopicNameConvention (e.g.,
o.Decorate<ITopicNameConvention>(c => new
AttributeTopicNameConvention(c.Get<ITopicNameConvention>()))). Update the
AttributeTopicNameConvention class to accept an ITopicNameConvention fallback
via its constructor and, inside GetTopic(Type eventType), return the
DedicatedTopicAttribute.TopicName when present/valid otherwise delegate to the
fallback.GetTopic(eventType).
- Line 103: You enabled System.Text.Json globally via .Serialization(s =>
s.UseSystemTextJson()) which changes the runtime serialization contract; to make
rollout safe, gate that call behind a configuration/feature flag and runtime
check in MessagingExtensions (e.g., read IConfiguration key
"Messaging:UseSystemTextJson") so the code only calls .Serialization(s =>
s.UseSystemTextJson()) when the flag is true, and log a clear warning when it's
disabled; additionally ensure MessagingExtensions includes a startup
validation/log message advising to clear queues/DLQs and confirm all
producers/consumers share the same serializer before enabling the flag.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 141-155: The code currently parses slot.start with new
Date(slot.start) in the booking-modal (inside the availability.slots.map render)
which treats timezone-less ISO strings as local time; update the parsing to
normalize missing timezone to UTC before creating the Date (e.g., detect if
slot.start lacks a timezone/suffix and append 'Z' or otherwise parse as UTC) so
the formatted time (format(new Date(...), "HH:mm")) shows consistent server UTC
times; adjust any logic around selectedSlot/setSelectedSlot to continue to use
the normalized Date/UTC string representation to avoid mismatches when comparing
selectedSlot === slot.
---
Duplicate comments:
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs`:
- Around line 70-73: O índice filtrado atual definido em builder.HasIndex(...)
usa { b.ProviderId, b.Status, b.Id } e não inclui as colunas de tempo usadas no
overlap check; altere a definição do índice (referência: builder.HasIndex e o
nome ix_bookings_provider_active_status) para usar pelo menos b.StartTime como
parte da chave do índice (por exemplo { b.ProviderId, b.Status, b.StartTime })
e, se fizer sentido, inclua b.EndTime via key ou IncludeProperties para acelerar
a verificação de overlap; depois gere/inspecione a migration e verifique a query
real para overlap conforme sugerido pelo comentário usando as ferramentas
indicadas antes de commitar.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 70-76: The component is still sending a hardcoded serviceId
"00000000-0000-0000-0000-000000000000" in the booking payload which creates
invalid bookings; update the booking flow in booking-modal.tsx so that serviceId
is provided externally (add a prop like serviceId or onSelectService) and used
in the JSON.stringify body instead of the placeholder, and prevent/disable the
confirm/submit action (the handler that uses providerId and selectedSlot) when
serviceId is null/undefined or present a UI selection step to choose a valid
service before allowing the submit.
---
Nitpick comments:
In `@docs/roadmap.md`:
- Around line 31-33: Update the Portuguese terminology for consistency: rename
the section title "🚀 Arquitetura Evolutiva e Messaging (Evolução)" to use
"Mensageria" instead of "Messaging", and change the bullet "Performance do
Service Bus" to "Desempenho do Service Bus"; keep the existing event tags
`[HighVolumeEvent]` and `[CriticalEvent]` unchanged and ensure the rest of the
paragraph wording remains Portuguese-consistent.
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 121-134: The test named
AddIfNoOverlapAsync_ShouldBeAtomicAndSucceed_WhenNoOverlap currently only
asserts the happy path; either rename it to
AddIfNoOverlapAsync_ShouldPersist_WhenNoOverlap to match behavior, or
replace/extend it with a real concurrency check: create two bookings with the
same provider/slot, start two parallel tasks that call
_repository.AddIfNoOverlapAsync(booking) concurrently (e.g., Task.WhenAll), then
assert that exactly one result.IsSuccess is true and the other failed, and
finally verify only one entity exists via _repository.GetByIdAsync or a query;
use the repository's AddIfNoOverlapAsync entry point and rely on the
repository’s Serializable transaction to enforce atomicity.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs`:
- Around line 51-70: O teste HandleAsync_Should_Fail_When_UserIsNotOwner
verifica apenas o status 403; adicione uma asserção para garantir que nenhuma
persistência ocorreu: verifique que o mock de repositório (_bookingRepoMock)
nunca chama UpdateAsync quando o usuário não é o owner. Ou seja, após chamar
_sut.HandleAsync(new ConfirmBookingCommand(booking.Id)), inclua uma verificação
do tipo _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(),
It.IsAny<CancellationToken>()), Times.Never()) para o método UpdateAsync do
repositório.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs`:
- Around line 13-92: Add unit tests covering three missing failure paths in
CreateBookingCommandHandler.HandleAsync: (1) when
_scheduleRepoMock.GetByProviderIdAsync returns null assert failure with
StatusCode 400 and error message containing "Prestador não possui agenda
configurada"; (2) when schedule.IsAvailable returns false (set up a
ProviderSchedule for providerId with no matching Availability/TimeSlot) assert
failure with StatusCode 400; (3) when _bookingRepoMock.AddIfNoOverlapAsync
returns Result.Failure indicating overlap assert failure with StatusCode 409.
Use the existing _providersApiMock to return provider exists true, use
ProviderSchedule.Create(providerId) to build schedules, and arrange
_scheduleRepoMock and _bookingRepoMock behaviors accordingly, then call
_sut.HandleAsync(command) and assert the expected IsFailure, StatusCode and
(where required) error message.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs`:
- Around line 48-50: O teste em GetProviderAvailabilityQueryHandlerTests apenas
verifica o número de slots retornados
(result.Value.Slots.Should().HaveCount(1)), permitindo regressões de data/hora;
adicione asserts que validem os campos Start e End do slot (por exemplo,
inspecionar result.Value.Slots[0].Start e .End contra as DateTime esperadas)
para garantir o contrato do handler permaneça correto; mantenha a verificação
de contagem e acrescente comparações explícitas de Start e End usando as mesmas
constantes/valores esperados usados para construir o cenário de teste.
In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs`:
- Around line 59-87: Ambos os testes de cancelamento devem validar não só o
Status mas também CancellationReason e UpdatedAt para evitar regressões; para
isso, in CreatePendingBooking test
(Cancel_Should_ChangeStatusToCancelled_When_Pending) keep o assert de
CancellationReason e adicione um assert que UpdatedAt foi atualizado (por
exemplo capturando um previousUpdatedAt antes de Cancel e verificando
booking.UpdatedAt > previousUpdatedAt ou que não é default), e no Confirmed test
(Cancel_Should_ChangeStatusToCancelled_When_Confirmed) adicione asserts para
booking.CancellationReason igual ao reason passado e para booking.UpdatedAt ter
sido atualizado da mesma forma; use referências Booking.Cancel, Booking.Confirm,
CancellationReason e UpdatedAt ao implementar os asserts.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Line 102: A condição que define isConfirmDisabled deve também checar a
presença do accessToken para refletir as precondições da mutation; atualize a
expressão em isConfirmDisabled (onde atualmente verifica selectedSlot,
createBooking.isPending e session?.user?.id) para incluir uma verificação de
session?.accessToken (p.ex. !session?.accessToken) para desabilitar o botão
quando faltar o token e evitar que mutationFn seja disparada sem accessToken.
🪄 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: 82a17e1a-f244-4b07-96a4-ecf2d92d6e88
⛔ Files ignored due to path filters (1)
src/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (32)
docs/roadmap-history.mddocs/roadmap.mddocs/technical-debt.mdsrc/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Queries/GetProviderAvailabilityQuery.cssrc/Modules/Bookings/Domain/Entities/Booking.cssrc/Modules/Bookings/Domain/Entities/ProviderSchedule.cssrc/Modules/Bookings/Domain/Repositories/IBookingRepository.cssrc/Modules/Bookings/Domain/ValueObjects/Availability.cssrc/Modules/Bookings/Domain/ValueObjects/TimeSlot.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csprojsrc/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cssrc/Shared/Events/Attributes.cssrc/Shared/Messaging/MessagingExtensions.cssrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.E2E.Tests/MeAjudaAi.E2E.Tests.csproj
💤 Files with no reviewable changes (1)
- src/Shared/Events/Attributes.cs
✅ Files skipped from review due to trivial changes (3)
- src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj
- docs/roadmap-history.md
- docs/technical-debt.md
🚧 Files skipped from review as they are similar to previous changes (13)
- src/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cs
- src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs
- src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs
- src/Modules/Bookings/Domain/ValueObjects/Availability.cs
- src/Modules/Bookings/Application/Bookings/Queries/GetProviderAvailabilityQuery.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs
- src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs
- src/Modules/Bookings/Domain/Repositories/IBookingRepository.cs
- src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs
- src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx
- src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs
- src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (1)
src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs (1)
68-74:⚠️ Potential issue | 🟠 MajorInclua colunas de tempo no índice do check de sobreposição.
O índice atual cobre só
{ ProviderId, Status }, mas o caminho quente deHasOverlapAsynctambém filtra porstart_time/end_time. Com muitos agendamentos ativos por prestador, a consulta ainda tende a varrer todos os registros ativos do provider. Considere um índice composto/filtrado alinhado ao predicado de overlap, por exemplo incluindostart_timee, se fizer sentido para o plano,end_time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs` around lines 68 - 74, The composite index on ProviderId and Status in BookingConfiguration should be extended to cover the time range used by HasOverlapAsync to avoid scanning all active bookings; update the builder.HasIndex call for the overlap path (e.g., the existing builder.HasIndex(b => new { b.ProviderId, b.Status })) to include the mapped time property/properties (e.g., StartTime and optionally EndTime) or add a filtered index aligned with the overlap predicate, making sure you reference the exact entity property names used by HasOverlapAsync and consider index ordering and INCLUDE columns if needed for the query projection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/roadmap.md`:
- Around line 31-33: The roadmap currently states that paralelismo via
[HighVolumeEvent] and Quorum Queues via [CriticalEvent] are implemented; update
the wording to reflect these are planned or in-progress features (not yet
applied by the current infra code) by changing the claims at the "Desempenho do
Service Bus" and "Resiliência Crítica" bullets to e.g. "planejado / em
andamento" or "objetivo" and remove language that implies existing
implementation; reference the attributes [HighVolumeEvent] and [CriticalEvent]
and the related infra implementations (e.g., RabbitMqInfrastructureManager and
AttributeTopicNameConvention) in your edit to make clear the docs are aligning
with the actual code state.
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 25-31: The endpoint in SetProviderScheduleEndpoint.cs checks
AuthConstants.Claims.ProviderId (providerIdClaim) and returns Results.Forbid()
when missing, but PermissionClaimsTransformation doesn't add that claim and
Keycloak doesn't emit it; fix by either adding the provider_id claim upstream in
Keycloak or by enriching the ClaimsPrincipal in PermissionClaimsTransformation
(when a user has a provider link) or by resolving the provider id inside the
endpoint via the user→provider repository lookup; specifically implement one of:
(A) add a Keycloak mapper to emit provider_id; (B) update
PermissionClaimsTransformation to query the user-provider relationship (using
your user/provider repository) and add a Claim with type
AuthConstants.Claims.ProviderId to the principal before this endpoint runs; or
(C) change SetProviderScheduleEndpoint to, when providerIdClaim is null/invalid,
call the user→provider repository using the current user id, derive the provider
id, and continue instead of returning Results.Forbid().
In
`@src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs`:
- Line 53: A leitura confirma localmente com booking.Confirm() mas
UpdateAsync(booking) faz Update() + SaveChangesAsync() sem proteção, permitindo
sobrescrita concorrente; change the repository update to perform a conditional
update (e.g., SQL/EF Core update that sets status = Confirmed WHERE Id =
booking.Id AND Status = Pending) or add optimistic concurrency
(RowVersion/timestamp property on Booking, configure as concurrency token, call
SaveChangesAsync and catch DbUpdateConcurrencyException to surface a conflict);
update BookingRepository.UpdateAsync, its use of Update() / SaveChangesAsync and
any consumers to either use the conditional WHERE-based confirm or enable
RowVersion handling so concurrent modifications fail instead of silently
overwriting.
- Line 49: The log message in ConfirmBookingCommandHandler uses Portuguese;
change the logger call so the message is in English by updating
logger.LogWarning(ex, "Erro de regra de negócio ao confirmar reserva
{BookingId}", command.BookingId) to an English message (e.g., "Business rule
error confirming booking {BookingId}") while keeping the same parameters (ex and
command.BookingId) so the exception and booking id are preserved.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 32-36: No handler should convert provider API integration failures
into a 404: in CreateBookingCommandHandler, change the
providersApi.ProviderExistsAsync handling to propagate IsFailure back to the
caller (e.g., return Result<BookingDto>.Failure(providerExists.Error) or map to
an appropriate integration error) and only convert to Error.NotFound("Prestador
não encontrado.") when providerExists.IsSuccess && !providerExists.Value; update
the logic around ProviderExistsAsync to return the original error on IsFailure
instead of always returning the NotFound result.
- Around line 53-58: The current catch in CreateBookingCommandHandler.cs
silently falls back to UTC for invalid time zones; instead catch specific
TimeZoneNotFoundException and InvalidTimeZoneException around the TimeZone
conversion (the current catch(Exception ex) that logs via logger.LogWarning and
sets localStartTime = command.Start.UtcDateTime), remove the silent UTC
fallback, and throw or return a controlled error containing a Portuguese
user-facing message (e.g., informing the user the timezone is invalid) so
validation fails fast; change the logger message to English to comply with repo
policy and include the exception details when logging (use logger.LogError or
LogWarning with ex) while keeping the exception types
(TimeZoneNotFoundException, InvalidTimeZoneException) targeted.
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs`:
- Around line 19-21: ProviderSchedule.TimeZoneId is not mapped so changes via
UpdateTimeZone() aren't persisted; update ProviderScheduleConfiguration to map
the property: add a mapping for TimeZoneId on the builder (e.g.
builder.Property(ps => ps.TimeZoneId)) and configure it as required, set
HasColumnName("time_zone_id") and a reasonable HasMaxLength (e.g. 50) so it's
stored correctly. After updating ProviderScheduleConfiguration, create a new EF
migration to add the time_zone_id column and populate existing rows with the
default "E. South America Standard Time".
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 159-166: In AddIfNoOverlapAsync in BookingRepository.cs, change
the catch block that currently rolls back the Serializable transaction and
rethrows to instead rollback the transaction and return a Result.Failure
representing a serialization/conflict error; specifically, in the catch
(Exception ex) path after await transaction.RollbackAsync(cancellationToken)
return a Result.Failure(Error.Conflict("Conflito de serialização ao validar
agendamento.")) (or similar domain error) so concurrent serialization errors
produce a Result.Failure rather than bubbling an exception to Task.WhenAll.
---
Duplicate comments:
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cs`:
- Around line 68-74: The composite index on ProviderId and Status in
BookingConfiguration should be extended to cover the time range used by
HasOverlapAsync to avoid scanning all active bookings; update the
builder.HasIndex call for the overlap path (e.g., the existing
builder.HasIndex(b => new { b.ProviderId, b.Status })) to include the mapped
time property/properties (e.g., StartTime and optionally EndTime) or add a
filtered index aligned with the overlap predicate, making sure you reference the
exact entity property names used by HasOverlapAsync and consider index ordering
and INCLUDE columns if needed for the query projection.
🪄 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: d9b2fea3-19ad-4a6c-86bc-8bc79cac9338
⛔ Files ignored due to path filters (64)
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/Bookings/API/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Domain/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421163938_Fix_Indices_And_Timestamps.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260421163938_Fix_Indices_And_Timestamps.csis excluded by!**/Migrations/**src/Modules/Bookings/Infrastructure/Persistence/Migrations/BookingsDbContextModelSnapshot.csis excluded by!**/Migrations/**src/Modules/Bookings/Infrastructure/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Modules/Bookings/Tests/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/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/Authorization/Keycloak/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Shared/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**tests/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 (27)
docs/roadmap.mdsrc/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/ConfirmBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cssrc/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Queries/GetProviderAvailabilityQuery.cssrc/Modules/Bookings/Domain/Entities/ProviderSchedule.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cssrc/Shared/MeAjudaAi.Shared.csprojsrc/Shared/Messaging/MessagingExtensions.cssrc/Shared/Messaging/Rebus/Conventions/AttributeTopicNameConvention.cssrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx
✅ Files skipped from review due to trivial changes (2)
- src/Shared/MeAjudaAi.Shared.csproj
- src/Shared/Messaging/MessagingExtensions.cs
🚧 Files skipped from review as they are similar to previous changes (14)
- src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs
- src/Modules/Bookings/Application/Bookings/Queries/GetProviderAvailabilityQuery.cs
- src/Shared/Messaging/Rebus/Conventions/AttributeTopicNameConvention.cs
- src/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommand.cs
- src/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cs
- src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs
- src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs
- src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs
- src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx
- src/Modules/Bookings/Application/Bookings/Commands/CreateBookingCommand.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs
- src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs (2)
63-67:catch (Exception)genérico mascara bugs não relacionados a fuso.O segundo
catchengloba qualquer exceção que vaze deFindSystemTimeZoneById/ConvertTimeFromUtc(incluindoArgumentNullException,OutOfMemoryExceptionreencapsulada, etc.) e transforma emError.Internal, silenciando possíveis bugs upstream. Como as únicas exceções plausíveis dessas APIs sãoTimeZoneNotFoundException/InvalidTimeZoneException(já tratadas acima) ouArgumentExceptionquandotzIdénull, considere remover estecatchgenérico e deixar exceções inesperadas propagarem para o middleware global, onde serão logadas e convertidas a 500 de forma centralizada.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 63 - 67, In CreateBookingCommandHandler, remove the broad catch (Exception ex) block that logs and returns Error.Internal for timezone conversion failures; instead let unexpected exceptions propagate to global middleware so they aren't silently masked—keep the existing specific catches for TimeZoneNotFoundException and InvalidTimeZoneException (or replace the generic catch only with a targeted catch for ArgumentException if you want to handle null tzId explicitly), and ensure logger.LogError isn't used to swallow all exceptions from FindSystemTimeZoneById/ConvertTimeFromUtc.
102-116: RemovaResolveTimeZoneId— mapeamento manual é desnecessário no .NET 6+.A partir do .NET 6,
TimeZoneInfo.FindSystemTimeZoneByIdresolve automaticamente ambos os formatos (IANA e Windows) em qualquer plataforma via ICU, de modo que"E. South America Standard Time"funciona no Linux e"America/Sao_Paulo"funciona no Windows sem código customizado. OResolveTimeZoneIdé redundante e cobre apenas um par específico — qualquer outro fuso depende do fallback automático de qualquer forma. Ocatch (TimeZoneNotFoundException or InvalidTimeZoneException)já cobre IDs inválidos.♻️ Refatoração sugerida
- var tzId = ResolveTimeZoneId(schedule.TimeZoneId); - var tz = TimeZoneInfo.FindSystemTimeZoneById(tzId); + var tz = TimeZoneInfo.FindSystemTimeZoneById(schedule.TimeZoneId); localStartTime = TimeZoneInfo.ConvertTimeFromUtc(command.Start.UtcDateTime, tz);- private static string ResolveTimeZoneId(string timeZoneId) - { - // Mapeamento básico para garantir funcionamento no Linux (CI) se vier do Windows - if (timeZoneId == "E. South America Standard Time" && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "America/Sao_Paulo"; - } - - if (timeZoneId == "America/Sao_Paulo" && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return "E. South America Standard Time"; - } - - return timeZoneId; - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 102 - 116, Remove the redundant ResolveTimeZoneId method and any calls to it in CreateBookingCommandHandler; instead pass the incoming timeZoneId directly into TimeZoneInfo.FindSystemTimeZoneById and rely on the existing catch for TimeZoneNotFoundException/InvalidTimeZoneException to handle invalid IDs, since .NET 6+ handles Windows/IANA mapping via ICU. Ensure no other code depends on ResolveTimeZoneId and delete the method to avoid one-off manual mappings (reference ResolveTimeZoneId and TimeZoneInfo.FindSystemTimeZoneById).src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (1)
108-118:HasOverlapAsyncpode virar código morto — considere remover ou marcar como[Obsolete].Após a introdução de
AddIfNoOverlapAsync(que encapsula check+insert atomicamente) e sua adoção emCreateBookingCommandHandler,HasOverlapAsyncdeixa de ser chamado no caminho de criação de agendamentos. Atualmente, só é exercitado em testes de integração. Se não há plano de uso em outros fluxos (como validações de UI ou preview), remova o método para reduzir a duplicação; caso contrário, marque como[Obsolete]com mensagem clara indicando a migração paraAddIfNoOverlapAsync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 108 - 118, HasOverlapAsync appears to be dead code since AddIfNoOverlapAsync now provides atomic check+insert and CreateBookingCommandHandler uses that flow; either remove the HasOverlapAsync method from BookingRepository or mark it with [Obsolete("Use AddIfNoOverlapAsync for atomic overlap checks and inserts")] so callers are warned; if you remove it, update any integration tests that reference HasOverlapAsync to instead exercise AddIfNoOverlapAsync or adjust tests accordingly, and ensure references in other code (if any) are replaced with the new API.
🤖 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/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs`:
- Around line 28-45: No handler for InvalidOperationException thrown by
BookingRepository.UpdateAsync: wrap the call to bookingRepository.UpdateAsync in
CancelBookingCommandHandler.Handle with a try-catch that catches
InvalidOperationException and returns Result.Failure(Error.Conflict("Conflito de
concorrência ao atualizar a reserva.") ) (same pattern used by
AddIfNoOverlapAsync), so concurrent-update conflicts produce a 409-style Result
instead of propagating a 500; reference the UpdateAsync call in
CancelBookingCommandHandler and the conflict throw in
BookingRepository.UpdateAsync to implement this.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cs`:
- Around line 43-57: The handler currently catches InvalidOperationException
from booking.Confirm() but UpdateAsync can also throw InvalidOperationException
for concurrency, causing a 500 instead of 409; change the repository to throw a
specific ConcurrencyConflictException (or return a Result/Conflict) from
BookingRepository.UpdateAsync, then update ConfirmBookingCommandHandler to catch
ConcurrencyConflictException separately and map it to
Result.Failure(Error.Conflict(...)); keep the existing catch for
InvalidOperationException to handle business-rule errors from booking.Confirm(),
and apply the same change pattern to CancelBookingCommandHandler so
concurrent-update scenarios yield a Conflict result.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 14-48: The handler CreateBookingCommandHandler.HandleAsync
currently only checks End > Start but doesn't prevent bookings in the past; add
a validation after the interval check to compare command.Start with
DateTimeOffset.UtcNow (use UTC) and return
Result<BookingDto>.Failure(Error.BadRequest("O horário de início deve ser no
futuro.")) if command.Start <= DateTimeOffset.UtcNow; place this check before
fetching provider/schedule to fail fast and reference command.Start and
DateTimeOffset.UtcNow in the comparison.
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs`:
- Around line 54-63: The slot mapping currently uses timestamptz which embeds
UTC semantics and breaks ProviderSchedule/TimeSlot logic (see slot.Property(s =>
s.Start), slot.Property(s => s.End), ProviderSchedule.TimeZoneId,
TimeSlot.Create and IsAvailable). Change the EF mapping to store pure
time-of-day (use HasColumnType("time") or "time without time zone") and update
the Slot/TimeSlot types or add a ValueConverter to persist only TimeOfDay (e.g.,
map DateTime -> TimeOnly or DateTime with Kind=Unspecified) so reads equal
writes; alternatively, if you must keep timestamps, modify IsAvailable to use
ProviderSchedule.TimeZoneId to convert incoming times into the provider zone
before comparing. Ensure TimeSlot.Create, IsAvailable and ProviderSchedule
references are updated to the chosen approach and add an integration test that
persists and reloads a slot under different server timezones.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 55-91: Substitua o uso síncrono "using var transaction = await
context.Database.BeginTransactionAsync(...)" por "await using var transaction =
..." para garantir descarte assíncrono do IDbContextTransaction; no bloco catch,
remova a verificação frágil de texto em InvalidOperationException e adicione
tratamento específico para Npgsql.PostgresException (ou PostgresException)
verificando ex.SqlState == "40001" (serialization_failure) ou ex.SqlState ==
"40P01" (deadlock_detected) e retorne Result.Failure(Error.Conflict(...)) nesses
casos, mantendo o rollback e relançando para outros erros; referencie o método
que contém a transação e as variáveis transaction,
context.Database.BeginTransactionAsync e o bloco catch para localizar onde
aplicar a alteração.
---
Nitpick comments:
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 63-67: In CreateBookingCommandHandler, remove the broad catch
(Exception ex) block that logs and returns Error.Internal for timezone
conversion failures; instead let unexpected exceptions propagate to global
middleware so they aren't silently masked—keep the existing specific catches for
TimeZoneNotFoundException and InvalidTimeZoneException (or replace the generic
catch only with a targeted catch for ArgumentException if you want to handle
null tzId explicitly), and ensure logger.LogError isn't used to swallow all
exceptions from FindSystemTimeZoneById/ConvertTimeFromUtc.
- Around line 102-116: Remove the redundant ResolveTimeZoneId method and any
calls to it in CreateBookingCommandHandler; instead pass the incoming timeZoneId
directly into TimeZoneInfo.FindSystemTimeZoneById and rely on the existing catch
for TimeZoneNotFoundException/InvalidTimeZoneException to handle invalid IDs,
since .NET 6+ handles Windows/IANA mapping via ICU. Ensure no other code depends
on ResolveTimeZoneId and delete the method to avoid one-off manual mappings
(reference ResolveTimeZoneId and TimeZoneInfo.FindSystemTimeZoneById).
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 108-118: HasOverlapAsync appears to be dead code since
AddIfNoOverlapAsync now provides atomic check+insert and
CreateBookingCommandHandler uses that flow; either remove the HasOverlapAsync
method from BookingRepository or mark it with [Obsolete("Use AddIfNoOverlapAsync
for atomic overlap checks and inserts")] so callers are warned; if you remove
it, update any integration tests that reference HasOverlapAsync to instead
exercise AddIfNoOverlapAsync or adjust tests accordingly, and ensure references
in other code (if any) are replaced with the new API.
🪄 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: 8cdc719d-046b-44d0-95df-c5fec930e560
⛔ Files ignored due to path filters (1)
src/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (13)
docs/roadmap.mdsrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/ConfirmBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Domain/Entities/Booking.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/BookingConfiguration.cssrc/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Payments/Domain/Events/SubscriptionActivatedDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionCanceledDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionExpiredDomainEvent.cssrc/Modules/Payments/Domain/Events/SubscriptionRenewedDomainEvent.cs
✅ Files skipped from review due to trivial changes (3)
- src/Modules/Payments/Domain/Events/SubscriptionCanceledDomainEvent.cs
- src/Modules/Payments/Domain/Events/SubscriptionRenewedDomainEvent.cs
- src/Modules/Bookings/Domain/Entities/Booking.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs
|
@coderabbitai estou com 2 problemas, o primeiro a versão do pacote OpenTelemetry.Instrumentation.AspNetCore difere da master, o que causa erro no step de Breaking changes, como corrigir? O seguno é que o módulo Bookings está com cobertura baixa, mas com os testes criados, está configurado corretamente? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 1790 🏁 Script executed: Length of output: 1082 🏁 Script executed: Length of output: 17987 🏁 Script executed: Length of output: 5943
Problema 1 — Downgrade de pacotes OpenTelemetryO PR foi criado a partir de uma versão mais antiga do
Correção: faça merge (ou rebase) do <PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />Problema 2 — Módulo Bookings sem cobertura no CIOs testes existem e estão configurados corretamente, mas o projeto de testes do Bookings não foi adicionado ao array Correção: adicionar a entrada no array em 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"
+ "src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.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"
)
🧠 Learnings used |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (10)
src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs (1)
68-71: Considere propagar um correlation id existente em vez de gerar um novo.
Guid.NewGuid()cria um correlation id desvinculado de qualquer contexto de requisição, dificultando a correlação ponta a ponta nos logs/tracing. Se houver um id já disponível (headerX-Correlation-Id,HttpContext.TraceIdentifierouActivity.Current?.TraceId), prefira reaproveitá-lo, caindo paraGuid.NewGuid()apenas quando ausente.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs` around lines 68 - 71, Ao criar o SetProviderScheduleCommand atualmente você passa Guid.NewGuid() como correlation id; em vez disso, reutilize um correlation id existente quando disponível (verificando na ordem: header "X-Correlation-Id" do request, HttpContext.TraceIdentifier e Activity.Current?.TraceId) e só chame Guid.NewGuid() como fallback; atualize o local onde instanciamos SetProviderScheduleCommand (os símbolos targetProviderId, request.Availabilities e o terceiro argumento) para usar essa lógica de resolução de correlation id antes de criar o comando.src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs (3)
51-51: Nit: adicioneusingparaEBookingStatuse evite o nome totalmente qualificado.As três asserções em
booking.Status.Should().Be(Contracts.Bookings.Enums.EBookingStatus.Cancelled)(linhas 51, 74, 146) ficariam mais legíveis comusing MeAjudaAi.Contracts.Bookings.Enums;no topo do arquivo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs` at line 51, Add a using for the enum namespace and update the fully-qualified enum references to the short name: add "using MeAjudaAi.Contracts.Bookings.Enums;" at the top of the test file and change the assertions that reference Contracts.Bookings.Enums.EBookingStatus (the assertions like booking.Status.Should().Be(Contracts.Bookings.Enums.EBookingStatus.Cancelled) in the CancelBookingCommandHandlerTests) to use EBookingStatus.Cancelled (and similarly for the other two occurrences) to improve readability.
120-147: Refator opcional: estenderSetupUserpara suportar admin e evitar duplicação.O bloco de montagem do
ClaimsPrincipalneste teste duplica a lógica já presente no helperSetupUser(linhas 230‑246). Um parâmetro opcionalisSystemAdmincentralizaria a criação do principal e reduziria ruído.♻️ Sugestão de refator
- // Configura Admin - var claims = new List<Claim> - { - new(AuthConstants.Claims.Subject, Guid.NewGuid().ToString()), - new(AuthConstants.Claims.IsSystemAdmin, "true") - }; - var identity = new ClaimsIdentity(claims, "Test"); - var principal = new ClaimsPrincipal(identity); - _httpContextMock.Setup(x => x.HttpContext).Returns(new DefaultHttpContext { User = principal }); + // Configura Admin + SetupUser(Guid.NewGuid(), null, isSystemAdmin: true);E ajustar o helper:
- private void SetupUser(Guid userId, Guid? providerId) + private void SetupUser(Guid userId, Guid? providerId, bool isSystemAdmin = false) { var claims = new List<Claim> { new(AuthConstants.Claims.Subject, userId.ToString()) }; if (providerId.HasValue) { claims.Add(new Claim(AuthConstants.Claims.ProviderId, providerId.Value.ToString())); } + if (isSystemAdmin) + { + claims.Add(new Claim(AuthConstants.Claims.IsSystemAdmin, "true")); + } + var identity = new ClaimsIdentity(claims, "Test");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs` around lines 120 - 147, Extend the existing test helper SetupUser to accept an optional isSystemAdmin boolean (default false) so it builds a ClaimsPrincipal with AuthConstants.Claims.IsSystemAdmin set to "true" when true; then update the test HandleAsync_Should_Succeed_When_UserIsSystemAdmin to call SetupUser(isSystemAdmin: true) instead of manually creating Claims/ClaimsIdentity/ClaimsPrincipal and setting _httpContextMock, leaving the rest of the Arrange/Act/Assert intact; use the SetupUser symbol to centralize principal creation and remove the duplicated ClaimsPrincipal construction in the test.
78-97: Teste cobre apenas um caminho de "não autorizado"; considere explicitar o cenário de provider ausente.
HandleAsync_Should_Fail_When_UserIsNotAuthorizedusaSetupUser(Guid.NewGuid(), null), que testa um cliente autenticado diferente do dono. Combinado comHandleAsync_Should_Fail_When_UserIsDifferentProvider(linhas 99‑118), a cobertura está boa, mas o nome do primeiro teste é genérico demais. Sugestão: renomear para algo comoHandleAsync_Should_ReturnForbidden_When_UserIsDifferentClientpara deixar explícito o branch coberto e facilitar leitura do relatório.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs` around lines 78 - 97, The test method HandleAsync_Should_Fail_When_UserIsNotAuthorized (in CancelBookingCommandHandlerTests) is too generic; rename it to HandleAsync_Should_ReturnForbidden_When_UserIsDifferentClient to explicitly document the covered branch where SetupUser(Guid.NewGuid(), null) creates an authenticated client that is not the booking owner; update the test method name and any references (e.g., test runner metadata) accordingly so the unit test suite and reports reflect the clearer scenario, leaving the Arrange/Act/Assert logic (SetupUser, booking setup, _sut.HandleAsync call and assertions) unchanged.src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs (1)
12-16: RemoverInclude/ThenIncluderedundantes para tipos owned.
AvailabilitieseSlotssão coleções owned (configuradas viaOwnsManyemProviderScheduleConfiguration). Desde o EF Core 6+, tipos owned são carregados automaticamente junto com o agregado raiz, portanto osInclude(ps => ps.Availabilities).ThenInclude(a => a.Slots)não têm efeito prático e podem ser removidos para reduzir ruído.♻️ Sugestão de simplificação
return await context.ProviderSchedules - .Include(ps => ps.Availabilities) - .ThenInclude(a => a.Slots) .FirstOrDefaultAsync(ps => ps.ProviderId == providerId, cancellationToken);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs` around lines 12 - 16, Remover os Include/ThenInclude redundantes na consulta em ProviderScheduleRepository: a chamada que usa context.ProviderSchedules.Include(ps => ps.Availabilities).ThenInclude(a => a.Slots).FirstOrDefaultAsync(...) deve ser simplificada removendo .Include(...) e .ThenInclude(...) porque Availabilities e Slots são owned (configurados via OwnsMany em ProviderScheduleConfiguration) e já são carregados automaticamente; mantenha apenas context.ProviderSchedules.FirstOrDefaultAsync(ps => ps.ProviderId == providerId, cancellationToken) dentro do método que resolve pelo providerId.src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs (2)
11-32: Estado compartilhado entre testes: considereResetDatabaseAsync()entre métodos.
BaseDatabaseTestinstancia um único container Postgres por classe e não limpa a base entre métodos (oRespawnersó existe e precisa ser chamado manualmente viaResetDatabaseAsync). Aqui você mitigou o risco usandoGuid.NewGuid()para cadaproviderId/clientId, o que na prática isola os cenários — porém o testeAddAsync_ShouldPersistBookingdepende deFirstOrDefaultAsync(b => b.Id == booking.Id)(também seguro por Id único), e qualquer teste futuro que iterarcontext.Bookings.CountAsync()sem filtro vai herdar dados de testes anteriores.Recomendação: adicionar
await ResetDatabaseAsync()no início (ou final) de cada[Fact]ou em umInitializeAsyncpor teste para garantir isolamento determinístico conforme a frota de testes crescer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 11 - 32, The tests share a single Postgres container and the Respawner must be invoked to reset state; call await ResetDatabaseAsync() to clear DB state before each test. Add a call to ResetDatabaseAsync() at the start of BookingRepositoryTests.InitializeAsync (or ensure each [Fact] calls ResetDatabaseAsync()), then create the BookingsDbContext and BookingRepository as you do now; this prevents cross-test leakage that would affect methods like AddAsync_ShouldPersistBooking or any future use of context.Bookings.CountAsync().
115-116: Prefiraawait usingparaBookingsDbContext.
BookingsDbContextimplementaIAsyncDisposable; ousingsíncrono forçaDisposeblocante, que pode prender a thread em cenários com connection pooling sob pressão. Padroniza com o resto do codebase (ex.:OutboxMessageRepository) e evita warning de analisadores.♻️ Ajuste sugerido
- using var ctx1 = new BookingsDbContext(options); - using var ctx2 = new BookingsDbContext(options); + await using var ctx1 = new BookingsDbContext(options); + await using var ctx2 = new BookingsDbContext(options);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 115 - 116, Substitua os blocos sincronizados "using var ctx1 = new BookingsDbContext(options);" e "using var ctx2 = new BookingsDbContext(options);" por "await using" para aproveitar IAsyncDisposable do BookingsDbContext (referência: BookingsDbContext, ctx1, ctx2); garanta que o método de teste seja assíncrono (mude a assinatura para retornar Task e adicione async) para poder usar await using sem erros e execute o restante do teste normalmente.src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs (1)
30-33: ValidaçãoStart <= UtcNowrejeita "agora mesmo" sem margem.A checagem estrita pode rejeitar bookings legítimos submetidos para o instante atual (relógios do cliente ligeiramente à frente, latência de rede, etc.). Mais relevante: mesmo quando passa aqui, há uma janela de tempo até a execução de
AddIfNoOverlapAsyncem que oStartpode se tornar passado — não é erro funcional, mas é uma armadilha sob carga.Considere uma tolerância mínima (ex.:
command.Start < DateTimeOffset.UtcNow.AddMinutes(-1)como rejeição, ou uma política configurável de "antecedência mínima" para bookings) em vez de um cutoff estrito.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 30 - 33, The Start-time validation in CreateBookingCommandHandler currently rejects any booking whose command.Start is <= DateTimeOffset.UtcNow; change this to use a minimal advance tolerance instead (e.g., compute a configurable minimumLead = TimeSpan.FromMinutes(1) or an injected policy value and reject only when command.Start < DateTimeOffset.UtcNow.Subtract(minimumLead) or equivalent), update the conditional that returns Result<BookingDto>.Failure to use that tolerance, and adjust the error message to indicate the required minimum lead time; ensure the new rule is applied before calling AddIfNoOverlapAsync and that the tolerance value is configurable/testable.src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (2)
83-107:catch (Exception)engoleOperationCanceledException/TaskCanceledException.Quando
cancellationTokené cancelado duranteBeginTransactionAsync/SaveChangesAsync/CommitAsync, aOperationCanceledException(ouTaskCanceledException) cai nestecatch, passa pela checagem dePostgresException(que nunca casa), e é relançada em linha 106 — o que é o comportamento correto. Porém, a tentativa deRollbackAsync(cancellationToken)na linha 87 usa o mesmo token já cancelado e muito provavelmente lançará de novo, sendo silenciosamente engolida pelocatchvazio da linha 89-92. Isso esconde o cancelamento na telemetria e mascara o motivo real.Sugestão: passe
CancellationToken.NoneaoRollbackAsyncpara garantir que o rollback execute mesmo sob cancelamento, e trateOperationCanceledExceptionantes docatchgenérico para relançar imediatamente preservando o stack original.♻️ Ajuste sugerido
- catch (Exception ex) + catch (OperationCanceledException) + { + try { await transaction.RollbackAsync(CancellationToken.None); } catch { /* já abortada */ } + throw; + } + catch (Exception ex) { try { - await transaction.RollbackAsync(cancellationToken); + await transaction.RollbackAsync(CancellationToken.None); } catch { // Ignora erro de rollback se a transação já tiver sido abortada pelo banco }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 83 - 107, The current catch(Exception ex) in BookingRepository swallows cancellation by handling OperationCanceledException/TaskCanceledException the same as other exceptions and calling transaction.RollbackAsync(cancellationToken) with the already-cancelled token; modify the try/catch so you first detect and rethrow OperationCanceledException/TaskCanceledException (preserve original stack) before the generic catch, and when performing transaction.RollbackAsync use CancellationToken.None so the rollback runs even if the ambient cancellation token was cancelled; update the catch block around transaction.RollbackAsync to still ignore rollback failures but do not pass the cancelled cancellationToken, and keep the existing recursive PostgresException (SqlState "40001"/"40P01") check and Result.Failure return as-is.
15-19:GetByIdAsyncsemAsNoTrackingé intencional, mas vale documentar.Diferente das demais consultas,
GetByIdAsyncnão usaAsNoTracking. Faz sentido porqueConfirmBookingCommandHandler/CancelBookingCommandHandlerusam este método e em seguida chamamUpdateAsync, que depende de entidade rastreada para detectar mudanças. Considere deixar isso explícito em um comentário XML para evitar que um futuro refactor adicioneAsNoTrackingpor analogia com os demais métodos e quebre os fluxos de update.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 15 - 19, GetByIdAsync currently omits AsNoTracking intentionally because ConfirmBookingCommandHandler and CancelBookingCommandHandler call GetByIdAsync and then UpdateAsync which requires a tracked entity; add an XML documentation comment above the GetByIdAsync method stating that AsNoTracking is deliberately not used to keep the entity tracked for subsequent UpdateAsync operations (mentioning ConfirmBookingCommandHandler and CancelBookingCommandHandler as consumers) so future refactors do not introduce AsNoTracking by analogy.
🤖 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/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 96-106: O código cria DateTimeOffset usando tz.GetUtcOffset sobre
DateTimeKind.Unspecified (via date.ToDateTime(booking.TimeSlot.Start)), o que
produz offsets incorretos em janelas DST ambíguas; corrija reconstruindo o
DateTimeOffset a partir do UTC originalmente preservado (por exemplo usar o
DateTimeOffset original do comando/request e converter com
TimeZoneInfo.ConvertTime) ou, se só tiver o horário local, verificar
tz.IsAmbiguousTime(...) e resolver explicitamente com
tz.GetAmbiguousTimeOffsets(...) escolhendo o offset desejado antes de chamar new
DateTimeOffset; localize a criação do BookingDto (as variáveis
startDate/endDate, date.ToDateTime(...), tz.GetUtcOffset(...)) e substitua pela
conversão segura descrita.
- Around line 68-78: The handler computes duration in UTC then validates
availability with IsAvailable(localStartTime, duration) but later recomputes
localEndTime from UTC (TimeZoneInfo.ConvertTimeFromUtc(command.End.UtcDateTime,
tz)), causing DST mismatches; fix by deriving localEndTime from the
already-converted localStartTime using localStartTime.Add(duration) (rather than
reconverting command.End), then use DateOnly.FromDateTime(localStartTime) and
TimeSlot.FromDateTime(localStartTime, localEndTime) so the interval validated by
IsAvailable exactly matches the persisted TimeSlot even across DST transitions.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 54-108: The AddIfNoOverlapAsync method must be executed under the
DbContext execution strategy to support EnableRetryOnFailure: wrap the existing
transaction + check+insert logic inside
context.Database.CreateExecutionStrategy().ExecuteAsync(async () => { ... })
instead of calling BeginTransactionAsync directly; inside that execution
strategy block start the serializable transaction via await using var
transaction = await
context.Database.BeginTransactionAsync(IsolationLevel.Serializable,
cancellationToken) and perform the AnyAsync check, AddAsync, SaveChangesAsync
and CommitAsync. Additionally implement a small retry loop (2–3 attempts) inside
the execution strategy to catch PostgresException SqlState "40001" or "40P01"
(or their wrapped InnerExceptions) and retry the entire attempt before returning
Result.Failure, preserving the existing rollback/exception handling in
AddIfNoOverlapAsync.
---
Nitpick comments:
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 68-71: Ao criar o SetProviderScheduleCommand atualmente você passa
Guid.NewGuid() como correlation id; em vez disso, reutilize um correlation id
existente quando disponível (verificando na ordem: header "X-Correlation-Id" do
request, HttpContext.TraceIdentifier e Activity.Current?.TraceId) e só chame
Guid.NewGuid() como fallback; atualize o local onde instanciamos
SetProviderScheduleCommand (os símbolos targetProviderId, request.Availabilities
e o terceiro argumento) para usar essa lógica de resolução de correlation id
antes de criar o comando.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 30-33: The Start-time validation in CreateBookingCommandHandler
currently rejects any booking whose command.Start is <= DateTimeOffset.UtcNow;
change this to use a minimal advance tolerance instead (e.g., compute a
configurable minimumLead = TimeSpan.FromMinutes(1) or an injected policy value
and reject only when command.Start < DateTimeOffset.UtcNow.Subtract(minimumLead)
or equivalent), update the conditional that returns Result<BookingDto>.Failure
to use that tolerance, and adjust the error message to indicate the required
minimum lead time; ensure the new rule is applied before calling
AddIfNoOverlapAsync and that the tolerance value is configurable/testable.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 83-107: The current catch(Exception ex) in BookingRepository
swallows cancellation by handling
OperationCanceledException/TaskCanceledException the same as other exceptions
and calling transaction.RollbackAsync(cancellationToken) with the
already-cancelled token; modify the try/catch so you first detect and rethrow
OperationCanceledException/TaskCanceledException (preserve original stack)
before the generic catch, and when performing transaction.RollbackAsync use
CancellationToken.None so the rollback runs even if the ambient cancellation
token was cancelled; update the catch block around transaction.RollbackAsync to
still ignore rollback failures but do not pass the cancelled cancellationToken,
and keep the existing recursive PostgresException (SqlState "40001"/"40P01")
check and Result.Failure return as-is.
- Around line 15-19: GetByIdAsync currently omits AsNoTracking intentionally
because ConfirmBookingCommandHandler and CancelBookingCommandHandler call
GetByIdAsync and then UpdateAsync which requires a tracked entity; add an XML
documentation comment above the GetByIdAsync method stating that AsNoTracking is
deliberately not used to keep the entity tracked for subsequent UpdateAsync
operations (mentioning ConfirmBookingCommandHandler and
CancelBookingCommandHandler as consumers) so future refactors do not introduce
AsNoTracking by analogy.
In
`@src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs`:
- Around line 12-16: Remover os Include/ThenInclude redundantes na consulta em
ProviderScheduleRepository: a chamada que usa
context.ProviderSchedules.Include(ps => ps.Availabilities).ThenInclude(a =>
a.Slots).FirstOrDefaultAsync(...) deve ser simplificada removendo .Include(...)
e .ThenInclude(...) porque Availabilities e Slots são owned (configurados via
OwnsMany em ProviderScheduleConfiguration) e já são carregados automaticamente;
mantenha apenas context.ProviderSchedules.FirstOrDefaultAsync(ps =>
ps.ProviderId == providerId, cancellationToken) dentro do método que resolve
pelo providerId.
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 11-32: The tests share a single Postgres container and the
Respawner must be invoked to reset state; call await ResetDatabaseAsync() to
clear DB state before each test. Add a call to ResetDatabaseAsync() at the start
of BookingRepositoryTests.InitializeAsync (or ensure each [Fact] calls
ResetDatabaseAsync()), then create the BookingsDbContext and BookingRepository
as you do now; this prevents cross-test leakage that would affect methods like
AddAsync_ShouldPersistBooking or any future use of
context.Bookings.CountAsync().
- Around line 115-116: Substitua os blocos sincronizados "using var ctx1 = new
BookingsDbContext(options);" e "using var ctx2 = new
BookingsDbContext(options);" por "await using" para aproveitar IAsyncDisposable
do BookingsDbContext (referência: BookingsDbContext, ctx1, ctx2); garanta que o
método de teste seja assíncrono (mude a assinatura para retornar Task e adicione
async) para poder usar await using sem erros e execute o restante do teste
normalmente.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs`:
- Line 51: Add a using for the enum namespace and update the fully-qualified
enum references to the short name: add "using
MeAjudaAi.Contracts.Bookings.Enums;" at the top of the test file and change the
assertions that reference Contracts.Bookings.Enums.EBookingStatus (the
assertions like
booking.Status.Should().Be(Contracts.Bookings.Enums.EBookingStatus.Cancelled) in
the CancelBookingCommandHandlerTests) to use EBookingStatus.Cancelled (and
similarly for the other two occurrences) to improve readability.
- Around line 120-147: Extend the existing test helper SetupUser to accept an
optional isSystemAdmin boolean (default false) so it builds a ClaimsPrincipal
with AuthConstants.Claims.IsSystemAdmin set to "true" when true; then update the
test HandleAsync_Should_Succeed_When_UserIsSystemAdmin to call
SetupUser(isSystemAdmin: true) instead of manually creating
Claims/ClaimsIdentity/ClaimsPrincipal and setting _httpContextMock, leaving the
rest of the Arrange/Act/Assert intact; use the SetupUser symbol to centralize
principal creation and remove the duplicated ClaimsPrincipal construction in the
test.
- Around line 78-97: The test method
HandleAsync_Should_Fail_When_UserIsNotAuthorized (in
CancelBookingCommandHandlerTests) is too generic; rename it to
HandleAsync_Should_ReturnForbidden_When_UserIsDifferentClient to explicitly
document the covered branch where SetupUser(Guid.NewGuid(), null) creates an
authenticated client that is not the booking owner; update the test method name
and any references (e.g., test runner metadata) accordingly so the unit test
suite and reports reflect the clearer scenario, leaving the Arrange/Act/Assert
logic (SetupUser, booking setup, _sut.HandleAsync call and assertions)
unchanged.
🪄 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: 1b97f15f-0367-4615-95eb-a9645bc90897
📒 Files selected for processing (7)
src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cstests/MeAjudaAi.Integration.Tests/Modules/Bookings/BookingsApiTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/MeAjudaAi.Integration.Tests/Modules/Bookings/BookingsApiTests.cs
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/roadmap-history.md (1)
72-72:⚠️ Potential issue | 🟡 MinorAtualizar a métrica de cobertura no Sumário Executivo após a entrega do Sprint 12.
O texto mantém "Backend 90.56% | Frontend 30 testes bUnit" mesmo com o novo módulo Bookings e toda a suíte de testes adicionada neste PR. Recomendo atualizar com o valor real medido pelo CI após o merge (e, se possível, referenciar o relatório gerado) para manter o histórico fiel.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/roadmap-history.md` at line 72, Update the "Cobertura de Testes" metric text in the summary ("Cobertura de Testes: Backend 90.56% | Frontend 30 testes bUnit") to the actual coverage value produced by CI after this PR (replace the placeholder Backend/Frontend values with the measured percentages and test counts) and, if available, append or reference the CI coverage report link or filename so the historical note reflects the real post-merge results.tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs (1)
6-19:⚠️ Potential issue | 🟡 MinorDiretiva
usingduplicada.
using MeAjudaAi.Modules.Payments.Infrastructure.Persistence;aparece na linha 6 e novamente na linha 19. Remova uma das ocorrências para evitar warning do compilador/analyzer.♻️ Correção proposta
using MeAjudaAi.Modules.Communications.Infrastructure.Persistence; -using MeAjudaAi.Modules.Payments.Infrastructure.Persistence; using MeAjudaAi.Modules.Bookings.Infrastructure.Persistence; using MeAjudaAi.Modules.Documents.Infrastructure.Persistence;🤖 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 6 - 19, Remove the duplicate using directive for MeAjudaAi.Modules.Payments.Infrastructure.Persistence: there are two identical lines importing that namespace (the duplicated "using MeAjudaAi.Modules.Payments.Infrastructure.Persistence;"); delete the extra occurrence so the file keeps a single using for that namespace (verify in the BaseApiTest class top imports), then rebuild to ensure no compiler/analyzer warning remains.
♻️ Duplicate comments (1)
src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (1)
64-113:⚠️ Potential issue | 🟠 MajorFallback para GUID zero ainda permite submeter agendamento sem serviço.
Em Line 65,
targetServiceId = serviceId || "00000000-0000-0000-0000-000000000000"reintroduz o placeholder já apontado em review anterior.isConfirmDisabled(Line 113) não checaserviceId, então o botão fica habilitado e oPOSTvai com GUID zero — o backend responderá 400 (validação) ou, pior, criará um booking comServiceIdinexistente se a validação do handler for frouxa.Torne
serviceIdobrigatório na prop e inclua na condição do disable:🛡️ Correção sugerida
interface BookingModalProps { providerId: string; providerName: string; - serviceId?: string; // Prop opcional, se não vier usa o default do prestador + serviceId: string; trigger?: React.ReactNode; } ... - // Garante que temos um serviceId válido - const targetServiceId = serviceId || "00000000-0000-0000-0000-000000000000"; - const apiUrl = process.env.NEXT_PUBLIC_API_URL; const res = await fetch(`${apiUrl}/api/v1/bookings`, { ... body: JSON.stringify({ providerId, - serviceId: targetServiceId, + serviceId, start: selectedSlot.start, end: selectedSlot.end }) }); ... - const isConfirmDisabled = !selectedSlot || createBooking.isPending || !session?.user?.id || !session?.accessToken; + const isConfirmDisabled = !selectedSlot || createBooking.isPending || !session?.user?.id || !session?.accessToken || !serviceId;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 64 - 113, Currently the component falls back to the zero-GUID (targetServiceId) and the confirm button state (isConfirmDisabled) does not check serviceId, allowing submissions with an invalid service; remove the fallback use of "00000000-..." in the POST payload and require serviceId as a non-nullable prop (update the component's prop type/signature), change the fetch body to use the provided serviceId directly, and add a check for !serviceId into isConfirmDisabled so the confirm button is disabled unless a real serviceId exists; also remove/unused targetServiceId variable and ensure createBooking (the mutation) and onSuccess/onError flows remain unchanged.
🧹 Nitpick comments (16)
src/Shared/MeAjudaAi.Shared.csproj (1)
45-45: Referência adicionada ok; revalide as divergências OpenTelemetry noDirectory.Packages.props.A referência sem versão depende do Central Package Management. Conforme apontado no próprio PR, há divergências/downgrades em vários pacotes OpenTelemetry versus
master(Exporter.Console/OpenTelemetryProtocol/Extensions.Hosting em 1.15.2 vs 1.15.3; Instrumentation.AspNetCore/Http/Runtime desalinhados). Faça o rebase/merge demastere mantenha as versões mais altas noDirectory.Packages.propspara desbloquear o check de breaking changes do CI.Também: o posicionamento do
<PackageReference Include="OpenTelemetry.Exporter.Console" />está dentro do bloco comentado como<!-- Feature Management -->. Mova para um bloco<!-- Observability/OpenTelemetry -->só para manter a organização existente do arquivo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/MeAjudaAi.Shared.csproj` at line 45, Rebase/merge master to resolve OpenTelemetry version mismatches, then update Directory.Packages.props to use the highest master versions (e.g., bump OpenTelemetry.Exporter.Console/OpenTelemetryProtocol/Extensions.Hosting to 1.15.3 and align Instrumentation.AspNetCore/Instrumentation.Http/Instrumentation.Runtime to the same family) so central package management prevents downgrades; finally move the <PackageReference Include="OpenTelemetry.Exporter.Console" /> entry out of the <!-- Feature Management --> block into a new or existing <!-- Observability/OpenTelemetry --> block in MeAjudaAi.Shared.csproj to keep organization consistent and re-run CI breaking-change checks.src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs (1)
34-35:FromDateTimeignora silenciosamente oDateTimeKinde diferenças de data.A factory apenas extrai a parte de hora de
start/end, então chamadas comoFromDateTime(2026-04-20 23:00, 2026-04-21 01:00)criam um slot inválido/lançamArgumentExceptionde forma confusa (pois01:00 < 23:00), eDateTimes emUtc/Localsão tratados como equivalentes. Considere validar questart.Date == end.Date(e/ou normalizarKind) antes de descartar a data, ou documentar explicitamente esse contrato no XML doc para evitar uso indevido pelos chamadores.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs` around lines 34 - 35, A factory bug: TimeSlot.FromDateTime blindly drops the Date/Kind causing wrong/ambiguous slots; update TimeSlot.FromDateTime to validate that start.Date == end.Date and start.Kind == end.Kind (or explicitly normalize both DateTimes to a chosen Kind first) before calling TimeOnly.FromDateTime, and throw a clear ArgumentException if the dates/kinds differ (with a message referencing the two inputs), so callers cannot pass cross-midnight or mismatched-kind DateTimes; adjust any XML doc on FromDateTime to state the precondition if you opt to require same date/kind instead of normalizing.src/Shared/Messaging/MessagingExtensions.cs (1)
150-154: Aviso de rollout do serializer: considerar emitir apenas uma vez ou em nível de inicialização.
EnsureMessagingInfrastructureAsyncé esperado ser chamado uma vez por processo, então o warning único aqui é razoável. Apenas garanta que o texto oriente claramente o operador quando estiver desabilitado também (ex.: deixar logInformationindicando que o serializer padrão do Rebus está em uso), para facilitar troubleshooting entre produtores/consumidores em versões diferentes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Messaging/MessagingExtensions.cs` around lines 150 - 154, The current warning in EnsureMessagingInfrastructureAsync (the useSystemTextJson check) only logs when System.Text.Json is enabled; update the method to also emit an explicit Information-level log when useSystemTextJson is false so operators know the default Rebus serializer is in use, and make the warning message more actionable (e.g., instruct clearing queues/DLQs and coordinating producers/consumers); keep the emission here because EnsureMessagingInfrastructureAsync is called once per process, but ensure the messages use logger.LogWarning and logger.LogInformation on the respective branches and reference the same configuration key ("Messaging:UseSystemTextJson") so operators can correlate logs with config.src/Modules/Bookings/Tests/BaseUnitTest.cs (1)
1-10: Classe base vazia comusings não utilizados.
BaseUnitTestestá vazia e osusing FluentAssertions; using Moq; using Xunit;não são usados aqui. Sugestão: ou remover os usings (deixando apenas onamespace), ou mover estes para umGlobalUsings.csdo projeto de testes para que todas as classes de teste se beneficiem sem ruído neste arquivo. Se a intenção é apenas prover um ponto de extensão futuro, tudo bem manter, mas sem os usings inúteis.♻️ Sugestão
-using FluentAssertions; -using Moq; -using Xunit; - namespace MeAjudaAi.Modules.Bookings.Tests; public abstract class BaseUnitTest { // Common setup for unit tests can go here }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/BaseUnitTest.cs` around lines 1 - 10, The BaseUnitTest class is empty and has unused usings (FluentAssertions, Moq, Xunit); either remove those three using lines from this file or move them into a test-wide GlobalUsings.cs so all tests share them without noise; if you intend to keep BaseUnitTest as an extension point, keep the empty public abstract class BaseUnitTest but delete the unused using statements or create GlobalUsings.cs containing "using FluentAssertions; using Moq; using Xunit;" and remove them from this file.src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs (1)
7-10: ConsidereIReadOnlyCollection<AvailabilityDto>em vez deIEnumerable.
IEnumerable<T>em record permite múltiplas enumerações acidentais e fontes preguiçosas/diferidas no handler (re-query, side effects). Para payloads de comandos, é preferível um tipo materializado (IReadOnlyCollection<T>ouIReadOnlyList<T>).♻️ Sugestão
public record SetProviderScheduleCommand( Guid ProviderId, - IEnumerable<AvailabilityDto> Availabilities, + IReadOnlyCollection<AvailabilityDto> Availabilities, Guid CorrelationId) : ICommand<Result>;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs` around lines 7 - 10, The command record SetProviderScheduleCommand currently exposes Availabilities as IEnumerable<AvailabilityDto>, which can allow multiple/deferred enumerations; change the type to IReadOnlyCollection<AvailabilityDto> in the record signature (keep ProviderId, CorrelationId and ICommand<Result> unchanged) so the payload is materialized and immutable; also ensure any place creating this command (command factory or caller) materializes the sequence into a collection before constructing SetProviderScheduleCommand and update the handler expecting Availabilities accordingly.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)
158-162: Use as variáveis já derivadas para consistência.Nas linhas 42-43 você já criou
isAuthenticatedeisLoadingAuth. Nas linhas 158 e 162 o código volta a compararstatus === "authenticated"diretamente. Unificar o uso reduz ruído e evita drift caso a regra derivada mude.♻️ Sugestão
- ) : (status === "authenticated") ? ( + ) : isAuthenticated ? ( <div className="w-full p-4 bg-blue-50 border border-blue-100 rounded-lg text-center"> <p className="text-sm text-gray-700">Este prestador não informou contatos.</p> </div> ) : !isLoadingAuth && (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 158 - 162, Use the already-derived booleans isAuthenticated and isLoadingAuth instead of directly comparing status === "authenticated" in the conditional rendering inside the component; update the ternary that currently checks (status === "authenticated") and the subsequent !isLoadingAuth check to use isAuthenticated and isLoadingAuth so the component's rendering logic consistently uses the derived variables (locate the conditional near the JSX block that renders the "Este prestador não informou contatos." message).src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs (1)
3-10: Considere usarTimeOnlyem vez deDateTimepara os slots.O próprio XML doc indica que "apenas a parte da hora é relevante para a agenda semanal", e o Value Object
TimeSlotdo domínio já usaTimeOnly. ExporDateTimeno DTO obriga conversões ida-e-volta e abre espaço para bugs de fuso horário/data arbitrária vinda do cliente.System.Text.Json(>= .NET 6) serializaTimeOnlynativamente como"HH:mm:ss", o que costuma ser mais preciso para o frontend do que umDateTimecom data fictícia.♻️ Sugestão
-public record TimeSlotDto(DateTime Start, DateTime End); +public record TimeSlotDto(TimeOnly Start, TimeOnly End);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs` around lines 3 - 10, The DTOs TimeSlotDto and AvailabilityDto should use TimeOnly instead of DateTime because only the time portion is relevant and the domain Value Object TimeSlot already uses TimeOnly; change the TimeSlotDto signature from DateTime Start/End to TimeOnly Start/End and update AvailabilityDto accordingly, then adjust any mapping code (mappers, constructors, AutoMapper profiles) that convert between the domain TimeSlot and DTO TimeSlotDto to map TimeOnly <-> TimeOnly (removing any DateTime roundtrips) and ensure System.Text.Json options or serializer usage supports TimeOnly in your app startup/tests if not already configured.src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs (1)
22-30: Prefira FluentValidation para validar o payload.A validação inline de
Reason(null/whitespace e comprimento > 500) funciona, mas foge do padrão de validação usado no restante do codebase (FluentValidation). Extrair umCancelBookingRequestValidatormelhora consistência, testabilidade e padroniza o formato de erro (ProblemDetails comerrors) em vez de um objeto anônimo.♻️ Exemplo
public sealed class CancelBookingRequestValidator : AbstractValidator<CancelBookingRequest> { public CancelBookingRequestValidator() { RuleFor(x => x.Reason) .NotEmpty().WithMessage("O motivo do cancelamento é obrigatório.") .MaximumLength(500).WithMessage("O motivo do cancelamento não pode exceder 500 caracteres."); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs` around lines 22 - 30, Replace the inline validations in CancelBookingEndpoint for request.Reason with a FluentValidation validator: create CancelBookingRequestValidator (subclassing AbstractValidator<CancelBookingRequest>) and add rules NotEmpty() and MaximumLength(500) with the existing messages; remove the null/whitespace and length checks from CancelBookingEndpoint and ensure the validator is registered in the DI/validation pipeline so that failures return standardized ProblemDetails errors instead of the anonymous object currently returned.src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs (1)
32-36: Metadados OpenAPI incompletos para respostas de autorização.O endpoint usa
RequireAuthorization(), mas só declara200e404nos metadados. Considere adicionarProducesProblem(StatusCodes.Status401Unauthorized)eProducesProblem(StatusCodes.Status403Forbidden)(como feito emCancelBookingEndpoint) para manter o contrato OpenAPI coerente.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs` around lines 32 - 36, O builder do endpoint GetProviderAvailability está declarando apenas 200 e 404 nos metadados enquanto também chama RequireAuthorization(); atualize a cadeia do endpoint (onde .WithName("GetProviderAvailability")/.WithSummary(...) é encadeado) para incluir ProducesProblem(StatusCodes.Status401Unauthorized) e ProducesProblem(StatusCodes.Status403Forbidden) — espelhe a abordagem usada em CancelBookingEndpoint para manter o contrato OpenAPI consistente com RequireAuthorization().src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs (1)
40-49: Slots longos são descartados inteiros ao primeiro conflito.A implementação atual remove qualquer slot da agenda que tenha qualquer sobreposição com um booking. Para janelas longas (ex.: agenda 08:00–18:00 e booking 10:00–11:00), o dia inteiro aparece como indisponível, o que é visível para o cliente final. Considere fatiar o intervalo livre ao redor dos bookings (ex.: gerar
[08:00–10:00]e[11:00–18:00]) ou pré-gerar slots em granularidade fixa (30/60 min a partir do serviço) e então filtrar.Como melhoria incremental, um utilitário em
Domainpode calcular a diferença entre o slot e a lista de intervalos ocupados, mantendo o handler enxuto.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs` around lines 40 - 49, The current availability filter in GetProviderAvailabilityQueryHandler removes entire schedule slots when any booking overlaps; instead implement interval subtraction: for each slot in daySchedule.Slots, compute the free sub-intervals by subtracting all overlapping dayBookings (use b.TimeSlot.Start/End as occupied intervals) and emit TimeSlotDto entries for each remaining fragment, then collect those into availableSlots and return the AvailabilityDto as before; factor the subtraction logic into a Domain utility (e.g., TimeIntervalUtils.SubtractIntervals or a method on TimeSlot domain type) that accepts one slot and a list of occupied intervals and returns a list of free intervals to keep the handler concise.src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs (1)
27-74: Cobertura adicional sugerida para branches não testadas.Os dois cenários cobertos validam o caminho feliz (schedule existente →
UpdateAsync) e a falha por prestador inexistente. Considere acrescentar:
- Caso em que
GetByProviderIdAsyncretornanull, exigindo criação de novoProviderScheduleviaAddAsync(verifica queUpdateAsyncnão é chamado e queAddAsyncé chamado exatamente uma vez).- Caso em que
ProviderExistsAsyncretornaResult<bool>.Failure(...)(verifica propagação do erro sem tocar o repositório).Isso fortalece a cobertura do handler e previne regressões silenciosas nesses caminhos.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` around lines 27 - 74, Add two unit tests to cover the missing branches: 1) a test where _scheduleRepoMock.GetByProviderIdAsync(providerId, ...) returns null and assert that _scheduleRepoMock.AddAsync(...) is called exactly once, _scheduleRepoMock.UpdateAsync(...) is not called, and the result is success when calling _sut.HandleAsync(command) (use SetProviderScheduleCommand and same arrange pattern); 2) a test where _providersApiMock.ProviderExistsAsync(providerId, ...) returns Result<bool>.Failure(someError) and assert that _sut.HandleAsync(command) returns the same failure (propagates the error) and that neither AddAsync nor UpdateAsync on _scheduleRepoMock are called. Use the existing _sut, _providersApiMock and _scheduleRepoMock mocks and Verify/ReturnsAsync as in the other tests.src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs (1)
27-89: LGTM — considere casos-limite adicionais.Os cenários principais (sem bookings e com overlap) estão corretos. Como refinamento opcional, valeria adicionar:
- Agenda não encontrada →
Error.NotFoundcom mensagem"Agenda do prestador não encontrada.".- Booking de outra data não afeta os slots retornados.
- Booking com status
Cancelled/Rejectedé ignorado e o slot permanece disponível.Esses cenários blindam o filtro em
HandleAsynccontra regressões e são baratos de cobrir.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs` around lines 27 - 89, Add three unit tests to GetProviderAvailabilityQueryHandlerTests covering edge cases: 1) when _scheduleRepoMock.GetByProviderIdAsync returns null assert HandleAsync(query) returns a failure Error.NotFound with message "Agenda do prestador não encontrada."; 2) when there is a Booking for the same provider but a different DateOnly (use Booking.Create with a different date) assert returned slots are unaffected (same as the no-bookings case); 3) when there is a Booking for the same date but with status Cancelled or Rejected (set the booking status appropriately on the Booking instance) assert those bookings are ignored and slots remain available; for each test set up the schedule via ProviderSchedule.Create and schedule.SetAvailability, stub _scheduleRepoMock and _bookingRepoMock accordingly, call _sut.HandleAsync(GetProviderAvailabilityQuery(...)) and assert result.IsSuccess and slot presence/count or failure+message as specified.src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs (1)
29-73: LGTM — cenários de autorização bem cobertos.Os testes validam corretamente o caminho feliz (owner → status
Confirmed+UpdateAsyncchamado uma vez) e o 403 (não-owner →UpdateAsyncnunca chamado). Como reforço opcional, considere adicionar:
- Booking inexistente →
Error.StatusCode == 404e nenhumUpdateAsync.- Tentativa de confirmar booking em status inválido (ex.:
Cancelled/Completed) retorna falha sem persistir.Esses casos complementam o contrato do handler sem aumentar complexidade significativa.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs` around lines 29 - 73, Add two unit tests: one named HandleAsync_Should_Fail_When_BookingNotFound that sets _bookingRepoMock.GetByIdAsync to return null, calls _sut.HandleAsync(new ConfirmBookingCommand(bookingId, userId)), then asserts result.IsFailure && result.Error!.StatusCode == 404 and verifies _bookingRepoMock.UpdateAsync was never called; and one named HandleAsync_Should_Fail_When_BookingInInvalidStatus that creates a Booking, sets its Status to an invalid value (e.g., Contracts.Bookings.Enums.EBookingStatus.Cancelled or Completed), stubs _bookingRepoMock.GetByIdAsync to return that booking, calls _sut.HandleAsync(new ConfirmBookingCommand(booking.Id, userId)), then asserts failure, appropriate error status (non-success/choose expected code) and verifies _bookingRepoMock.UpdateAsync was never called; use the existing patterns and helpers (Booking, ConfirmBookingCommand, _bookingRepoMock, SetupUser, _sut.HandleAsync) to mirror the other tests.src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs (1)
44-44: URL da Location está hardcoded e pode desalinhar do prefixo real.
/api/v1/bookings/{booking.Id}é montado literalmente. Se o versionamento do route group mudar (por ex./api/v2/bookingsou injeção de prefixo viaRoutePrefix), o headerLocationapontará para uma URL inexistente — quebra clientes que dependem do header paraGETdo recurso criado.Considere usar
LinkGenerator.GetPathByName("GetBookingById", ...)ou extrair uma constante emBookingsEndpointspara manter sincronizado com a rota real do endpoint de consulta.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs` at line 44, The Location header is built with a hardcoded string in the CreateBookingEndpoint onSuccess handler (Results.Created($"/api/v1/bookings/{booking.Id}", booking)), which can drift from the actual route prefix; replace the literal URL construction by resolving the route for the booking retrieval endpoint (e.g. use LinkGenerator.GetPathByName("GetBookingById", new { id = booking.Id }, ...)) or reference a shared route constant defined in BookingsEndpoints so the Location value always matches the actual GetBookingById route.src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs (2)
122-140: VerificarUpdateAsynctambém no cenário de admin para manter consistência.Os testes
UserIsClientOwnereUserIsProviderOwnerverificam explicitamente queIBookingRepository.UpdateAsyncfoi chamado uma vez, mas o testeUserIsSystemAdminomite essa verificação. Para blindar contra regressões em que um admin retornaria sucesso sem efetivamente persistir o cancelamento, adicione a mesma asserção.♻️ Ajuste sugerido
result.IsSuccess.Should().BeTrue(); booking.Status.Should().Be(EBookingStatus.Cancelled); + _bookingRepoMock.Verify(x => x.UpdateAsync(booking, 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/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs` around lines 122 - 140, In the CancelBookingCommandHandlerTests test HandleAsync_Should_Succeed_When_UserIsSystemAdmin add the same verification used in UserIsClientOwner and UserIsProviderOwner to assert that IBookingRepository.UpdateAsync was invoked once: after calling _sut.HandleAsync(...) verify _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Once()); this ensures the booking persistence path is exercised for the admin scenario.
218-221: Acoplamento ao texto exato da mensagem de domínio.A asserção compara por
Containcom a string exata"Apenas agendamentos pendentes ou confirmados podem ser cancelados.". Qualquer ajuste de redação/pontuação no domínio quebrará este teste sem haver mudança de comportamento real. Considere extrair a mensagem para uma constante compartilhada (ex.: emBookingDomainMessagesou similar) e referenciá-la aqui — ou afrouxar para um fragmento estável (ex.:"pendentes ou confirmados").🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs` around lines 218 - 221, The test in CancelBookingCommandHandlerTests is tightly coupled to the exact domain message string; change the assertion that checks result.Error.Message to use a stable reference instead: either compare against a shared constant (e.g., BookingDomainMessages.<appropriateConstant>) so the test references the canonical domain message, or relax the assertion to match a stable fragment such as "pendentes ou confirmados" (e.g., Assert that result.Error.Message contains that fragment) instead of the full sentence; update the assertion in the test method that currently calls result.Error.Message.Should().Contain(...) to reference the constant or the shorter fragment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/technical-debt.md`:
- Around line 31-34: Update the technical-debt entry text to accurately state
that RabbitMQ consolidation is incomplete: mention that RabbitMQ.Client is still
present in RabbitMqDeadLetterService and RabbitMqInfrastructureManager and that
RabbitMqInfrastructureManager's methods CreateQueueAsync, CreateExchangeAsync
and BindQueueToExchangeAsync are stubs pending real implementation; keep the
note that RebusMessageBus and IMessageBus were introduced but clarify that full
removal of direct RabbitMQ usage and full infrastructure implementation remain
outstanding.
In `@src/Bootstrapper/MeAjudaAi.ApiService/Program.cs`:
- Around line 144-149: The booking endpoints are being mapped at top-level
instead of inside the module initializer; update the UseBookingsModule()
implementation to call MapBookingsEndpoints() (so UseBookingsModule registers
its own endpoints like other modules do) and remove the separate top-level call
to MapBookingsEndpoints() in Program.cs; keep MapProviderRegistrationEndpoints()
and MapCommunicationsEndpoints() mapped at the top level since they are
cross-module orchestration endpoints.
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 70-74: The TryParse against context.TraceIdentifier is dead code
because Kestrel never produces a GUID there; in SetProviderScheduleEndpoint.cs
remove the Guid.TryParse(context.TraceIdentifier, out var traceId) branch and
instead when the "X-Correlation-Id" header is missing set correlationId =
Guid.NewGuid(); if you need to preserve the trace id for observability, emit
context.TraceIdentifier as a separate string field (e.g. TraceIdentifier or
TraceId) on the command/payload rather than reusing CorrelationId.
In `@src/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommand.cs`:
- Around line 6-9: Update CancelBookingCommand handling to populate
CorrelationId from the request header "X-Correlation-Id" with fallback to
context.TraceIdentifier and then Guid.NewGuid() (use the same logic as in
SetProviderScheduleEndpoint) and remove any inline endpoint validation for
Reason; instead add a FluentValidation validator class
CancelBookingCommandValidator that registers rules for Reason (e.g., RuleFor(x
=> x.Reason).NotEmpty().MaximumLength(500) or change Reason to string? if
business allows null and validate accordingly). Ensure the validator type name
is CancelBookingCommandValidator and that the endpoint/mediator uses it for
validation before dispatching CancelBookingCommand.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 30-35: The current check in CreateBookingCommandHandler uses
minimumLead and compares command.Start to
DateTimeOffset.UtcNow.Subtract(minimumLead), which allows starts up to 1 minute
in the past while the error message states a minimum 1-minute lead; change the
logic to enforce a minimum future lead by comparing command.Start <
DateTimeOffset.UtcNow.Add(minimumLead) and keep the
Result<BookingDto>.Failure(Error.BadRequest(...)) message as-is (or adjust the
message if you intended a clock-tolerance rule), ensuring the minimumLead
variable and the error path remain used in the same method.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs`:
- Around line 32-38: O filtro que constrói dayBookings em
GetProviderAvailabilityQueryHandler está atualmente excluindo apenas
EBookingStatus.Cancelled e EBookingStatus.Rejected, deixando
EBookingStatus.Completed como bloqueador; atualize a cláusula Where que usa
bookingRepository.GetByProviderIdAsync(query.ProviderId, cancellationToken) para
também excluir EBookingStatus.Completed (e, se desejado por regra de negócio,
reavaliar/remover EBookingStatus.Rejected) ao comparar b.Date == query.Date para
que reservas concluídas não marquem o horário como ocupado.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 62-66: Replace the broad catch in
SetProviderScheduleCommandHandler with specific exception handlers: catch
ArgumentException and InvalidOperationException (thrown by TimeSlot.Create,
Availability.Create, or schedule.SetAvailability) to log a warning in English
via logger.LogWarning and return Result.Failure(Error.BadRequest(...)); add a
final catch (Exception) that logs an error in English via logger.LogError and
either rethrows or returns a 500/InternalServerError result so non-validation
faults (e.g., null refs on command.Availabilities) are not converted to 400;
update all log messages to English.
- Around line 43-66: The code currently calls schedule.ClearAvailabilities()
before validating and creating new Availability objects, which can leave the
tracked aggregate in an empty state if TimeSlot.Create or Availability.Create
throws; instead, first fully validate and map command.Availabilities into a
local List<Availability> by iterating and calling
TimeSlot.Create/Availability.Create inside the try, and only after the mapping
succeeds call schedule.ClearAvailabilities() and then apply
schedule.SetAvailability(...) for each built Availability; alternatively, if you
prefer the current flow, detach or rollback the tracked schedule on exception
(use DbContext.Detach(schedule) or transaction rollback) so in-memory changes
from schedule.ClearAvailabilities() are not persisted.
In `@src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj`:
- Around line 31-32: Remova as referências de pacote não usadas
`Microsoft.EntityFrameworkCore.InMemory` e
`Microsoft.EntityFrameworkCore.Sqlite` do arquivo de projeto (elimine as
entradas <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" /> e
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />) e rode
dotnet restore/build para garantir que nada mais depende desses pacotes;
verifique também por referências a `UseInMemoryDatabase()` ou `UseSqlite()` para
confirmar que a exclusão não impacta testes ou código.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs`:
- Around line 68-100: The unit test
HandleAsync_Should_Succeed_OnDifferentDates_EvenWithSameTime is misleading
because it uses a mocked AddIfNoOverlapAsync (always succeeding) and only a
single CreateBookingCommand, so it cannot verify "different dates with same
time" behavior; either delete/rename this unit test or convert it into an
integration test that exercises the real booking repository logic: create two
CreateBookingCommand instances with the same time but different dates and invoke
the real repository (not the mocked _bookingRepoMock) to assert the second
booking is allowed, or if keeping it as a unit test, change it to assert only
that HandleAsync calls AddIfNoOverlapAsync once for the provided command (use
_bookingRepoMock.Verify for AddIfNoOverlapAsync) and remove the misleading claim
about different dates—references:
HandleAsync_Should_Succeed_OnDifferentDates_EvenWithSameTime,
CreateBookingCommand, _bookingRepoMock.AddIfNoOverlapAsync, and
ProviderSchedule.Create/SetAvailability.
In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cs`:
- Around line 85-101: The test
IsAvailable_Should_ReturnFalse_When_CrossesMidnight doesn't isolate the
midnight-crossing condition because the request end already falls outside the
slot; update the test so the slot fully covers the request before midnight
(e.g., create the TimeSlot in the test via TimeSlot.Create to end at 23:59:59 or
otherwise include the full second before midnight when calling
schedule.SetAvailability(Availability.Create(..., new[]{ slot }))) and then
choose a request start/duration that crosses into the next day (for example
start at 23:30 with duration 1 hour) so that
ProviderSchedule.IsAvailable(dateTime, duration) fails specifically due to
localDateTime.Add(duration).Date != localDateTime.Date, or alternatively add a
separate focused test that only verifies the midnight-crossing guard on
IsAvailable.
In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx`:
- Around line 86-99: The test assertion for "10:00" is flaky across non-UTC
environments because BookingModal code formats UTC strings with
format(parseAsUtc(slot.start), "HH:mm") which uses the local timezone; update
the test or code to force UTC: either set process.env.TZ = 'UTC' in the Vitest
setup used by this test, or change the component formatting to use date-fns-tz's
formatInTimeZone(parseAsUtc(slot.start), 'UTC', 'HH:mm') (or an equivalent
explicit UTC formatting) so the rendered time in BookingModal is stable; touch
the test file referencing BookingModal and the component code path where
format(parseAsUtc(...)) is used.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 107-111: The parseAsUtc helper incorrectly treats any ISO date
with '-' in the date portion as having a timezone; change parseAsUtc to detect
timezone only in the time suffix after the 'T' (e.g., extract the substring
after isoString.indexOf('T') and check that suffix for 'Z' or '+' or '-' ), keep
the existing fallback to return new Date() for empty input, and when no timezone
is found append 'Z' before calling new Date; update or add unit tests for
parseAsUtc covering "2026-04-22T10:00:00" (no offset) and
"2026-04-22T10:00:00-03:00" (with offset) to validate both branches.
---
Outside diff comments:
In `@docs/roadmap-history.md`:
- Line 72: Update the "Cobertura de Testes" metric text in the summary
("Cobertura de Testes: Backend 90.56% | Frontend 30 testes bUnit") to the actual
coverage value produced by CI after this PR (replace the placeholder
Backend/Frontend values with the measured percentages and test counts) and, if
available, append or reference the CI coverage report link or filename so the
historical note reflects the real post-merge results.
In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Around line 6-19: Remove the duplicate using directive for
MeAjudaAi.Modules.Payments.Infrastructure.Persistence: there are two identical
lines importing that namespace (the duplicated "using
MeAjudaAi.Modules.Payments.Infrastructure.Persistence;"); delete the extra
occurrence so the file keeps a single using for that namespace (verify in the
BaseApiTest class top imports), then rebuild to ensure no compiler/analyzer
warning remains.
---
Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 64-113: Currently the component falls back to the zero-GUID
(targetServiceId) and the confirm button state (isConfirmDisabled) does not
check serviceId, allowing submissions with an invalid service; remove the
fallback use of "00000000-..." in the POST payload and require serviceId as a
non-nullable prop (update the component's prop type/signature), change the fetch
body to use the provided serviceId directly, and add a check for !serviceId into
isConfirmDisabled so the confirm button is disabled unless a real serviceId
exists; also remove/unused targetServiceId variable and ensure createBooking
(the mutation) and onSuccess/onError flows remain unchanged.
---
Nitpick comments:
In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs`:
- Around line 22-30: Replace the inline validations in CancelBookingEndpoint for
request.Reason with a FluentValidation validator: create
CancelBookingRequestValidator (subclassing
AbstractValidator<CancelBookingRequest>) and add rules NotEmpty() and
MaximumLength(500) with the existing messages; remove the null/whitespace and
length checks from CancelBookingEndpoint and ensure the validator is registered
in the DI/validation pipeline so that failures return standardized
ProblemDetails errors instead of the anonymous object currently returned.
In `@src/Modules/Bookings/API/Endpoints/Public/CreateBookingEndpoint.cs`:
- Line 44: The Location header is built with a hardcoded string in the
CreateBookingEndpoint onSuccess handler
(Results.Created($"/api/v1/bookings/{booking.Id}", booking)), which can drift
from the actual route prefix; replace the literal URL construction by resolving
the route for the booking retrieval endpoint (e.g. use
LinkGenerator.GetPathByName("GetBookingById", new { id = booking.Id }, ...)) or
reference a shared route constant defined in BookingsEndpoints so the Location
value always matches the actual GetBookingById route.
In
`@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs`:
- Around line 32-36: O builder do endpoint GetProviderAvailability está
declarando apenas 200 e 404 nos metadados enquanto também chama
RequireAuthorization(); atualize a cadeia do endpoint (onde
.WithName("GetProviderAvailability")/.WithSummary(...) é encadeado) para incluir
ProducesProblem(StatusCodes.Status401Unauthorized) e
ProducesProblem(StatusCodes.Status403Forbidden) — espelhe a abordagem usada em
CancelBookingEndpoint para manter o contrato OpenAPI consistente com
RequireAuthorization().
In
`@src/Modules/Bookings/Application/Bookings/Commands/SetProviderScheduleCommand.cs`:
- Around line 7-10: The command record SetProviderScheduleCommand currently
exposes Availabilities as IEnumerable<AvailabilityDto>, which can allow
multiple/deferred enumerations; change the type to
IReadOnlyCollection<AvailabilityDto> in the record signature (keep ProviderId,
CorrelationId and ICommand<Result> unchanged) so the payload is materialized and
immutable; also ensure any place creating this command (command factory or
caller) materializes the sequence into a collection before constructing
SetProviderScheduleCommand and update the handler expecting Availabilities
accordingly.
In `@src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs`:
- Around line 3-10: The DTOs TimeSlotDto and AvailabilityDto should use TimeOnly
instead of DateTime because only the time portion is relevant and the domain
Value Object TimeSlot already uses TimeOnly; change the TimeSlotDto signature
from DateTime Start/End to TimeOnly Start/End and update AvailabilityDto
accordingly, then adjust any mapping code (mappers, constructors, AutoMapper
profiles) that convert between the domain TimeSlot and DTO TimeSlotDto to map
TimeOnly <-> TimeOnly (removing any DateTime roundtrips) and ensure
System.Text.Json options or serializer usage supports TimeOnly in your app
startup/tests if not already configured.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs`:
- Around line 40-49: The current availability filter in
GetProviderAvailabilityQueryHandler removes entire schedule slots when any
booking overlaps; instead implement interval subtraction: for each slot in
daySchedule.Slots, compute the free sub-intervals by subtracting all overlapping
dayBookings (use b.TimeSlot.Start/End as occupied intervals) and emit
TimeSlotDto entries for each remaining fragment, then collect those into
availableSlots and return the AvailabilityDto as before; factor the subtraction
logic into a Domain utility (e.g., TimeIntervalUtils.SubtractIntervals or a
method on TimeSlot domain type) that accepts one slot and a list of occupied
intervals and returns a list of free intervals to keep the handler concise.
In `@src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs`:
- Around line 34-35: A factory bug: TimeSlot.FromDateTime blindly drops the
Date/Kind causing wrong/ambiguous slots; update TimeSlot.FromDateTime to
validate that start.Date == end.Date and start.Kind == end.Kind (or explicitly
normalize both DateTimes to a chosen Kind first) before calling
TimeOnly.FromDateTime, and throw a clear ArgumentException if the dates/kinds
differ (with a message referencing the two inputs), so callers cannot pass
cross-midnight or mismatched-kind DateTimes; adjust any XML doc on FromDateTime
to state the precondition if you opt to require same date/kind instead of
normalizing.
In `@src/Modules/Bookings/Tests/BaseUnitTest.cs`:
- Around line 1-10: The BaseUnitTest class is empty and has unused usings
(FluentAssertions, Moq, Xunit); either remove those three using lines from this
file or move them into a test-wide GlobalUsings.cs so all tests share them
without noise; if you intend to keep BaseUnitTest as an extension point, keep
the empty public abstract class BaseUnitTest but delete the unused using
statements or create GlobalUsings.cs containing "using FluentAssertions; using
Moq; using Xunit;" and remove them from this file.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CancelBookingCommandHandlerTests.cs`:
- Around line 122-140: In the CancelBookingCommandHandlerTests test
HandleAsync_Should_Succeed_When_UserIsSystemAdmin add the same verification used
in UserIsClientOwner and UserIsProviderOwner to assert that
IBookingRepository.UpdateAsync was invoked once: after calling
_sut.HandleAsync(...) verify _bookingRepoMock.Verify(x =>
x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()),
Times.Once()); this ensures the booking persistence path is exercised for the
admin scenario.
- Around line 218-221: The test in CancelBookingCommandHandlerTests is tightly
coupled to the exact domain message string; change the assertion that checks
result.Error.Message to use a stable reference instead: either compare against a
shared constant (e.g., BookingDomainMessages.<appropriateConstant>) so the test
references the canonical domain message, or relax the assertion to match a
stable fragment such as "pendentes ou confirmados" (e.g., Assert that
result.Error.Message contains that fragment) instead of the full sentence;
update the assertion in the test method that currently calls
result.Error.Message.Should().Contain(...) to reference the constant or the
shorter fragment.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs`:
- Around line 29-73: Add two unit tests: one named
HandleAsync_Should_Fail_When_BookingNotFound that sets
_bookingRepoMock.GetByIdAsync to return null, calls _sut.HandleAsync(new
ConfirmBookingCommand(bookingId, userId)), then asserts result.IsFailure &&
result.Error!.StatusCode == 404 and verifies _bookingRepoMock.UpdateAsync was
never called; and one named HandleAsync_Should_Fail_When_BookingInInvalidStatus
that creates a Booking, sets its Status to an invalid value (e.g.,
Contracts.Bookings.Enums.EBookingStatus.Cancelled or Completed), stubs
_bookingRepoMock.GetByIdAsync to return that booking, calls _sut.HandleAsync(new
ConfirmBookingCommand(booking.Id, userId)), then asserts failure, appropriate
error status (non-success/choose expected code) and verifies
_bookingRepoMock.UpdateAsync was never called; use the existing patterns and
helpers (Booking, ConfirmBookingCommand, _bookingRepoMock, SetupUser,
_sut.HandleAsync) to mirror the other tests.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs`:
- Around line 27-89: Add three unit tests to
GetProviderAvailabilityQueryHandlerTests covering edge cases: 1) when
_scheduleRepoMock.GetByProviderIdAsync returns null assert HandleAsync(query)
returns a failure Error.NotFound with message "Agenda do prestador não
encontrada."; 2) when there is a Booking for the same provider but a different
DateOnly (use Booking.Create with a different date) assert returned slots are
unaffected (same as the no-bookings case); 3) when there is a Booking for the
same date but with status Cancelled or Rejected (set the booking status
appropriately on the Booking instance) assert those bookings are ignored and
slots remain available; for each test set up the schedule via
ProviderSchedule.Create and schedule.SetAvailability, stub _scheduleRepoMock and
_bookingRepoMock accordingly, call
_sut.HandleAsync(GetProviderAvailabilityQuery(...)) and assert result.IsSuccess
and slot presence/count or failure+message as specified.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Around line 27-74: Add two unit tests to cover the missing branches: 1) a test
where _scheduleRepoMock.GetByProviderIdAsync(providerId, ...) returns null and
assert that _scheduleRepoMock.AddAsync(...) is called exactly once,
_scheduleRepoMock.UpdateAsync(...) is not called, and the result is success when
calling _sut.HandleAsync(command) (use SetProviderScheduleCommand and same
arrange pattern); 2) a test where
_providersApiMock.ProviderExistsAsync(providerId, ...) returns
Result<bool>.Failure(someError) and assert that _sut.HandleAsync(command)
returns the same failure (propagates the error) and that neither AddAsync nor
UpdateAsync on _scheduleRepoMock are called. Use the existing _sut,
_providersApiMock and _scheduleRepoMock mocks and Verify/ReturnsAsync as in the
other tests.
In `@src/Shared/MeAjudaAi.Shared.csproj`:
- Line 45: Rebase/merge master to resolve OpenTelemetry version mismatches, then
update Directory.Packages.props to use the highest master versions (e.g., bump
OpenTelemetry.Exporter.Console/OpenTelemetryProtocol/Extensions.Hosting to
1.15.3 and align
Instrumentation.AspNetCore/Instrumentation.Http/Instrumentation.Runtime to the
same family) so central package management prevents downgrades; finally move the
<PackageReference Include="OpenTelemetry.Exporter.Console" /> entry out of the
<!-- Feature Management --> block into a new or existing <!--
Observability/OpenTelemetry --> block in MeAjudaAi.Shared.csproj to keep
organization consistent and re-run CI breaking-change checks.
In `@src/Shared/Messaging/MessagingExtensions.cs`:
- Around line 150-154: The current warning in EnsureMessagingInfrastructureAsync
(the useSystemTextJson check) only logs when System.Text.Json is enabled; update
the method to also emit an explicit Information-level log when useSystemTextJson
is false so operators know the default Rebus serializer is in use, and make the
warning message more actionable (e.g., instruct clearing queues/DLQs and
coordinating producers/consumers); keep the emission here because
EnsureMessagingInfrastructureAsync is called once per process, but ensure the
messages use logger.LogWarning and logger.LogInformation on the respective
branches and reference the same configuration key
("Messaging:UseSystemTextJson") so operators can correlate logs with config.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 158-162: Use the already-derived booleans isAuthenticated and
isLoadingAuth instead of directly comparing status === "authenticated" in the
conditional rendering inside the component; update the ternary that currently
checks (status === "authenticated") and the subsequent !isLoadingAuth check to
use isAuthenticated and isLoadingAuth so the component's rendering logic
consistently uses the derived variables (locate the conditional near the JSX
block that renders the "Este prestador não informou contatos." message).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
…n handlers, and public API endpoints
…tions and end-to-end tests
There was a problem hiding this comment.
Actionable comments posted: 18
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)
274-282:⚠️ Potential issue | 🟡 MinorAtualize o comentário de migração e adicione dependências implícitas para Bookings.
O comentário na linha 274 está desatualizado, omitindo
PaymentseBookings. Além disso, diferentemente deSearchProviderseProviders, o móduloBookingsnão declara suas dependências implícitas (Users, ServiceCatalogs, Providers) nas linhas 255–266, criando inconsistência no padrão e risco de falhas em testes que declarem apenasTestModule.Bookings.♻️ Sugestão
// Dependências implícitas if (modules.HasFlag(TestModule.SearchProviders)) { if (!modules.HasFlag(TestModule.Providers)) modules |= TestModule.Providers; if (!modules.HasFlag(TestModule.ServiceCatalogs)) modules |= TestModule.ServiceCatalogs; if (!modules.HasFlag(TestModule.Locations)) modules |= TestModule.Locations; } if (modules.HasFlag(TestModule.Providers)) { if (!modules.HasFlag(TestModule.ServiceCatalogs)) modules |= TestModule.ServiceCatalogs; } + if (modules.HasFlag(TestModule.Bookings)) + { + if (!modules.HasFlag(TestModule.Users)) modules |= TestModule.Users; + if (!modules.HasFlag(TestModule.ServiceCatalogs)) modules |= TestModule.ServiceCatalogs; + if (!modules.HasFlag(TestModule.Providers)) modules |= TestModule.Providers; + } // Lock para evitar que múltiplas migrações ocorram simultaneamente no MESMO banco,- // Apply migrations in production priority order: Users -> ServiceCatalogs -> Locations -> Documents -> Providers -> Communications + // Apply migrations in production priority order: Users -> ServiceCatalogs -> Locations -> Documents -> Providers -> Communications -> Payments -> Bookings🤖 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 274 - 282, Update the outdated migration comment and make Bookings follow the same implicit-dependency pattern: revise the comment above the ApplyMigrationForContextAsync calls to include Payments and Bookings, and add the implicit dependency entries for TestModule.Bookings (Users, ServiceCatalogs, Providers) wherever other modules declare implicit dependencies (the same block that currently adds dependencies for SearchProviders and Providers) so tests that only include TestModule.Bookings will also enable Users, ServiceCatalogs and Providers before ApplyMigrationForContextAsync is invoked.
♻️ Duplicate comments (1)
src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs (1)
20-25:⚠️ Potential issue | 🟡 Minor
requestourequest.Availabilitiesnulo causaNullReferenceException.Se o body vier vazio/
null,requestseránullerequest.ProviderId(linha 36) lança NRE → 500 em vez de 400. E seAvailabilitiesfornull, o handler fazforeachdireto e também NRE. Adicione validação de entrada (FluentValidation ou checagem explícita) retornandoProblem(400):🛠️ Sugestão
+ if (request is null || request.Availabilities is null) + { + return Results.Problem("Corpo da requisição inválido.", statusCode: StatusCodes.Status400BadRequest); + } + var user = context.User;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs` around lines 20 - 25, The endpoint handling MapPost("/schedule") can throw NullReferenceException when the body is null or when request.Availabilities is null; update the SetProviderSchedule handler to validate inputs early (either via FluentValidation or explicit checks) by returning Problem(400) when request is null or when request.Availabilities is null/empty, and only proceed to use request.ProviderId and enumerate Availabilities after those checks; reference the SetProviderScheduleRequest type, the MapPost("/schedule") handler delegate, and the properties ProviderId and Availabilities when adding the validation and early return.
🧹 Nitpick comments (12)
src/Shared/Exceptions/GlobalExceptionHandler.cs (1)
129-134: Considere honrarBadHttpRequestException.StatusCode.
Microsoft.AspNetCore.Http.BadHttpRequestExceptioncarrega umStatusCodepróprio (ex.:413 Payload Too Large,414 URI Too Long,400). Ao fixar em 400 perde-se essa semântica. Além disso,Messagepode trazer texto interno do Kestrel em inglês (ex.: "Request body too large.") — considere uma mensagem amigável em português e incluir a mensagem original apenas como extension/diagnóstico.♻️ Refactor sugerido
- Microsoft.AspNetCore.Http.BadHttpRequestException badHttpRequestException => ( - StatusCodes.Status400BadRequest, - "Requisição Mal Formatada", - badHttpRequestException.Message, - null, - []), + BadHttpRequestException badHttpRequestException => ( + badHttpRequestException.StatusCode, + "Requisição Mal Formatada", + "A requisição não pôde ser processada por estar malformada", + null, + CreateExtensionsWithNonNullValues( + ("reason", badHttpRequestException.Message))),Com o
using Microsoft.AspNetCore.Http;já presente (Line 3), a qualificação completa do tipo também pode ser removida.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Exceptions/GlobalExceptionHandler.cs` around lines 129 - 134, Update the BadHttpRequestException case in GlobalExceptionHandler to use the exception's own StatusCode instead of hard-coding 400, provide a user-friendly Portuguese message (e.g., "Requisição inválida" or context-specific like "Payload muito grande" where appropriate) as the main message, and move the original badHttpRequestException.Message into the diagnostics/extensions payload; also remove the fully-qualified type name (use BadHttpRequestException directly) since Microsoft.AspNetCore.Http is already imported.src/Modules/Bookings/API/API.Client/Bookings/RejectBooking.bru (1)
7-39: Parametrize obookingIdno client.Com o GUID zerado, a request tende a retornar 404 até alguém editar a URL manualmente. Use variável para manter o arquivo executável em ambientes locais/CI.
🧩 Ajuste sugerido
put { - url: {{baseUrl}}/api/v1/bookings/00000000-0000-0000-0000-000000000000/reject + url: {{baseUrl}}/api/v1/bookings/{{bookingId}}/reject body: json auth: bearer } @@ ## Parâmetros Body + - `bookingId` (path): Identificador do agendamento - `reason` (string): Motivo da rejeição (Obrigatório, máx 500 caracteres)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/API.Client/Bookings/RejectBooking.bru` around lines 7 - 39, The request URL currently hardcodes the zero GUID; replace that literal with a parameterized variable (e.g. {{bookingId}}) so the endpoint becomes /api/v1/bookings/{{bookingId}}/reject, ensure the auth token still uses {{accessToken}}, and add or document a bookingId variable in the client/run configuration so local/CI runs can set a real booking id (also update the docs block/example to reference {{bookingId}} instead of the zero GUID).src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs (1)
22-22: Inconsistência noCorrelationId: useX-Correlation-Idquando presente.Outros endpoints do módulo (ex.:
SetProviderScheduleEndpoint) leemX-Correlation-Iddo header e só geram um novoGuidcomo fallback. Aqui é sempre gerado um novoGuid, quebrando rastreabilidade fim-a-fim de uma mesma requisição.🛠️ Sugestão
- app.MapGet("/{id}", async ( - Guid id, - [FromServices] IQueryDispatcher dispatcher, - CancellationToken cancellationToken) => - { - var query = new GetBookingByIdQuery(id, Guid.NewGuid()); + app.MapGet("/{id}", async ( + Guid id, + [FromServices] IQueryDispatcher dispatcher, + HttpContext context, + CancellationToken cancellationToken) => + { + var correlationHeader = context.Request.Headers["X-Correlation-Id"].ToString(); + if (!Guid.TryParse(correlationHeader, out var correlationId)) + { + correlationId = Guid.NewGuid(); + } + var query = new GetBookingByIdQuery(id, correlationId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs` at line 22, A GetBookingByIdEndpoint está sempre gerando um novo CorrelationId com Guid.NewGuid() ao criar GetBookingByIdQuery, quebrando a rastreabilidade; altere para ler o header "X-Correlation-Id" do request (HttpContext.Request.Headers["X-Correlation-Id"]) e, se presente e for um GUID válido (Guid.TryParse), use-o ao construir new GetBookingByIdQuery(id, correlationId), caso contrário gere um Guid.NewGuid() como fallback; garanta que o nome GetBookingByIdEndpoint e o uso de GetBookingByIdQuery sejam atualizados sem alterar a assinatura da query.src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs (1)
18-20: Validarcommand.Availabilitiesnão-nulo logo no início.Se
Availabilitieschegarnull(por ex., desserialização falha ou caller defeituoso), oforeachna linha 38 lançaNullReferenceExceptionque cai no catch genérico. Uma guarda explícita no começo produz400previsível e evita depender do catch-all.logger.LogInformation("Setting schedule for Provider {ProviderId}", command.ProviderId); + + if (command.Availabilities is null) + { + return Result.Failure(Error.BadRequest("A lista de disponibilidades é obrigatória.")); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs` around lines 18 - 20, No início de HandleAsync (SetProviderScheduleCommandHandler.HandleAsync) verifique explicitamente se command.Availabilities é null; se for, faça um log apropriado e retorne um resultado de validação/400 previsível em vez de permitir que o foreach sobre command.Availabilities gere NullReferenceException; altere o fluxo antes do logger.LogInformation para validar command.Availabilities e retornar um Result de erro de validação (mensagem clara) para o chamador.src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cs (1)
7-9: Considerar paginação para a consulta "meus agendamentos".
GetBookingsByClientQueryretornaIReadOnlyList<BookingDto>sem limites. Para um cliente recorrente, essa lista pode crescer bastante, levando a payloads grandes, memória elevada no servidor e latência ruim no endpointGetMyBookingsEndpoint. Considere adicionar parâmetros de paginação (Page,PageSize) e/ou filtros (Status,From/To) já na assinatura da query, retornando algo comoPagedResult<BookingDto>. Mesmo que o MVP aceite o contrato atual, adicionar agora evita breaking change no contrato público depois.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cs` around lines 7 - 9, GetBookingsByClientQuery currently returns IReadOnlyList<BookingDto> with no limits; change its signature to include pagination and optional filters (e.g., add int Page, int PageSize, optional BookingStatus? Status, DateTime? From, DateTime? To) and change the return type from Result<IReadOnlyList<BookingDto>> to Result<PagedResult<BookingDto>> (or equivalent paged DTO) so callers like GetMyBookingsEndpoint and the query handler (the class handling GetBookingsByClientQuery) can return a bounded page; update the query handler, repository calls, and mapping to apply the page/filters and populate PagedResult metadata (total count, page, pageSize) to avoid unbounded payloads.src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs (1)
27-74: Cobertura insuficiente: caminhos críticos do handler não testados.Há apenas dois testes (sucesso feliz e provider inexistente). Faltam cenários importantes que o handler trata explicitamente e que já geraram discussão em revisões anteriores:
ProviderExistsAsyncretornandoFailure→ erro propagado (não confundido com 404).- Schedule inexistente →
AddAsyncchamado (eUpdateAsyncnão chamado).TimeSlotinválido (start ≥ end) →BadRequestcom a mensagem em português.Availabilitycom slots sobrepostos →BadRequest.- Verificação de que, ao falhar a validação,
ClearAvailabilities/UpdateAsyncnão são chamados (protege a regra "mutar só após validar").Também:
baseDatena linha 32 é declarado e nunca usado.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` around lines 27 - 74, Add unit tests to cover the missing critical paths for SetProviderScheduleCommandHandler: (1) when _providersApiMock.ProviderExistsAsync returns Result.Failure — assert the handler returns that failure (not converted to 404) and no repo mutations occur; (2) when _scheduleRepoMock.GetByProviderIdAsync returns null — assert AddAsync is called and UpdateAsync is not called; (3) invalid TimeSlot (start >= end) — send a command with an AvailabilityDto containing such a TimeSlotDto and assert a BadRequest result with the Portuguese message and that ClearAvailabilities/UpdateAsync/AddAsync are not called; (4) overlapping TimeSlots in an AvailabilityDto — assert BadRequest and no repo mutation; and remove the unused baseDate local variable in the existing happy-path test. Use the existing test fixtures/mocks (_providersApiMock, _scheduleRepoMock, _sut.HandleAsync, SetProviderScheduleCommand, AvailabilityDto, TimeSlotDto, ProviderSchedule.Create, ClearAvailabilities, AddAsync, UpdateAsync) to locate and implement these tests.src/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cs (1)
2-9: Remova a exclusão de cobertura deste evento de domínio.
BookingCreatedDomainEventnão se enquadra nas exceções globais de cobertura mencionadas; como ele é exercitado pela criação do aggregate, deixe-o entrar na métrica.♻️ Ajuste sugerido
using MeAjudaAi.Shared.Events; -using System.Diagnostics.CodeAnalysis; namespace MeAjudaAi.Modules.Bookings.Domain.Events; /// <summary> /// Evento de domínio disparado quando um novo agendamento é criado. /// </summary> -[ExcludeFromCodeCoverage] public record BookingCreatedDomainEvent(Based on learnings, manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cs` around lines 2 - 9, Remova a anotação que exclui cobertura nesse evento de domínio: localize a classe BookingCreatedDomainEvent e remova o atributo [ExcludeFromCodeCoverage] (e o using System.Diagnostics.CodeAnalysis; se ficar sem uso) para que o evento entre nas métricas de cobertura; mantenha o resto da classe inalterado.src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs (1)
21-25: Evite chamadas repetidas de agenda por provider.A listagem faz uma consulta sequencial de agenda para cada booking; clientes com muitos agendamentos do mesmo provider vão repetir I/O desnecessário. Um cache local por
ProviderIdjá reduz o N+1 sem mudar o contrato do repositório.♻️ Refatoração sugerida
var bookings = await bookingRepository.GetByClientIdAsync(query.ClientId, cancellationToken); var dtos = new List<BookingDto>(); + var timeZonesByProviderId = new Dictionary<Guid, TimeZoneInfo>(); + foreach (var booking in bookings) { - var schedule = await scheduleRepository.GetByProviderIdAsync(booking.ProviderId, cancellationToken); - var tz = ResolveTimeZone(schedule?.TimeZoneId); + if (!timeZonesByProviderId.TryGetValue(booking.ProviderId, out var tz)) + { + var schedule = await scheduleRepository.GetByProviderIdAsync(booking.ProviderId, cancellationToken); + tz = ResolveTimeZone(schedule?.TimeZoneId); + timeZonesByProviderId[booking.ProviderId] = tz; + } var startDate = booking.Date.ToDateTime(booking.TimeSlot.Start);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs` around lines 21 - 25, O loop atual sobre bookings faz chamadas sequenciais a scheduleRepository.GetByProviderIdAsync para cada booking causando N+1; altere o handler para manter um cache local (por exemplo Dictionary<Guid, Schedule?>) keyed por booking.ProviderId, ao processar cada booking verifique o cache e só invoque scheduleRepository.GetByProviderIdAsync quando o ProviderId não estiver presente, então reuse o Schedule e chame ResolveTimeZone(schedule?.TimeZoneId) a partir do valor em cache antes de criar o BookingDto; referências: bookings, scheduleRepository.GetByProviderIdAsync, ResolveTimeZone e BookingDto.tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs (2)
86-95: Bloco de diagnóstico com asserção contraintuitiva.O
ifde diagnóstico forçacontentMsg.Should().BeEmpty("Because create should succeed")quando o status não éCreated— isso gera uma mensagem de falha confusa ("esperava vazio") para o que na prática é uma falha de criação. Como a linha 95 já fazcreateResponse.StatusCode.Should().Be(HttpStatusCode.Created), o bloco só precisa logar o corpo para diagnóstico; a asserção em si pode ser removida.♻️ Refator sugerido
- if (createResponse.StatusCode != HttpStatusCode.Created) - { - var contentMsg = await createResponse.Content.ReadAsStringAsync(); - _output.WriteLine($"Creation failed: {contentMsg}"); - contentMsg.Should().BeEmpty("Because create should succeed"); - } - createResponse.StatusCode.Should().Be(HttpStatusCode.Created); + if (createResponse.StatusCode != HttpStatusCode.Created) + { + var contentMsg = await createResponse.Content.ReadAsStringAsync(); + _output.WriteLine($"Creation failed: {createResponse.StatusCode} - {contentMsg}"); + } + createResponse.StatusCode.Should().Be(HttpStatusCode.Created);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs` around lines 86 - 95, The diagnostic block around createResponse incorrectly asserts contentMsg.Should().BeEmpty when the status is not HttpStatusCode.Created, causing a misleading failure; instead remove the contentMsg.Should().BeEmpty assertion and only read and log the response body (using contentMsg and _output.WriteLine) for debugging when createResponse.StatusCode != HttpStatusCode.Created, leaving the final createResponse.StatusCode.Should().Be(HttpStatusCode.Created) assertion intact.
34-69: Chamadas redundantes aAuthenticateAsAdmin().
AuthenticateAsAdmin()é chamado na linha 34, novamente na 59 e de novo na 69; além dissoCreateTestProviderAsyncjá chamaAuthenticateAsAdmin()internamente (linha 122). Considere centralizar o setup de admin em um único ponto no início do teste e remover as chamadas repetidas para reduzir ruído e evitar que futuras alterações no helper disparem efeitos colaterais duplicados.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs` around lines 34 - 69, Remove the redundant AuthenticateAsAdmin() calls and centralize admin authentication to a single invocation at the start of the test; specifically, keep one AuthenticateAsAdmin() before calling CreateTestProviderAsync() (noting CreateTestProviderAsync() already calls AuthenticateAsAdmin()), delete the later AuthenticateAsAdmin() calls before the schedule POST and before creating the client user, and ensure subsequent calls that depend on admin context (e.g., the ApiClient.PostAsJsonAsync("/api/v1/bookings/schedule", scheduleRequest) call) rely on that single initial authentication so the test flow remains authenticated.src/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cs (1)
77-115: Consistência: adicionarTimes.NeverdoUpdateAsyncnos cenáriosBookingNotFoundeBookingAlreadyConfirmed.O cenário
UserIsNotOwner(linha 74) já verifica queUpdateAsyncnão é invocado, mas os cenáriosBookingNotFoundeBookingAlreadyConfirmedomitem essa garantia. Para manter consistência e prevenir regressões silenciosas do handler, adicione a mesma verificação nesses dois testes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cs` around lines 77 - 115, Add assertions in the two tests HandleAsync_Should_Fail_When_BookingNotFound and HandleAsync_Should_Fail_When_BookingAlreadyConfirmed to verify that the repository UpdateAsync is never called: after invoking _sut.HandleAsync(new RejectBookingCommand(...)) assert _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never); this mirrors the existing check in the UserIsNotOwner test and ensures the RejectBookingCommandHandler (HandleAsync) does not call BookingRepository.UpdateAsync in those failure paths.src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs (1)
55-116: Sugestão: verificar queUpdateAsyncnão é chamado nos caminhos de falha.Os cenários de falha (
BookingIsPending,UserIsNotOwner,BookingNotFound) validam oStatusCodemas não garantem que nenhuma persistência indesejada acontece. Adicionar_bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never);blindaria os testes contra regressões do handler (similar ao já feito emRejectBookingCommandHandlerTestsno cenárioUserIsNotOwner).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs` around lines 55 - 116, Add verification that the repository's UpdateAsync is never called in the failing test cases: in HandleAsync_Should_Fail_When_BookingIsPending, HandleAsync_Should_Fail_When_UserIsNotOwner, and HandleAsync_Should_Fail_When_BookingNotFound add a call after the assertions: _bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never); so each test not only checks result.Error.StatusCode but also ensures CompleteBookingCommandHandler (via _sut.HandleAsync) does not persist changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/modules/bookings.md`:
- Around line 15-22: The code block showing the directory tree (starting with
"Bookings/") is missing a language tag and triggers markdownlint MD040; update
that fenced block to include the "text" language specifier (i.e., change the
opening ``` to ```text) so the tree is treated as plain text—locate the block
containing "Bookings/ ├── Domain/ ..." in docs and add the language tag.
In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs`:
- Around line 16-30: Antes de criar e despachar CancelBookingCommand no endpoint
MapPut (no método que recebe CancelBookingRequest), valide request.Reason —
verifique null/empty e limite de tamanho igual ao usado no domínio/fluxo de
rejeição — e, se inválido, retorne um BadRequest contendo o erro de validação em
vez de prosseguir; mantenha a lógica de correlação (correlationId) e apenas
instancie/dispatch o CancelBookingCommand quando Reason passar a validação
(referencie CancelBookingRequest.Reason, CancelBookingCommand e
dispatcher.SendAsync).
In `@src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs`:
- Around line 17-35: The endpoint currently allows any authenticated user to
fetch bookings by ID; update authorization so only the booking's customer,
provider, or a system admin can read it: modify GetBookingByIdQueryHandler (or
add a pipeline behavior) to accept the caller's ClaimsPrincipal (or pass
sub/provider_id/is_system_admin into GetBookingByIdQuery) and after loading the
booking verify the caller matches booking.CustomerId or booking.ProviderId or
has is_system_admin=true; if not authorized, return a Result failure that maps
to 404 (not 403) so existence isn't leaked. Ensure the unique symbols involved
are GetBookingByIdEndpoint, GetBookingByIdQuery, and GetBookingByIdQueryHandler
and check claim names "sub", "provider_id", and "is_system_admin" when enforcing
the check.
In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cs`:
- Around line 17-30: The endpoint registered by app.MapGet (the lambda accepting
Guid providerId, IQueryDispatcher dispatcher, CancellationToken) currently
trusts any providerId; before creating GetBookingsByProviderQuery and calling
dispatcher.QueryAsync, verify the authenticated user's provider identity by
reading the relevant claim (e.g., HttpContext.User.FindFirst("sub") or the
specific provider id claim) and compare it to the providerId parameter,
returning Results.Forbid()/Results.Problem(…, statusCode: 403) on mismatch;
alternatively wire RequireAuthorization with a policy that ensures the caller
has the provider role/owns-the-resource claim and use that policy name on the
MapGet to prevent unauthorized access.
In `@src/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cs`:
- Around line 22-45: As respostas de validação usam Results.BadRequest(new {
error = ... }) mas o endpoint declara ProducesProblem(400) e também pode
retornar 409; atualize o bloco de validação (a checagem de request.Reason) para
retornar um ProblemDetails-style response (por exemplo Results.Problem(detail:
"...", statusCode: StatusCodes.Status400BadRequest) ou
Results.ValidationProblem(...) para incluir campo), mantenha o fluxo do comando
RejectBookingCommand e o result.Match como está, e adicione
.ProducesProblem(StatusCodes.Status409Conflict) à cadeia de metadata para
documentar o possível 409 Conflict.
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 38-62: When a non-admin caller has their provider resolved via
providersApi.GetProviderByUserIdAsync (in the branch that sets targetProviderId
from providerResult.Value.Id), validate request.ProviderId: if
request.ProviderId != Guid.Empty and request.ProviderId != targetProviderId
return a 400 Bad Request indicating the ProviderId mismatch; place this check
immediately after assigning targetProviderId (in SetProviderScheduleEndpoint
logic handling the userIdClaim path) so non-admin clients are rejected when they
supply a conflicting ProviderId rather than silently ignoring it.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs`:
- Around line 29-38: The DateTimeOffset construction in
GetBookingByIdQueryHandler (where booking.Date.ToDateTime(...) and
tz.GetUtcOffset(...) are used) is vulnerable to DST gaps/ambiguous times;
instead validate each DateTime with tz.IsInvalidTime(dt) /
tz.IsAmbiguousTime(dt) and handle those cases (e.g., shift to the nearest valid
time or choose the correct ambiguous offset), or convert the local dt to UTC
with TimeZoneInfo.ConvertTimeToUtc(dt, tz) and build the DTO times as new
DateTimeOffset(utcDt, TimeSpan.Zero); update the creation of startDate/endDate
DateTimeOffset values to perform this validation/conversion before calling new
DateTimeOffset.
- Around line 44-57: The ResolveTimeZone method (used in
GetBookingByIdQueryHandler, GetBookingsByProviderQueryHandler,
GetBookingsByClientQueryHandler) currently calls FindSystemTimeZoneById("E.
South America Standard Time") outside a try/catch and can throw on Linux; change
it to follow the HangfireBackgroundJobService pattern: attempt to resolve the
provided timeZoneId inside try/catch, if null/empty or that fails then try
resolving "E. South America Standard Time" inside try/catch, then fall back to
TimeZoneInfo.Local inside try/catch, and finally TimeZoneInfo.Utc; ensure
exceptions are caught and optionally logged (reusing existing logging) at each
step so no unhandled TimeZoneNotFoundException propagates.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs`:
- Around line 32-39: The handler GetProviderAvailabilityQueryHandler currently
calls bookingRepository.GetByProviderIdAsync which loads all provider bookings
into memory and then filters by Date and Status; change the repository instead:
add a new method on IBookingRepository (e.g., GetActiveByProviderAndDateAsync or
GetByProviderIdAndDateAsync) implemented in BookingRepository to query
DbContext.Bookings with AsNoTracking() and a WHERE that restricts ProviderId,
Date and excludes Cancelled/Rejected/Completed, returning
IReadOnlyList<Booking>, then update GetProviderAvailabilityQueryHandler to call
that new repository method and remove the in-handler filtering so only the
relevant day's active bookings are materialized.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cs`:
- Around line 22-33: No handler-level validation exists for the rejection
reason, so add checks inside the RejectBookingCommandHandler.Handle method to
validate the RejectBookingCommand.Reason (e.g., not null/empty and within
allowed length) before loading the booking; if invalid return Result.Failure
with an appropriate Error (e.g., BadRequest or Validation) so direct dispatchers
are protected; reference RejectBookingCommandHandler.Handle and the
RejectBookingCommand.Reason property when adding this validation.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 50-54: The catch block in SetProviderScheduleCommandHandler
currently returns Result.Failure(Error.BadRequest(...)) and includes ex.Message,
which leaks internals and uses the wrong status; change it to return
Result.Failure(Error.Internal("Erro interno ao processar disponibilidades."))
(or the project's standard generic internal error message) and keep the existing
logger.LogError(ex, "Unexpected error processing availabilities for Provider
{ProviderId}", command.ProviderId) so the detailed exception stays in logs;
replace the Error.BadRequest + ex.Message usage with Error.Internal in the
Result.Failure return.
In
`@src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cs`:
- Around line 7-9: O contrato GetBookingsByProviderQuery atualmente permite
retornar todos os BookingDto de um Provider sem limites; altere o record
GetBookingsByProviderQuery para incluir parâmetros de janela de datas (por
exemplo DateFrom, DateTo) e/ou paginação (por exemplo PageNumber, PageSize) como
opcionais, atualize o handler responsável pela query para aplicar filtros de
data e/ou OFFSET/LIMIT (ou equivalente) na consulta e ajuste quaisquer
chamadas/tests que constroem GetBookingsByProviderQuery para fornecer defaults
seguros quando ausentes; garanta que a assinatura continue implementando
IQuery<Result<IReadOnlyList<BookingDto>>> e que a lógica de paginação/data seja
usada antes de mapear/retornar a lista.
In `@src/Modules/Bookings/Domain/Entities/Booking.cs`:
- Around line 18-97: Booking incorrectly emits domain events with a hardcoded 0
and never updates Version; change Version from uint to int (to match
DomainEvent), increment Version before each event emission, and pass the current
Version (not 0) into AddDomainEvent calls: in the private constructor
(Booking(Guid...,DateOnly,TimeSlot)) call Version++; then in Confirm(),
Reject(), Cancel(), and Complete() call Version++; and replace the literal 0 in
each
BookingCreatedDomainEvent/BookingConfirmedDomainEvent/BookingRejectedDomainEvent/BookingCancelledDomainEvent/BookingCompletedDomainEvent
invocation with the Version property. Ensure Version stays public get/private
set.
In `@src/Modules/Bookings/Domain/Events/BookingCompletedDomainEvent.cs`:
- Around line 2-15: The BookingCompletedDomainEvent is currently marked with
[ExcludeFromCodeCoverage], which prevents it from being covered by aggregate
transition tests; remove the ExcludeFromCodeCoverage attribute (the annotation
above the record) so BookingCompletedDomainEvent participates in coverage
validation, leaving the record declaration BookingCompletedDomainEvent(...) :
DomainEvent(...) unchanged.
In `@src/Modules/Bookings/Domain/Events/BookingRejectedDomainEvent.cs`:
- Around line 2-16: Remove the [ExcludeFromCodeCoverage] attribute from the
BookingRejectedDomainEvent record so this domain event remains covered by
aggregate transition validations; locate the BookingRejectedDomainEvent
declaration (record BookingRejectedDomainEvent(...): DomainEvent(...)) and
delete the ExcludeFromCodeCoverage using directive/attribute so it is included
in test coverage like other domain events.
In `@src/Shared/Exceptions/GlobalExceptionHandler.cs`:
- Line 139: The response body currently exposes internal exception details by
building detail with exception.GetType().Name and exception.Message (and
InnerException.Message); modify the GlobalExceptionHandler so the response
detail is a generic message for non-dev environments and only includes the
exception type/message when env.IsDevelopment() is true: inject IHostEnvironment
into the handler, replace the current detail construction that references
exception.GetType().Name/exception.Message/exception.InnerException with a
conditional that returns a generic "An unexpected error occurred" (or similar)
for production and the full exception info in development, and keep the existing
detailed logging (the log call on Line 150) unchanged for operator diagnostics.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs`:
- Line 33: Fix the typo in the comment inside the BookingsEndToEndTests class:
change "feto" to "feito" in the comment that starts with "Criar um prestador
feto com um providerId gerado" so it reads "Criar um prestador feito com um
providerId gerado".
- Around line 103-118: The test calls AuthenticateAsProvider(providerIdClaim)
but that method currently delegates to AuthenticateAsAdmin(), so it doesn't
exercise provider-specific authorization in ConfirmBookingCommandHandler; update
AuthenticateAsProvider to issue a real JWT with the ProviderId claim (mirror how
AuthenticateAsUser constructs its token) so the PUT
/api/v1/bookings/{bookingId}/confirm runs as the actual provider, and keep the
ConfirmBookingCommandHandler ownership check exercised; if implementing provider
tokens isn't feasible now, mark the test with [Fact(Skip = "...")] or add a
clear comment that AuthenticateAsProvider uses admin credentials so the test is
not validating provider authorization.
---
Outside diff comments:
In `@tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs`:
- Around line 274-282: Update the outdated migration comment and make Bookings
follow the same implicit-dependency pattern: revise the comment above the
ApplyMigrationForContextAsync calls to include Payments and Bookings, and add
the implicit dependency entries for TestModule.Bookings (Users, ServiceCatalogs,
Providers) wherever other modules declare implicit dependencies (the same block
that currently adds dependencies for SearchProviders and Providers) so tests
that only include TestModule.Bookings will also enable Users, ServiceCatalogs
and Providers before ApplyMigrationForContextAsync is invoked.
---
Duplicate comments:
In `@src/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cs`:
- Around line 20-25: The endpoint handling MapPost("/schedule") can throw
NullReferenceException when the body is null or when request.Availabilities is
null; update the SetProviderSchedule handler to validate inputs early (either
via FluentValidation or explicit checks) by returning Problem(400) when request
is null or when request.Availabilities is null/empty, and only proceed to use
request.ProviderId and enumerate Availabilities after those checks; reference
the SetProviderScheduleRequest type, the MapPost("/schedule") handler delegate,
and the properties ProviderId and Availabilities when adding the validation and
early return.
---
Nitpick comments:
In `@src/Modules/Bookings/API/API.Client/Bookings/RejectBooking.bru`:
- Around line 7-39: The request URL currently hardcodes the zero GUID; replace
that literal with a parameterized variable (e.g. {{bookingId}}) so the endpoint
becomes /api/v1/bookings/{{bookingId}}/reject, ensure the auth token still uses
{{accessToken}}, and add or document a bookingId variable in the client/run
configuration so local/CI runs can set a real booking id (also update the docs
block/example to reference {{bookingId}} instead of the zero GUID).
In `@src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs`:
- Line 22: A GetBookingByIdEndpoint está sempre gerando um novo CorrelationId
com Guid.NewGuid() ao criar GetBookingByIdQuery, quebrando a rastreabilidade;
altere para ler o header "X-Correlation-Id" do request
(HttpContext.Request.Headers["X-Correlation-Id"]) e, se presente e for um GUID
válido (Guid.TryParse), use-o ao construir new GetBookingByIdQuery(id,
correlationId), caso contrário gere um Guid.NewGuid() como fallback; garanta que
o nome GetBookingByIdEndpoint e o uso de GetBookingByIdQuery sejam atualizados
sem alterar a assinatura da query.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs`:
- Around line 21-25: O loop atual sobre bookings faz chamadas sequenciais a
scheduleRepository.GetByProviderIdAsync para cada booking causando N+1; altere o
handler para manter um cache local (por exemplo Dictionary<Guid, Schedule?>)
keyed por booking.ProviderId, ao processar cada booking verifique o cache e só
invoque scheduleRepository.GetByProviderIdAsync quando o ProviderId não estiver
presente, então reuse o Schedule e chame ResolveTimeZone(schedule?.TimeZoneId) a
partir do valor em cache antes de criar o BookingDto; referências: bookings,
scheduleRepository.GetByProviderIdAsync, ResolveTimeZone e BookingDto.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 18-20: No início de HandleAsync
(SetProviderScheduleCommandHandler.HandleAsync) verifique explicitamente se
command.Availabilities é null; se for, faça um log apropriado e retorne um
resultado de validação/400 previsível em vez de permitir que o foreach sobre
command.Availabilities gere NullReferenceException; altere o fluxo antes do
logger.LogInformation para validar command.Availabilities e retornar um Result
de erro de validação (mensagem clara) para o chamador.
In
`@src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cs`:
- Around line 7-9: GetBookingsByClientQuery currently returns
IReadOnlyList<BookingDto> with no limits; change its signature to include
pagination and optional filters (e.g., add int Page, int PageSize, optional
BookingStatus? Status, DateTime? From, DateTime? To) and change the return type
from Result<IReadOnlyList<BookingDto>> to Result<PagedResult<BookingDto>> (or
equivalent paged DTO) so callers like GetMyBookingsEndpoint and the query
handler (the class handling GetBookingsByClientQuery) can return a bounded page;
update the query handler, repository calls, and mapping to apply the
page/filters and populate PagedResult metadata (total count, page, pageSize) to
avoid unbounded payloads.
In `@src/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cs`:
- Around line 2-9: Remova a anotação que exclui cobertura nesse evento de
domínio: localize a classe BookingCreatedDomainEvent e remova o atributo
[ExcludeFromCodeCoverage] (e o using System.Diagnostics.CodeAnalysis; se ficar
sem uso) para que o evento entre nas métricas de cobertura; mantenha o resto da
classe inalterado.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs`:
- Around line 55-116: Add verification that the repository's UpdateAsync is
never called in the failing test cases: in
HandleAsync_Should_Fail_When_BookingIsPending,
HandleAsync_Should_Fail_When_UserIsNotOwner, and
HandleAsync_Should_Fail_When_BookingNotFound add a call after the assertions:
_bookingRepoMock.Verify(x => x.UpdateAsync(It.IsAny<Booking>(),
It.IsAny<CancellationToken>()), Times.Never); so each test not only checks
result.Error.StatusCode but also ensures CompleteBookingCommandHandler (via
_sut.HandleAsync) does not persist changes.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cs`:
- Around line 77-115: Add assertions in the two tests
HandleAsync_Should_Fail_When_BookingNotFound and
HandleAsync_Should_Fail_When_BookingAlreadyConfirmed to verify that the
repository UpdateAsync is never called: after invoking _sut.HandleAsync(new
RejectBookingCommand(...)) assert _bookingRepoMock.Verify(x =>
x.UpdateAsync(It.IsAny<Booking>(), It.IsAny<CancellationToken>()), Times.Never);
this mirrors the existing check in the UserIsNotOwner test and ensures the
RejectBookingCommandHandler (HandleAsync) does not call
BookingRepository.UpdateAsync in those failure paths.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Around line 27-74: Add unit tests to cover the missing critical paths for
SetProviderScheduleCommandHandler: (1) when
_providersApiMock.ProviderExistsAsync returns Result.Failure — assert the
handler returns that failure (not converted to 404) and no repo mutations occur;
(2) when _scheduleRepoMock.GetByProviderIdAsync returns null — assert AddAsync
is called and UpdateAsync is not called; (3) invalid TimeSlot (start >= end) —
send a command with an AvailabilityDto containing such a TimeSlotDto and assert
a BadRequest result with the Portuguese message and that
ClearAvailabilities/UpdateAsync/AddAsync are not called; (4) overlapping
TimeSlots in an AvailabilityDto — assert BadRequest and no repo mutation; and
remove the unused baseDate local variable in the existing happy-path test. Use
the existing test fixtures/mocks (_providersApiMock, _scheduleRepoMock,
_sut.HandleAsync, SetProviderScheduleCommand, AvailabilityDto, TimeSlotDto,
ProviderSchedule.Create, ClearAvailabilities, AddAsync, UpdateAsync) to locate
and implement these tests.
In `@src/Shared/Exceptions/GlobalExceptionHandler.cs`:
- Around line 129-134: Update the BadHttpRequestException case in
GlobalExceptionHandler to use the exception's own StatusCode instead of
hard-coding 400, provide a user-friendly Portuguese message (e.g., "Requisição
inválida" or context-specific like "Payload muito grande" where appropriate) as
the main message, and move the original badHttpRequestException.Message into the
diagnostics/extensions payload; also remove the fully-qualified type name (use
BadHttpRequestException directly) since Microsoft.AspNetCore.Http is already
imported.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs`:
- Around line 86-95: The diagnostic block around createResponse incorrectly
asserts contentMsg.Should().BeEmpty when the status is not
HttpStatusCode.Created, causing a misleading failure; instead remove the
contentMsg.Should().BeEmpty assertion and only read and log the response body
(using contentMsg and _output.WriteLine) for debugging when
createResponse.StatusCode != HttpStatusCode.Created, leaving the final
createResponse.StatusCode.Should().Be(HttpStatusCode.Created) assertion intact.
- Around line 34-69: Remove the redundant AuthenticateAsAdmin() calls and
centralize admin authentication to a single invocation at the start of the test;
specifically, keep one AuthenticateAsAdmin() before calling
CreateTestProviderAsync() (noting CreateTestProviderAsync() already calls
AuthenticateAsAdmin()), delete the later AuthenticateAsAdmin() calls before the
schedule POST and before creating the client user, and ensure subsequent calls
that depend on admin context (e.g., the
ApiClient.PostAsJsonAsync("/api/v1/bookings/schedule", scheduleRequest) call)
rely on that single initial authentication so the test flow remains
authenticated.
🪄 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: 4920ab4a-a475-48c5-bf3b-853482c49118
⛔ Files ignored due to path filters (4)
src/Modules/Bookings/Infrastructure/Persistence/Migrations/20260422143101_AddProviderSchedules.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260422143101_AddProviderSchedules.csis excluded by!**/Migrations/**src/Modules/Bookings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (61)
docs/modules/bookings.mddocs/roadmap-history.mddocs/technical-debt.mdsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Modules/Bookings/API/API.Client/Bookings/CancelBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/CompleteBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/ConfirmBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/CreateBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/GetBookingById.brusrc/Modules/Bookings/API/API.Client/Bookings/GetMyBookings.brusrc/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.brusrc/Modules/Bookings/API/API.Client/Bookings/GetProviderBookings.brusrc/Modules/Bookings/API/API.Client/Bookings/RejectBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/SetProviderSchedule.brusrc/Modules/Bookings/API/Endpoints/BookingsEndpoints.cssrc/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/CompleteBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/API/Extensions.cssrc/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommandValidator.cssrc/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/RejectBookingCommand.cssrc/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cssrc/Modules/Bookings/Application/Bookings/Handlers/CompleteBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingByIdQuery.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cssrc/Modules/Bookings/Application/Extensions.cssrc/Modules/Bookings/Domain/Entities/Booking.cssrc/Modules/Bookings/Domain/Events/BookingCancelledDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingCompletedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingRejectedDomainEvent.cssrc/Modules/Bookings/Domain/ValueObjects/TimeSlot.cssrc/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csprojsrc/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingByIdQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cssrc/Shared/Exceptions/GlobalExceptionHandler.cssrc/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs
✅ Files skipped from review due to trivial changes (13)
- src/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.bru
- src/Modules/Bookings/API/API.Client/Bookings/GetProviderBookings.bru
- src/Modules/Bookings/API/API.Client/Bookings/GetBookingById.bru
- src/Modules/Bookings/API/API.Client/Bookings/CompleteBooking.bru
- src/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cs
- src/Modules/Bookings/API/API.Client/Bookings/SetProviderSchedule.bru
- src/Modules/Bookings/API/API.Client/Bookings/CreateBooking.bru
- src/Modules/Bookings/API/API.Client/Bookings/GetMyBookings.bru
- src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs
- src/Modules/Bookings/API/API.Client/Bookings/ConfirmBooking.bru
- docs/roadmap-history.md
- src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj
- src/Web/MeAjudaAi.Web.Customer/tests/components/bookings/booking-modal.test.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- src/Modules/Bookings/Application/Extensions.cs
- src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx
- src/Bootstrapper/MeAjudaAi.ApiService/Program.cs
- src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs
- src/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs
- src/Modules/Bookings/API/Endpoints/BookingsEndpoints.cs
- src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs
There was a problem hiding this comment.
Actionable comments posted: 12
♻️ Duplicate comments (2)
src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs (1)
19-37:⚠️ Potential issue | 🟠 MajorEmpurre filtros e paginação para o repositório antes de materializar a lista.
A linha 19 ainda carrega todos os agendamentos do prestador e só depois aplica
From/To/Skip/Takeem memória. Para agendas históricas, isso mantém o gargalo que a paginação do contrato deveria evitar; validePage/PageSizee apliqueWHERE+LIMIT/OFFSETna consulta do banco.🛠️ Direção sugerida
- var bookings = await bookingRepository.GetByProviderIdAsync(query.ProviderId, cancellationToken); - - // Apply Date Filters - if (query.From.HasValue) - { - var fromDate = DateOnly.FromDateTime(query.From.Value); - bookings = bookings.Where(b => b.Date >= fromDate).ToList(); - } - - if (query.To.HasValue) - { - var toDate = DateOnly.FromDateTime(query.To.Value); - bookings = bookings.Where(b => b.Date <= toDate).ToList(); - } - - // Apply Pagination - var pageNumber = query.Page ?? 1; - var pageSize = query.PageSize ?? 10; - var pagedBookings = bookings.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList(); + var pageNumber = Math.Max(query.Page ?? 1, 1); + var pageSize = Math.Clamp(query.PageSize ?? 10, 1, 100); + var fromDate = query.From.HasValue ? DateOnly.FromDateTime(query.From.Value) : (DateOnly?)null; + var toDate = query.To.HasValue ? DateOnly.FromDateTime(query.To.Value) : (DateOnly?)null; + + var pagedBookings = await bookingRepository.GetByProviderIdAsync( + query.ProviderId, + fromDate, + toDate, + pageNumber, + pageSize, + cancellationToken);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs` around lines 19 - 37, The handler currently materializes all bookings via bookingRepository.GetByProviderIdAsync and then filters and paginates in memory; instead validate query.Page and query.PageSize and push the date filters and pagination into the repository (e.g., add or overload bookingRepository.GetByProviderIdAsync to accept From/To DateOnly?, int page, int pageSize or a query object) so the repository applies WHERE Date >= From AND Date <= To and LIMIT/OFFSET (or equivalent) at the DB level; then call that repository method from GetBookingsByProviderQueryHandler and remove the in-memory Where/Skip/Take logic and the local pagedBookings materialization.src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs (1)
39-52:⚠️ Potential issue | 🟡 MinorValide horários inválidos/ambíguos antes de converter para UTC.
ConvertTimeToUtcmelhora a montagem do DTO, mas ainda pode lançar para horários locais inválidos em fusos com DST; em horários ambíguos, a escolha do offset também fica implícita. Antes das Lines 43-44, validetz.IsInvalidTime(...)/tz.IsAmbiguousTime(...)e aplique uma política explícita ou retorne uma falha controlada. Comentário já apontado anteriormente neste PR..NET TimeZoneInfo ConvertTimeToUtc DateTime invalid ambiguous time daylight saving behavior🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs` around lines 39 - 52, Before calling TimeZoneInfo.ConvertTimeToUtc on startDate/endDate in GetBookingByIdQueryHandler (where startDate, endDate and tz are used), check tz.IsInvalidTime(startDate) / tz.IsAmbiguousTime(startDate) and same for endDate; implement an explicit policy (e.g., reject with a controlled error/exception, normalize to a specific offset, or choose the earlier/later offset) and return a failure result or throw a well-defined exception instead of letting ConvertTimeToUtc throw; ensure the same handling is applied consistently before creating the BookingDto so ambiguous/invalid DST times are handled deterministically.
🧹 Nitpick comments (10)
src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs (1)
27-76: Cubra os ramos novos de filtro, paginação e timezone.Os testes atuais exercitam apenas happy path e lista vazia; faltam casos para
From/To,Page/PageSizee fallback/conversão de timezone, que são exatamente as partes mais propensas a regressão no handler.🧪 Cenários mínimos sugeridos
+ [Fact] + public async Task HandleAsync_Should_Apply_DateFilters() + { + // Arrange + // Act + // Assert + } + + [Fact] + public async Task HandleAsync_Should_Apply_Pagination() + { + // Arrange + // Act + // Assert + } + + [Fact] + public async Task HandleAsync_Should_Fallback_ToUtc_When_TimeZoneIsInvalid() + { + // Arrange + // Act + // Assert + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs` around lines 27 - 76, Adicionar testes unitários em GetBookingsByProviderQueryHandlerTests para cobrir os ramos de filtro (From/To), paginação (Page/PageSize) e comportamento de timezone (conversão/fallback): crie métodos de teste que configurem _bookingRepoMock.GetByProviderIdAsync com um conjunto de bookings em várias datas/horários, invoquem _sut.HandleAsync passando GetBookingsByProviderQuery com From/To e com Page/PageSize para validar que apenas os bookings dentro do intervalo e página são retornados, e teste também queries com timezone inválido/null para assegurar que o handler aplica o timezone fallback/conversão esperada; use ProviderSchedule via _scheduleRepoMock.GetByProviderIdAsync quando necessário e asserte counts, ProviderId, e que os TimeSlot/DateOnly resultantes foram convertidos/filtrados corretamente.src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (2)
113-113: Dê feedback quando o confirmar estiver bloqueado por falta deserviceId/sessão.
isConfirmDisabledcombina várias condições (sem slot, semserviceId, semsession.user.id, semaccessToken, pendente). Em produção o usuário pode ver o botão permanentemente desabilitado sem entender o motivo (ex.: o pai passouserviceId=""— veja comentário empage.tsx). Considere:
- Exibir uma mensagem inline quando
!serviceId("Selecione um serviço para agendar").- Fechar o modal e mostrar um
toast.errorse a sessão sumir durante a operação.Also applies to: 180-186
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` at line 113, O botão de confirmar está desabilitado por múltiplas condições agrupadas em isConfirmDisabled (variáveis: selectedSlot, serviceId, createBooking.isPending, session, session.user.id, session.accessToken) e o usuário não recebe feedback sobre o motivo; ajuste a UI para mostrar uma mensagem inline quando !serviceId (por exemplo "Selecione um serviço para agendar") próxima ao botão/seleção de serviço e torne o estado visível, e adicione lógica ao fluxo de criação em createBooking para detectar perda de sessão durante a operação (checar session/user/accessToken antes e após a chamada) e, se a sessão sumir, fechar o modal e disparar toast.error com mensagem clara; aplique a mesma correção nos trechos semelhantes referenciados (linhas correspondentes ao mesmo componente/fluxo).
154-166: Exiba também o horário de término do slot.Hoje o botão mostra apenas
HH:mmdo início (slot.start). Para o usuário avaliar o slot é útil verinício – fim, principalmente se a duração dos serviços variar.♻️ Sugestão
- {format(parseAsUtc(slot.start), "HH:mm")} + {`${format(parseAsUtc(slot.start), "HH:mm")} – ${format(parseAsUtc(slot.end), "HH:mm")}`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 154 - 166, O botão exibe só o horário de início (format(parseAsUtc(slot.start), "HH:mm")); altere a label para mostrar "início – fim": dentro do map em availability.slots (referência: TimeSlot, selectedSlot, parseAsUtc, format) formate tanto o horário de início quanto o horário de término; if TimeSlot expõe slot.end use format(parseAsUtc(slot.end), "HH:mm"), otherwise calcule o fim a partir de slot.start + slot.duration (ou adicione minutos conforme o campo de duração existente) e exiba `${start} – ${end}` na label do botão mantendo a lógica de seleção e classes existentes.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)
159-163: Use os flagsisAuthenticated/isLoadingAuthde forma consistente.Você acabou de criar
isAuthenticated(linha 42), mas nesta ramificação volta a checarstatus === "authenticated". Pequena inconsistência; uniformize para facilitar manutenção.♻️ Refactor sugerido
- ) : (status === "authenticated") ? ( + ) : isAuthenticated ? ( <div className="w-full p-4 bg-blue-50 border border-blue-100 rounded-lg text-center"> <p className="text-sm text-gray-700">Este prestador não informou contatos.</p> </div> ) : !isLoadingAuth && (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 159 - 163, The branch mixes the new boolean flag isAuthenticated with the old status string check (status === "authenticated"); update the conditional to use isAuthenticated consistently (and keep isLoadingAuth as the loading guard) so the ternary reads the same predicate everywhere; search for any other occurrences of status === "authenticated" in this component and replace them with isAuthenticated (or adjust surrounding logic if status is still required) to maintain consistent auth checks in the component containing isAuthenticated, isLoadingAuth, and status.src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs (1)
32-32: Variável não utilizada.
baseDateé declarada mas nunca referenciada no teste — pode ser removida.♻️ Sugestão
- var providerId = Guid.NewGuid(); - var baseDate = DateTime.Today; - var availabilities = new List<AvailabilityDto> + var providerId = Guid.NewGuid(); + var availabilities = new List<AvailabilityDto>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` at line 32, Remova a variável não utilizada baseDate declarada em SetProviderScheduleCommandHandlerTests (variável "baseDate") — simplesmente eliminar a declaração onde aparece no teste para limpar código e evitar warning; não é necessário substituí-la por outra referência.tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs (1)
605-625: MétodoReconfigureSearchProvidersDbContextficou órfão.Após mover
SearchProvidersDbContextpara o caminho genéricoReconfigureDbContext<T>(linha 211), este método não é mais referenciado. Pode ser removido para evitar dead code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs` around lines 605 - 625, Remove the now-orphaned ReconfigureSearchProvidersDbContext method: delete the private method ReconfigureSearchProvidersDbContext from BaseTestContainerTest and any direct calls to it, and rely on the generic ReconfigureDbContext<T> implementation to reconfigure SearchProvidersDbContext instead; verify that ReconfigureDbContext<SearchProvidersDbContext> is used where the old method was referenced and that migrations/history schema and UseNetTopologySuite configuration are preserved in the generic reconfiguration code.src/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cs (1)
37-50: Inconsistência no tratamento do Correlation-Id e gaps no contrato OpenAPI.Este endpoint gera sempre um novo
Guidpara correlação (linha 37), enquantoSetProviderScheduleEndpointrespeita o headerX-Correlation-Id. Por consistência, convém reutilizar o mesmo padrão aqui.Além disso, o handler pode retornar falhas (e.g.,
StatusCodes.Status500InternalServerError) viaResults.Problem, mas o contrato só declara 200 e 401. Considere declarar também os demais status possíveis (por exemplo, 400/500) para refletir a superfície real.♻️ Sugestão
+ var correlationIdHeader = context.Request.Headers["X-Correlation-Id"].ToString(); + if (!Guid.TryParse(correlationIdHeader, out var correlationId)) + { + correlationId = Guid.NewGuid(); + } + - var query = new GetBookingsByClientQuery(clientId, Guid.NewGuid(), page, pageSize, from, to); + var query = new GetBookingsByClientQuery(clientId, correlationId, page, pageSize, from, to); var result = await dispatcher.QueryAsync<GetBookingsByClientQuery, Result<PagedResult<BookingDto>>>(query, cancellationToken); @@ .Produces<PagedResult<BookingDto>>(StatusCodes.Status200OK) + .ProducesProblem(StatusCodes.Status400BadRequest) .ProducesProblem(StatusCodes.Status401Unauthorized) + .ProducesProblem(StatusCodes.Status500InternalServerError)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cs` around lines 37 - 50, The endpoint currently creates a new Guid for correlation when constructing GetBookingsByClientQuery; change it to read the incoming "X-Correlation-Id" header (fallback to Guid.NewGuid() if missing) and pass that value into the GetBookingsByClientQuery constructor (same pattern used by SetProviderScheduleEndpoint), and update the OpenAPI contract by adding ProducesProblem entries for other possible error statuses (e.g., .ProducesProblem(StatusCodes.Status400BadRequest) and .ProducesProblem(StatusCodes.Status500InternalServerError)) so the endpoint's declared responses match the actual Results.Problem usage.src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cs (1)
47-49: Configuração de mock não utilizada.O
_scheduleRepoMock.Setup(...)(linhas 47-49) não é exercitado porGetBookingsByClientQueryHandlerneste caminho (a consulta é porClientId, não porProviderId). Remover o bloco reduz ruído e alinha o teste ao comportamento real do handler.♻️ Sugestão
- var schedule = ProviderSchedule.Create(providerId); - _scheduleRepoMock.Setup(x => x.GetByProviderIdAsync(providerId, It.IsAny<CancellationToken>())) - .ReturnsAsync(schedule); - // Act🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cs` around lines 47 - 49, Remova a configuração de mock desnecessária: o trecho que chama ProviderSchedule.Create(providerId) e o _scheduleRepoMock.Setup(x => x.GetByProviderIdAsync(providerId, It.IsAny<CancellationToken>())).ReturnsAsync(schedule) deve ser excluído do teste GetBookingsByClientQueryHandlerTests porque o handler testado busca por ClientId, não por ProviderId, e esse setup nunca é exercitado; procure pelas referências a _scheduleRepoMock e GetByProviderIdAsync dentro do método de teste e apague apenas essas linhas para reduzir ruído.src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs (2)
88-99: Simplificação opcional do retorno emDateTimeOffsetnoBookingDto.
new DateTimeOffset(startUtc)comstartUtc.Kind == Utccria um offset+00:00; em seguida oTimeZoneInfo.ConvertTime(..., tz)reajusta para o offset do fuso. O resultado é o mesmo instante, apenas com representação no fuso do prestador — dá para deixar mais direto (e evitar uma camada extra de conversão) usandoTimeZoneInfo.ConvertTimeFromUtccombinado com o offset já conhecido, ou simplesmente construindo oDateTimeOffseta partir do UTC. Nenhuma mudança funcional, só reduz ruído.♻️ Sugestão
- var startUtc = TimeZoneInfo.ConvertTimeToUtc(localStartTime, tz); - var endUtc = TimeZoneInfo.ConvertTimeToUtc(localEndTime, tz); - - return new BookingDto( + var startUtc = TimeZoneInfo.ConvertTimeToUtc(localStartTime, tz); + var endUtc = TimeZoneInfo.ConvertTimeToUtc(localEndTime, tz); + var startOffset = new DateTimeOffset(startUtc, TimeSpan.Zero).ToOffset(tz.GetUtcOffset(startUtc)); + var endOffset = new DateTimeOffset(endUtc, TimeSpan.Zero).ToOffset(tz.GetUtcOffset(endUtc)); + + return new BookingDto( booking.Id, booking.ProviderId, booking.ClientId, booking.ServiceId, - TimeZoneInfo.ConvertTime(new DateTimeOffset(startUtc), tz), - TimeZoneInfo.ConvertTime(new DateTimeOffset(endUtc), tz), + startOffset, + endOffset, booking.Status);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 88 - 99, The current code constructs DateTimeOffset via TimeZoneInfo.ConvertTime(new DateTimeOffset(startUtc), tz) (same for endUtc) which is an extra conversion; replace those expressions with a direct conversion like TimeZoneInfo.ConvertTimeFromUtc(startUtc, tz) (and for endUtc) or build a DateTimeOffset by applying tz.GetUtcOffset(startUtc) to the UTC instant before passing to the BookingDto constructor (refer to BookingDto creation where startUtc/endUtc are converted).
102-134: Logue quando o fuso configurado não puder ser resolvido, em vez de silenciar a exceção.O
catch { }nas linhas 110-113 engole qualquer falha ao resolverschedule.TimeZoneId(inclusiveTimeZoneNotFoundException/InvalidTimeZoneException) e segue para o fallback de Brasília/Local/Utc. Em produção isso mascara configurações inválidas de agenda e faz oIsAvailablerodar num fuso diferente do esperado pelo prestador, gerandoDayOfWeek/hora local equivocados sem trilha para diagnóstico.Mantenha o fallback (útil em CI misto Linux/Windows), mas registre um
warningcom otimeZoneIde a exceção para que o problema seja visível.🛠️ Sugestão
- private TimeZoneInfo ResolveTimeZone(string? timeZoneId) + private TimeZoneInfo ResolveTimeZone(string? timeZoneId) { if (!string.IsNullOrWhiteSpace(timeZoneId)) { try { return TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); } - catch + catch (Exception ex) when (ex is TimeZoneNotFoundException or InvalidTimeZoneException) { - // Ignora e tenta fallback + logger.LogWarning(ex, + "Provider timezone {TimeZoneId} could not be resolved; falling back.", + timeZoneId); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 102 - 134, In ResolveTimeZone, do not swallow exceptions when failing to resolve schedule.TimeZoneId; instead catch the exception, log a warning including the timeZoneId and the exception (e.g., logger.LogWarning(ex, "Failed to resolve time zone '{TimeZoneId}'", timeZoneId)), then continue with the existing fallback logic that calls TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time") and ultimately TimeZoneInfo.Local/TimeZoneInfo.Utc; keep the same catch/fallback structure but ensure the initial catch records the exception details (including TimeZoneNotFoundException/InvalidTimeZoneException) so misconfigured schedules are visible in logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/roadmap-history.md`:
- Around line 7-17: Update the roadmap text to indicate the Rebus migration is
partial rather than final: change "Migração final para Rebus v3" to something
like "Migração parcial para Rebus v3" and add a brief note that RabbitMQ.Client
is still used directly in RabbitMqDeadLetterService and that
RabbitMqInfrastructureManager contains unfinished stubs (CreateQueueAsync,
CreateExchangeAsync, BindQueueToExchangeAsync) which must be completed to
finalize the migration; reference these symbols so reviewers can verify
consistency with technical-debt.md and code snippets.
In `@src/Modules/Bookings/API/Endpoints/Public/CompleteBookingEndpoint.cs`:
- Around line 30-34: O endpoint em CompleteBookingEndpoint.cs não documenta o
409 Conflict; como o CompleteBookingCommandHandler retorna Error.Conflict(...)
para ConcurrencyConflictException, adicione ao builder/route chain a declaração
que expõe 409 Conflict (por exemplo usando
.ProducesProblem(StatusCodes.Status409Conflict) ou equivalente) para que o
OpenAPI reflita o retorno de conflito de concorrência.
- Line 21: O comando CompleteBookingCommand tem um parâmetro morto
CorrelationId; remova esse parâmetro do construtor e da definição de
CompleteBookingCommand e atualize todas as criações do comando (por exemplo em
CompleteBookingEndpoint) para instanciar apenas com id (var command = new
CompleteBookingCommand(id)); ajuste também a assinatura do handler que consome
CompleteBookingCommand e qualquer chamada/registro relacionado (mapeadores,
testes e event handlers) para refletir a nova API sem CorrelationId.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs`:
- Around line 72-82: The current fallback tries Windows ID "E. South America
Standard Time" and then falls back to TimeZoneInfo.Local; update the chain in
GetBookingByIdQueryHandler to attempt an explicit IANA lookup
("America/Sao_Paulo") via TimeZoneInfo.FindSystemTimeZoneById before returning
TimeZoneInfo.Local, and add logging in both catch blocks (including the caught
Exception message) to record which fallback was used and why; reference the
existing TimeZoneInfo.FindSystemTimeZoneById call, the new IANA lookup, and
TimeZoneInfo.Local so reviewers can find and replace the nested try/catch
sequence.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Line 21: No handler comments devem ficar em inglês; substitua os comentários
"// Apply Date Filters" e "// Apply Pagination" no arquivo
GetBookingsByProviderQueryHandler.cs (especificamente dentro do método Handle da
classe GetBookingsByProviderQueryHandler) por versões em português: use "//
Aplica filtros de data" e "// Aplica paginação". Mantenha as mensagens de log em
inglês conforme a política e verifique também qualquer documentação XML (///
<summary>, /// <param>, etc.) naquele mesmo arquivo para que esteja em
português.
- Around line 62-74: A função ResolveTimeZone está chamando
TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time") fora do
bloco try, fazendo o handler falhar se esse ID não existir; mova essa chamada
para dentro do try/catch e, em qualquer exceção (tanto para timeZoneId
nulo/vazio quanto para IDs inválidos), retorne TimeZoneInfo.Utc; aplique a mesma
correção ao método equivalente em GetBookingsByClientQueryHandler (mesmo padrão
na linha mencionada).
In `@src/Modules/Bookings/Domain/Events/BookingCancelledDomainEvent.cs`:
- Around line 2-9: Remova o atributo [ExcludeFromCodeCoverage] do tipo
BookingCancelledDomainEvent: abra a classe BookingCancelledDomainEvent, delete a
anotação [ExcludeFromCodeCoverage] (e a diretiva using
System.Diagnostics.CodeAnalysis se ela ficar não utilizada) para que este evento
passe a ser coberto pelos testes, mantendo o restante da implementação
inalterado.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs`:
- Around line 35-37: Os testes usam datas fixas (ex.: DateOnly(2026,4,25)) que
irão expirar; altere as instâncias onde cria-se o booking (Booking.Create) e os
TimeSlot.Create nos testes (linhas citadas) para usar uma data relativa ou um
relógio fake: por exemplo, substitua DateOnly(2026,4,25) por um helper/shared
TestClock (ou DateOnly.FromDateTime(DateTime.UtcNow).AddDays(30) via helper) e
centralize essa lógica num método de teste (e.g. TestHelpers.FutureDate(days))
ou injete um IClock falso usado pelos testes; ajuste todas as ocorrências em
CompleteBookingCommandHandlerTests (incluindo as demais em 60-62 e 84-86) para
usar esse helper/clock em vez de datas literais.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 124-128: The callbackUrl is built using raw id from useParams(),
which can contain characters that break the query string and cause open‑redirect
fallback; update the Link href construction in the Button (the instance using id
in page.tsx around the Entrar para Agendar button and the other occurrence
mentioned at line 167) to URL‑encode the path portion (e.g. use
encodeURIComponent on `/prestador/${id}`) when interpolating into
`/api/auth/signin?callbackUrl=...` so the callbackUrl query value is properly
escaped.
- Around line 118-122: O problema é que `services` em `page.tsx` contém apenas
nomes (conforme `PublicProviderSchema`) mas
`CreateBookingRequest`/`CreateBookingEndpoint` exige um `ServiceId` GUID;
portanto o `BookingModal` está enviando nomes (ex.: "Corte de cabelo") ou "" e
isso causa 400. Solução: ou ajustar o contrato do endpoint público
(`GetPublicProviderByIdOrSlugEndpoint`) para retornar serviços como objetos {
id: Guid, name: string } e propagar isso para `services`/Badges, ou, enquanto
não mudamos o backend, impedir renderizar/abrir `BookingModal` sem IDs válidos —
por exemplo não renderizar o botão ou desabilitá‑lo com mensagem quando
`services` não contém itens com `id` GUID, e alterar `BookingModal` para
requerer seleção de um `serviceId` válido antes de enviar o POST (ref:
`BookingModal`, `services`, `PublicProviderSchema`, `CreateBookingRequest`,
`CreateBookingEndpoint`, `GetPublicProviderByIdOrSlugEndpoint`).
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 70-76: The POST is sending TimeOnly strings (slot.start/slot.end)
which parseAsUtc cannot parse (no "T") and leads to Invalid Date and JSON
deserialization failure; update booking-modal.tsx to combine the selected date
with the TimeOnly values into full DateTime strings before sending: build
start/end by merging selectedDate (the date the user picked) with
selectedSlot.start and selectedSlot.end into an ISO/local DateTime (e.g.
YYYY-MM-DDTHH:mm:ss) and use those values in the request body (the
providerId/serviceId/start/end payload), and also ensure any local formatting
calls (format(parseAsUtc(slot.start), "...")) use the combined Date or skip
parseAsUtc for pure time-only values; this fixes parseAsUtc, format(...) and the
CreateBookingCommand DateTimeOffset deserialization errors.
In `@tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs`:
- Line 30: Substitua o comentário em inglês "Default to Development for existing
tests" por sua versão em português no arquivo de teste (mantenha a mesma posição
do comentário); por exemplo, troque por "Padrão para Development para testes
existentes" ou uma tradução equivalente em português que preserve o contexto;
lembre-se de manter mensagens de log em inglês, mas quaisquer comentários e
documentação XML (/// <summary>, /// <param>, etc.) devem estar em português
conforme o padrão do repositório.
---
Duplicate comments:
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs`:
- Around line 39-52: Before calling TimeZoneInfo.ConvertTimeToUtc on
startDate/endDate in GetBookingByIdQueryHandler (where startDate, endDate and tz
are used), check tz.IsInvalidTime(startDate) / tz.IsAmbiguousTime(startDate) and
same for endDate; implement an explicit policy (e.g., reject with a controlled
error/exception, normalize to a specific offset, or choose the earlier/later
offset) and return a failure result or throw a well-defined exception instead of
letting ConvertTimeToUtc throw; ensure the same handling is applied consistently
before creating the BookingDto so ambiguous/invalid DST times are handled
deterministically.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Around line 19-37: The handler currently materializes all bookings via
bookingRepository.GetByProviderIdAsync and then filters and paginates in memory;
instead validate query.Page and query.PageSize and push the date filters and
pagination into the repository (e.g., add or overload
bookingRepository.GetByProviderIdAsync to accept From/To DateOnly?, int page,
int pageSize or a query object) so the repository applies WHERE Date >= From AND
Date <= To and LIMIT/OFFSET (or equivalent) at the DB level; then call that
repository method from GetBookingsByProviderQueryHandler and remove the
in-memory Where/Skip/Take logic and the local pagedBookings materialization.
---
Nitpick comments:
In `@src/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cs`:
- Around line 37-50: The endpoint currently creates a new Guid for correlation
when constructing GetBookingsByClientQuery; change it to read the incoming
"X-Correlation-Id" header (fallback to Guid.NewGuid() if missing) and pass that
value into the GetBookingsByClientQuery constructor (same pattern used by
SetProviderScheduleEndpoint), and update the OpenAPI contract by adding
ProducesProblem entries for other possible error statuses (e.g.,
.ProducesProblem(StatusCodes.Status400BadRequest) and
.ProducesProblem(StatusCodes.Status500InternalServerError)) so the endpoint's
declared responses match the actual Results.Problem usage.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 88-99: The current code constructs DateTimeOffset via
TimeZoneInfo.ConvertTime(new DateTimeOffset(startUtc), tz) (same for endUtc)
which is an extra conversion; replace those expressions with a direct conversion
like TimeZoneInfo.ConvertTimeFromUtc(startUtc, tz) (and for endUtc) or build a
DateTimeOffset by applying tz.GetUtcOffset(startUtc) to the UTC instant before
passing to the BookingDto constructor (refer to BookingDto creation where
startUtc/endUtc are converted).
- Around line 102-134: In ResolveTimeZone, do not swallow exceptions when
failing to resolve schedule.TimeZoneId; instead catch the exception, log a
warning including the timeZoneId and the exception (e.g., logger.LogWarning(ex,
"Failed to resolve time zone '{TimeZoneId}'", timeZoneId)), then continue with
the existing fallback logic that calls TimeZoneInfo.FindSystemTimeZoneById("E.
South America Standard Time") and ultimately
TimeZoneInfo.Local/TimeZoneInfo.Utc; keep the same catch/fallback structure but
ensure the initial catch records the exception details (including
TimeZoneNotFoundException/InvalidTimeZoneException) so misconfigured schedules
are visible in logs.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cs`:
- Around line 47-49: Remova a configuração de mock desnecessária: o trecho que
chama ProviderSchedule.Create(providerId) e o _scheduleRepoMock.Setup(x =>
x.GetByProviderIdAsync(providerId,
It.IsAny<CancellationToken>())).ReturnsAsync(schedule) deve ser excluído do
teste GetBookingsByClientQueryHandlerTests porque o handler testado busca por
ClientId, não por ProviderId, e esse setup nunca é exercitado; procure pelas
referências a _scheduleRepoMock e GetByProviderIdAsync dentro do método de teste
e apague apenas essas linhas para reduzir ruído.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs`:
- Around line 27-76: Adicionar testes unitários em
GetBookingsByProviderQueryHandlerTests para cobrir os ramos de filtro (From/To),
paginação (Page/PageSize) e comportamento de timezone (conversão/fallback): crie
métodos de teste que configurem _bookingRepoMock.GetByProviderIdAsync com um
conjunto de bookings em várias datas/horários, invoquem _sut.HandleAsync
passando GetBookingsByProviderQuery com From/To e com Page/PageSize para validar
que apenas os bookings dentro do intervalo e página são retornados, e teste
também queries com timezone inválido/null para assegurar que o handler aplica o
timezone fallback/conversão esperada; use ProviderSchedule via
_scheduleRepoMock.GetByProviderIdAsync quando necessário e asserte counts,
ProviderId, e que os TimeSlot/DateOnly resultantes foram convertidos/filtrados
corretamente.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Line 32: Remova a variável não utilizada baseDate declarada em
SetProviderScheduleCommandHandlerTests (variável "baseDate") — simplesmente
eliminar a declaração onde aparece no teste para limpar código e evitar warning;
não é necessário substituí-la por outra referência.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 159-163: The branch mixes the new boolean flag isAuthenticated
with the old status string check (status === "authenticated"); update the
conditional to use isAuthenticated consistently (and keep isLoadingAuth as the
loading guard) so the ternary reads the same predicate everywhere; search for
any other occurrences of status === "authenticated" in this component and
replace them with isAuthenticated (or adjust surrounding logic if status is
still required) to maintain consistent auth checks in the component containing
isAuthenticated, isLoadingAuth, and status.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Line 113: O botão de confirmar está desabilitado por múltiplas condições
agrupadas em isConfirmDisabled (variáveis: selectedSlot, serviceId,
createBooking.isPending, session, session.user.id, session.accessToken) e o
usuário não recebe feedback sobre o motivo; ajuste a UI para mostrar uma
mensagem inline quando !serviceId (por exemplo "Selecione um serviço para
agendar") próxima ao botão/seleção de serviço e torne o estado visível, e
adicione lógica ao fluxo de criação em createBooking para detectar perda de
sessão durante a operação (checar session/user/accessToken antes e após a
chamada) e, se a sessão sumir, fechar o modal e disparar toast.error com
mensagem clara; aplique a mesma correção nos trechos semelhantes referenciados
(linhas correspondentes ao mesmo componente/fluxo).
- Around line 154-166: O botão exibe só o horário de início
(format(parseAsUtc(slot.start), "HH:mm")); altere a label para mostrar "início –
fim": dentro do map em availability.slots (referência: TimeSlot, selectedSlot,
parseAsUtc, format) formate tanto o horário de início quanto o horário de
término; if TimeSlot expõe slot.end use format(parseAsUtc(slot.end), "HH:mm"),
otherwise calcule o fim a partir de slot.start + slot.duration (ou adicione
minutos conforme o campo de duração existente) e exiba `${start} – ${end}` na
label do botão mantendo a lógica de seleção e classes existentes.
In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs`:
- Around line 605-625: Remove the now-orphaned
ReconfigureSearchProvidersDbContext method: delete the private method
ReconfigureSearchProvidersDbContext from BaseTestContainerTest and any direct
calls to it, and rely on the generic ReconfigureDbContext<T> implementation to
reconfigure SearchProvidersDbContext instead; verify that
ReconfigureDbContext<SearchProvidersDbContext> is used where the old method was
referenced and that migrations/history schema and UseNetTopologySuite
configuration are preserved in the generic reconfiguration code.
🪄 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: 7bf45773-2895-4c27-9427-ed54a92ae68c
⛔ Files ignored due to path filters (4)
src/Modules/Bookings/Infrastructure/Persistence/Migrations/20260422143101_AddProviderSchedules.Designer.csis excluded by!**/Migrations/**,!**/*.Designer.cssrc/Modules/Bookings/Infrastructure/Persistence/Migrations/20260422143101_AddProviderSchedules.csis excluded by!**/Migrations/**src/Modules/Bookings/Tests/packages.lock.jsonis excluded by!**/packages.lock.jsonsrc/Web/MeAjudaAi.Web.Provider/components/dashboard/schedule-manager.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (68)
docs/modules/bookings.mddocs/roadmap-history.mddocs/technical-debt.mdsrc/Bootstrapper/MeAjudaAi.ApiService/Program.cssrc/Modules/Bookings/API/API.Client/Bookings/CancelBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/CompleteBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/ConfirmBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/CreateBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/GetBookingById.brusrc/Modules/Bookings/API/API.Client/Bookings/GetMyBookings.brusrc/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.brusrc/Modules/Bookings/API/API.Client/Bookings/GetProviderBookings.brusrc/Modules/Bookings/API/API.Client/Bookings/RejectBooking.brusrc/Modules/Bookings/API/API.Client/Bookings/SetProviderSchedule.brusrc/Modules/Bookings/API/Endpoints/BookingsEndpoints.cssrc/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/CompleteBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/SetProviderScheduleEndpoint.cssrc/Modules/Bookings/API/Extensions.cssrc/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommandValidator.cssrc/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Commands/RejectBookingCommand.cssrc/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cssrc/Modules/Bookings/Application/Bookings/Handlers/CompleteBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingByIdQuery.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cssrc/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cssrc/Modules/Bookings/Application/Extensions.cssrc/Modules/Bookings/Domain/Entities/Booking.cssrc/Modules/Bookings/Domain/Events/BookingCancelledDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingCompletedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cssrc/Modules/Bookings/Domain/Events/BookingRejectedDomainEvent.cssrc/Modules/Bookings/Domain/Repositories/IBookingRepository.cssrc/Modules/Bookings/Domain/ValueObjects/TimeSlot.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csprojsrc/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingByIdQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cssrc/Shared/Exceptions/GlobalExceptionHandler.cssrc/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsxsrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GlobalExceptionHandlerTests.cstests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cstests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cstests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cstests/MeAjudaAi.Shared.Tests/Unit/Exceptions/Exceptions/GlobalExceptionHandlerTests.cstests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs
💤 Files with no reviewable changes (1)
- tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/Exceptions/GlobalExceptionHandlerTests.cs
✅ Files skipped from review due to trivial changes (20)
- src/Modules/Bookings/API/API.Client/Bookings/GetMyBookings.bru
- src/Modules/Bookings/Application/Bookings/Commands/RejectBookingCommand.cs
- src/Modules/Bookings/API/API.Client/Bookings/GetProviderBookings.bru
- src/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cs
- src/Modules/Bookings/API/API.Client/Bookings/GetBookingById.bru
- src/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.bru
- src/Modules/Bookings/Application/Bookings/Commands/CancelBookingCommandValidator.cs
- src/Modules/Bookings/API/API.Client/Bookings/CompleteBooking.bru
- src/Modules/Bookings/API/API.Client/Bookings/CreateBooking.bru
- src/Modules/Bookings/API/API.Client/Bookings/SetProviderSchedule.bru
- src/Modules/Bookings/API/API.Client/Bookings/CancelBooking.bru
- src/Modules/Bookings/API/API.Client/Bookings/RejectBooking.bru
- src/Modules/Bookings/Application/Extensions.cs
- src/Modules/Bookings/Tests/MeAjudaAi.Modules.Bookings.Tests.csproj
- src/Web/MeAjudaAi.Web.Customer/tests/components/bookings/booking-modal.test.tsx
- src/Modules/Bookings/Application/Bookings/DTOs/AvailabilityDto.cs
- src/Modules/Bookings/API/API.Client/Bookings/ConfirmBooking.bru
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs
- docs/modules/bookings.md
- src/Modules/Bookings/Domain/Entities/Booking.cs
🚧 Files skipped from review as they are similar to previous changes (22)
- src/Modules/Bookings/Domain/Events/BookingRejectedDomainEvent.cs
- src/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cs
- src/Modules/Bookings/Application/Bookings/Queries/GetBookingByIdQuery.cs
- src/Modules/Bookings/Domain/Events/BookingCompletedDomainEvent.cs
- src/Modules/Bookings/Domain/Events/BookingCreatedDomainEvent.cs
- src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByClientQuery.cs
- src/Shared/Exceptions/GlobalExceptionHandler.cs
- src/Modules/Bookings/API/Extensions.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingByIdQueryHandlerTests.cs
- src/Modules/Bookings/API/Endpoints/BookingsEndpoints.cs
- src/Bootstrapper/MeAjudaAi.ApiService/Program.cs
- tests/MeAjudaAi.Integration.Tests/Base/BaseApiTest.cs
- src/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cs
- src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs
- src/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/RejectBookingCommandHandlerTests.cs
- src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs
- src/Modules/Bookings/Domain/Repositories/IBookingRepository.cs
…obal exception handling
…itory methods with timezone support
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (2)
src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (2)
75-81:⚠️ Potential issue | 🔴 CriticalPayload
start/endsem sufixo de fuso + premissa de formato incorreta.Duas falhas combinadas:
- Se
selectedSlot.startvem como"HH:mm:ss"(formato padrão deTimeOnlyemSystem.Text.Json), concatenar+ ":00"gera"2026-04-22T09:00:00:00", que o backend falha a desserializar comoDateTimeOffset(400).- Mesmo se o formato fosse
"HH:mm", a string gerada"2026-04-22T09:00:00"não tem offset/Z. Ao ser parseada emDateTimeOffset, o .NET aplica o offset da máquina do servidor — divergência silenciosa quando cliente e servidor estão em fusos distintos, resultando em agendamentos com horário diferente do que o usuário viu.Recomenda-se normalizar o formato do horário e enviar um ISO-8601 completo com offset local (ou UTC) derivado da data local do usuário. Usar a função
combineDateAndTimesugerida no comentário acima resolve ambos os pontos.🛡️ Sugestão
- body: JSON.stringify({ - providerId, - serviceId, - start: format(selectedDate, "yyyy-MM-dd") + "T" + selectedSlot.start + ":00", - end: format(selectedDate, "yyyy-MM-dd") + "T" + selectedSlot.end + ":00" - }) + body: JSON.stringify({ + providerId, + serviceId, + // ISO-8601 com offset local do usuário (ex.: 2026-04-22T09:00:00-03:00) + start: combineDateAndTime(selectedDate, selectedSlot.start).toISOString(), + end: combineDateAndTime(selectedDate, selectedSlot.end).toISOString() + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 75 - 81, The payload currently builds start/end by concatenating format(selectedDate, "yyyy-MM-dd") + "T" + selectedSlot.start + ":00" which produces invalid times (double seconds) and omits an ISO-8601 offset; replace that logic in the booking-modal payload construction so it uses the helper combineDateAndTime(selectedDate, selectedSlot.start) and combineDateAndTime(selectedDate, selectedSlot.end) (which should parse the TimeOnly string correctly, normalize to a Date/DateTimeOffset in the user's local zone and return an ISO-8601 string with offset or Z) instead of manual string concatenation; remove the extra ":00" concatenation and ensure the JSON body fields providerId/serviceId remain unchanged.
110-118:⚠️ Potential issue | 🔴 Critical
parseAsUtcainda produzInvalid Datequandoslot.startnão contémT.
TimeSlotDtono backend usaTimeOnly, que oSystem.Text.Jsonserializa como"HH:mm:ss"por padrão. Quandoslot.start === "09:00:00",tIndex === -1e a função cai emreturn new Date(isoString)— que em JS produzInvalid Datepara strings puras de tempo. Consequentemente, a linha 171 (format(parseAsUtc(slot.start), "HH:mm")) renderiza"Invalid Date"ou lança exceção emdate-fns.A correção recomendada é combinar a data selecionada com o horário do slot aqui também, em vez de tentar parsear um tempo isolado. Por exemplo, receber
selectedDatecomo referência e construirnew Date(year, month-1, day, hh, mm, ss)— o que inclusive reflete o horário no fuso local do usuário, consistente com o servidor.#!/bin/bash # Confirmar o formato real retornado pelo endpoint de availability (TimeOnly → JSON) fd -t f 'AvailabilityDto.cs' -e cs | xargs cat fd -t f 'TimeSlotDto.cs' -e cs 2>/dev/null | xargs cat 2>/dev/null # Buscar configuração customizada de JsonSerializerOptions para TimeOnly rg -nP --type=cs -C2 'TimeOnly.*Converter|JsonConverter.*TimeOnly' rg -nP --type=cs -C2 'JsonSerializerOptions|AddJsonOptions' | head -40 # Verificar testes do booking-modal que mockam resposta fd -i 'booking-modal.test' | xargs cat 2>/dev/null | head -80🛡️ Abordagem sugerida
- // Auxiliar para garantir parsing UTC de strings ISO sem fuso - const parseAsUtc = (isoString: string) => { - if (!isoString) return new Date(); - const tIndex = isoString.indexOf('T'); - if (tIndex === -1) return new Date(isoString); // Formato não-ISO - - const suffix = isoString.substring(tIndex); - const hasTz = suffix.includes('Z') || suffix.includes('+') || suffix.includes('-'); - return new Date(hasTz ? isoString : `${isoString}Z`); - }; + // Combina o dia selecionado com o horário do slot (TimeOnly "HH:mm:ss" ou "HH:mm") no fuso local + const combineDateAndTime = (baseDate: Date, time: string) => { + const [hh = "0", mm = "0", ss = "0"] = (time || "").split(":"); + return new Date( + baseDate.getFullYear(), + baseDate.getMonth(), + baseDate.getDate(), + Number(hh), Number(mm), Number(ss), 0 + ); + };E na renderização (linha 171):
- {format(parseAsUtc(slot.start), "HH:mm")} - {format(parseAsUtc(slot.end), "HH:mm")} + {format(combineDateAndTime(selectedDate, slot.start), "HH:mm")} - {format(combineDateAndTime(selectedDate, slot.end), "HH:mm")}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 110 - 118, parseAsUtc currently returns new Date(isoString) for time-only strings (e.g., "09:00:00") which yields Invalid Date; change the logic so that when slot.start lacks a 'T' you combine the selectedDate with the TimeOnly string to build a local Date (extract year/month/day from the existing selectedDate and hours/minutes/seconds from slot.start) instead of calling new Date(isoString). Update parseAsUtc (or replace it with a helper like buildDateFromSelectedAndTime) to accept selectedDate (or read it from the surrounding scope) and return new Date(year, month-1, day, hh, mm, ss) so format(parseAsUtc(slot.start), ...) renders correctly and matches server intent.
🧹 Nitpick comments (13)
src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs (2)
56-91: Proteja os fluxos de falha contra persistência acidental.Os testes validam o
Result, mas ainda passariam se o handler escrevesse no repositório antes de retornar falha. AdicioneVerify(..., Times.Never)paraAddAsync/UpdateAsyncnos cenários de erro; paraProviderNotFounde falha da API de Providers, também verifique queGetByProviderIdAsyncnão foi chamado.Also applies to: 123-174
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` around lines 56 - 91, In the failing-case tests HandleAsync_Should_Fail_When_ProviderNotFound and HandleAsync_Should_Fail_When_ProvidersApi_Returns_Failure, add verifications to ensure no persistence or retrieval is performed: call Verify(..., Times.Never) on the repository methods AddAsync and UpdateAsync and also Verify(..., Times.Never) on GetByProviderIdAsync after arranging the ProviderExistsAsync setup and invoking _sut.HandleAsync(command); this ensures no accidental calls to persistence or provider lookup occur when ProviderExistsAsync returns false or a failure.
27-54: Valide o conteúdo salvo, não apenas a chamada ao repositório.Estes testes passariam mesmo se o handler chamasse
UpdateAsync/AddAsynccom uma agenda vazia ou com horários diferentes dos enviados no comando. Capture oProviderSchedulerecebido pelo mock e valide o dia/intervalo esperado (Monday,08:00-12:00); no cenário de update, também vale verificar queAddAsyncnão foi chamado.Also applies to: 93-120
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` around lines 27 - 54, Capture the ProviderSchedule passed into the repository mocks and assert its contents match the command's availabilities (e.g., Monday 08:00-12:00) instead of only verifying UpdateAsync was invoked: in the test for HandleAsync (SetProviderScheduleCommandHandlerTests) use Moq's Callback/It.Is to capture the ProviderSchedule argument from _scheduleRepoMock.UpdateAsync/ AddAsync and assert the internal entries/days/time slots equal the command's AvailabilityDto (DayOfWeek.Monday, TimeOnly 08:00–12:00); for the update scenario also assert _scheduleRepoMock.Verify(x => x.AddAsync(...), Times.Never) so AddAsync wasn't called.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (2)
195-207: Useservice.idcomokeyem vez do índice.Agora que
servicesretorna objetos comid: uuid, okey={i}emBadgepode ser substituído pelo GUID para ajudar o React a reconciliar corretamente e evitar bugs sutis em reorderings/atualizações parciais.💡 Sugestão
- {services.map((service, i) => ( + {services.map((service) => ( <Badge - key={i} + key={service.id} className="px-3 py-1 bg-[`#E0702B`] text-white text-sm rounded-full" > {service.name} </Badge> ))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 195 - 207, The Badge elements are using the array index as the React key which can cause reconciliation bugs; update the map in the services rendering (the services.map callback that returns <Badge>) to use the unique service.id (the UUID) as the key instead of key={i}, i.e. replace the index-based key with key={service.id} so React can correctly track items during reorders/updates.
110-128: Comportamento silencioso quando o prestador não possui serviços.Quando
servicesestá vazio,serviceId={services[0]?.id || ""}passa string vazia, e oBookingModalinternamente desabilita o botão "Confirmar" com a mensagem "Nenhum serviço disponível para agendamento" — porém o botão "Agendar Horário" do trigger ainda é renderizado e abre a modal, levando o usuário a uma experiência confusa (abre a modal só para descobrir que não pode agendar).Considere esconder/desabilitar o trigger quando
services.length === 0, análogo ao que já é feito para o estado de loading, para dar feedback imediato sem abrir a modal.💡 Sugestão
- ) : isAuthenticated ? ( + ) : isAuthenticated && services.length > 0 ? ( <BookingModal providerId={id} providerName={displayName} serviceId={services[0]?.id || ""} /> - ) : ( + ) : isAuthenticated ? ( + <Button disabled className="w-full bg-slate-200 text-slate-500 py-6 text-lg"> + Agendamento indisponível + </Button> + ) : ( <Button asChild className="w-full bg-[`#E0702B`] hover:bg-[`#C55A1F`] text-white font-bold py-6 text-lg"> <Link href={`/api/auth/signin?callbackUrl=${encodeURIComponent(`/prestador/${id}`)}`}>Entrar para Agendar</Link> </Button> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 110 - 128, The BookingModal is still triggered when services is empty (serviceId becomes ""), causing a confusing modal that only shows "Nenhum serviço disponível..."; update the rendering logic around isLoadingAuth/isAuthenticated to also check services.length > 0: when services.length === 0 render a disabled full-width Button (or a non-clickable placeholder) with the same loading-style feedback instead of the BookingModal, and only render BookingModal when services.length > 0 (preserve providerId/id and providerName/displayName props and the existing auth flows with Link for unauthenticated users).src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (1)
159-161: Tipagem implícita do payload de availability.
availabilityéany(resultado deres.json()), entãoavailability?.slots?.length > 0escapa ao checker. Considere tipar a resposta (ex.:{ dayOfWeek: number; slots: TimeSlot[] }) e validar comzod, alinhado ao padrão usado empage.tsxcomPublicProviderSchema. Isso previne regressões caso o backend mude o contrato (ex.: renomearslotsou alterar o formato deTimeOnly).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 159 - 161, O payload de availability está sendo tratado como any (resultado de res.json()), o que permite fugas do type checker ao usar availability?.slots; altere o fetch/parsing para definir e validar o tipo esperado (ex.: { dayOfWeek: number; slots: TimeSlot[] }) e adote validação com zod similar a PublicProviderSchema em page.tsx: create a schema (incluindo TimeOnly/TimeSlot shape), parse the response from res.json() and only use availability.slots after successful validation; update usages in booking-modal.tsx (referencing availability, slots, TimeSlot) to rely on the validated/typed object to prevent runtime regressions.tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs (1)
586-600:UseNetTopologySuite()agora é aplicado a todos os DbContexts.Ao consolidar
ReconfigureSearchProvidersDbContextno helper genérico,npgsqlOptions.UseNetTopologySuite()(linha 592) passa a ser habilitado paraUsersDbContext,DocumentsDbContext,CommunicationsDbContext,BookingsDbContext, etc. Em runtime é inofensivo (apenas registra mappers de tipos geométricos), mas:
- Requer que
Npgsql.NetTopologySuiteesteja transitivamente resolvido no assembly do host de testes (já é, via SearchProviders).- Esconde o fato de que somente SearchProviders realmente depende de PostGIS — alguém lendo o helper pode achar que todos os módulos precisam disso.
Considere encapsular a ativação do NetTopologySuite por contexto (ex.: via predicado
typeof(TContext) == typeof(SearchProvidersDbContext)) ou ao menos um comentário explicando por que está aplicado globalmente. Não-bloqueador.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs` around lines 586 - 600, The UseNetTopologySuite() call is being applied to every DbContext in the generic service registration; restrict it to only the SearchProviders context or document why it's global. Update the DbContext registration in ReconfigureSearchProvidersDbContext (where TContext is used) to conditionally call npgsqlOptions.UseNetTopologySuite() only when typeof(TContext) == typeof(SearchProvidersDbContext) (or an equivalent predicate), or add a clear inline comment next to UseNetTopologySuite explaining that only SearchProviders requires PostGIS and why the transitive dependency is acceptable.tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs (1)
79-86:serviceIdaleatório não exercita integração com ServiceCatalogs.O
CreateBookingCommandHandlervalida a existência do provider e sua agenda, mas não valida se oserviceIdexiste ou pertence ao provider. Assim, o teste passa com um GUID aleatório, sem exercitar o fluxo realista onde um serviço deve ser criado antes do agendamento.Considere criar um
ServiceCatalog/Servicereal via API antes de abrir o booking para refletir o fluxo ponta-a-ponta completo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs` around lines 79 - 86, O teste está usando um GUID aleatório para serviceId, o que não exercita integração com ServiceCatalogs; update the test to create a real service via the API (create a ServiceCatalog/Service owned by providerIdClaim) and use the returned service ID in bookingRequest so CreateBookingCommandHandler validates against an existing provider-owned service; locate where bookingRequest is built and replace the random Guid.NewGuid() for serviceId with the serviceId returned from the service-creation call, ensuring any auth/headers use providerIdClaim.src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs (1)
15-132: LGTM — boa cobertura dos quatro cenários.Os quatro casos (sucesso, status inválido, dono incorreto e booking inexistente) exercitam as transições e verificações de autorização do handler de forma adequada, com
Verify(..., Times.Once/Never)apropriados emUpdateAsync. Datas relativas (UtcNow.AddDays(30)) já evitam a expiração de testes com datas fixas.Nota opcional: o helper
SetupUserse repete em outros*BookingCommandHandlerTests. Se quiser reduzir duplicação em um próximo passo, um método emBaseUnitTestou um pequenoHttpContextTestBuilderdeixaria os testes ainda mais enxutos — não é bloqueante.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs` around lines 15 - 132, No code changes required — the tests in CompleteBookingCommandHandlerTests (methods HandleAsync_Should_Complete_When_BookingIsConfirmed, HandleAsync_Should_Fail_When_BookingIsPending, HandleAsync_Should_Fail_When_UserIsNotOwner, HandleAsync_Should_Fail_When_BookingNotFound) correctly cover success, invalid status, unauthorized user and not-found scenarios with appropriate UpdateAsync Verify assertions; optionally consider extracting SetupUser to BaseUnitTest or an HttpContextTestBuilder in a follow-up to reduce duplication.src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (2)
167-176: Catch genérico pode engolir exceções não relacionadas a concorrência.O
catch (Exception ex)captura qualquer falha dentro do bloco — incluindo bugs de mapeamento, validação de modelo EF,DbUpdateExceptionnão-PG, etc. Hoje o fluxo relança no final (linha 191), então o comportamento é correto, mas vale registrar o erro original antes de seguir para a lógica de retry/re-throw para facilitar triagem em produção:📝 Ajuste sugerido
catch (Exception ex) { try { await transaction.RollbackAsync(CancellationToken.None); } - catch + catch (Exception rollbackEx) { - // Ignora erro de rollback + // Ignora erro de rollback, mas registra para diagnóstico + // logger.LogWarning(rollbackEx, "Rollback failed after primary error"); }Para isso, injete
ILogger<BookingRepository>no construtor (o padrão já é adotado em outros repositórios do módulo). Não é bloqueante, mas melhora bastante a observabilidade em incidentes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 167 - 176, The generic catch in BookingRepository (catch (Exception ex)) swallows context; inject an ILogger<BookingRepository> into the BookingRepository constructor (matching other repositories) and use it to log the caught exception (ex) with a clear message before attempting transaction.RollbackAsync and before rethrowing/retrying; ensure you log inside that catch block and keep the existing rollback and rethrow behavior intact so observability is improved without changing control flow.
125-195: Resetar o ChangeTracker entre tentativas evita estados residuais no retry.Boa evolução endereçando os feedbacks anteriores (execution strategy,
await using, filtro por data, detecção deSqlStatevia cadeia deInnerException). Um ponto sutil ainda:Quando
SaveChangesAsync(linha 158) falha com40001/40P01e a transação é revertida, a entidadebookingpermanece noChangeTrackercomoAdded. Na próxima iteração dowhile (true), oAnyAsynccontinua sendo resolvido no banco (ok), mas quaisquer alterações pendentes do primeiro attempt continuam rastreadas e podem interagir de forma inesperada (ex.: se obookingganhar navegações ou eventos de domínio que disparem handlers). Para isolamento máximo entre tentativas, limpe o tracker antes doAddAsyncdo retry:♻️ Ajuste sugerido
while (true) { attempt++; + if (attempt > 1) + { + context.ChangeTracker.Clear(); + } await using var transaction = await context.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken);Alternativamente, mover o
AddAsync+SaveChangesAsyncpara um bloco que crie explicitamente um escopo limpo a cada iteração. Não é crítico hoje (o caso comum deve ser sucesso na primeira tentativa), mas protege contra regressões futuras.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 125 - 195, In AddIfNoOverlapAsync the tracked booking entity can remain in the DbContext across retry attempts causing side effects; before calling context.Bookings.AddAsync(booking, ...) on each loop iteration (i.e. inside the while in AddIfNoOverlapAsync, right before the AddAsync/SaveChangesAsync sequence) clear the context ChangeTracker to detach previous entities (use context.ChangeTracker.Clear() or equivalent) so each retry runs against a clean scope; ensure this clearing happens on every attempt after starting the transaction and before AddAsync to avoid residual Added state affecting subsequent attempts.src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs (2)
25-33: Log distinto para acesso não autorizado ajuda em auditoria de segurança.A resposta
NotFoundpara o caminho não-autorizado está correta (evita leaking de existência), mas internamente é útil distinguir os dois cenários no log para detectar tentativas de acesso cruzado (ex.: enumeração de IDs por um cliente tentando acessar bookings de outro provider). Acrescente umLogWarningno branch de autorização:📝 Sugestão
if (!isAuthorized) { + logger.LogWarning( + "Unauthorized booking access attempt. BookingId={BookingId}, UserId={UserId}, ProviderId={ProviderId}", + query.BookingId, query.UserId, query.ProviderId); return Result<BookingDto>.Failure(Error.NotFound("Agendamento não encontrado.")); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs` around lines 25 - 33, Add an internal warning log in the unauthorized branch to aid auditing without changing the external NotFound response: when isAuthorized is false (the block that currently returns Result<BookingDto>.Failure(Error.NotFound("Agendamento não encontrado."))), call the handler's logger (e.g., _logger.LogWarning or processLogger.LogWarning) including non-sensitive context such as booking.Id, query.UserId, query.ProviderId and query.IsSystemAdmin to record who attempted access and which booking was targeted; keep the return value unchanged so the caller still receives NotFound.
42-69: Comentário sobre ambiguidade diverge da semântica deGetAmbiguousTimeOffsets.Na documentação do .NET,
GetAmbiguousTimeOffsetsretornaoffsets[0]= offset antes da transição eoffsets[1]= offset depois. Para fall-back (ex.: fim do horário de verão),offsets[0]é o offset de horário de verão (daylight) eoffsets[1]é o offset de horário padrão. O comentário da linha 52 ("o primeiro offset (geralmente o horário de verão que está terminando)") está correto para fall-back de DST, mas o log da linha 51 diz "earlier offset" — que é ambíguo, pois "earlier" pode ser lido como offset numericamente menor ou temporalmente anterior. Esclareça:📝 Sugestão
- logger.LogInformation("Ambiguous start time detected for booking {BookingId}. Choosing the earlier offset.", booking.Id); + logger.LogInformation("Ambiguous start time detected for booking {BookingId}. Choosing the pre-transition (DST) offset.", booking.Id);Adicionalmente,
DateTime.SpecifyKind(startDate, DateTimeKind.Unspecified)na linha 54 é redundante —DateOnly.ToDateTime(TimeOnly)já retornaKind=Unspecified. Pode ser removido.TimeZoneInfo.GetAmbiguousTimeOffsets ordering first element before or after transition .NET🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs` around lines 42 - 69, The code mishandles/logs ambiguous DST offsets: update the log messages in GetBookingByIdQueryHandler to explicitly state that you are choosing the offset returned as offsets[0] (the offset before the transition, typically the daylight offset on fall-back) rather than saying "earlier offset"; remove the redundant DateTime.SpecifyKind(...) calls for startDate and endDate (DateOnly.ToDateTime already yields Kind=Unspecified), and keep the existing behavior of constructing a DateTimeOffset from the ambiguous local date with offsets[0] and assigning dto.UtcDateTime to startDate/endDate so the subsequent TimeZoneInfo.ConvertTimeToUtc logic remains correct.src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs (1)
59-88: Duplicação deResolveTimeZoneentre três handlers.A mesma cadeia de fallback (
timeZoneId→E. South America Standard Time→America/Sao_Paulo→ UTC) aparece emGetBookingByIdQueryHandler, neste handler e — pelo padrão — emGetBookingsByClientQueryHandler. Extraia para um utilitário compartilhado (ex.:MeAjudaAi.Modules.Bookings.Application.Common.TimeZoneResolver) aceitando umILoggere injete via DI ou use como método estático. Isso evita divergência de comportamento (hoje este handler não loga quando falha emtimeZoneId, enquantoGetBookingByIdQueryHandlerloga).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs` around lines 59 - 88, The ResolveTimeZone method is duplicated across GetBookingsByProviderQueryHandler, GetBookingByIdQueryHandler and GetBookingsByClientQueryHandler; extract the fallback logic (timeZoneId → "E. South America Standard Time" → "America/Sao_Paulo" → UTC) into a shared utility (e.g., class MeAjudaAi.Modules.Bookings.Application.Common.TimeZoneResolver) that exposes either a static ResolveTimeZone(string? timeZoneId) or an instance method ResolveTimeZone(string? timeZoneId, ILogger logger) and register/inject it via DI into the handlers (or call the static method), then remove the local ResolveTimeZone methods from GetBookingsByProviderQueryHandler, GetBookingByIdQueryHandler and GetBookingsByClientQueryHandler and replace calls with the shared resolver; ensure the resolver logs the failure to parse the provided timeZoneId consistently (use the injected ILogger or internal logging) so all handlers share the same behavior.
🤖 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/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cs`:
- Around line 32-35: The 401 branch in GetMyBookingsEndpoint currently returns
Results.Unauthorized() while the endpoint metadata documents a 401 as
ProblemDetails, causing a contract mismatch; either update the endpoint metadata
to Produces(StatusCodes.Status401Unauthorized) to reflect a body-less 401, or
change the branch to return a ProblemDetails response (use Results.Problem(...)
with status 401 and appropriate title/detail) so it matches the documented
RFC7807 shape—apply the same change to the other occurrence around the lines
noted (the second 401 at the 50–51 area).
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Around line 39-54: The current mapping in GetBookingsByProviderQueryHandler
builds startDate/endDate from DateOnly.ToDateTime and then does a no-op
UTC→local conversion which can throw on DST gaps; replace that conversion with
the same validation used in GetBookingByIdQueryHandler: check
TimeZoneInfo.IsInvalidTime and TimeZoneInfo.IsAmbiguousTime for the generated
DateTime (startDate and endDate) and handle/normalize ambiguous/invalid times
the same way as GetBookingByIdQueryHandler before assigning to the BookingDto.
Extract the validation + DTO construction into a helper method (e.g.,
BuildValidatedBookingDto or ValidateAndCreateBookingDto) and call it from both
GetBookingsByProviderQueryHandler and reuse in GetBookingByIdQueryHandler to
ensure consistent handling of DST gaps/ambiguous times when creating BookingDto
instances.
In `@src/Modules/Bookings/Domain/Repositories/IBookingRepository.cs`:
- Around line 10-14: Remova os métodos não usados ou marque-os obsoletos: no
IBookingRepository remova ou adicione [Obsolete("Use paged methods")] aos
métodos GetByProviderIdAsync, GetByClientIdAsync e GetByProviderAndStatusAsync;
faça a mesma mudança na implementação concreta (ex.: BookingRepository)
eliminando as implementações correspondentes ou decorando-as com [Obsolete] e
uma mensagem de migração para GetByProviderIdPagedAsync /
GetByClientIdPagedAsync, e atualize quaisquer referências restantes
(handlers/endpoints) para usar as variantes paginadas se encontradas.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs`:
- Around line 115-146: The test HandleAsync_Should_Use_Provider_TimeZone
currently does a round-trip that always returns the same local time; change the
assertion to validate an actual timezone conversion by comparing UTC values:
compute the expected UTC start (using TimeZoneInfo.FindSystemTimeZoneById or a
CI-stable IANA id like "Asia/Tokyo") from the local date/time and assert
dto.Start.ToUniversalTime() equals that expected UTC, or alternatively assert
using DateTimeOffset if BookingDto exposes an offset; update the test to
reference GetBookingsByProviderQueryHandlerTests, the dto (BookingDto) Start/End
properties, and the TimeZoneInfo lookup so it actually verifies conversion
rather than a no-op round-trip.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Line 132: Traduza todos os comentários auxiliares e a documentação XML dentro
de SetProviderScheduleCommandHandlerTests para pt-BR, mantendo os cabeçalhos AAA
"Arrange", "Act" e "Assert" em inglês; por exemplo, atualize comentários
próximos à expressão new(new TimeOnly(12, 0), new TimeOnly(8, 0)) e a outra
ocorrência apontada (linha similar cerca de 159) para português, sem alterar os
labels "Arrange"/"Act"/"Assert", e verifique métodos de teste na classe
SetProviderScheduleCommandHandlerTests para garantir consistência.
In `@src/Shared/Exceptions/GlobalExceptionHandler.cs`:
- Around line 142-144: O texto de fallback exibido quando env.IsDevelopment()
for falso está em inglês; substitua a string "An unexpected error occurred" por
uma mensagem em português consistente com o restante do handler (por exemplo
"Ocorreu um erro inesperado") no bloco que monta a resposta dentro de
GlobalExceptionHandler.cs; verifique o uso de exception.GetType().Name e
exception.Message para manter o comportamento atual em dev e garantir que apenas
a versão de produção seja a mensagem em português.
- Around line 132-137: Update the BadHttpRequestException mapping inside
GlobalExceptionHandler so it clamps the StatusCode to 400 when the
exception.StatusCode is < 400 or >= 500 instead of propagating potentially
invalid 500s, and conditionally include the originalMessage (from
badHttpRequestException.Message) only when the hosting environment indicates
development (e.g., using the existing env.IsDevelopment() or IHostEnvironment)
to avoid leaking parsing/internal details in production.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 53-99: The mutationFn inside createBooking is performing UI
side-effects and returning early instead of throwing (calling setOpen(false) and
toast.error(...) then return), which causes onSuccess to run incorrectly; change
mutationFn (in createBooking) to be pure: remove setOpen and toast calls from
mutationFn, throw an Error when there's no session or missing
clientId/accessToken (instead of returning), and let the existing onError handle
toast.error and onSuccess handle setOpen/cleanup; ensure mutationFn only
performs the API call and returns the parsed JSON or throws on non-ok responses
so UI changes live in onSuccess/onError.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs`:
- Around line 153-159: Antes de usar o operador null-forgiving em location,
valide que response.Headers.Location não é nulo e lance/afirme com mensagem
clara se faltar (replicar a verificação usada em CreateTestUserAsync);
specifically, replace the direct call to ExtractIdFromLocation(location!) after
ApiClient.PostAsJsonAsync with a null-check on response.Headers.Location (or
Assert.NotNull(response.Headers.Location, "Expected Location header when
creating provider")) then call ExtractIdFromLocation(location.ToString()); this
ensures a clear failure when the Location header is not returned.
- Around line 41-85: The test calculates dates in UTC causing flaky availability
checks; change the date derivation to use the provider's timezone (e.g.,
TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time")).
Specifically, replace the UTC-based tomorrow/dayOfWeek with a provider-local
date (var tz = TimeZoneInfo.FindSystemTimeZoneById(...); var localTomorrow =
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz).Date.AddDays(1); int
dayOfWeek = (int)localTomorrow.DayOfWeek) and build scheduleRequest using that
dayOfWeek; for booking, construct local start/end datetimes (localTomorrow +
TimeSpan.Parse("10:00:00") etc.) then convert those local times back to UTC for
startIso/endIso (TimeZoneInfo.ConvertTimeToUtc(...)) so schedule and booking use
the same provider timezone when compared in CreateBookingCommandHandler.
---
Duplicate comments:
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 75-81: The payload currently builds start/end by concatenating
format(selectedDate, "yyyy-MM-dd") + "T" + selectedSlot.start + ":00" which
produces invalid times (double seconds) and omits an ISO-8601 offset; replace
that logic in the booking-modal payload construction so it uses the helper
combineDateAndTime(selectedDate, selectedSlot.start) and
combineDateAndTime(selectedDate, selectedSlot.end) (which should parse the
TimeOnly string correctly, normalize to a Date/DateTimeOffset in the user's
local zone and return an ISO-8601 string with offset or Z) instead of manual
string concatenation; remove the extra ":00" concatenation and ensure the JSON
body fields providerId/serviceId remain unchanged.
- Around line 110-118: parseAsUtc currently returns new Date(isoString) for
time-only strings (e.g., "09:00:00") which yields Invalid Date; change the logic
so that when slot.start lacks a 'T' you combine the selectedDate with the
TimeOnly string to build a local Date (extract year/month/day from the existing
selectedDate and hours/minutes/seconds from slot.start) instead of calling new
Date(isoString). Update parseAsUtc (or replace it with a helper like
buildDateFromSelectedAndTime) to accept selectedDate (or read it from the
surrounding scope) and return new Date(year, month-1, day, hh, mm, ss) so
format(parseAsUtc(slot.start), ...) renders correctly and matches server intent.
---
Nitpick comments:
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs`:
- Around line 25-33: Add an internal warning log in the unauthorized branch to
aid auditing without changing the external NotFound response: when isAuthorized
is false (the block that currently returns
Result<BookingDto>.Failure(Error.NotFound("Agendamento não encontrado."))), call
the handler's logger (e.g., _logger.LogWarning or processLogger.LogWarning)
including non-sensitive context such as booking.Id, query.UserId,
query.ProviderId and query.IsSystemAdmin to record who attempted access and
which booking was targeted; keep the return value unchanged so the caller still
receives NotFound.
- Around line 42-69: The code mishandles/logs ambiguous DST offsets: update the
log messages in GetBookingByIdQueryHandler to explicitly state that you are
choosing the offset returned as offsets[0] (the offset before the transition,
typically the daylight offset on fall-back) rather than saying "earlier offset";
remove the redundant DateTime.SpecifyKind(...) calls for startDate and endDate
(DateOnly.ToDateTime already yields Kind=Unspecified), and keep the existing
behavior of constructing a DateTimeOffset from the ambiguous local date with
offsets[0] and assigning dto.UtcDateTime to startDate/endDate so the subsequent
TimeZoneInfo.ConvertTimeToUtc logic remains correct.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Around line 59-88: The ResolveTimeZone method is duplicated across
GetBookingsByProviderQueryHandler, GetBookingByIdQueryHandler and
GetBookingsByClientQueryHandler; extract the fallback logic (timeZoneId → "E.
South America Standard Time" → "America/Sao_Paulo" → UTC) into a shared utility
(e.g., class MeAjudaAi.Modules.Bookings.Application.Common.TimeZoneResolver)
that exposes either a static ResolveTimeZone(string? timeZoneId) or an instance
method ResolveTimeZone(string? timeZoneId, ILogger logger) and register/inject
it via DI into the handlers (or call the static method), then remove the local
ResolveTimeZone methods from GetBookingsByProviderQueryHandler,
GetBookingByIdQueryHandler and GetBookingsByClientQueryHandler and replace calls
with the shared resolver; ensure the resolver logs the failure to parse the
provided timeZoneId consistently (use the injected ILogger or internal logging)
so all handlers share the same behavior.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 167-176: The generic catch in BookingRepository (catch (Exception
ex)) swallows context; inject an ILogger<BookingRepository> into the
BookingRepository constructor (matching other repositories) and use it to log
the caught exception (ex) with a clear message before attempting
transaction.RollbackAsync and before rethrowing/retrying; ensure you log inside
that catch block and keep the existing rollback and rethrow behavior intact so
observability is improved without changing control flow.
- Around line 125-195: In AddIfNoOverlapAsync the tracked booking entity can
remain in the DbContext across retry attempts causing side effects; before
calling context.Bookings.AddAsync(booking, ...) on each loop iteration (i.e.
inside the while in AddIfNoOverlapAsync, right before the
AddAsync/SaveChangesAsync sequence) clear the context ChangeTracker to detach
previous entities (use context.ChangeTracker.Clear() or equivalent) so each
retry runs against a clean scope; ensure this clearing happens on every attempt
after starting the transaction and before AddAsync to avoid residual Added state
affecting subsequent attempts.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cs`:
- Around line 15-132: No code changes required — the tests in
CompleteBookingCommandHandlerTests (methods
HandleAsync_Should_Complete_When_BookingIsConfirmed,
HandleAsync_Should_Fail_When_BookingIsPending,
HandleAsync_Should_Fail_When_UserIsNotOwner,
HandleAsync_Should_Fail_When_BookingNotFound) correctly cover success, invalid
status, unauthorized user and not-found scenarios with appropriate UpdateAsync
Verify assertions; optionally consider extracting SetupUser to BaseUnitTest or
an HttpContextTestBuilder in a follow-up to reduce duplication.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Around line 56-91: In the failing-case tests
HandleAsync_Should_Fail_When_ProviderNotFound and
HandleAsync_Should_Fail_When_ProvidersApi_Returns_Failure, add verifications to
ensure no persistence or retrieval is performed: call Verify(..., Times.Never)
on the repository methods AddAsync and UpdateAsync and also Verify(...,
Times.Never) on GetByProviderIdAsync after arranging the ProviderExistsAsync
setup and invoking _sut.HandleAsync(command); this ensures no accidental calls
to persistence or provider lookup occur when ProviderExistsAsync returns false
or a failure.
- Around line 27-54: Capture the ProviderSchedule passed into the repository
mocks and assert its contents match the command's availabilities (e.g., Monday
08:00-12:00) instead of only verifying UpdateAsync was invoked: in the test for
HandleAsync (SetProviderScheduleCommandHandlerTests) use Moq's Callback/It.Is to
capture the ProviderSchedule argument from _scheduleRepoMock.UpdateAsync/
AddAsync and assert the internal entries/days/time slots equal the command's
AvailabilityDto (DayOfWeek.Monday, TimeOnly 08:00–12:00); for the update
scenario also assert _scheduleRepoMock.Verify(x => x.AddAsync(...), Times.Never)
so AddAsync wasn't called.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 195-207: The Badge elements are using the array index as the React
key which can cause reconciliation bugs; update the map in the services
rendering (the services.map callback that returns <Badge>) to use the unique
service.id (the UUID) as the key instead of key={i}, i.e. replace the
index-based key with key={service.id} so React can correctly track items during
reorders/updates.
- Around line 110-128: The BookingModal is still triggered when services is
empty (serviceId becomes ""), causing a confusing modal that only shows "Nenhum
serviço disponível..."; update the rendering logic around
isLoadingAuth/isAuthenticated to also check services.length > 0: when
services.length === 0 render a disabled full-width Button (or a non-clickable
placeholder) with the same loading-style feedback instead of the BookingModal,
and only render BookingModal when services.length > 0 (preserve providerId/id
and providerName/displayName props and the existing auth flows with Link for
unauthenticated users).
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 159-161: O payload de availability está sendo tratado como any
(resultado de res.json()), o que permite fugas do type checker ao usar
availability?.slots; altere o fetch/parsing para definir e validar o tipo
esperado (ex.: { dayOfWeek: number; slots: TimeSlot[] }) e adote validação com
zod similar a PublicProviderSchema em page.tsx: create a schema (incluindo
TimeOnly/TimeSlot shape), parse the response from res.json() and only use
availability.slots after successful validation; update usages in
booking-modal.tsx (referencing availability, slots, TimeSlot) to rely on the
validated/typed object to prevent runtime regressions.
In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs`:
- Around line 586-600: The UseNetTopologySuite() call is being applied to every
DbContext in the generic service registration; restrict it to only the
SearchProviders context or document why it's global. Update the DbContext
registration in ReconfigureSearchProvidersDbContext (where TContext is used) to
conditionally call npgsqlOptions.UseNetTopologySuite() only when
typeof(TContext) == typeof(SearchProvidersDbContext) (or an equivalent
predicate), or add a clear inline comment next to UseNetTopologySuite explaining
that only SearchProviders requires PostGIS and why the transitive dependency is
acceptable.
In `@tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs`:
- Around line 79-86: O teste está usando um GUID aleatório para serviceId, o que
não exercita integração com ServiceCatalogs; update the test to create a real
service via the API (create a ServiceCatalog/Service owned by providerIdClaim)
and use the returned service ID in bookingRequest so CreateBookingCommandHandler
validates against an existing provider-owned service; locate where
bookingRequest is built and replace the random Guid.NewGuid() for serviceId with
the serviceId returned from the service-creation call, ensuring any auth/headers
use providerIdClaim.
🪄 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: 47959ee9-1076-46bb-bb8f-ddcfb131e123
⛔ Files ignored due to path filters (1)
src/Web/MeAjudaAi.Web.Provider/__tests__/components/dashboard/schedule-manager.test.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (26)
docs/roadmap-history.mdsrc/Modules/Bookings/API/Endpoints/Public/CompleteBookingEndpoint.cssrc/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cssrc/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cssrc/Modules/Bookings/Application/errors.jsonsrc/Modules/Bookings/Domain/Events/BookingCancelledDomainEvent.cssrc/Modules/Bookings/Domain/Repositories/IBookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CompleteBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Modules/Providers/Application/DTOs/PublicProviderDto.cssrc/Modules/Providers/Application/DTOs/PublicProviderServiceDto.cssrc/Modules/Providers/Application/Handlers/Queries/GetPublicProviderByIdOrSlugQueryHandler.cssrc/Modules/Providers/Tests/Unit/Application/Handlers/Queries/GetPublicProviderByIdOrSlugQueryHandlerTests.cssrc/Shared/Exceptions/GlobalExceptionHandler.cssrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cstests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cstests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs
✅ Files skipped from review due to trivial changes (7)
- src/Modules/Bookings/Domain/Events/BookingCancelledDomainEvent.cs
- tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs
- docs/roadmap-history.md
- src/Modules/Bookings/Application/errors.json
- src/Modules/Providers/Application/DTOs/PublicProviderServiceDto.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs
- src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs
🚧 Files skipped from review as they are similar to previous changes (3)
- src/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cs
- src/Modules/Bookings/API/Endpoints/Public/CompleteBookingEndpoint.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cs
…e domain services and API endpoints
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)
45-69:⚠️ Potential issue | 🟠 MajorInclua o estado de autenticação na chave de cache da query do prestador.
A query usa
session?.accessTokennas headers, mas aqueryKeynão inclui o estado de autenticação. Isso causa um problema de cache: o endpoint/api/v1/providers/{id}/publicretorna dados diferentes para usuários autenticados (inclui telefone, email e serviços) vs anônimos (estes dados são redactados). React Query pode servir a resposta em cache de uma requisição não autenticada para um usuário autenticado, ou vice-versa.🛠️ Ajuste sugerido
const { data: session, status } = useSession(); const isAuthenticated = status === "authenticated"; const isLoadingAuth = status === "loading"; + const providerAuthMode = isAuthenticated && session?.accessToken ? "authenticated" : "anonymous"; const { data: providerData, isLoading, error } = useQuery({ - queryKey: ["public-provider", id], + queryKey: ["public-provider", id, providerAuthMode], queryFn: async () => { const apiUrl = process.env.NEXT_PUBLIC_API_URL; const res = await fetch(`${apiUrl}/api/v1/providers/${id}/public`, { @@ - enabled: !!id, + enabled: !!id && !isLoadingAuth, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 45 - 69, The queryKey for the useQuery call (currently ["public-provider", id]) doesn't reflect authentication state while the fetch uses session?.accessToken; update the queryKey to include the auth state (e.g., include session?.accessToken or a boolean like Boolean(session?.accessToken) / session?.user?.id) so cache entries differ for authenticated vs anonymous requests, keeping the same queryFn, enabled: !!id, and fetch usage in place.
♻️ Duplicate comments (2)
src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs (1)
30-30:⚠️ Potential issue | 🟡 MinorMantenha os cabeçalhos AAA em inglês.
Troque
Organizar,AgireAssertarporArrange,ActeAssert. Os comentários auxiliares em português, como// Início > Fime// Sobreposição, estão corretos.🌐 Ajuste sugerido
- // Organizar + // Arrange ... - // Agir + // Act ... - // Assertar + // AssertBased on learnings, nos testes do MeAjudaAi os rótulos AAA devem permanecer em inglês como Arrange/Act/Assert, enquanto comentários auxiliares podem ficar em português.
Also applies to: 48-48, 51-51, 59-59, 66-66, 69-69, 78-78, 85-85, 88-88, 96-96, 114-114, 117-117, 126-126, 141-141, 144-144, 152-152, 168-168, 171-171
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs` at line 30, Replace the Portuguese AAA test section headers with English ones in SetProviderScheduleCommandHandlerTests.cs: change any "// Organizar" to "// Arrange", "// Agir" to "// Act" and "// Assertar" to "// Assert" (apply the same replacements for the other listed occurrences), leaving auxiliary Portuguese comments like "// Início > Fim" and "// Sobreposição" untouched; update all occurrences in the file so the test labels follow the Arrange/Act/Assert convention.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)
118-123:⚠️ Potential issue | 🟠 MajorNão agende implicitamente sempre o primeiro serviço.
Com múltiplos serviços,
services[0]?.idcria o booking para um serviço arbitrário, sem o usuário escolher. Exija seleção explícita ou renderize a ação de agendamento vinculada a um serviço específico.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 118 - 123, O componente está agendando implicitamente o primeiro serviço passando services[0]?.id para BookingModal; em vez disso remova essa escolha automática e exija seleção explícita: ou renderize um botão/ação de agendamento por item do array services (por exemplo mapear services e passar cada service.id ao abrir BookingModal) ou mantenha BookingModal fechado até que o usuário selecione um serviço (controlar via estado selecionado) e só então passar providerId, providerName e o serviceId selecionado; atualize a lógica que referencia services[0]?.id em favor da seleção do usuário.
🧹 Nitpick comments (2)
src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs (1)
59-99: Datas fixas podem envelhecer — prefira data dinâmica.
new DateOnly(2026, 4, 22)fica no passado a partir de 2026-04-23. EmboraBookingRepository.AddAsync/AddIfNoOverlapAsyncnão imponham regra de "futuridade" (essa validação está no handler), alinhar com o padrão já usado no próprio teste (linha 106 eCreateBookinglinha 143:DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1)) melhora consistência e evita surpresas se regras futuras forem adicionadas no repositório.♻️ Sugestão
- var date = new DateOnly(2026, 4, 22); + var date = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 59 - 99, Substitua a data fixa new DateOnly(2026, 4, 22) usada nas fixtures dos testes AddIfNoOverlapAsync_ShouldSucceed_WhenAdjacent and AddIfNoOverlapAsync_ShouldFail_WhenOverlapsExist por uma data dinâmica consistente com o restante dos testes (por exemplo DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1)) para evitar que a data caia no passado; atualize as criações de existingBooking e newBooking nas methods AddIfNoOverlapAsync_ShouldSucceed_WhenAdjacent e AddIfNoOverlapAsync_ShouldFail_WhenOverlapsExist para usar essa data dinâmica em vez da constante 2026-04-22.src/Modules/Bookings/Application/Common/TimeZoneResolver.cs (1)
85-97: Conversão UTC→Local redundante pode ser simplificada.
startLocal/endLocalrecalculam o mesmo valor destartDate/endDate(em horas não ambíguas,ConvertTimeFromUtc(ConvertTimeToUtc(x))volta ao original; em horas ambíguas já resolvidas, também). Poderia reaproveitarstartDate/endDatediretamente e usartz.GetUtcOffset(startUtc)para construir oDateTimeOffset. Não é bug, mas remove uma ida-e-volta desnecessária e facilita leitura.♻️ Refatoração sugerida
- var startLocal = TimeZoneInfo.ConvertTimeFromUtc(startUtc, tz); - var endLocal = TimeZoneInfo.ConvertTimeFromUtc(endUtc, tz); - return Result<BookingDto>.Success(new BookingDto( booking.Id, booking.ProviderId, booking.ClientId, booking.ServiceId, - new DateTimeOffset(startLocal, tz.GetUtcOffset(startUtc)), - new DateTimeOffset(endLocal, tz.GetUtcOffset(endUtc)), + new DateTimeOffset(startDate, tz.GetUtcOffset(startUtc)), + new DateTimeOffset(endDate, tz.GetUtcOffset(endUtc)), booking.Status, booking.RejectionReason, booking.CancellationReason));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Common/TimeZoneResolver.cs` around lines 85 - 97, O trecho cria startLocal/endLocal via TimeZoneInfo.ConvertTimeFromUtc(startUtc/endUtc, tz) apenas para construir DateTimeOffset, o que é redundante; em TimeZoneResolver substitua o uso de startLocal/endLocal pela data já disponível (por exemplo startDate/endDate ou diretamente startUtc/endUtc convertidos por offset) e construa os DateTimeOffset usando tz.GetUtcOffset(startUtc)/tz.GetUtcOffset(endUtc) ao chamar o construtor de BookingDto, removendo as chamadas a TimeZoneInfo.ConvertTimeFromUtc para evitar a ida-e-volta desnecessária (referências: startUtc, endUtc, tz, TimeZoneInfo.ConvertTimeFromUtc, tz.GetUtcOffset, BookingDto).
🤖 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/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 172-193: Replace the Portuguese runtime log messages in the catch
block that uses logger.LogError and logger.LogWarning inside BookingRepository
(the catch handling of transaction.RollbackAsync and concurrency retry logic)
with English text; specifically update the LogError message "Erro ao tentar
adicionar agendamento {BookingId} (Tentativa {Attempt})" and the LogWarning
message "Conflito de concorrência ao validar agendamento {BookingId}. Retentando
(Tentativa {Attempt})..." to clear English equivalents while keeping the same
structured properties ({BookingId}, {Attempt}); leave comments/XML in Portuguese
unchanged and preserve the use of IsConcurrencyError, attempt, maxRetryAttempts,
transaction.RollbackAsync and Random.Shared jitter/delay logic.
- Around line 138-143: The loop's call to context.ChangeTracker.Clear() can
silently drop tracked entities from prior operations (seen in
CreateBookingCommandHandler flow where scheduleRepository.GetByProviderIdAsync()
is read before AddIfNoOverlapAsync()), so update
ProviderScheduleRepository.GetByProviderIdAsync() to load the schedule with
AsNoTracking() since the entity is used only for validation and not for
persistence; alternatively, if you prefer isolation, change the retry logic to
use an IDbContextFactory<BookingsDbContext> to create a new context instance per
attempt instead of clearing the change tracker.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Around line 52-54: O teste atual verifica apenas que UpdateAsync foi chamado,
mas não impede que AddAsync também seja chamado; atualize o teste em
SetProviderScheduleCommandHandlerTests (o método que assert result.IsSuccess)
para também verificar que _scheduleRepoMock.Verify(x =>
x.AddAsync(It.IsAny<ProviderSchedule>(), It.IsAny<CancellationToken>()),
Times.Never) — espelhe a asserção presente no teste de criação para garantir que
apenas UpdateAsync é executado quando o agendamento já existe.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 64-75: The availability query currently lets fetch/parse failures
be treated the same as an empty schedule; update the queryFn and consuming UI so
real errors surface: in the useQuery queryFn (the function passed to useQuery)
include response details when throwing (e.g., include res.status and body/text)
and let errors propagate (don’t swallow them), and in the component consume
useQuery's isError and error (alongside isLoadingAvailability and availability)
to show an error message instead of falling back to “Nenhum horário disponível”;
also apply the same change for the other booking availability query block that
uses AvailabilitySchema.parse so parsing failures are surfaced too (references:
useQuery, queryFn, AvailabilitySchema.parse, availability,
isLoadingAvailability, isError, error).
- Around line 123-129: No handleDateChange, valide dateString antes de
instanciar a Date: se dateString for vazio ou os componentes (year, month, day)
não resultarem em uma data válida, chame setSelectedDate(null) (ou outro estado
"limpo") e retorne; caso contrário crie a Date como feito atualmente e continue
com setSelectedSlot(null). Isso evita que format(selectedDate, ...) nas chamadas
que usam selectedDate quebrem quando o input date for limpo.
In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs`:
- Around line 608-610: Fix the malformed XML comment for ExtractIdFromLocation
by splitting the combined line into two separate XML comment lines: keep the
first sentence inside the <summary> on its own triple-slash line and move the
"Suporta formatos: /api/v1/resource/{id}, /api/v1/resource?id={id}" text onto a
new leading-triple-slash line (still within the <summary> block) so the XML
summary renders correctly without literal "///" visible.
---
Outside diff comments:
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 45-69: The queryKey for the useQuery call (currently
["public-provider", id]) doesn't reflect authentication state while the fetch
uses session?.accessToken; update the queryKey to include the auth state (e.g.,
include session?.accessToken or a boolean like Boolean(session?.accessToken) /
session?.user?.id) so cache entries differ for authenticated vs anonymous
requests, keeping the same queryFn, enabled: !!id, and fetch usage in place.
---
Duplicate comments:
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs`:
- Line 30: Replace the Portuguese AAA test section headers with English ones in
SetProviderScheduleCommandHandlerTests.cs: change any "// Organizar" to "//
Arrange", "// Agir" to "// Act" and "// Assertar" to "// Assert" (apply the same
replacements for the other listed occurrences), leaving auxiliary Portuguese
comments like "// Início > Fim" and "// Sobreposição" untouched; update all
occurrences in the file so the test labels follow the Arrange/Act/Assert
convention.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 118-123: O componente está agendando implicitamente o primeiro
serviço passando services[0]?.id para BookingModal; em vez disso remova essa
escolha automática e exija seleção explícita: ou renderize um botão/ação de
agendamento por item do array services (por exemplo mapear services e passar
cada service.id ao abrir BookingModal) ou mantenha BookingModal fechado até que
o usuário selecione um serviço (controlar via estado selecionado) e só então
passar providerId, providerName e o serviceId selecionado; atualize a lógica que
referencia services[0]?.id em favor da seleção do usuário.
---
Nitpick comments:
In `@src/Modules/Bookings/Application/Common/TimeZoneResolver.cs`:
- Around line 85-97: O trecho cria startLocal/endLocal via
TimeZoneInfo.ConvertTimeFromUtc(startUtc/endUtc, tz) apenas para construir
DateTimeOffset, o que é redundante; em TimeZoneResolver substitua o uso de
startLocal/endLocal pela data já disponível (por exemplo startDate/endDate ou
diretamente startUtc/endUtc convertidos por offset) e construa os DateTimeOffset
usando tz.GetUtcOffset(startUtc)/tz.GetUtcOffset(endUtc) ao chamar o construtor
de BookingDto, removendo as chamadas a TimeZoneInfo.ConvertTimeFromUtc para
evitar a ida-e-volta desnecessária (referências: startUtc, endUtc, tz,
TimeZoneInfo.ConvertTimeFromUtc, tz.GetUtcOffset, BookingDto).
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 59-99: Substitua a data fixa new DateOnly(2026, 4, 22) usada nas
fixtures dos testes AddIfNoOverlapAsync_ShouldSucceed_WhenAdjacent and
AddIfNoOverlapAsync_ShouldFail_WhenOverlapsExist por uma data dinâmica
consistente com o restante dos testes (por exemplo
DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1)) para evitar que a data caia
no passado; atualize as criações de existingBooking e newBooking nas methods
AddIfNoOverlapAsync_ShouldSucceed_WhenAdjacent e
AddIfNoOverlapAsync_ShouldFail_WhenOverlapsExist para usar essa data dinâmica em
vez da constante 2026-04-22.
🪄 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: 502024c0-3c79-4aba-bda4-776871cf87f9
⛔ Files ignored due to path filters (1)
src/Web/MeAjudaAi.Web.Provider/app/provider/[slug]/page.tsxis excluded by!**/MeAjudaAi.Web.Provider/**
📒 Files selected for processing (16)
src/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cssrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cssrc/Modules/Bookings/Application/Common/TimeZoneResolver.cssrc/Modules/Bookings/Domain/Repositories/IBookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Shared/Exceptions/GlobalExceptionHandler.cssrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxtests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cstests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs
✅ Files skipped from review due to trivial changes (1)
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs
🚧 Files skipped from review as they are similar to previous changes (6)
- src/Shared/Exceptions/GlobalExceptionHandler.cs
- src/Modules/Bookings/API/Endpoints/Public/GetMyBookingsEndpoint.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs
- tests/MeAjudaAi.E2E.Tests/Modules/Bookings/BookingsEndToEndTests.cs
- src/Modules/Bookings/Domain/Repositories/IBookingRepository.cs
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (7)
docs/api-automation.md (1)
284-312: Nova seção de versionamento e breaking changes bem estruturada.A documentação descreve com clareza o fluxo para aprovar breaking changes via
.oasdiff-ignore.yamle está consistente com o step do CI (err-ignoreem.github/workflows/ci-backend.yml). As estratégias futuras (path, parallel change com[Obsolete]/deprecated: true, headerX-API-Version) estão alinhadas com boas práticas.Sugestão opcional: considerar adicionar um critério/prazo para limpeza periódica de entradas no
.oasdiff-ignore.yamlantes do release a produção, evitando que o arquivo acumule supressões esquecidas.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/api-automation.md` around lines 284 - 312, Add a short cleanup policy to the versioning section that requires periodic review and removal of entries from .oasdiff-ignore.yaml before production releases: specify a retention timeframe (e.g., remove ignores older than 3 months), require a ticket/PR linking the rationale for each ignore, and mandate confirmation in the CI step that active ignores are still valid (reference the CI workflow key err-ignore), and mention reviewers should verify migration or alternate compatibility strategies (e.g., X-API-Version, path versioning, use of [Obsolete] / deprecated: true) before keeping any suppression.src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx (2)
8-8: Definirprocess.env.TZem runtime é pouco confiável.Atribuir
process.env.TZ = 'UTC'após o Node já ter inicializado não garante queDate/Intlpassem a usar UTC (o TZ costuma ser lido no startup; em várias plataformas não hátzset()automático). Como o componente agora usaparseSlotTime, que extraiHH:mmdiretamente da string (independente de fuso), essa linha tende a ser desnecessária. Se quiser manter a proteção, definaTZ=UTCviaenvnovitest.config.ts(ouglobalSetup/script de CI), e não no próprio arquivo de teste.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx` at line 8, A linha que define process.env.TZ = 'UTC' em runtime no teste é pouco confiável e desnecessária porque parseSlotTime extrai HH:mm da string independentemente de fuso; remova essa atribuição do arquivo de teste (procure por process.env.TZ) e, se quiser garantir TZ em todos os testes, configure TZ=UTC no ambiente de testes (por exemplo em vitest.config.ts env or via globalSetup/CI), não no próprio teste.
31-37:queryClientcompartilhado entre testes.O
QueryClienté instanciado no escopo do módulo e reutilizado por todos os testes. Apesar dequeryClient.clear()nobeforeEach, estado interno (observers, mutations em voo) pode vazar entre casos e causar flakiness. Prefira criar umQueryClientnovo por teste dentro dowrapper/beforeEach.♻️ Sugestão
-const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, -}); - -const wrapper = ({ children }: { children: React.ReactNode }) => ( - <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> -); +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + return ({ children }: { children: React.ReactNode }) => ( + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> + ); +};E em cada
render(..., { wrapper: createWrapper() }).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx` around lines 31 - 37, O QueryClient está sendo compartilhado entre testes via a variável queryClient no escopo do módulo; isso pode vazar estado e causar flakiness. Remove a instância module-scoped queryClient e em vez disso crie um novo QueryClient por teste (ou dentro da factory createWrapper), por exemplo instanciando o QueryClient dentro do beforeEach ou dentro de createWrapper() e usando render(..., { wrapper: createWrapper() }); garanta também que qualquer option (ex.: defaultOptions) seja reaplicada à nova instância e elimine chamadas a queryClient.clear() que dependam da instância global.src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx (1)
108-116: Invalidar cache de disponibilidade após booking criado.Após sucesso em
createBooking, o slot reservado continua em cache para a mesmaqueryKey(["provider-availability", providerId, date]). Se o usuário reabrir o modal para a mesma data antes dostaleTimeexpirar, verá o horário recém-reservado como disponível, aumentando a chance de 409 no backend. Considere invalidar/refetch viaqueryClient.invalidateQueriesemonSuccess.♻️ Sugestão
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; ... + const queryClient = useQueryClient(); ... onSuccess: () => { toast.success("Solicitação de agendamento enviada com sucesso!"); + queryClient.invalidateQueries({ queryKey: ["provider-availability", providerId] }); setOpen(false); setSelectedSlot(null); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx` around lines 108 - 116, The onSuccess handler for createBooking must invalidate the provider availability cache so the newly reserved slot is refetched; modify the onSuccess in the createBooking mutation (the callback that currently calls toast.success, setOpen(false) and setSelectedSlot(null)) to call queryClient.invalidateQueries(["provider-availability", providerId, date]) or queryClient.refetchQueries for that key before/after closing the modal so the cached availability is refreshed and the reserved slot no longer appears available.src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs (1)
8-8: Nit: marcar a classe comosealed.Os demais repositórios/handlers do módulo seguem o padrão
sealed; aplicar aqui evita herança não intencional e habilita pequenas otimizações do runtime.-public class ProviderScheduleRepository(BookingsDbContext context) : IProviderScheduleRepository +public sealed class ProviderScheduleRepository(BookingsDbContext context) : IProviderScheduleRepository🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs` at line 8, Marque a classe ProviderScheduleRepository como sealed para seguir o padrão dos demais repositórios e evitar herança não intencional: atualize a declaração da classe ProviderScheduleRepository(BookingsDbContext context) : IProviderScheduleRepository adicionando o modificador sealed; verifique também que não haja heranças ou usos que dependam de extensão da classe antes de aplicar a mudança.src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (1)
37-99: Duplicação entreGetByProviderIdPagedAsynceGetByClientIdPagedAsync.Os dois métodos são praticamente idênticos, variando apenas no predicado inicial (
ProviderIdvsClientId). Considere extrair um helper privadoGetBookingsPagedAsync(Expression<Func<Booking, bool>> baseFilter, DateOnly? from, DateOnly? to, int page, int pageSize, ...)para eliminar a duplicação e centralizar eventuais ajustes (validação de paginação, filtros adicionais, ordenação).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 37 - 99, Both GetByProviderIdPagedAsync and GetByClientIdPagedAsync duplicate paging/filter logic; extract a private helper GetBookingsPagedAsync(Expression<Func<Booking,bool>> baseFilter, DateOnly? from, DateOnly? to, int page, int pageSize, CancellationToken ct) that builds the IQueryable (AsNoTracking, applies baseFilter, optional from/to filters), computes totalCount, applies OrderByDescending(b => b.Date).ThenByDescending(b => b.TimeSlot.Start), Skip/Take and returns (Items, TotalCount); then simplify GetByProviderIdPagedAsync to call GetBookingsPagedAsync(b => b.ProviderId == providerId, ...) and GetByClientIdPagedAsync to call GetBookingsPagedAsync(b => b.ClientId == clientId, ...). Ensure the helper validates page/pageSize if you want centralized checks.src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs (1)
54-99: Considere cobrir o cenário "mesma faixa horária em datas diferentes".Os testes atuais de overlap usam a mesma
datepara booking existente e novo booking. A correção crítica aplicada emAddIfNoOverlapAsync(inclusão deb.Date == booking.Dateno predicado) permite agendamentos no mesmo provider/horário em datas distintas — cenário que não está coberto. Um teste explícito previne regressões caso alguém volte a ignorar a data no predicado.💡 Teste sugerido
[Fact] public async Task AddIfNoOverlapAsync_ShouldSucceed_WhenSameTimeSlotOnDifferentDate() { // Arrange var providerId = Guid.NewGuid(); var date1 = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(2); var date2 = date1.AddDays(1); var existingBooking = Booking.Create( providerId, Guid.NewGuid(), Guid.NewGuid(), date1, TimeSlot.Create(new TimeOnly(10, 0), new TimeOnly(12, 0))); await _repository.AddAsync(existingBooking); var newBooking = Booking.Create( providerId, Guid.NewGuid(), Guid.NewGuid(), date2, TimeSlot.Create(new TimeOnly(10, 0), new TimeOnly(12, 0))); // Act var result = await _repository.AddIfNoOverlapAsync(newBooking); // Assert result.IsSuccess.Should().BeTrue(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs` around lines 54 - 99, Add test to ensure bookings with identical time slots on different dates are allowed: add a new test method (e.g., AddIfNoOverlapAsync_ShouldSucceed_WhenSameTimeSlotOnDifferentDate) that uses Booking.Create to insert an existing booking on date1 and then creates a new booking for the same provider and same TimeSlot on date2 (date2 = date1.AddDays(1)), call _repository.AddIfNoOverlapAsync(newBooking) and assert result.IsSuccess.Should().BeTrue(); this covers the case ensured by the date check in AddIfNoOverlapAsync and prevents regressions if the date predicate is later removed.
🤖 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/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 37-62: Validate and normalize pagination parameters at the start
of GetByProviderIdPagedAsync (and similarly GetByClientIdPagedAsync): ensure
page >= 1 and pageSize > 0 (or clamp to sane defaults like page = Math.Max(1,
page) and pageSize = Math.Clamp(pageSize, 1, MaxPageSize)), and throw an
ArgumentException for invalid inputs if you prefer fail-fast; then use the
normalized values when computing Skip((page - 1) * pageSize) and Take(pageSize)
to avoid negative skips or silent empty results and to bound maximum load.
In
`@src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs`:
- Around line 10-27: GetByProviderIdAsync currently uses AsNoTracking which
detaches the aggregate (ProviderSchedule) so later mutations in
SetProviderScheduleCommandHandler (calls to ClearAvailabilities and
SetAvailability) followed by ProviderScheduleRepository.UpdateAsync
(DbSet.Update + SaveChangesAsync) can leave owned collections (Availabilities ->
TimeSlot) inconsistent; fix by removing AsNoTracking from GetByProviderIdAsync
so EF tracks the aggregate during write flows, then perform mutations and call
SaveChangesAsync without DbSet.Update, or if you must keep a no-tracking read
path, add a separate GetByProviderIdReadOnlyAsync (with AsNoTracking) for
validations and use the tracked GetByProviderIdAsync for command handlers like
SetProviderScheduleCommandHandler and update via SaveChangesAsync only.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 46-59: The two functions are inconsistent: update
combineDateAndTime to handle full ISO strings the same way parseSlotTime does—if
timeString includes "T" (an ISO datetime) create a Date from timeString and
format that result; otherwise keep the existing split(":").map(Number) logic
(with seconds defaulting to 0) to build a Date from the provided date param,
then return format(result, "yyyy-MM-dd'T'HH:mm:ssXXX"). Refer to
combineDateAndTime and parseSlotTime when making the change.
In `@tests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs`:
- Line 593: Altere o comentário em inglês "Only SearchProviders requires
NetTopologySuite (PostGIS)" para português mantendo o mesmo significado; por
exemplo substitua por "Apenas SearchProviders requer NetTopologySuite
(PostGIS)". Localize o comentário atualizado no arquivo BaseTestContainerTest.cs
(comentário atual perto de onde é referenciado SearchProviders) e troque somente
o texto do comentário sem alterar a lógica ou formatação do código.
---
Nitpick comments:
In `@docs/api-automation.md`:
- Around line 284-312: Add a short cleanup policy to the versioning section that
requires periodic review and removal of entries from .oasdiff-ignore.yaml before
production releases: specify a retention timeframe (e.g., remove ignores older
than 3 months), require a ticket/PR linking the rationale for each ignore, and
mandate confirmation in the CI step that active ignores are still valid
(reference the CI workflow key err-ignore), and mention reviewers should verify
migration or alternate compatibility strategies (e.g., X-API-Version, path
versioning, use of [Obsolete] / deprecated: true) before keeping any
suppression.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 37-99: Both GetByProviderIdPagedAsync and GetByClientIdPagedAsync
duplicate paging/filter logic; extract a private helper
GetBookingsPagedAsync(Expression<Func<Booking,bool>> baseFilter, DateOnly? from,
DateOnly? to, int page, int pageSize, CancellationToken ct) that builds the
IQueryable (AsNoTracking, applies baseFilter, optional from/to filters),
computes totalCount, applies OrderByDescending(b => b.Date).ThenByDescending(b
=> b.TimeSlot.Start), Skip/Take and returns (Items, TotalCount); then simplify
GetByProviderIdPagedAsync to call GetBookingsPagedAsync(b => b.ProviderId ==
providerId, ...) and GetByClientIdPagedAsync to call GetBookingsPagedAsync(b =>
b.ClientId == clientId, ...). Ensure the helper validates page/pageSize if you
want centralized checks.
In
`@src/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cs`:
- Line 8: Marque a classe ProviderScheduleRepository como sealed para seguir o
padrão dos demais repositórios e evitar herança não intencional: atualize a
declaração da classe ProviderScheduleRepository(BookingsDbContext context) :
IProviderScheduleRepository adicionando o modificador sealed; verifique também
que não haja heranças ou usos que dependam de extensão da classe antes de
aplicar a mudança.
In
`@src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs`:
- Around line 54-99: Add test to ensure bookings with identical time slots on
different dates are allowed: add a new test method (e.g.,
AddIfNoOverlapAsync_ShouldSucceed_WhenSameTimeSlotOnDifferentDate) that uses
Booking.Create to insert an existing booking on date1 and then creates a new
booking for the same provider and same TimeSlot on date2 (date2 =
date1.AddDays(1)), call _repository.AddIfNoOverlapAsync(newBooking) and assert
result.IsSuccess.Should().BeTrue(); this covers the case ensured by the date
check in AddIfNoOverlapAsync and prevents regressions if the date predicate is
later removed.
In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx`:
- Line 8: A linha que define process.env.TZ = 'UTC' em runtime no teste é pouco
confiável e desnecessária porque parseSlotTime extrai HH:mm da string
independentemente de fuso; remova essa atribuição do arquivo de teste (procure
por process.env.TZ) e, se quiser garantir TZ em todos os testes, configure
TZ=UTC no ambiente de testes (por exemplo em vitest.config.ts env or via
globalSetup/CI), não no próprio teste.
- Around line 31-37: O QueryClient está sendo compartilhado entre testes via a
variável queryClient no escopo do módulo; isso pode vazar estado e causar
flakiness. Remove a instância module-scoped queryClient e em vez disso crie um
novo QueryClient por teste (ou dentro da factory createWrapper), por exemplo
instanciando o QueryClient dentro do beforeEach ou dentro de createWrapper() e
usando render(..., { wrapper: createWrapper() }); garanta também que qualquer
option (ex.: defaultOptions) seja reaplicada à nova instância e elimine chamadas
a queryClient.clear() que dependam da instância global.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 108-116: The onSuccess handler for createBooking must invalidate
the provider availability cache so the newly reserved slot is refetched; modify
the onSuccess in the createBooking mutation (the callback that currently calls
toast.success, setOpen(false) and setSelectedSlot(null)) to call
queryClient.invalidateQueries(["provider-availability", providerId, date]) or
queryClient.refetchQueries for that key before/after closing the modal so the
cached availability is refreshed and the reserved slot no longer appears
available.
🪄 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: b5ed8ee8-2afe-4a2f-abae-bc0a1a71b889
📒 Files selected for processing (13)
.github/workflows/ci-backend.yml.oasdiff-ignore.yamldocs/api-automation.mdsrc/Modules/Bookings/Application/Common/TimeZoneResolver.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cssrc/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsxsrc/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxsrc/Web/e2e/support/mocks.tstests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs
✅ Files skipped from review due to trivial changes (2)
- .oasdiff-ignore.yaml
- src/Modules/Bookings/Tests/Unit/Application/Handlers/SetProviderScheduleCommandHandlerTests.cs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/Modules/Bookings/Application/Common/TimeZoneResolver.cs
- src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx
…repositories, and frontend modal component
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs (1)
57-64:⚠️ Potential issue | 🟠 MajorFalhe em timezone inválido no fluxo de criação, não faça fallback.
Para leitura de DTO, fallback de timezone é aceitável. Para criação, validar agenda em UTC quando
schedule.TimeZoneIdestá inválido pode aceitar/rejeitar horários no referencial errado e persistirDate/TimeSlotincorretos. Use um resolver estrito aqui e retorne erro controlado em português quando o fuso do prestador não puder ser resolvido.🛡️ Direção sugerida
- var tz = TimeZoneResolver.ResolveTimeZone(schedule.TimeZoneId, logger); + var timezoneResult = TimeZoneResolver.TryResolveTimeZone(schedule.TimeZoneId, logger); + if (timezoneResult.IsFailure) + { + return Result<BookingDto>.Failure(Error.BadRequest("Fuso horário do prestador inválido.")); + } + + var tz = timezoneResult.Value; var localStartTime = TimeZoneInfo.ConvertTimeFromUtc(command.Start.UtcDateTime, tz);Confirme se
ResolveTimeZoneainda faz fallback antes de aplicar a mudança:#!/bin/bash # Inspeciona a implementação atual do resolvedor de timezone. rg -nP --type=cs -C8 'ResolveTimeZone\s*\('🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs` around lines 57 - 64, No fluxo de CreateBookingCommandHandler, não faça fallback ao resolver o fuso horário: use um resolvedor estrito chamando ResolveTimeZone(schedule.TimeZoneId, logger) e, se ele indicar fuso inválido, retorne imediatamente Result<BookingDto>.Failure(Error.BadRequest("Fuso horário do prestador inválido.")) em vez de continuar; assim evite converter com TimeZoneInfo.ConvertTimeFromUtc usando um TZ incorreto e valide schedule.IsAvailable(localStartTime, duration) apenas quando o timezone for válido. Certifique-se de não alterar o fallback usado apenas para leitura de DTOs e verifique a implementação atual de ResolveTimeZone antes de aplicar a mudança.
🧹 Nitpick comments (2)
docs/api-automation.md (1)
294-301: Alinhar exemplo do doc com o formato real usado no.oasdiff-ignore.yaml.O exemplo usa
err-id(correto para breaking changes de nívelerror), mas o arquivo adicionado no PR duplica entradas usando tambémid. Recomendo:
- Manter o exemplo apenas com
err-id(como está) e adicionar uma nota explicando quando usaridvserr-id.- Incluir no template um campo de rastreabilidade (comentário com PR/ticket), reforçando a política da seção 4 ("Rastreabilidade").
📝 Sugestão
1. Edite o arquivo `.oasdiff-ignore.yaml` na raiz do projeto. -2. Adicione a rota e o ID do erro que deseja ignorar: +2. Adicione a rota e o ID do erro que deseja ignorar (use `err-id` para diagnósticos de nível `error`/breaking, `id` para `warning`/`info`): ```yaml + # Motivo: <PR `#xxx` ou ticket> - <descrição curta> - method: GET path: /api/v1/exemplo err-id: response-property-type-changed ``` 3. Documente no commit o motivo da mudança.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/api-automation.md` around lines 294 - 301, Update the docs example in docs/api-automation.md so it matches the real `.oasdiff-ignore.yaml` format: keep the example entry using only `err-id` (remove the duplicated `id` usage), add a short note explaining when to use `err-id` vs `id` (e.g., `err-id` for breaking-level "error" changes, `id` for non-error entries), and modify the example/template to include a traceability comment field (a commented line like "# Motivo: <PR `#xxx` ou ticket> - <descrição curta>") to satisfy the "Rastreabilidade" policy referenced in the doc.src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (1)
162-190: Evite registrar retries esperados como erro.Conflitos
40001/40P01são esperados com isolamentoSerializable; hoje eles geramLogErrorantes do retry, o que pode poluir alertas. Registre como warning durante retry e deixeLogErrorsó para falhas não tratadas.♻️ Ajuste sugerido
catch (Exception ex) { - logger.LogError(ex, "Error while attempting to add booking {BookingId} (Attempt {Attempt})", booking.Id, attempt); - try { await transaction.RollbackAsync(CancellationToken.None); } @@ // Checa por conflitos de concorrência (40001 ou 40P01) if (IsConcurrencyError(ex) && attempt < maxRetryAttempts) { - logger.LogWarning("Concurrency conflict while validating booking {BookingId}. Retrying (Attempt {Attempt})...", booking.Id, attempt); + logger.LogWarning(ex, "Concurrency conflict while validating booking {BookingId}. Retrying (Attempt {Attempt})...", booking.Id, attempt); // Aguarda um tempo aleatório curto antes de tentar novamente (jitter) await Task.Delay(Random.Shared.Next(50, 200), cancellationToken); continue; @@ if (IsConcurrencyError(ex)) { return Result.Failure(Error.Conflict("Conflito de concorrência ao validar agendamento. Tente novamente em instantes.")); } + logger.LogError(ex, "Error while attempting to add booking {BookingId} (Attempt {Attempt})", booking.Id, attempt); throw; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 162 - 190, The current catch logs all exceptions with logger.LogError before checking IsConcurrencyError, causing expected Serializable conflicts to be treated as errors; change the flow in the catch block so that you first check IsConcurrencyError(ex): if it's a concurrency conflict and attempt < maxRetryAttempts, log a warning via logger.LogWarning (including booking.Id and attempt), rollback and retry after jitter; if it's a concurrency conflict and retries are exhausted return Result.Failure(...) (or log as error only here if desired); for all other exceptions only then call logger.LogError(ex, ...) and rethrow; update references in this block (logger.LogError, logger.LogWarning, IsConcurrencyError, booking.Id, attempt, maxRetryAttempts, transaction.RollbackAsync) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/ci-backend.yml:
- Around line 368-373: The CI step currently sets continue-on-error: true which
silences breaking-change failures despite fail-on: 'ERR' and
.oasdiff-ignore.yaml; either revert continue-on-error to false to make the gate
blocking again (and create a tracking issue to restore this after Sprint 12), or
keep it true but add a visible failure behavior: emit an explicit echo warning
or post the oasdiff result as a PR comment when a breaking change is detected
(use the same job that references fail-on: 'ERR' and .oasdiff-ignore.yaml), and
record an issue to revert the non-blocking behavior after Sprint 12.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Around line 26-51: The handler currently calls
bookingRepository.GetByProviderIdPagedAsync and ignores totalCount while the
query defines Page/PageSize but the endpoint (GetProviderBookingsEndpoint)
doesn't forward them, causing an inconsistent contract; choose one: A) If
pagination is desired, change the handler return type to
Result<PagedResult<BookingDto>> (instead of Result<IReadOnlyList<BookingDto>>),
build a PagedResult<BookingDto> using the bookings and totalCount from
GetByProviderIdPagedAsync, and update GetProviderBookingsEndpoint to pass
query.Page and query.PageSize into the handler; or B) If pagination is not
desired, remove Page and PageSize from GetBookingsByProviderQuery and replace
the call to bookingRepository.GetByProviderIdPagedAsync with the non-paged
repository method (or adjust to request all results), then keep returning
Result<IReadOnlyList<BookingDto>> from GetBookingsByProviderQueryHandler; ensure
TimeZoneResolver.CreateValidatedBookingDto and error handling remain unchanged.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 73-75: O comentário acima da normalização de paginação está em
inglês; substitua "// Normalize pagination" por um comentário em português (por
exemplo "// Normalizar paginação") junto ao trecho que usa page = Math.Max(1,
page) e pageSize = Math.Clamp(pageSize, 1, 100); mantenha a regra do
repositório: comentários e documentação XML em C# devem ser em português,
enquanto mensagens de log permanecem em inglês.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cs`:
- Around line 130-131: Altere o comentário explicativo acima da criação do
schedule para português; especificamente substitua "// Tokyo is UTC+9" por um
comentário em PT-BR que explique o mesmo (por exemplo "// Tóquio está em UTC+9")
próximo à linha onde ProviderSchedule.Create(providerId, "Tokyo Standard Time")
é chamado; mantenha quaisquer cabeçalhos AAA (Arrange/Act/Assert) em inglês se
existirem, apenas traduza comentários e documentação livre.
In `@src/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsx`:
- Around line 86-87: The current guard in booking-modal.tsx rejects requests
when session.user?.id is missing, but the backend derives clientId from the JWT
and only requires a valid access token; remove the session.user?.id check so the
guard only verifies session and session.accessToken (use session &&
session.accessToken) and update the thrown Error message if needed to reference
authentication via access token; also apply the same fix to the other identical
check near the second occurrence in this file (the block around the later
booking submission).
- Around line 140-195: The selectedSlot is compared by object identity and can
become stale when availability refreshes; update the slot selection and
confirm-disable logic to compare by start/end values instead. In the slots
render (where availability.slots.map and the button onClick live) change the
active-check from selectedSlot === slot to a comparison like selectedSlot &&
slot.start === selectedSlot.start && slot.end === selectedSlot.end; update
isConfirmDisabled to also check that selectedSlot exists in availability.slots
using the same start/end comparison (e.g., isConfirmDisabled = ... ||
!availability?.slots.some(s => s.start===selectedSlot.start &&
s.end===selectedSlot.end)); finally add a small effect or guard (watching
availability) to clear selectedSlot (setSelectedSlot(null)) when the selected
slot is no longer present to keep UI and confirm state consistent.
---
Duplicate comments:
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cs`:
- Around line 57-64: No fluxo de CreateBookingCommandHandler, não faça fallback
ao resolver o fuso horário: use um resolvedor estrito chamando
ResolveTimeZone(schedule.TimeZoneId, logger) e, se ele indicar fuso inválido,
retorne imediatamente Result<BookingDto>.Failure(Error.BadRequest("Fuso horário
do prestador inválido.")) em vez de continuar; assim evite converter com
TimeZoneInfo.ConvertTimeFromUtc usando um TZ incorreto e valide
schedule.IsAvailable(localStartTime, duration) apenas quando o timezone for
válido. Certifique-se de não alterar o fallback usado apenas para leitura de
DTOs e verifique a implementação atual de ResolveTimeZone antes de aplicar a
mudança.
---
Nitpick comments:
In `@docs/api-automation.md`:
- Around line 294-301: Update the docs example in docs/api-automation.md so it
matches the real `.oasdiff-ignore.yaml` format: keep the example entry using
only `err-id` (remove the duplicated `id` usage), add a short note explaining
when to use `err-id` vs `id` (e.g., `err-id` for breaking-level "error" changes,
`id` for non-error entries), and modify the example/template to include a
traceability comment field (a commented line like "# Motivo: <PR `#xxx` ou ticket>
- <descrição curta>") to satisfy the "Rastreabilidade" policy referenced in the
doc.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 162-190: The current catch logs all exceptions with
logger.LogError before checking IsConcurrencyError, causing expected
Serializable conflicts to be treated as errors; change the flow in the catch
block so that you first check IsConcurrencyError(ex): if it's a concurrency
conflict and attempt < maxRetryAttempts, log a warning via logger.LogWarning
(including booking.Id and attempt), rollback and retry after jitter; if it's a
concurrency conflict and retries are exhausted return Result.Failure(...) (or
log as error only here if desired); for all other exceptions only then call
logger.LogError(ex, ...) and rethrow; update references in this block
(logger.LogError, logger.LogWarning, IsConcurrencyError, booking.Id, attempt,
maxRetryAttempts, transaction.RollbackAsync) accordingly.
🪄 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: 3ec8ba98-202e-4a84-aa93-2829678ba34a
📒 Files selected for processing (21)
.github/workflows/ci-backend.yml.oasdiff-ignore.yamldocs/api-automation.mdsrc/Modules/Bookings/Application/Bookings/Handlers/CreateBookingCommandHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByClientQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cssrc/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cssrc/Modules/Bookings/Domain/Repositories/IProviderScheduleRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cssrc/Modules/Bookings/Infrastructure/Repositories/ProviderScheduleRepository.cssrc/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingByIdQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByClientQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingsByProviderQueryHandlerTests.cssrc/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cssrc/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsxsrc/Web/MeAjudaAi.Web.Customer/components/bookings/booking-modal.tsxsrc/Web/e2e/support/mocks.tstests/MeAjudaAi.E2E.Tests/Base/BaseTestContainerTest.cs
🚧 Files skipped from review as they are similar to previous changes (7)
- src/Web/MeAjudaAi.Web.Customer/tests/components/bookings/booking-modal.test.tsx
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/GetBookingByIdQueryHandlerTests.cs
- src/Modules/Bookings/Domain/Repositories/IProviderScheduleRepository.cs
- src/Modules/Bookings/Tests/Unit/Application/Handlers/CreateBookingCommandHandlerTests.cs
- src/Modules/Bookings/Application/Bookings/Handlers/GetProviderAvailabilityQueryHandler.cs
- src/Modules/Bookings/Tests/Integration/Repositories/BookingRepositoryTests.cs
…ion, and concurrency handling in booking repository
Code Coverage Report
|
Minimum allowed line rate is |
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 14
♻️ Duplicate comments (3)
src/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cs (1)
1-15:⚠️ Potential issue | 🟡 MinorRemova a exclusão de cobertura do domain event.
BookingConfirmedDomainEventfaz parte das transições do aggregate e deve ser coberto pelos testes de domínio, como os demais eventos de lifecycle.🧹 Ajuste sugerido
using MeAjudaAi.Shared.Events; -using System.Diagnostics.CodeAnalysis; namespace MeAjudaAi.Modules.Bookings.Domain.Events; /// <summary> /// Evento de domínio disparado quando um agendamento é confirmado pelo prestador. /// </summary> -[ExcludeFromCodeCoverage] public record BookingConfirmedDomainEvent(Based on learnings: manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cs` around lines 1 - 15, Remova o atributo de exclusão de cobertura do domain event: abra o record BookingConfirmedDomainEvent e retire a annotation [ExcludeFromCodeCoverage] (ou remova a linha inteira contendo ExcludeFromCodeCoverage) para que o evento seja coberto pelos testes de domínio; mantenha o resto da declaração do record BookingConfirmedDomainEvent (AggregateId, Version, ProviderId, ClientId) inalterado.src/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cs (1)
21-22:⚠️ Potential issue | 🟠 MajorNão passe
Guid.NewGuid()como possível ator da confirmação.Se o segundo argumento de
ConfirmBookingCommandrepresenta o provider/usuário que confirma, a linha 21 cria um ator falso: providers legítimos podem receber 403 ou a auditoria/autorização fica inconsistente. Extraia o identificador dos claims/policy, ou remova esse parâmetro e deixe o handler resolver o usuário atual.Confirme a semântica do segundo argumento antes de corrigir:
#!/bin/bash set -euo pipefail # Objetivo: verificar se o segundo parâmetro de ConfirmBookingCommand é ator/provedor/usuário ou apenas correlação. # Esperado: se for ProviderId/UserId/ConfirmedBy, o endpoint deve usar claims do usuário autenticado, não Guid.NewGuid(). fd 'ConfirmBooking(Command|CommandHandler)\.cs$' src/Modules/Bookings --exec sh -c ' for file do printf "\n--- %s ---\n" "$file" sed -n "1,220p" "$file" done ' sh {} rg -n -C4 'new ConfirmBookingCommand|ConfirmBookingCommand\(|AuthConstants\.Claims|ProviderId|NameIdentifier|FindFirst|IsInRole' src/Modules/Bookings --glob '*.cs'🛠️ Direção de ajuste se o handler já lê o usuário atual
- var command = new ConfirmBookingCommand(id, Guid.NewGuid()); + var command = new ConfirmBookingCommand(id);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cs` around lines 21 - 22, O endpoint está criando um ator falso ao instanciar ConfirmBookingCommand(id, Guid.NewGuid()); em vez disso, obtenha o identificador do utilizador autenticado a partir dos claims/policy (por exemplo HttpContext.User.FindFirst(ClaimTypes.NameIdentifier) ou o claim definido em AuthConstants.Claims) e passe esse id como segundo parâmetro, ou remova esse parâmetro do construtor e deixe o ConfirmBookingCommandHandler resolver o utilizador atual; atualize a chamada dispatcher.SendAsync<ConfirmBookingCommand, Result>(...) e o construtor de ConfirmBookingCommand conforme necessário para usar o identificador real do ator (ConfirmBookingCommand e ConfirmBookingCommandHandler são os símbolos a ajustar).src/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cs (1)
22-25:⚠️ Potential issue | 🟡 MinorComplete a validação do motivo no handler.
A validação local cobre apenas vazio/branco; dispatch direto ainda pode enviar um motivo acima do limite aceito pelo endpoint/validador e chegar ao domínio/persistência.
🐛 Ajuste sugerido
if (string.IsNullOrWhiteSpace(command.Reason)) { return Result.Failure(Error.BadRequest("O motivo da rejeição deve ser informado.")); } + + if (command.Reason.Length > 500) + { + return Result.Failure(Error.BadRequest("O motivo da rejeição não pode exceder 500 caracteres.")); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cs` around lines 22 - 25, The handler currently only checks string.IsNullOrWhiteSpace(command.Reason) and must also enforce a maximum length to prevent oversized reasons reaching the domain/persistence; update RejectBookingCommandHandler (the method handling the RejectBookingCommand, e.g., Handle) to validate command.Reason.Length against the allowed maximum (use the existing max constant if one exists in the command/validator/domain, otherwise introduce a shared constant like ReasonMaxLength) and return Result.Failure(Error.BadRequest("O motivo da rejeição deve ter no máximo X caracteres.")) when exceeded, keeping the empty/whitespace check intact.
🧹 Nitpick comments (20)
src/Modules/Bookings/Tests/BaseUnitTest.cs (1)
1-10: Classe base vazia — considere adiar até haver necessidade real (YAGNI).
BaseUnitTestestá apenas como placeholder, e osusingdeFluentAssertions,MoqeXunitsão não utilizados no arquivo (podem ativar warnings IDE0005 dependendo da configuração). Se derivar dela ainda não agrega comportamento compartilhado, remova o arquivo até que haja setup comum real; caso contrário, remova os usings ociosos.♻️ Sugestão mínima
-using FluentAssertions; -using Moq; -using Xunit; - -namespace MeAjudaAi.Modules.Bookings.Tests; +namespace MeAjudaAi.Modules.Bookings.Tests; public abstract class BaseUnitTest { // Common setup for unit tests can go here }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/BaseUnitTest.cs` around lines 1 - 10, The BaseUnitTest class is an empty placeholder and the using directives for FluentAssertions, Moq, and Xunit are unused; either delete the entire BaseUnitTest class/file until shared setup is needed, or remove the unused usings from the file so it no longer causes IDE warnings. Locate the class named BaseUnitTest and apply one of the two fixes: delete the file if no shared behavior is required, or strip the unnecessary using statements (FluentAssertions, Moq, Xunit) and keep the file only if you add real shared setup code.src/Shared/MeAjudaAi.Shared.csproj (1)
44-46: Agrupamento incorreto do PackageReference.O
OpenTelemetry.Exporter.Consolefoi adicionado dentro da seção<!-- Feature Management -->, o que prejudica a legibilidade. Considere criar uma seção dedicada (ex.:<!-- Observability / OpenTelemetry -->) ou movê-lo para próximo de outras referências de telemetria.♻️ Sugestão
<!-- Feature Management --> <PackageReference Include="Microsoft.FeatureManagement.AspNetCore" /> - <PackageReference Include="OpenTelemetry.Exporter.Console" /> - + + <!-- Observability --> + <PackageReference Include="OpenTelemetry.Exporter.Console" /> + <!-- Dependency Injection -->🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/MeAjudaAi.Shared.csproj` around lines 44 - 46, O PackageReference for "OpenTelemetry.Exporter.Console" está agrupado incorretamente dentro da seção <!-- Feature Management -->; mova a linha PackageReference Include="OpenTelemetry.Exporter.Console" para uma nova ou existente seção apropriada (por exemplo <!-- Observability / OpenTelemetry -->) ou para perto de outras referências de telemetria para melhorar legibilidade e organização do arquivo, preservando o mesmo elemento PackageReference.src/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csproj (1)
33-33: Nitpick: manter a ordem alfabética das referências de módulos.Os demais
ProjectReferencede módulos estão em ordem alfabética; moverBookingspara antes deCommunications(linha 29) preserva esse padrão e reduz ruído em diffs futuros.♻️ Diff sugerido
<ProjectReference Include="..\..\Aspire\MeAjudaAi.ServiceDefaults\MeAjudaAi.ServiceDefaults.csproj" /> + <ProjectReference Include="..\..\Modules\Bookings\API\MeAjudaAi.Modules.Bookings.API.csproj" /> <ProjectReference Include="..\..\Modules\Communications\API\MeAjudaAi.Modules.Communications.API.csproj" /> <ProjectReference Include="..\..\Modules\Documents\API\MeAjudaAi.Modules.Documents.API.csproj" /> <ProjectReference Include="..\..\Modules\Locations\API\MeAjudaAi.Modules.Locations.API.csproj" /> <ProjectReference Include="..\..\Modules\Payments\API\MeAjudaAi.Modules.Payments.API.csproj" /> - <ProjectReference Include="..\..\Modules\Bookings\API\MeAjudaAi.Modules.Bookings.API.csproj" /> <ProjectReference Include="..\..\Modules\Providers\API\MeAjudaAi.Modules.Providers.API.csproj" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csproj` at line 33, Mover a linha ProjectReference que inclui "..\..\Modules\Bookings\API\MeAjudaAi.Modules.Bookings.API.csproj" para manter a ordem alfabética das referências de módulos: coloque esse ProjectReference antes do ProjectReference de "Communications" (a entrada ProjectReference correspondente) dentro do mesmo .csproj, preservando identação e sem alterar outros itens.tests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csproj (1)
87-102: LGTM — módulos novos referenciados de forma consistente.Bookings/Communications/Ratings/SearchProviders agora cobertos pelo build do projeto de Integration Tests, alinhando com as novas integrações do sprint.
Observação (não bloqueante): diferentemente de Users/Documents/Payments, Bookings não referencia seu projeto
.Testsaqui. Se helpers/fixtures (builders, factories) doMeAjudaAi.Modules.Bookings.Testsforem necessários nos testes de integração, considere adicionarProjectReferenceanálogo; caso contrário, pode ser mantido como está.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csproj` around lines 87 - 102, Integration tests now reference Bookings/Communications/Ratings/SearchProviders projects but Bookings' test helpers aren't referenced; if integration tests rely on builders/fixtures from MeAjudaAi.Modules.Bookings.Tests, add a ProjectReference to the MeAjudaAi.Integration.Tests project pointing to MeAjudaAi.Modules.Bookings.Tests (i.e., mirror how Users/Documents/Payments reference their *.Tests projects), otherwise leave as-is.tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs (1)
22-33: LGTM — ajuste consistente com o teste do ApiService.A injeção do
IHostEnvironmentespelha o setup emtests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GlobalExceptionHandlerTests.cs, e o comentário já está em português conforme a política do repositório.Sugestão opcional: considerar um
[Theory]adicional cobrindoEnvironmentName = Productionpara garantir que detalhes de diagnóstico (p.ex. deBadHttpRequestException) não vazem fora de Development — dado que o novo comportamento do handler passou a depender explicitamente do ambiente.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs` around lines 22 - 33, Add a test case to verify behavior in Production: in GlobalExceptionHandlerTests update or add a [Theory] that sets _envMock.Setup(e => e.EnvironmentName).Returns(Environments.Production) and invokes the same handler path used in existing tests (e.g., the test that exercises BadHttpRequestException handling) and assert that diagnostic details (stack traces or exception messages) are not present in the response body and status remains appropriate; mirror the setup/assertion pattern used in tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GlobalExceptionHandlerTests.cs to keep consistency with existing test structure.src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cs (1)
8-14: Paginação adicionada — considere cap máximo no handler.Feedback anterior de paginação endereçado. Apenas confirme que o handler valida limites superiores de
PageSize(ex.:Math.Min(PageSize ?? 10, 100)) para evitar que um cliente solicite páginas excessivamente grandes e degrade o banco. Nullable com default10também permite que um caller passenullexplicitamente — reforça a importância de normalizar no handler.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cs` around lines 8 - 14, The handler for GetBookingsByProviderQuery must normalize and cap the incoming PageSize to prevent overly large queries: in the query handler (e.g., GetBookingsByProviderQueryHandler) replace direct use of request.PageSize with a normalized value such as int pageSize = Math.Min(request.PageSize ?? 10, 100) (or a configurable max) and use a similar normalization for Page (e.g., default to 1 if null or <1); ensure all paging calculations and repository calls reference these normalized variables rather than the raw nullable properties.src/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.bru (1)
8-8: Data fixa no exemplo pode expirar rapidamente.O parâmetro
date=2026-04-25é uma data fixa que pode rapidamente ficar no passado e tornar o exemplo menos útil para testes manuais. Considere usar um placeholder de variável (ex.:{{availabilityDate}}) definido no environment para facilitar manutenção.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.bru` at line 8, O exemplo atual usa uma data fixa na query string ("date=2026-04-25") dentro do template GetProviderAvailability.bru; substitua esse valor por um placeholder de environment (por exemplo {{availabilityDate}}) e atualize a documentação/env do collection para definir availabilityDate dinamicamente, garantindo que a URL (a parte ".../bookings/availability/... ?date=...") passe a usar o placeholder em vez da data hardcoded.src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx (1)
131-141: Fortaleça a asserção do POST /bookings.O
expect.objectContainingsó valida o método e que o body contémserviceId. Verifique tambémproviderId,starteendno body — caso contrário um bug que omita/troqueproviderIdpassa despercebido. Exemplo:- body: expect.stringContaining('"serviceId":"service-456"') + body: expect.stringMatching(/"serviceId":"service-456".*"providerId":"provider-123"/s)Ou faça
JSON.parse(fetchCall[1].body)e asserte campo a campo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx` around lines 131 - 141, Atualize a asserção dentro do bloco await waitFor() que verifica a chamada global.fetch para também validar providerId, start e end no corpo do POST; em vez de apenas expect.objectContaining({ method: "POST", body: expect.stringContaining('"serviceId":"service-456"') }), extraia e parseie o body da chamada (por exemplo capturando fetch.mock.calls ou usando fetch.mock.calls[0][1].body) e faça assertions campo a campo para serviceId, providerId, start e end para garantir que booking POST enviado por the booking modal contém todos os campos esperados.src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs (2)
31-36: Metadados OpenAPI incompletos para respostas de erro.O endpoint usa
RequireAuthorization()e pode retornarProblem(...)com códigos variados viaerror.StatusCode, mas só documenta 200 e 404. Considere adicionar pelo menos401e500(e400se aplicável) para alinhar o contrato OpenAPI com as respostas reais..Produces<AvailabilityDto>(StatusCodes.Status200OK) + .ProducesProblem(StatusCodes.Status400BadRequest) + .ProducesProblem(StatusCodes.Status401Unauthorized) .ProducesProblem(StatusCodes.Status404NotFound) + .ProducesProblem(StatusCodes.Status500InternalServerError)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs` around lines 31 - 36, The OpenAPI metadata for the GetProviderAvailability endpoint is incomplete: after the RequireAuthorization()/ProducesProblem(...) chain in GetProviderAvailabilityEndpoint.cs (the endpoint registration using WithName("GetProviderAvailability")/WithSummary(...)), add explicit ProducesProblem entries for the additional status codes the handler can return (at minimum StatusCodes.Status401Unauthorized and StatusCodes.Status500InternalServerError, and include StatusCodes.Status400BadRequest if the endpoint can return validation/client errors) so the documented responses match the actual Problem(...) status codes emitted by the handler.
23-23: Correlation ID ignora cabeçalho X-Correlation-Id.Este endpoint gera sempre um novo
Guid.NewGuid()para o correlation ID, masGetMyBookingsEndpoint(linhas 37-38) lêX-Correlation-Iddo request e faz fallback paraGuid.NewGuid(). Para manter observabilidade e rastreabilidade consistentes entre endpoints do módulo Bookings, aplique o mesmo padrão aqui.♻️ Sugestão
- app.MapGet("/availability/{providerId}", async ( + app.MapGet("/availability/{providerId}", async ( Guid providerId, [FromQuery] DateOnly date, [FromServices] IQueryDispatcher dispatcher, + HttpContext context, CancellationToken cancellationToken) => { - var query = new GetProviderAvailabilityQuery(providerId, date, Guid.NewGuid()); + var correlationIdHeader = context.Request.Headers["X-Correlation-Id"].ToString(); + var correlationId = Guid.TryParse(correlationIdHeader, out var parsedId) ? parsedId : Guid.NewGuid(); + var query = new GetProviderAvailabilityQuery(providerId, date, correlationId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs` at line 23, O endpoint GetProviderAvailabilityEndpoint está sempre gerando Guid.NewGuid() para o correlation ID; ajuste-o para ler o header "X-Correlation-Id" do request (mesma lógica usada em GetMyBookingsEndpoint: tentar parsear o header para Guid e, se ausente/ inválido, fazer fallback para Guid.NewGuid()) e passar esse valor para o construtor GetProviderAvailabilityQuery(providerId, date, correlationId) em vez de usar Guid.NewGuid() diretamente.src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs (1)
27-97: Cobertura de casos boa; considere cenários adicionais.Os dois cenários (sem bookings e com overlap parcial) estão bem modelados. Para aumentar a robustez, sugiro adicionar casos opcionais:
- Schedule nulo/inexistente para o
providerId(o handler deve falhar de forma previsível ou retornar slots vazios).- Booking cobrindo todo o slot (resultado esperado: 0 slots).
- Booking em dia diferente do solicitado (não deve afetar os slots).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs` around lines 27 - 97, Add three new unit tests alongside HandleAsync_Should_ReturnAvailableSlots_When_NoBookingsExist and HandleAsync_Should_FilterOut_BookedSlots: (1) HandleAsync_Should_Handle_NullSchedule: mock _scheduleRepoMock.GetByProviderIdReadOnlyAsync to return null and assert the handler either returns a predictable failure (IsSuccess false) or an empty Slots list according to current contract; (2) HandleAsync_Should_ReturnNoSlots_When_BookingCoversEntireSlot: set schedule with TimeSlot.Create(08:00,10:00) and _bookingRepoMock to return a Booking that covers 08:00-10:00 and assert result.Value.Slots is empty; (3) HandleAsync_Should_Ignore_BookingsOnDifferentDate: set schedule for date X, return a Booking for a different date and assert the slot for date X remains available. Use the same setup pattern and mocks (_scheduleRepoMock, _bookingRepoMock) and name tests clearly to match existing conventions.src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs (1)
29-73: Considere adicionar cenários complementares.Os dois testes cobrem autorização corretamente. Para robustez, sugiro adicionar:
- Booking inexistente (
GetByIdAsyncretornanull) → falha com 404.- Usuário sem claim
ProviderIdno contexto HTTP → falha com 401/403.- Booking já em estado não-transicionável (ex.: já
ConfirmedouCancelled) → validar comportamento.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs` around lines 29 - 73, Add three complementary unit tests in ConfirmBookingCommandHandlerTests to cover edge cases: (1) "HandleAsync_Should_ReturnNotFound_When_BookingDoesNotExist" — arrange _bookingRepoMock.GetByIdAsync to return null and assert result.IsFailure with 404 and that _bookingRepoMock.UpdateAsync is never called; (2) "HandleAsync_Should_RequireProviderClaim_When_UserHasNoProviderId" — call SetupUser without a providerId claim (or create a SetupUser overload/fixture that leaves ProviderId unset), invoke _sut.HandleAsync(new ConfirmBookingCommand(booking.Id, Guid.NewGuid())), and assert failure with 401/403 and no repository update; (3) "HandleAsync_Should_Fail_When_BookingStateIsNotTransitionable" — create a Booking already in Confirmed or Cancelled state (use Booking.Create then set Status or use factory), have _bookingRepoMock return it, call _sut.HandleAsync and assert failure and that _bookingRepoMock.UpdateAsync is never called; reuse ConfirmBookingCommand, _bookingRepoMock, SetupUser, and booking/TimeSlot helpers from existing tests for consistency.src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs (1)
43-43: Considere inverter a ordem das colunas do índice único para(provider_schedule_id, DayOfWeek).A unicidade é garantida em ambas as ordens, mas como as consultas por disponibilidade sempre partem de um
provider_schedule_idespecífico, colocar a FK como primeira coluna do índice também permite que ele atenda lookups/filtros por schedule sem um índice adicional.♻️ Ajuste sugerido
- availability.HasIndex("DayOfWeek", "provider_schedule_id").IsUnique(); + availability.HasIndex("provider_schedule_id", "DayOfWeek").IsUnique();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs` at line 43, O índice único definido em availability via availability.HasIndex("DayOfWeek", "provider_schedule_id").IsUnique() deve inverter a ordem das colunas para ("provider_schedule_id", "DayOfWeek") para otimizar consultas que sempre filtram por provider_schedule_id; localize a chamada availability.HasIndex(...) na configuração ProviderScheduleConfiguration e trocar os argumentos para availability.HasIndex("provider_schedule_id", "DayOfWeek").IsUnique(), mantendo a unicidade e migrando ou atualizando a migração/EF model se necessário.src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs (1)
40-42: Evite o!emtz!— valide ou garanta no contrato doTimeZoneResolver.O uso de
tz!indica queResolveTimeZonepode retornarnull, masCreateValidatedBookingDtonão tolera isso. Se o resolver algum dia retornarnull(ex.: todos os fallbacks falharem), o!silencia o aviso e umNullReferenceExceptionescapa aqui. Prefira queResolveTimeZoneretorne umTimeZoneInfonão-nulo (caindo emTimeZoneInfo.Utccomo último recurso) e tipe o retorno como não‑nullable, ou valide explicitamente antes de seguir.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs` around lines 40 - 42, O uso de tz! está escondendo um possível null retornado por TimeZoneResolver.ResolveTimeZone; atualize a função ResolveTimeZone para nunca retornar null (por exemplo, garantir um fallback para TimeZoneInfo.Utc e mudar seu tipo de retorno para não‑nullable) ou valide tz imediatamente antes de chamar TimeZoneResolver.CreateValidatedBookingDto (checar if (tz == null) e tratar: lançar uma InvalidOperationException com mensagem clara ou atribuir um fallback), referenciando TimeZoneResolver.ResolveTimeZone(schedule?.TimeZoneId, logger), a variável tz e a chamada TimeZoneResolver.CreateValidatedBookingDto(booking, tz, logger).src/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cs (1)
32-32:CorrelationIdgerado localmente em vez de ler o headerX-Correlation-Id.Outros endpoints do módulo (ex.:
GetBookingByIdEndpoint) leemX-Correlation-Iddo request e só fazem fallback paraGuid.NewGuid()quando ausente/inválido. Aqui um ID novo é sempre criado, quebrando o encadeamento de correlação entre client → API → command.♻️ Ajuste sugerido
- app.MapPut("/{id}/reject", async ( + app.MapPut("/{id}/reject", async ( Guid id, RejectBookingRequest request, [FromServices] ICommandDispatcher dispatcher, + HttpContext context, CancellationToken cancellationToken) => { + var correlationIdHeader = context.Request.Headers["X-Correlation-Id"].ToString(); + if (!Guid.TryParse(correlationIdHeader, out var correlationId)) + { + correlationId = Guid.NewGuid(); + } ... - var command = new RejectBookingCommand(id, request.Reason, Guid.NewGuid()); + var command = new RejectBookingCommand(id, request.Reason, correlationId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cs` at line 32, RejectBookingEndpoint currently always generates a new CorrelationId when constructing RejectBookingCommand (var command = new RejectBookingCommand(id, request.Reason, Guid.NewGuid())), breaking correlation chaining; change the endpoint to read the X-Correlation-Id header from the incoming request (e.g., from Request.Headers or HttpContext.Request.Headers), validate/parse it to a Guid and pass that Guid into the RejectBookingCommand, only falling back to Guid.NewGuid() when the header is missing or invalid so correlation is preserved.src/Web/MeAjudaAi.Web.Customer/app/(main)/prestador/[id]/page.tsx (1)
47-48: Removasession?.accessTokendaqueryKey.Incluir o access token na chave força um refetch completo toda vez que o token é renovado (inclusive em refreshes silenciosos), além de materializar o token como parte da chave de cache do React Query. Para variar o cache por estado de autenticação,
isAuthenticatedjá é suficiente — o token continua sendo lido dinamicamente dentro daqueryFn.♻️ Ajuste sugerido
- queryKey: ["public-provider", id, isAuthenticated, session?.accessToken], + queryKey: ["public-provider", id, isAuthenticated],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx around lines 47 - 48, The queryKey for the useQuery call (const { data: providerData, isLoading, error } = useQuery(...)) currently includes session?.accessToken which forces refetches on token rotation and bakes the token into the cache; remove session?.accessToken from the queryKey and use a stable key like ["public-provider", id, isAuthenticated] instead, while keeping the queryFn responsible for reading session?.accessToken dynamically when making the request so the token is not part of the cache identity.src/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cs (1)
40-48: O fallback porSubjectchama o módulo Providers a cada request — considere cachear/emitir oprovider_idno token.Quando o claim
ProviderIdnão está presente, cada chamada a este endpoint disparaprovidersApi.GetProviderByUserIdAsync(...)só para autorizar, o que adiciona latência ao hot path e aumenta o acoplamento. Em regime, o ideal é que oprovider_idseja emitido no JWT para usuários que são prestadores (evitando o lookup); enquanto isso, um cache curto (memória/distribuído) poruserId → providerIdreduz significativamente o custo.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cs` around lines 40 - 48, In the Subject-fallback else block of GetProviderBookingsEndpoint (the code that reads user.FindFirst(AuthConstants.Claims.Subject) and calls providersApi.GetProviderByUserIdAsync), avoid calling providersApi on every request: first try to read a ProviderId claim from the token and compare to providerId; if that claim is absent, consult a short-lived cache (memory or distributed) keyed by userId → providerId and only call providersApi.GetProviderByUserIdAsync(userId, ...) on cache miss, then store the result in the cache and set isAuthorized based on the cached/returned providerId.src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs (1)
44-48: Complete a documentação OpenAPI com os demais status possíveis.Como o endpoint exige autenticação e o handler pode retornar erros mapeados (ex.: 400/401/403/500), inclua os
ProducesProblem(...)correspondentes para que o contrato exposto reflita o real comportamento.♻️ Ajuste sugerido
.Produces<BookingDto>(StatusCodes.Status200OK) + .ProducesProblem(StatusCodes.Status401Unauthorized) .ProducesProblem(StatusCodes.Status404NotFound)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs` around lines 44 - 48, O endpoint "GetBookingById" expõe 200 e 404 mas não documenta os demais códigos que podem ser retornados pelo handler (ex.: 400, 401, 403, 500); adicione chamadas .ProducesProblem(StatusCodes.Status400BadRequest), .ProducesProblem(StatusCodes.Status401Unauthorized), .ProducesProblem(StatusCodes.Status403Forbidden) e .ProducesProblem(StatusCodes.Status500InternalServerError) à cadeia que configura o endpoint (onde estão .Produces<BookingDto>(...), .WithName("GetBookingById") etc.) para que o OpenAPI reflita todos os status possíveis retornados pelo handler.src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs (1)
9-146: Complete a cobertura paraVersione transições inválidas restantes.Os testes validam bem o fluxo feliz e algumas exceções, mas:
- Nenhum teste verifica que
Versioné incrementado em cada transição (Create → 1,Confirm/Reject/Cancel/Complete → +1). Como essa propriedade foi introduzida especificamente para concorrência otimista e foi alvo de revisão anterior (incremento + evento usandoVersionreal em vez de0), vale travar com asserção para evitar regressão silenciosa.- Nenhum teste verifica a emissão do
DomainEventcorrespondente por transição (tipo do evento e aVersioncarregada).- Faltam transições inválidas:
Confirmem booking jáConfirmed/Cancelled/Completed,RejectemConfirmed/Rejected/Cancelled,CancelemCompleted/Cancelled, eCompleteemRejected/Cancelled/Completed.Sugestão de ajuste parcial:
[Fact] public void Confirm_Should_ChangeStatusToConfirmed_When_Pending() { // Arrange var booking = CreatePendingBooking(); + var previousVersion = booking.Version; // Act booking.Confirm(); // Assert booking.Status.Should().Be(EBookingStatus.Confirmed); booking.UpdatedAt.Should().NotBeNull(); + booking.Version.Should().Be(previousVersion + 1); }Based on learnings, AAA headers (Arrange/Act/Assert) devem permanecer em inglês nos testes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs` around lines 9 - 146, Add unit tests that assert the Booking.Version is set to 1 on Create and increments by 1 on each state transition (Confirm, Reject, Cancel, Complete), and that each transition raises the correct DomainEvent type with the emitted event carrying the same Version value; extend the suite around CreatePendingBooking to cover invalid transitions: calling Confirm on already Confirmed/Cancelled/Completed, Reject on Confirmed/Rejected/Cancelled, Cancel on Completed/Cancelled, and Complete on Rejected/Cancelled/Completed, each asserting InvalidOperationException and the exact message; reference the Booking.Create, Booking.Confirm, Booking.Reject, Booking.Cancel, Booking.Complete members and the Version property and domain events produced to locate where to add these tests.src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs (1)
137-145: Nit: divergência na lista de status excluídos entreAddIfNoOverlapAsynceGetActiveByProviderAndDateAsync.O predicado de overlap exclui apenas
CancelledeRejected, enquantoGetActiveByProviderAndDateAsync(linhas 105–110) exclui tambémCompleted. Na prática o cenário é improvável (umCompletedpara um slot futuro seria dado inconsistente), mas a divergência pode confundir manutenção futura — considere alinhar as duas listas ou documentar por queCompletedbloqueia novos agendamentos no overlap mas não no cálculo de disponibilidade.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs` around lines 137 - 145, The overlap check in AddIfNoOverlapAsync currently excludes only Cancelled and Rejected while GetActiveByProviderAndDateAsync also excludes Completed, causing inconsistency; update the overlap predicate in AddIfNoOverlapAsync (the AnyAsync lambda) to also exclude EBookingStatus.Completed (add && b.Status != EBookingStatus.Completed) so both methods use the same active-status set, or alternatively remove Completed from GetActiveByProviderAndDateAsync—pick one consistent behavior and apply it to the methods AddIfNoOverlapAsync and GetActiveByProviderAndDateAsync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.oasdiff-ignore.yaml:
- Around line 1-25: O arquivo .oasdiff-ignore.yaml está no formato errado (usa
chaves err-id/id); substitua as entradas estruturadas por linhas no formato
esperado pelo oasdiff, por exemplo converta cada bloco que refere-se a "GET
/api/v1/providers/public/{idOrSlug}" e às tags "response-property-type-changed"
ou "response-body-type-changed" para entradas simples como: "GET
/api/v1/providers/public/{idOrSlug} response-property-type-changed" (ou
response-body-type-changed) — remova os campos err-id/id e os blocos duplicados
e, se precisar corresponder por padrão/regex na descrição, use a sintaxe de
correspondência de texto conforme a documentação do oasdiff.
In `@docs/modules/bookings.md`:
- Around line 13-21: Update the header line "O módulo segue a Clean Architecture
com separação em 4 camadas:" to align with the directory tree that includes
Tests; e.g., change it to "O módulo segue a Clean Architecture com separação em
4 camadas de produção, além de Tests" or "…separação em 5 blocos (incluindo
Tests)". Ensure the description near the Bookings/ tree mentions Tests/ as a
separate testing block so the text and the tree (Bookings/ ... Tests/) are
consistent.
- Around line 136-150: The repository docs are missing recently added methods;
update the IBookingRepository and IProviderScheduleRepository sections to
include the new method signatures and short descriptions for
GetByClientIdPagedAsync (paged listing for a client) and
GetByProviderIdReadOnlyAsync (read-only provider listing/optimized for reads),
and verify any other new methods used in the PR are documented (e.g., ensure
AddIfNoOverlapAsync, GetByIdAsync, UpdateAsync still have their descriptions).
Use the exact method names GetByClientIdPagedAsync and
GetByProviderIdReadOnlyAsync when adding entries and keep descriptions
consistent with the existing style (purpose and behavior like tracked/read-only
or pagination).
In `@src/Modules/Bookings/API/API.Client/Bookings/CreateBooking.bru`:
- Around line 26-27: O payload de exemplo em CreateBooking.bru usa datas fixas
nos campos "start" e "end", que vão expirar; substitua esses valores por
variáveis reutilizáveis da coleção/ambiente (p.ex. placeholders {{start}} e
{{end}} ou a função de data dinâmica do seu runner) para gerar timestamps ISO
futuros automaticamente; atualize a geração/descrição do request para documentar
como as variáveis devem ser populadas (ex.: start = agora+1dia, end =
start+1hora) e garanta que os nomes das variáveis correspondam aos usados no
request ("start", "end").
In `@src/Modules/Bookings/API/Endpoints/Public/CancelBookingEndpoint.cs`:
- Around line 23-30: The two early-return branches that check request.Reason
currently call Results.BadRequest(new { error = ... }), which contradicts the
endpoint's advertised ProblemDetails contract; replace those returns with
Results.Problem (or Results.BadRequest(new ProblemDetails { Title = "...",
Detail = "...", Status = 400 })) so clients receive a proper ProblemDetails
body. Update the checks around request.Reason in the CancelBookingEndpoint
method (replace the Result.BadRequest calls) to return ProblemDetails with a
clear Title/Detail and Status = 400.
In
`@src/Modules/Bookings/Application/Bookings/Commands/CompleteBookingCommand.cs`:
- Around line 6-10: The CompleteBookingCommand record does not satisfy
ICommand<Result>.CorrelationId and differs from ConfirmBookingCommand; change
CompleteBookingCommand to include Guid CorrelationId as a positional parameter
in the primary constructor (e.g., record CompleteBookingCommand(Guid BookingId,
Guid CorrelationId) : ICommand<Result>) and remove the init property with
Guid.NewGuid(); then update call sites (CompleteBookingEndpoint and related
tests) to pass the CorrelationId explicitly when constructing
CompleteBookingCommand so the interface member is implemented and behavior is
consistent with ConfirmBookingCommand.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/CancelBookingCommandHandler.cs`:
- Around line 35-41: The owner check in CancelBookingCommandHandler only reads
AuthConstants.Claims.Subject, but CreateBookingEndpoint accepts either that or
ClaimTypes.NameIdentifier; update the authorization logic so userIdClaim is
obtained from either claim (AuthConstants.Claims.Subject OR
ClaimTypes.NameIdentifier) before parsing and computing isOwner (and ensure you
still parse to Guid and compare to booking.ClientId), so that a user
authenticated with NameIdentifier is recognized as the booking owner; adjust
references to userIdClaim and isOwner in the handler accordingly.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingsByProviderQueryHandler.cs`:
- Around line 41-50: O compilador acusa CS8604 porque Result<BookingDto>.Value é
anulável; após checar dtoResult.IsFailure você deve desambiguar o valor para o
compilador. Localize o loop em GetBookingsByProviderQueryHandler que chama
TimeZoneResolver.CreateValidatedBookingDto e, após a verificação
dtoResult.IsFailure, use o operador de confirmação de não-nulo (por exemplo
aplicando ! ao acessar Value) ou faça um pattern match/local var para extrair o
BookingDto não-nulo antes de chamar dtos.Add, garantindo que o compilador
reconheça que dtoResult.Value não pode ser null.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/SetProviderScheduleCommandHandler.cs`:
- Around line 40-60: O loop que cria Availability não valida availabilityDto ou
availabilityDto.Slots nulos e hoje cai no catch genérico (500); antes de chamar
TimeSlot.Create/Availability.Create em SetProviderScheduleCommandHandler,
verifique explicitamente cada availabilityDto != null e availabilityDto.Slots !=
null (e opcionalmente vazio) e retorne Result.Failure(Error.BadRequest(...)) com
mensagem de payload inválido para command.ProviderId; preserve os catches para
erros de criação (ArgumentException/InvalidOperationException) mas não deixe
nulos chegarem até o catch genérico que gera 500.
In `@src/Modules/Bookings/Application/errors.json`:
- Around line 1-51: The committed SARIF errors.json containing an absolute
Windows path and a compilation error must be removed and the code fixed: remove
src/Modules/Bookings/Application/errors.json from the PR and add ignore patterns
(e.g., **/errors.json and *.sarif) to .gitignore to prevent future commits; then
fix the CS0535 by implementing the missing ICommand<Result>.CorrelationId member
on the CompleteBookingCommand class in CompleteBookingCommand.cs (add the
required CorrelationId property or explicit interface implementation consistent
with ICommand<Result>), run the build to ensure no remaining compiler errors,
and recommit only the code changes and updated .gitignore (not the SARIF file).
In `@src/Modules/Bookings/Domain/ValueObjects/TimeSlot.cs`:
- Around line 17-40: Altere as mensagens de validação do value object TimeSlot
para português: substitua o texto em inglês no construtor de TimeSlot (onde
lança ArgumentException quando start >= end) por uma mensagem em português e
também troque a mensagem do throw em FromDateTime(DateTime start, DateTime end)
(que atualmente informa "Start and End must have the same Date and Kind. Start:
{start}, End: {end}") por uma versão em português; os símbolos a ajustar são o
construtor de TimeSlot (validação start >= end) e o método estático FromDateTime
para garantir que as exceções visíveis ao frontend estejam em português.
In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/ProviderScheduleTests.cs`:
- Line 90: No teste ProviderScheduleTests (comentário: "Slot covers until the
very end of the day to ensure we fail by date crossing") substitua esse
comentário não-AAA por uma versão em pt-BR mantendo os cabeçalhos AAA em inglês;
por exemplo traduza para algo como "Slot cobre até o final do dia para garantir
que falhemos por cruzamento de data" e certifique-se de que comentários
explicativos/XML do teste estejam em português conforme o padrão do repositório.
- Around line 73-82: The test currently asserts duration behavior against an
empty schedule so failures can be masked; update the Arrange step for
ProviderSchedule.Create to add a valid availability covering the target dateTime
(so IsAvailable(dateTime, TimeSpan.FromHours(1)) would return true) and then
assert that schedule.IsAvailable(dateTime, TimeSpan.Zero) and
schedule.IsAvailable(dateTime, TimeSpan.FromHours(-1)) return false, ensuring
the test isolates the zero/negative duration validation in IsAvailable.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 215-227: O Badge usado in services.map com onClick (see Badge,
setSelectedServiceId, selectedServiceId, BookingModal) não é acessível via
teclado porque renderiza um elemento não focável; substitua Badge por um
elemento focável/ativável (preferred: <button type="button"> styled as Badge)
ou, se mantiver Badge, adicione role="button", tabIndex={0} e um onKeyDown
handler que dispare setSelectedServiceId(service.id) ao detectar Enter ou Space;
mantenha as mesmas classes de estilo condicional para selectedServiceId e
garanta que a interação abra o BookingModal como hoje.
---
Duplicate comments:
In `@src/Modules/Bookings/API/Endpoints/Public/ConfirmBookingEndpoint.cs`:
- Around line 21-22: O endpoint está criando um ator falso ao instanciar
ConfirmBookingCommand(id, Guid.NewGuid()); em vez disso, obtenha o identificador
do utilizador autenticado a partir dos claims/policy (por exemplo
HttpContext.User.FindFirst(ClaimTypes.NameIdentifier) ou o claim definido em
AuthConstants.Claims) e passe esse id como segundo parâmetro, ou remova esse
parâmetro do construtor e deixe o ConfirmBookingCommandHandler resolver o
utilizador atual; atualize a chamada dispatcher.SendAsync<ConfirmBookingCommand,
Result>(...) e o construtor de ConfirmBookingCommand conforme necessário para
usar o identificador real do ator (ConfirmBookingCommand e
ConfirmBookingCommandHandler são os símbolos a ajustar).
In
`@src/Modules/Bookings/Application/Bookings/Handlers/RejectBookingCommandHandler.cs`:
- Around line 22-25: The handler currently only checks
string.IsNullOrWhiteSpace(command.Reason) and must also enforce a maximum length
to prevent oversized reasons reaching the domain/persistence; update
RejectBookingCommandHandler (the method handling the RejectBookingCommand, e.g.,
Handle) to validate command.Reason.Length against the allowed maximum (use the
existing max constant if one exists in the command/validator/domain, otherwise
introduce a shared constant like ReasonMaxLength) and return
Result.Failure(Error.BadRequest("O motivo da rejeição deve ter no máximo X
caracteres.")) when exceeded, keeping the empty/whitespace check intact.
In `@src/Modules/Bookings/Domain/Events/BookingConfirmedDomainEvent.cs`:
- Around line 1-15: Remova o atributo de exclusão de cobertura do domain event:
abra o record BookingConfirmedDomainEvent e retire a annotation
[ExcludeFromCodeCoverage] (ou remova a linha inteira contendo
ExcludeFromCodeCoverage) para que o evento seja coberto pelos testes de domínio;
mantenha o resto da declaração do record BookingConfirmedDomainEvent
(AggregateId, Version, ProviderId, ClientId) inalterado.
---
Nitpick comments:
In `@src/Bootstrapper/MeAjudaAi.ApiService/MeAjudaAi.ApiService.csproj`:
- Line 33: Mover a linha ProjectReference que inclui
"..\..\Modules\Bookings\API\MeAjudaAi.Modules.Bookings.API.csproj" para manter a
ordem alfabética das referências de módulos: coloque esse ProjectReference antes
do ProjectReference de "Communications" (a entrada ProjectReference
correspondente) dentro do mesmo .csproj, preservando identação e sem alterar
outros itens.
In `@src/Modules/Bookings/API/API.Client/Bookings/GetProviderAvailability.bru`:
- Line 8: O exemplo atual usa uma data fixa na query string ("date=2026-04-25")
dentro do template GetProviderAvailability.bru; substitua esse valor por um
placeholder de environment (por exemplo {{availabilityDate}}) e atualize a
documentação/env do collection para definir availabilityDate dinamicamente,
garantindo que a URL (a parte ".../bookings/availability/... ?date=...") passe a
usar o placeholder em vez da data hardcoded.
In `@src/Modules/Bookings/API/Endpoints/Public/GetBookingByIdEndpoint.cs`:
- Around line 44-48: O endpoint "GetBookingById" expõe 200 e 404 mas não
documenta os demais códigos que podem ser retornados pelo handler (ex.: 400,
401, 403, 500); adicione chamadas
.ProducesProblem(StatusCodes.Status400BadRequest),
.ProducesProblem(StatusCodes.Status401Unauthorized),
.ProducesProblem(StatusCodes.Status403Forbidden) e
.ProducesProblem(StatusCodes.Status500InternalServerError) à cadeia que
configura o endpoint (onde estão .Produces<BookingDto>(...),
.WithName("GetBookingById") etc.) para que o OpenAPI reflita todos os status
possíveis retornados pelo handler.
In
`@src/Modules/Bookings/API/Endpoints/Public/GetProviderAvailabilityEndpoint.cs`:
- Around line 31-36: The OpenAPI metadata for the GetProviderAvailability
endpoint is incomplete: after the RequireAuthorization()/ProducesProblem(...)
chain in GetProviderAvailabilityEndpoint.cs (the endpoint registration using
WithName("GetProviderAvailability")/WithSummary(...)), add explicit
ProducesProblem entries for the additional status codes the handler can return
(at minimum StatusCodes.Status401Unauthorized and
StatusCodes.Status500InternalServerError, and include
StatusCodes.Status400BadRequest if the endpoint can return validation/client
errors) so the documented responses match the actual Problem(...) status codes
emitted by the handler.
- Line 23: O endpoint GetProviderAvailabilityEndpoint está sempre gerando
Guid.NewGuid() para o correlation ID; ajuste-o para ler o header
"X-Correlation-Id" do request (mesma lógica usada em GetMyBookingsEndpoint:
tentar parsear o header para Guid e, se ausente/ inválido, fazer fallback para
Guid.NewGuid()) e passar esse valor para o construtor
GetProviderAvailabilityQuery(providerId, date, correlationId) em vez de usar
Guid.NewGuid() diretamente.
In `@src/Modules/Bookings/API/Endpoints/Public/GetProviderBookingsEndpoint.cs`:
- Around line 40-48: In the Subject-fallback else block of
GetProviderBookingsEndpoint (the code that reads
user.FindFirst(AuthConstants.Claims.Subject) and calls
providersApi.GetProviderByUserIdAsync), avoid calling providersApi on every
request: first try to read a ProviderId claim from the token and compare to
providerId; if that claim is absent, consult a short-lived cache (memory or
distributed) keyed by userId → providerId and only call
providersApi.GetProviderByUserIdAsync(userId, ...) on cache miss, then store the
result in the cache and set isAuthorized based on the cached/returned
providerId.
In `@src/Modules/Bookings/API/Endpoints/Public/RejectBookingEndpoint.cs`:
- Line 32: RejectBookingEndpoint currently always generates a new CorrelationId
when constructing RejectBookingCommand (var command = new
RejectBookingCommand(id, request.Reason, Guid.NewGuid())), breaking correlation
chaining; change the endpoint to read the X-Correlation-Id header from the
incoming request (e.g., from Request.Headers or HttpContext.Request.Headers),
validate/parse it to a Guid and pass that Guid into the RejectBookingCommand,
only falling back to Guid.NewGuid() when the header is missing or invalid so
correlation is preserved.
In
`@src/Modules/Bookings/Application/Bookings/Handlers/GetBookingByIdQueryHandler.cs`:
- Around line 40-42: O uso de tz! está escondendo um possível null retornado por
TimeZoneResolver.ResolveTimeZone; atualize a função ResolveTimeZone para nunca
retornar null (por exemplo, garantir um fallback para TimeZoneInfo.Utc e mudar
seu tipo de retorno para não‑nullable) ou valide tz imediatamente antes de
chamar TimeZoneResolver.CreateValidatedBookingDto (checar if (tz == null) e
tratar: lançar uma InvalidOperationException com mensagem clara ou atribuir um
fallback), referenciando TimeZoneResolver.ResolveTimeZone(schedule?.TimeZoneId,
logger), a variável tz e a chamada
TimeZoneResolver.CreateValidatedBookingDto(booking, tz, logger).
In
`@src/Modules/Bookings/Application/Bookings/Queries/GetBookingsByProviderQuery.cs`:
- Around line 8-14: The handler for GetBookingsByProviderQuery must normalize
and cap the incoming PageSize to prevent overly large queries: in the query
handler (e.g., GetBookingsByProviderQueryHandler) replace direct use of
request.PageSize with a normalized value such as int pageSize =
Math.Min(request.PageSize ?? 10, 100) (or a configurable max) and use a similar
normalization for Page (e.g., default to 1 if null or <1); ensure all paging
calculations and repository calls reference these normalized variables rather
than the raw nullable properties.
In
`@src/Modules/Bookings/Infrastructure/Persistence/Configurations/ProviderScheduleConfiguration.cs`:
- Line 43: O índice único definido em availability via
availability.HasIndex("DayOfWeek", "provider_schedule_id").IsUnique() deve
inverter a ordem das colunas para ("provider_schedule_id", "DayOfWeek") para
otimizar consultas que sempre filtram por provider_schedule_id; localize a
chamada availability.HasIndex(...) na configuração ProviderScheduleConfiguration
e trocar os argumentos para availability.HasIndex("provider_schedule_id",
"DayOfWeek").IsUnique(), mantendo a unicidade e migrando ou atualizando a
migração/EF model se necessário.
In `@src/Modules/Bookings/Infrastructure/Repositories/BookingRepository.cs`:
- Around line 137-145: The overlap check in AddIfNoOverlapAsync currently
excludes only Cancelled and Rejected while GetActiveByProviderAndDateAsync also
excludes Completed, causing inconsistency; update the overlap predicate in
AddIfNoOverlapAsync (the AnyAsync lambda) to also exclude
EBookingStatus.Completed (add && b.Status != EBookingStatus.Completed) so both
methods use the same active-status set, or alternatively remove Completed from
GetActiveByProviderAndDateAsync—pick one consistent behavior and apply it to the
methods AddIfNoOverlapAsync and GetActiveByProviderAndDateAsync.
In `@src/Modules/Bookings/Tests/BaseUnitTest.cs`:
- Around line 1-10: The BaseUnitTest class is an empty placeholder and the using
directives for FluentAssertions, Moq, and Xunit are unused; either delete the
entire BaseUnitTest class/file until shared setup is needed, or remove the
unused usings from the file so it no longer causes IDE warnings. Locate the
class named BaseUnitTest and apply one of the two fixes: delete the file if no
shared behavior is required, or strip the unnecessary using statements
(FluentAssertions, Moq, Xunit) and keep the file only if you add real shared
setup code.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/ConfirmBookingCommandHandlerTests.cs`:
- Around line 29-73: Add three complementary unit tests in
ConfirmBookingCommandHandlerTests to cover edge cases: (1)
"HandleAsync_Should_ReturnNotFound_When_BookingDoesNotExist" — arrange
_bookingRepoMock.GetByIdAsync to return null and assert result.IsFailure with
404 and that _bookingRepoMock.UpdateAsync is never called; (2)
"HandleAsync_Should_RequireProviderClaim_When_UserHasNoProviderId" — call
SetupUser without a providerId claim (or create a SetupUser overload/fixture
that leaves ProviderId unset), invoke _sut.HandleAsync(new
ConfirmBookingCommand(booking.Id, Guid.NewGuid())), and assert failure with
401/403 and no repository update; (3)
"HandleAsync_Should_Fail_When_BookingStateIsNotTransitionable" — create a
Booking already in Confirmed or Cancelled state (use Booking.Create then set
Status or use factory), have _bookingRepoMock return it, call _sut.HandleAsync
and assert failure and that _bookingRepoMock.UpdateAsync is never called; reuse
ConfirmBookingCommand, _bookingRepoMock, SetupUser, and booking/TimeSlot helpers
from existing tests for consistency.
In
`@src/Modules/Bookings/Tests/Unit/Application/Handlers/GetProviderAvailabilityQueryHandlerTests.cs`:
- Around line 27-97: Add three new unit tests alongside
HandleAsync_Should_ReturnAvailableSlots_When_NoBookingsExist and
HandleAsync_Should_FilterOut_BookedSlots: (1)
HandleAsync_Should_Handle_NullSchedule: mock
_scheduleRepoMock.GetByProviderIdReadOnlyAsync to return null and assert the
handler either returns a predictable failure (IsSuccess false) or an empty Slots
list according to current contract; (2)
HandleAsync_Should_ReturnNoSlots_When_BookingCoversEntireSlot: set schedule with
TimeSlot.Create(08:00,10:00) and _bookingRepoMock to return a Booking that
covers 08:00-10:00 and assert result.Value.Slots is empty; (3)
HandleAsync_Should_Ignore_BookingsOnDifferentDate: set schedule for date X,
return a Booking for a different date and assert the slot for date X remains
available. Use the same setup pattern and mocks (_scheduleRepoMock,
_bookingRepoMock) and name tests clearly to match existing conventions.
In `@src/Modules/Bookings/Tests/Unit/Domain/Entities/BookingTests.cs`:
- Around line 9-146: Add unit tests that assert the Booking.Version is set to 1
on Create and increments by 1 on each state transition (Confirm, Reject, Cancel,
Complete), and that each transition raises the correct DomainEvent type with the
emitted event carrying the same Version value; extend the suite around
CreatePendingBooking to cover invalid transitions: calling Confirm on already
Confirmed/Cancelled/Completed, Reject on Confirmed/Rejected/Cancelled, Cancel on
Completed/Cancelled, and Complete on Rejected/Cancelled/Completed, each
asserting InvalidOperationException and the exact message; reference the
Booking.Create, Booking.Confirm, Booking.Reject, Booking.Cancel,
Booking.Complete members and the Version property and domain events produced to
locate where to add these tests.
In `@src/Shared/MeAjudaAi.Shared.csproj`:
- Around line 44-46: O PackageReference for "OpenTelemetry.Exporter.Console"
está agrupado incorretamente dentro da seção <!-- Feature Management -->; mova a
linha PackageReference Include="OpenTelemetry.Exporter.Console" para uma nova ou
existente seção apropriada (por exemplo <!-- Observability / OpenTelemetry -->)
ou para perto de outras referências de telemetria para melhorar legibilidade e
organização do arquivo, preservando o mesmo elemento PackageReference.
In
`@src/Web/MeAjudaAi.Web.Customer/__tests__/components/bookings/booking-modal.test.tsx`:
- Around line 131-141: Atualize a asserção dentro do bloco await waitFor() que
verifica a chamada global.fetch para também validar providerId, start e end no
corpo do POST; em vez de apenas expect.objectContaining({ method: "POST", body:
expect.stringContaining('"serviceId":"service-456"') }), extraia e parseie o
body da chamada (por exemplo capturando fetch.mock.calls ou usando
fetch.mock.calls[0][1].body) e faça assertions campo a campo para serviceId,
providerId, start e end para garantir que booking POST enviado por the booking
modal contém todos os campos esperados.
In `@src/Web/MeAjudaAi.Web.Customer/app/`(main)/prestador/[id]/page.tsx:
- Around line 47-48: The queryKey for the useQuery call (const { data:
providerData, isLoading, error } = useQuery(...)) currently includes
session?.accessToken which forces refetches on token rotation and bakes the
token into the cache; remove session?.accessToken from the queryKey and use a
stable key like ["public-provider", id, isAuthenticated] instead, while keeping
the queryFn responsible for reading session?.accessToken dynamically when making
the request so the token is not part of the cache identity.
In `@tests/MeAjudaAi.Integration.Tests/MeAjudaAi.Integration.Tests.csproj`:
- Around line 87-102: Integration tests now reference
Bookings/Communications/Ratings/SearchProviders projects but Bookings' test
helpers aren't referenced; if integration tests rely on builders/fixtures from
MeAjudaAi.Modules.Bookings.Tests, add a ProjectReference to the
MeAjudaAi.Integration.Tests project pointing to MeAjudaAi.Modules.Bookings.Tests
(i.e., mirror how Users/Documents/Payments reference their *.Tests projects),
otherwise leave as-is.
In `@tests/MeAjudaAi.Shared.Tests/Unit/Exceptions/GlobalExceptionHandlerTests.cs`:
- Around line 22-33: Add a test case to verify behavior in Production: in
GlobalExceptionHandlerTests update or add a [Theory] that sets _envMock.Setup(e
=> e.EnvironmentName).Returns(Environments.Production) and invokes the same
handler path used in existing tests (e.g., the test that exercises
BadHttpRequestException handling) and assert that diagnostic details (stack
traces or exception messages) are not present in the response body and status
remains appropriate; mirror the setup/assertion pattern used in
tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GlobalExceptionHandlerTests.cs
to keep consistency with existing test structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
Summary by CodeRabbit
New Features
Frontend
Documentation
Tests
Chores