Sprint 8c: add provider web app & slug implementation#165
Sprint 8c: add provider web app & slug implementation#165
Conversation
…ities, DTOs, mappers, repositories, event handlers, APIs, and comprehensive tests.
📝 WalkthroughWalkthroughAdiciona suporte a Slug como identificador amigável para URLs em provedores, com geração automática a partir do nome, persistência no banco de dados, e exposição através de DTOs, repositórios e eventos de domínio. Inclui utilidade SlugHelper para normalização de texto em slugs. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Modules/SearchProviders/Domain/Entities/SearchableProvider.cs (1)
147-170:⚠️ Potential issue | 🟡 MinorNormalize o
Slugtambém na reconstituição.
Reconstituteainda copia o valor bruto do banco, então dados legados com espaços ou casing diferente quebram a mesma invariável garantida porCreateeUpdateBasicInfo.🛠️ Ajuste sugerido
internal static SearchableProvider Reconstitute( Guid id, Guid providerId, string name, string slug, GeoPoint location, ESubscriptionTier subscriptionTier, decimal averageRating, int totalReviews, Guid[] serviceIds, bool isActive, string? description = null, string? city = null, string? state = null) { + if (string.IsNullOrWhiteSpace(slug)) + { + throw new ArgumentException("Provider slug cannot be empty.", nameof(slug)); + } + var searchableProvider = new SearchableProvider( new SearchableProviderId(id), providerId, name, location, subscriptionTier) { - Slug = slug, + Slug = slug.Trim().ToLowerInvariant(), Description = description, City = city, State = state, AverageRating = averageRating,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Domain/Entities/SearchableProvider.cs` around lines 147 - 170, No método Reconstitute da classe SearchableProvider, normalize o valor de Slug antes de atribuí-lo para manter a mesma invariante aplicada em Create e UpdateBasicInfo; localize SearchableProvider.Reconstitute e substituir a atribuição Slug = slug por uma atribuição que aplica a mesma rotina de normalização usada por Create/UpdateBasicInfo (por exemplo o helper/método de slugificação existente — nome similar a NormalizeSlug/Slugify — ou reimplemente a mesma lógica de trim, lowercase e remoção de espaços/caracteres), garantindo que dados legados sejam normalizados ao reconstituir a entidade.
🧹 Nitpick comments (14)
src/Modules/SearchProviders/Tests/Unit/Domain/Models/SearchResultTests.cs (1)
188-197: PrefiraSlugHelper.Generateno teste para evitar divergência de regra.Hoje o teste duplica a lógica de slug manualmente; isso pode mascarar regressões quando a regra oficial mudar.
Refactor sugerido
+using MeAjudaAi.Shared.Utilities; ... var providerName = _faker.Company.CompanyName(); var provider = SearchableProvider.Create( providerId: providerId, name: providerName, - slug: providerName.ToLower().Replace(" ", "-"), + slug: SlugHelper.Generate(providerName), location: location, subscriptionTier: _faker.Random.Enum<ESubscriptionTier>(),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Unit/Domain/Models/SearchResultTests.cs` around lines 188 - 197, O teste está replicando a lógica de geração de slug manualmente; em vez de usar providerName.ToLower().Replace(" ", "-") passe o slug gerado por SlugHelper.Generate(providerName) ao criar o objeto via SearchableProvider.Create (referenciar providerName e o parâmetro slug) para garantir que o teste acompanhe a regra oficial de geração de slugs.prompts/design-react-project.md (1)
138-152: Adicione especificador de linguagem ao bloco de código.O bloco de código para variáveis CSS não possui especificador de linguagem, o que dispara um aviso do markdownlint. Adicione
cssoutextpara melhorar a formatação e silenciar o warning.📝 Sugestão de correção
-``` +```css bg-surface, bg-surface-raised → fundos🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@prompts/design-react-project.md` around lines 138 - 152, The code block containing the CSS variable mappings (lines with identifiers like bg-surface, bg-primary, bg-destructive, text-foreground, border-border, ring-ring, etc.) lacks a language specifier; update the fenced code block start to include a language (e.g., ```css or ```text) so markdownlint warning is silenced and formatting is improved—locate the block showing the mappings and change the opening fence to ```css.tests/MeAjudaAi.Shared.Tests/Unit/Utilities/SlugHelperTests.cs (1)
6-7: Considere adicionar o atributo de categoria para consistência.Os outros arquivos de teste no projeto utilizam
[Trait("Category", "Unit")]para categorização. Adicionar este atributo manteria a consistência e facilitaria a filtragem de testes por categoria.♻️ Sugestão de refatoração
+[Trait("Category", "Unit")] public class SlugHelperTests🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/SlugHelperTests.cs` around lines 6 - 7, A classe de teste SlugHelperTests está faltando a categorização consistente usada no projeto; adicione o atributo [Trait("Category", "Unit")] acima da declaração da classe SlugHelperTests (ou em cada método de teste se preferir granularidade) para alinhar com os outros testes; verifique que o namespace/xUnit esteja disponível (usando Xunit) e mantenha o mesmo padrão de categoria usado pelo restante do repositório.src/Modules/SearchProviders/Infrastructure/Persistence/Configurations/SearchableProviderConfiguration.cs (1)
44-47: Padronize o limite de tamanho doSlugentre Providers e SearchProviders.Aqui está
200, mas no módulo Providers está120. Vale alinhar (ou extrair constante compartilhada) para evitar drift de schema/regra de domínio.Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Infrastructure/Persistence/Configurations/SearchableProviderConfiguration.cs` around lines 44 - 47, In SearchableProviderConfiguration (builder.Property(p => p.Slug)) the Slug max length is set to 200 but Providers uses 120; change HasMaxLength(200) to the shared canonical value (preferably use the constant from Shared.Contracts, e.g. import and reference the Slug length constant) or set it to 120 to match Providers, and update any related EF migration/schema to keep database and domain rules aligned.src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderRegisteredDomainEventHandlerTests.cs (1)
70-71: Boa atualização dos testes para o novo construtor, mas falta validar o payload doSlug.Como este PR introduz
Slugno evento de domínio, vale reforçar oVerifypara checar o conteúdo publicado (não só o tipo), assim que o contrato de integração expuser esse campo.Also applies to: 96-97, 123-124
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderRegisteredDomainEventHandlerTests.cs` around lines 70 - 71, Atualize os asserts/verificações nos testes em ProviderRegisteredDomainEventHandlerTests para não só checar o tipo do evento publicado, mas também validar o payload.Slug; especificamente, ao verificar a chamada de Publish/Send (o Verify usando o mock do bus ou publisher), confirme que o evento publicado (ProviderRegisteredDomainEvent) tem a propriedade Slug igual a "provider-test". Aplique a mesma verificação nas outras ocorrências apontadas (asserções nas seções correspondentes às linhas 96-97 e 123-124) para garantir que todos os testes validem o conteúdo do Slug além do tipo do evento.src/Modules/Providers/Application/Mappers/ProviderMapper.cs (1)
17-36: Mistura inconsistente de parâmetros nomeados e posicionais.O método começa com parâmetros nomeados (linhas 18-22:
Id,UserId,Name,Slug,Type) mas depois muda para parâmetros posicionais (linhas 23-35). Isso reduz a legibilidade e aumenta o risco de erros se a ordem dos parâmetros do construtor mudar.♻️ Usar parâmetros nomeados consistentemente
public static ProviderDto ToDto(this Provider provider) { return new ProviderDto( Id: provider.Id.Value, UserId: provider.UserId, Name: provider.Name, Slug: provider.Slug, Type: provider.Type, - provider.BusinessProfile.ToDto(), - provider.Status, - provider.VerificationStatus, - provider.Tier, - provider.Documents.Select(d => d.ToDto()).ToList(), - provider.Qualifications.Select(q => q.ToDto()).ToList(), - provider.Services.Select(s => s.ToDto()).ToList(), - provider.CreatedAt, - provider.UpdatedAt, - provider.IsDeleted, - provider.DeletedAt, - provider.SuspensionReason, - provider.RejectionReason + BusinessProfile: provider.BusinessProfile.ToDto(), + Status: provider.Status, + VerificationStatus: provider.VerificationStatus, + Tier: provider.Tier, + Documents: provider.Documents.Select(d => d.ToDto()).ToList(), + Qualifications: provider.Qualifications.Select(q => q.ToDto()).ToList(), + Services: provider.Services.Select(s => s.ToDto()).ToList(), + CreatedAt: provider.CreatedAt, + UpdatedAt: provider.UpdatedAt, + IsDeleted: provider.IsDeleted, + DeletedAt: provider.DeletedAt, + SuspensionReason: provider.SuspensionReason, + RejectionReason: provider.RejectionReason ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Providers/Application/Mappers/ProviderMapper.cs` around lines 17 - 36, O trecho que instancia ProviderDto mistura argumentos nomeados e posicionais; abraçar consistência e segurança: atualize a chamada em ProviderMapper para usar apenas parâmetros nomeados (por exemplo Id:, UserId:, Name:, Slug:, Type:, BusinessProfile:, Status:, VerificationStatus:, Tier:, Documents:, Qualifications:, Services:, CreatedAt:, UpdatedAt:, IsDeleted:, DeletedAt:, SuspensionReason:, RejectionReason:) referenciando as propriedades atuais (p.ex. provider.BusinessProfile.ToDto(), provider.Documents.Select(...).ToList(), etc.), garantindo que cada valor corresponda explicitamente ao parâmetro do construtor ProviderDto.src/Modules/SearchProviders/Tests/Integration/SearchProvidersIntegrationTestBase.cs (2)
164-172: Derivação de slug inconsistente com a produção.Assim como no arquivo de testes unitários do repositório, a geração de slug aqui usa
name.ToLower().Replace(" ", "-")(linha 167), que não corresponde ao comportamento deSlugHelper.Generate(). Em testes de integração isso é ainda mais crítico, pois os dados persistidos no banco podem ter slugs que nunca seriam gerados em produção.♻️ Usar SlugHelper.Generate para consistência
var provider = SearchableProvider.Create( providerId: providerId, name: name, - slug: name.ToLower().Replace(" ", "-"), + slug: SlugHelper.Generate(name), location: location, subscriptionTier: tier, description: description, city: city, state: state);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Integration/SearchProvidersIntegrationTestBase.cs` around lines 164 - 172, A geração de slug em SearchableProvider.Create está usando name.ToLower().Replace(" ", "-"), o que difere do comportamento de produção; substitua essa expressão pelo uso de SlugHelper.Generate(name) ao construir o argumento slug (na chamada de SearchableProvider.Create) e importe/ajuste o namespace se necessário para garantir que os slugs em testes de integração sejam gerados exatamente como em produção.
192-200: Mesma inconsistência de slug no segundo helper.O método
CreateTestSearchableProviderWithProviderIdtambém usa a derivação naive de slug na linha 195.♻️ Usar SlugHelper.Generate para consistência
var provider = SearchableProvider.Create( providerId: providerId, name: name, - slug: name.ToLower().Replace(" ", "-"), + slug: SlugHelper.Generate(name), location: location, subscriptionTier: tier, description: description, city: city, state: state);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Integration/SearchProvidersIntegrationTestBase.cs` around lines 192 - 200, The CreateTestSearchableProviderWithProviderId helper currently derives the slug naively using name.ToLower().Replace(" ", "-"); update this to use the centralized SlugHelper.Generate method for consistency — modify the provider creation in CreateTestSearchableProviderWithProviderId (the SearchableProvider.Create(...) call that sets slug) to call SlugHelper.Generate(name) instead of the manual replace so slugs match other helpers.src/Modules/SearchProviders/Tests/Unit/Infrastructure/Repositories/SearchableProviderRepositoryTests.cs (1)
53-67: Derivação de slug inconsistente com a produção.A geração de slug na linha 55 usa
actualName.ToLower().Replace(" ", "-"), que difere significativamente doSlugHelper.Generate()usado em produção. O helper de produção também:
- Remove diacríticos (ex: "café" → "cafe")
- Remove caracteres não-alfanuméricos (ex: "&" é removido)
- Colapsa hífens duplicados
- Faz trim de hífens nas extremidades
Isso pode criar dados de teste que nunca existiriam em produção, potencialmente mascarando bugs ou causando falsos positivos.
♻️ Usar SlugHelper.Generate para consistência
- var actualSlug = actualName.ToLower().Replace(" ", "-"); + var actualSlug = SlugHelper.Generate(actualName);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Unit/Infrastructure/Repositories/SearchableProviderRepositoryTests.cs` around lines 53 - 67, The test builds a slug with actualName.ToLower().Replace(" ", "-") which diverges from production slug behavior; update the test to generate slugs using the same production helper (call SlugHelper.Generate(actualName) when constructing the SearchableProvider in SearchableProvider.Create) so diacritics, non-alphanumerics, duplicate hyphens and edge hyphens are handled consistently with production (ensure any required using/import for SlugHelper is added).src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderProfileUpdatedDomainEventHandlerTests.cs (1)
43-46: Documentação XML deve estar em Português.As tags
<summary>estão em inglês, mas de acordo com a política de linguagem do projeto, comentários e documentação XML devem estar em Português.📝 Traduzir documentação XML para Português
- /// <summary> - /// Verifies that HandleAsync publishes a ProviderProfileUpdatedIntegrationEvent with correct provider details - /// when processing a valid profile update domain event. - /// </summary> + /// <summary> + /// Verifica que HandleAsync publica um ProviderProfileUpdatedIntegrationEvent com os detalhes corretos do provedor + /// ao processar um evento de domínio de atualização de perfil válido. + /// </summary>- /// <summary> - /// Verifies that when message bus publishing fails, the handler logs an error containing - /// "Error handling ProviderProfileUpdatedDomainEvent" and re-throws the exception. - /// </summary> + /// <summary> + /// Verifica que quando a publicação no message bus falha, o handler registra um erro contendo + /// "Error handling ProviderProfileUpdatedDomainEvent" e relança a exceção. + /// </summary>Baseado em learnings: "Comments and XML documentation can remain in Portuguese, logs should be in English".
Also applies to: 82-85
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderProfileUpdatedDomainEventHandlerTests.cs` around lines 43 - 46, A documentação XML nas tags <summary> para o teste que descreve a verificação de HandleAsync deve ser traduzida para Português; localize no arquivo ProviderProfileUpdatedDomainEventHandlerTests (as ocorrências atuais nas linhas próximas aos trechos que descrevem "Verifies that HandleAsync publishes a ProviderProfileUpdatedIntegrationEvent..." e a segunda ocorrência perto das linhas 82-85) e substitua o texto em inglês por um resumo em Português mantendo as mesmas tags XML /// <summary> ... </summary>, por exemplo descrevendo que o teste verifica se HandleAsync publica um ProviderProfileUpdatedIntegrationEvent com os detalhes corretos do provider; preserve a formatação XML e outros comentários adjacentes.src/Shared/Utilities/SlugHelper.cs (1)
26-48: Implementação sólida com uma consideração de edge case.A implementação está bem estruturada usando
GeneratedRegexpara performance. Um edge case a considerar: se o texto de entrada contiver apenas caracteres especiais (ex:"!@#$%"), o método retornará uma string vazia, o que causaráArgumentExceptionemSearchableProvider.Create()eUpdateBasicInfo().Isso é comportamento defensivo adequado, mas pode ser útil documentar ou validar isso no ponto de entrada (ex: validação de nome do provider antes de gerar o slug).
📝 Sugestão: Adicionar documentação sobre o edge case
/// <summary> /// Gera um slug a partir de um texto /// </summary> /// <param name="text">Texto original</param> - /// <returns>Slug formatado</returns> + /// <returns>Slug formatado ou string vazia se o texto não contiver caracteres alfanuméricos válidos</returns> + /// <remarks> + /// Se o texto contiver apenas caracteres especiais, o método retornará string vazia. + /// Chamadores devem validar o resultado antes de usar em contextos que requerem slug não-vazio. + /// </remarks> public static string Generate(string text)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Shared/Utilities/SlugHelper.cs` around lines 26 - 48, The Generate method can return an empty string for inputs with only non-alphanumeric chars (e.g. "!@#$%"), which later causes an ArgumentException in SearchableProvider.Create and UpdateBasicInfo; add defensive validation at the call sites: in SearchableProvider.Create and UpdateBasicInfo, validate the provider name (not null/whitespace) before calling SlugHelper.Generate, then call Generate and if it returns null/empty throw a clear ArgumentException (or return a meaningful default/validation error) that explains the name produced no valid slug; alternatively, if you prefer centralizing the check, modify SlugHelper.Generate to detect the empty-result case and either throw a descriptive ArgumentException or return a documented fallback value, and update callers to handle that behavior accordingly.src/Modules/SearchProviders/Tests/Unit/Application/Handlers/SearchProvidersQueryHandlerTests.cs (1)
357-362: Prefira gerar oSlugcom a mesma utility da produção.Os fixtures aqui misturam slug hardcoded e
ToLower().Replace(" ", "-"), o que pode divergir doSlugHelperem casos de acento, pontuação ou múltiplos espaços. Vale centralizar isso para os testes continuarem refletindo a regra real.♻️ Exemplo
+using MeAjudaAi.Shared.Utilities; ... var name = $"Provider {i + 1}"; + var slug = SlugHelper.Generate(name); providers.Add(SearchableProvider.Create( providerId: Guid.NewGuid(), name: name, - slug: name.ToLower().Replace(" ", "-"), + slug: slug, location: new GeoPoint(-23.5505 + i * 0.01, -46.6333 + i * 0.01),Also applies to: 396-405
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Unit/Application/Handlers/SearchProvidersQueryHandlerTests.cs` around lines 357 - 362, Os fixtures criam slugs manualmente, o que pode divergir da lógica de produção; update os testes para gerar o slug usando a mesma utility que a aplicação usa (ex.: SlugHelper or whatever slug generator is used) ao construir o SearchableProvider via SearchableProvider.Create so that providerId/name/slug use the canonical slugging logic; apply the same change to the other fixture block referenced (around the 396-405 area) so tests mirror production behavior for accents, punctuation and multiple spaces.src/Modules/SearchProviders/Tests/Unit/Domain/Entities/SearchableProviderTests.cs (1)
26-34: Faltam cenários para a nova invariável deSlug.A suíte foi adaptada para passar
slug, mas ainda não cobre os novos guards deCreate/UpdateBasicInfonem a normalização para lowercase. Um teste comslugvazio/branco e outro verificando o valor normalizado protegeriam melhor a mudança.Also applies to: 58-63, 73-77, 90-111
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Unit/Domain/Entities/SearchableProviderTests.cs` around lines 26 - 34, Os testes faltam cenários cobrindo a nova invariável slug: adicione casos unitários em SearchableProviderTests que verifiquem (1) que Create(...) e UpdateBasicInfo(...) falham (lançam/retornam erro de validação) quando slug é null/empty/whitespace e (2) que quando um slug válido com letras maiúsculas é passado, o valor armazenado em SearchableProvider.Slug é normalizado para lowercase; foque nas chamadas SearchableProvider.Create(...) e no método UpdateBasicInfo(...) para inserir asserts que confirmem as guards e a normalização.src/Modules/SearchProviders/Tests/Unit/Application/ModuleApi/SearchProvidersModuleApiTests.cs (1)
158-222: Os testes ainda não validam a propagação doSlug.Os fixtures agora populam
Slug, mas as asserções só conferem contagem/chamada ao repositório. Se o mapping da API devolver ou persistir o slug errado, esses cenários continuam verdes. Vale validar oSlugno resultado de busca e nas entidades enviadas paraAddAsync/UpdateAsync.♻️ Exemplo
provider.ProviderId.Should().Be(providerId); provider.Name.Should().Be("Provider 1"); + provider.Slug.Should().Be("provider-1"); ... - _repositoryMock.Verify(x => x.AddAsync(It.IsAny<SearchableProvider>(), It.IsAny<CancellationToken>()), Times.Once); + _repositoryMock.Verify(x => x.AddAsync( + It.Is<SearchableProvider>(p => p.Slug == "test-provider"), + It.IsAny<CancellationToken>()), Times.Once); ... - _repositoryMock.Verify(x => x.UpdateAsync(existingProvider, It.IsAny<CancellationToken>()), Times.Once); + _repositoryMock.Verify(x => x.UpdateAsync( + It.Is<SearchableProvider>(p => p.Slug == "updated-provider"), + It.IsAny<CancellationToken>()), Times.Once);Also applies to: 327-357, 375-405, 468-509
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Modules/SearchProviders/Tests/Unit/Application/ModuleApi/SearchProvidersModuleApiTests.cs` around lines 158 - 222, The tests currently don't assert that the provider Slug is propagated; update the unit tests that exercise SearchProvidersAsync (and the other search/add/update tests mentioned) to assert provider.Slug equals the expected "provider-1" in the returned ModuleSearchableProviderDto (from PagedResult<SearchableProviderDto>) and add mock verifications that repository methods (e.g., AddAsync/UpdateAsync) are called with entities whose Slug property equals the expected slug; locate assertions around result.Value.Items[0] (variable provider) and the mock setups on _queryDispatcherMock and repository mocks to add these Slug checks so mapping and persistence of Slug are validated.
🤖 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/Providers/Domain/Entities/Provider.cs`:
- Around line 139-140: O campo Slug está sendo gerado apenas a partir do nome
(Slug = SlugHelper.Generate(Name)) o que causa colisões com o índice único em
providers.slug; altere a lógica para garantir slug único antes de persistir:
crie/usar um método como EnsureUniqueSlugAsync/GenerateUniqueSlug que tenta
SlugHelper.Generate(Name) e, enquanto ProviderRepository.SlugExists(slug,
excludeId) for true, anexe um sufixo incremental ("-1", "-2", ...) ou um hash
curto até obter um slug livre, então atribua Slug ao entity; alternativamente,
ao invés de só confiar no banco, capture conflito de unique constraint durante
Save/Update e reexecute a geração/atualização do slug e re-persista; aplique
mesma correção nas outras ocorrências mencionadas (blocos em 174-189 e 211-241)
referenciando as mesmas funções e o repositório de providers.
In `@src/Modules/Providers/Domain/Events/ProviderProfileUpdatedDomainEvent.cs`:
- Around line 12-22: The domain event ProviderProfileUpdatedDomainEvent now
includes Slug but the integration event ProviderProfileUpdatedIntegrationEvent
and the mapper ProviderEventMappers.ToIntegrationEvent() were not updated; add a
nullable Slug property to ProviderProfileUpdatedIntegrationEvent (e.g., string?
Slug = null) and update ProviderEventMappers.ToIntegrationEvent to set Slug:
domainEvent.Slug when constructing the integration event so consumers like
SearchProvidersModuleApi receive slug updates when "Slug" appears in
UpdatedFields.
In `@src/Modules/Providers/Domain/Events/ProviderRegisteredDomainEvent.cs`:
- Around line 27-28: O evento de domínio ProviderRegisteredDomainEvent agora
inclui o campo Slug, mas ele não está sendo propagado: atualize o contrato de
integração ProviderRegisteredIntegrationEvent para expor uma propriedade Slug
(mesmo tipo que no domínio) e ajuste o mapeador em ProviderEventMappers (a
função que transforma ProviderRegisteredDomainEvent em
ProviderRegisteredIntegrationEvent) para atribuir integrationEvent.Slug =
domainEvent.Slug; garanta também que construtores/serialização do
ProviderRegisteredIntegrationEvent aceitem/definam o Slug para evitar perda de
dado ao publicar.
In
`@src/Modules/Providers/Infrastructure/Persistence/Configurations/ProviderConfiguration.cs`:
- Around line 250-252: The unique index declared by ProviderConfiguration via
builder.HasIndex(p => p.Slug).IsUnique() will cause persistence errors for
providers with duplicate names; remove the uniqueness constraint (drop
.IsUnique()) or replace it with a non-unique index, or alternatively make the
index composite (e.g., include a tenant or owner key) and/or update the slug
generation logic to append a disambiguating suffix so Slug values are unique;
locate the HasIndex(p => p.Slug) call in ProviderConfiguration and apply one of
these fixes consistently with existing slug generation code.
---
Outside diff comments:
In `@src/Modules/SearchProviders/Domain/Entities/SearchableProvider.cs`:
- Around line 147-170: No método Reconstitute da classe SearchableProvider,
normalize o valor de Slug antes de atribuí-lo para manter a mesma invariante
aplicada em Create e UpdateBasicInfo; localize SearchableProvider.Reconstitute e
substituir a atribuição Slug = slug por uma atribuição que aplica a mesma rotina
de normalização usada por Create/UpdateBasicInfo (por exemplo o helper/método de
slugificação existente — nome similar a NormalizeSlug/Slugify — ou reimplemente
a mesma lógica de trim, lowercase e remoção de espaços/caracteres), garantindo
que dados legados sejam normalizados ao reconstituir a entidade.
---
Nitpick comments:
In `@prompts/design-react-project.md`:
- Around line 138-152: The code block containing the CSS variable mappings
(lines with identifiers like bg-surface, bg-primary, bg-destructive,
text-foreground, border-border, ring-ring, etc.) lacks a language specifier;
update the fenced code block start to include a language (e.g., ```css or
```text) so markdownlint warning is silenced and formatting is improved—locate
the block showing the mappings and change the opening fence to ```css.
In `@src/Modules/Providers/Application/Mappers/ProviderMapper.cs`:
- Around line 17-36: O trecho que instancia ProviderDto mistura argumentos
nomeados e posicionais; abraçar consistência e segurança: atualize a chamada em
ProviderMapper para usar apenas parâmetros nomeados (por exemplo Id:, UserId:,
Name:, Slug:, Type:, BusinessProfile:, Status:, VerificationStatus:, Tier:,
Documents:, Qualifications:, Services:, CreatedAt:, UpdatedAt:, IsDeleted:,
DeletedAt:, SuspensionReason:, RejectionReason:) referenciando as propriedades
atuais (p.ex. provider.BusinessProfile.ToDto(),
provider.Documents.Select(...).ToList(), etc.), garantindo que cada valor
corresponda explicitamente ao parâmetro do construtor ProviderDto.
In
`@src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderProfileUpdatedDomainEventHandlerTests.cs`:
- Around line 43-46: A documentação XML nas tags <summary> para o teste que
descreve a verificação de HandleAsync deve ser traduzida para Português;
localize no arquivo ProviderProfileUpdatedDomainEventHandlerTests (as
ocorrências atuais nas linhas próximas aos trechos que descrevem "Verifies that
HandleAsync publishes a ProviderProfileUpdatedIntegrationEvent..." e a segunda
ocorrência perto das linhas 82-85) e substitua o texto em inglês por um resumo
em Português mantendo as mesmas tags XML /// <summary> ... </summary>, por
exemplo descrevendo que o teste verifica se HandleAsync publica um
ProviderProfileUpdatedIntegrationEvent com os detalhes corretos do provider;
preserve a formatação XML e outros comentários adjacentes.
In
`@src/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderRegisteredDomainEventHandlerTests.cs`:
- Around line 70-71: Atualize os asserts/verificações nos testes em
ProviderRegisteredDomainEventHandlerTests para não só checar o tipo do evento
publicado, mas também validar o payload.Slug; especificamente, ao verificar a
chamada de Publish/Send (o Verify usando o mock do bus ou publisher), confirme
que o evento publicado (ProviderRegisteredDomainEvent) tem a propriedade Slug
igual a "provider-test". Aplique a mesma verificação nas outras ocorrências
apontadas (asserções nas seções correspondentes às linhas 96-97 e 123-124) para
garantir que todos os testes validem o conteúdo do Slug além do tipo do evento.
In
`@src/Modules/SearchProviders/Infrastructure/Persistence/Configurations/SearchableProviderConfiguration.cs`:
- Around line 44-47: In SearchableProviderConfiguration (builder.Property(p =>
p.Slug)) the Slug max length is set to 200 but Providers uses 120; change
HasMaxLength(200) to the shared canonical value (preferably use the constant
from Shared.Contracts, e.g. import and reference the Slug length constant) or
set it to 120 to match Providers, and update any related EF migration/schema to
keep database and domain rules aligned.
In
`@src/Modules/SearchProviders/Tests/Integration/SearchProvidersIntegrationTestBase.cs`:
- Around line 164-172: A geração de slug em SearchableProvider.Create está
usando name.ToLower().Replace(" ", "-"), o que difere do comportamento de
produção; substitua essa expressão pelo uso de SlugHelper.Generate(name) ao
construir o argumento slug (na chamada de SearchableProvider.Create) e
importe/ajuste o namespace se necessário para garantir que os slugs em testes de
integração sejam gerados exatamente como em produção.
- Around line 192-200: The CreateTestSearchableProviderWithProviderId helper
currently derives the slug naively using name.ToLower().Replace(" ", "-");
update this to use the centralized SlugHelper.Generate method for consistency —
modify the provider creation in CreateTestSearchableProviderWithProviderId (the
SearchableProvider.Create(...) call that sets slug) to call
SlugHelper.Generate(name) instead of the manual replace so slugs match other
helpers.
In
`@src/Modules/SearchProviders/Tests/Unit/Application/Handlers/SearchProvidersQueryHandlerTests.cs`:
- Around line 357-362: Os fixtures criam slugs manualmente, o que pode divergir
da lógica de produção; update os testes para gerar o slug usando a mesma utility
que a aplicação usa (ex.: SlugHelper or whatever slug generator is used) ao
construir o SearchableProvider via SearchableProvider.Create so that
providerId/name/slug use the canonical slugging logic; apply the same change to
the other fixture block referenced (around the 396-405 area) so tests mirror
production behavior for accents, punctuation and multiple spaces.
In
`@src/Modules/SearchProviders/Tests/Unit/Application/ModuleApi/SearchProvidersModuleApiTests.cs`:
- Around line 158-222: The tests currently don't assert that the provider Slug
is propagated; update the unit tests that exercise SearchProvidersAsync (and the
other search/add/update tests mentioned) to assert provider.Slug equals the
expected "provider-1" in the returned ModuleSearchableProviderDto (from
PagedResult<SearchableProviderDto>) and add mock verifications that repository
methods (e.g., AddAsync/UpdateAsync) are called with entities whose Slug
property equals the expected slug; locate assertions around
result.Value.Items[0] (variable provider) and the mock setups on
_queryDispatcherMock and repository mocks to add these Slug checks so mapping
and persistence of Slug are validated.
In
`@src/Modules/SearchProviders/Tests/Unit/Domain/Entities/SearchableProviderTests.cs`:
- Around line 26-34: Os testes faltam cenários cobrindo a nova invariável slug:
adicione casos unitários em SearchableProviderTests que verifiquem (1) que
Create(...) e UpdateBasicInfo(...) falham (lançam/retornam erro de validação)
quando slug é null/empty/whitespace e (2) que quando um slug válido com letras
maiúsculas é passado, o valor armazenado em SearchableProvider.Slug é
normalizado para lowercase; foque nas chamadas SearchableProvider.Create(...) e
no método UpdateBasicInfo(...) para inserir asserts que confirmem as guards e a
normalização.
In `@src/Modules/SearchProviders/Tests/Unit/Domain/Models/SearchResultTests.cs`:
- Around line 188-197: O teste está replicando a lógica de geração de slug
manualmente; em vez de usar providerName.ToLower().Replace(" ", "-") passe o
slug gerado por SlugHelper.Generate(providerName) ao criar o objeto via
SearchableProvider.Create (referenciar providerName e o parâmetro slug) para
garantir que o teste acompanhe a regra oficial de geração de slugs.
In
`@src/Modules/SearchProviders/Tests/Unit/Infrastructure/Repositories/SearchableProviderRepositoryTests.cs`:
- Around line 53-67: The test builds a slug with actualName.ToLower().Replace("
", "-") which diverges from production slug behavior; update the test to
generate slugs using the same production helper (call
SlugHelper.Generate(actualName) when constructing the SearchableProvider in
SearchableProvider.Create) so diacritics, non-alphanumerics, duplicate hyphens
and edge hyphens are handled consistently with production (ensure any required
using/import for SlugHelper is added).
In `@src/Shared/Utilities/SlugHelper.cs`:
- Around line 26-48: The Generate method can return an empty string for inputs
with only non-alphanumeric chars (e.g. "!@#$%"), which later causes an
ArgumentException in SearchableProvider.Create and UpdateBasicInfo; add
defensive validation at the call sites: in SearchableProvider.Create and
UpdateBasicInfo, validate the provider name (not null/whitespace) before calling
SlugHelper.Generate, then call Generate and if it returns null/empty throw a
clear ArgumentException (or return a meaningful default/validation error) that
explains the name produced no valid slug; alternatively, if you prefer
centralizing the check, modify SlugHelper.Generate to detect the empty-result
case and either throw a descriptive ArgumentException or return a documented
fallback value, and update callers to handle that behavior accordingly.
In `@tests/MeAjudaAi.Shared.Tests/Unit/Utilities/SlugHelperTests.cs`:
- Around line 6-7: A classe de teste SlugHelperTests está faltando a
categorização consistente usada no projeto; adicione o atributo
[Trait("Category", "Unit")] acima da declaração da classe SlugHelperTests (ou em
cada método de teste se preferir granularidade) para alinhar com os outros
testes; verifique que o namespace/xUnit esteja disponível (usando Xunit) e
mantenha o mesmo padrão de categoria usado pelo restante do repositório.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ca172342-9452-418d-9afe-182a55ada172
⛔ Files ignored due to path filters (2)
src/Web/MeAjudaAi.Web.Admin/packages.lock.jsonis excluded by!**/packages.lock.jsontests/MeAjudaAi.Web.Admin.Tests/packages.lock.jsonis excluded by!**/packages.lock.json
📒 Files selected for processing (39)
prompts/design-react-project.mdsrc/Contracts/Contracts/Modules/Providers/DTOs/ModuleProviderBasicDto.cssrc/Contracts/Contracts/Modules/Providers/DTOs/ModuleProviderDto.cssrc/Contracts/Contracts/Modules/Providers/DTOs/ModuleProviderIndexingDto.cssrc/Modules/Providers/Application/DTOs/ProviderDto.cssrc/Modules/Providers/Application/DTOs/PublicProviderDto.cssrc/Modules/Providers/Application/Handlers/Queries/GetPublicProviderByIdQueryHandler.cssrc/Modules/Providers/Application/Mappers/ProviderMapper.cssrc/Modules/Providers/Application/ModuleApi/ProvidersModuleApi.cssrc/Modules/Providers/Domain/Entities/Provider.cssrc/Modules/Providers/Domain/Events/ProviderProfileUpdatedDomainEvent.cssrc/Modules/Providers/Domain/Events/ProviderRegisteredDomainEvent.cssrc/Modules/Providers/Domain/Repositories/IProviderRepository.cssrc/Modules/Providers/Infrastructure/Persistence/Configurations/ProviderConfiguration.cssrc/Modules/Providers/Infrastructure/Persistence/Repositories/ProviderRepository.cssrc/Modules/Providers/Tests/Unit/API/Endpoints/GetMyProviderProfileEndpointTests.cssrc/Modules/Providers/Tests/Unit/API/Endpoints/GetMyProviderStatusEndpointTests.cssrc/Modules/Providers/Tests/Unit/API/Endpoints/UpdateMyProviderProfileEndpointTests.cssrc/Modules/Providers/Tests/Unit/API/Endpoints/UploadMyDocumentEndpointTests.cssrc/Modules/Providers/Tests/Unit/Application/Services/ProvidersModuleApiTests.cssrc/Modules/Providers/Tests/Unit/Domain/Entities/ProviderTests.cssrc/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderProfileUpdatedDomainEventHandlerTests.cssrc/Modules/Providers/Tests/Unit/Infrastructure/Events/Handlers/ProviderRegisteredDomainEventHandlerTests.cssrc/Modules/SearchProviders/Application/DTOs/SearchableProviderDto.cssrc/Modules/SearchProviders/Application/Handlers/SearchProvidersQueryHandler.cssrc/Modules/SearchProviders/Application/ModuleApi/SearchProvidersModuleApi.cssrc/Modules/SearchProviders/Domain/Entities/SearchableProvider.cssrc/Modules/SearchProviders/Infrastructure/Persistence/Configurations/SearchableProviderConfiguration.cssrc/Modules/SearchProviders/Infrastructure/Persistence/DTOs/ProviderSearchResultDto.cssrc/Modules/SearchProviders/Infrastructure/Persistence/Repositories/SearchableProviderRepository.cssrc/Modules/SearchProviders/Tests/Integration/SearchProvidersIntegrationTestBase.cssrc/Modules/SearchProviders/Tests/Unit/Application/Handlers/SearchProvidersQueryHandlerTests.cssrc/Modules/SearchProviders/Tests/Unit/Application/ModuleApi/SearchProvidersModuleApiTests.cssrc/Modules/SearchProviders/Tests/Unit/Domain/Entities/SearchableProviderTests.cssrc/Modules/SearchProviders/Tests/Unit/Domain/Models/SearchResultTests.cssrc/Modules/SearchProviders/Tests/Unit/Infrastructure/Repositories/SearchableProviderRepositoryTests.cssrc/Shared/Utilities/SlugHelper.cstests/MeAjudaAi.Shared.Tests/Unit/Utilities/SlugHelperTests.cstests/MeAjudaAi.Web.Admin.Tests/Pages/ProvidersPageTests.cs
| Name = name.Trim(); | ||
| Slug = SlugHelper.Generate(Name); |
There was a problem hiding this comment.
Evite colisões de slug gerado só pelo nome.
Com o índice único em providers.slug, SlugHelper.Generate(Name) cria a mesma chave para providers com nomes repetidos ou para um rename que copie um nome já existente. Hoje o fluxo de cadastro só recupera conflito de UserId, então esse caso tende a estourar como erro genérico de banco. Precisamos gerar slug único antes de persistir ou tratar conflito de slug explicitamente na aplicação.
Also applies to: 174-189, 211-241
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Modules/Providers/Domain/Entities/Provider.cs` around lines 139 - 140, O
campo Slug está sendo gerado apenas a partir do nome (Slug =
SlugHelper.Generate(Name)) o que causa colisões com o índice único em
providers.slug; altere a lógica para garantir slug único antes de persistir:
crie/usar um método como EnsureUniqueSlugAsync/GenerateUniqueSlug que tenta
SlugHelper.Generate(Name) e, enquanto ProviderRepository.SlugExists(slug,
excludeId) for true, anexe um sufixo incremental ("-1", "-2", ...) ou um hash
curto até obter um slug livre, então atribua Slug ao entity; alternativamente,
ao invés de só confiar no banco, capture conflito de unique constraint durante
Save/Update e reexecute a geração/atualização do slug e re-persista; aplique
mesma correção nas outras ocorrências mencionadas (blocos em 174-189 e 211-241)
referenciando as mesmas funções e o repositório de providers.
| /// <param name="Slug">Novo slug amigável para URL</param> | ||
| /// <param name="UpdatedBy">Quem fez a atualização</param> | ||
| /// <param name="UpdatedFields">Lista dos campos que foram atualizados</param> | ||
| public record ProviderProfileUpdatedDomainEvent( | ||
| Guid AggregateId, | ||
| int Version, | ||
| string Name, | ||
| string Email, | ||
| string Slug, | ||
| string? UpdatedBy, | ||
| string[] UpdatedFields |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Objetivo: confirmar se o payload de integração e seu mapper propagam Slug.
# Esperado: existência de propriedade/campo Slug no evento de integração
# e mapeamento explícito no ToIntegrationEvent.
rg -n -C3 'ProviderProfileUpdatedIntegrationEvent|ToIntegrationEvent|UpdatedFields|Slug'Repository: frigini/MeAjudaAi
Length of output: 50373
🏁 Script executed:
cat -n "src/Shared/Messaging/Messages/Providers/ProviderProfileUpdatedIntegrationEvent.cs"Repository: frigini/MeAjudaAi
Length of output: 1091
🏁 Script executed:
cat -n "src/Modules/Providers/Infrastructure/Events/Mappers/ProviderEventMappers.cs" | sed -n '60,75p'Repository: frigini/MeAjudaAi
Length of output: 826
Adicione o campo Slug ao evento de integração e mapei-o no mapper.
Slug foi adicionado ao evento de domínio, mas está ausente do ProviderProfileUpdatedIntegrationEvent e não é mapeado em ProviderEventMappers.ToIntegrationEvent(). Se "Slug" constar em UpdatedFields, consumidores (como SearchProvidersModuleApi) não receberão a atualização.
Alterações necessárias
-
Adicione propriedade em
ProviderProfileUpdatedIntegrationEvent:public sealed record ProviderProfileUpdatedIntegrationEvent( ... string? Slug = null, ... ) : IntegrationEvent(Source);
-
Mapeie em
ProviderEventMappers.cs:Slug: domainEvent.Slug
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Modules/Providers/Domain/Events/ProviderProfileUpdatedDomainEvent.cs`
around lines 12 - 22, The domain event ProviderProfileUpdatedDomainEvent now
includes Slug but the integration event ProviderProfileUpdatedIntegrationEvent
and the mapper ProviderEventMappers.ToIntegrationEvent() were not updated; add a
nullable Slug property to ProviderProfileUpdatedIntegrationEvent (e.g., string?
Slug = null) and update ProviderEventMappers.ToIntegrationEvent to set Slug:
domainEvent.Slug when constructing the integration event so consumers like
SearchProvidersModuleApi receive slug updates when "Slug" appears in
UpdatedFields.
| string Email, | ||
| string Slug |
There was a problem hiding this comment.
Slug foi adicionado no evento de domínio, mas está sendo descartado na integração.
Hoje o ProviderRegisteredDomainEvent carrega Slug, porém src/Modules/Providers/Infrastructure/Events/Mappers/ProviderEventMappers.cs:16-27 não o mapeia e src/Shared/Messaging/Messages/Providers/ProviderRegisteredIntegrationEvent.cs:1-12 não tem esse campo. Resultado: perda de dado no publish.
🔧 Ajuste sugerido (contrato + mapper)
// src/Shared/Messaging/Messages/Providers/ProviderRegisteredIntegrationEvent.cs
public sealed record ProviderRegisteredIntegrationEvent(
string Source,
Guid ProviderId,
Guid UserId,
string Name,
+ string Slug,
string ProviderType,
string Email,
string? PhoneNumber = null,
string? City = null,
string? State = null,
DateTime? RegisteredAt = null
) : IntegrationEvent(Source);// src/Modules/Providers/Infrastructure/Events/Mappers/ProviderEventMappers.cs
return new ProviderRegisteredIntegrationEvent(
Source: ModuleName,
ProviderId: domainEvent.AggregateId,
UserId: domainEvent.UserId,
Name: domainEvent.Name,
+ Slug: domainEvent.Slug,
ProviderType: domainEvent.Type.ToString(),
Email: domainEvent.Email,
RegisteredAt: DateTime.UtcNow
);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Modules/Providers/Domain/Events/ProviderRegisteredDomainEvent.cs` around
lines 27 - 28, O evento de domínio ProviderRegisteredDomainEvent agora inclui o
campo Slug, mas ele não está sendo propagado: atualize o contrato de integração
ProviderRegisteredIntegrationEvent para expor uma propriedade Slug (mesmo tipo
que no domínio) e ajuste o mapeador em ProviderEventMappers (a função que
transforma ProviderRegisteredDomainEvent em ProviderRegisteredIntegrationEvent)
para atribuir integrationEvent.Slug = domainEvent.Slug; garanta também que
construtores/serialização do ProviderRegisteredIntegrationEvent aceitem/definam
o Slug para evitar perda de dado ao publicar.
| builder.HasIndex(p => p.Slug) | ||
| .IsUnique() | ||
| .HasDatabaseName("ix_providers_slug"); |
There was a problem hiding this comment.
Índice único em slug pode quebrar cadastro com nomes repetidos.
Com o índice único em Line [250], qualquer colisão de slug vira erro de persistência. Como o gerador atual deriva slug apenas do nome (sem sufixo de desambiguação), dois providers com mesmo nome tendem a conflitar.
Direção de correção
// Fluxo sugerido (fora deste arquivo): resolver unicidade antes de salvar.
-var slug = SlugHelper.Generate(name);
+var baseSlug = SlugHelper.Generate(name);
+var slug = await slugUniquenessService.ResolveAsync(baseSlug, cancellationToken);
// Ex.: "acme", "acme-2", "acme-3"...🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/Modules/Providers/Infrastructure/Persistence/Configurations/ProviderConfiguration.cs`
around lines 250 - 252, The unique index declared by ProviderConfiguration via
builder.HasIndex(p => p.Slug).IsUnique() will cause persistence errors for
providers with duplicate names; remove the uniqueness constraint (drop
.IsUnique()) or replace it with a non-unique index, or alternatively make the
index composite (e.g., include a tenant or owner key) and/or update the slug
generation logic to append a disambiguating suffix so Slug values are unique;
locate the HasIndex(p => p.Slug) call in ProviderConfiguration and apply one of
these fixes consistently with existing slug generation code.
Summary by CodeRabbit
Novas Funcionalidades