Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
ef5414a
docs(roadmap): atualizar para Sprint 3 Parte 2
Dec 12, 2025
a57c34c
feat(locations): adicionar endpoints CRUD admin para AllowedCities
Dec 12, 2025
313a622
wip: migrar Commands/Queries/Handlers para implementação própria do M…
Dec 12, 2025
b0cb72f
test(locations): adicionar testes unitários e de integração para Allo…
Dec 12, 2025
ce1f3fa
test(locations): adicionar testes E2E para endpoints AllowedCities
Dec 12, 2025
2f81213
fix(locations): registrar handlers no DI container e corrigir parâmet…
Dec 12, 2025
4cbde3a
refactor(locations): IbgeService agora usa banco de dados ao invés de…
Dec 12, 2025
ea1a963
feat(locations): implementar exception handling com status codes HTTP…
Dec 12, 2025
0c48b7a
fix(locations): remover 15 warnings do módulo Locations
Dec 12, 2025
0f7ff0a
docs(roadmap): atualizar progresso do Sprint 3 Parte 2
Dec 12, 2025
d1ce745
fix: corrigir erros de compilação e exception handling em E2E tests
Dec 12, 2025
c21e2c3
docs: adicionar tarefas originais do roadmap à Sprint 3 Parte 4
Dec 12, 2025
e8683c0
refactor: substituir NSubstitute por Moq para padronização
Dec 12, 2025
0a44810
refactor: substituir Guid.CreateVersion7() por UuidGenerator.NewId()
Dec 12, 2025
1de5dc1
build: migrar solução para formato .slnx (.NET 9+)
Dec 12, 2025
ae6ef2d
feat(docs): automação completa de OpenAPI e GitHub Pages
Dec 12, 2025
ae65929
feat(scripts): adicionar scripts de seeding de dados
Dec 12, 2025
fe5a964
feat(shared): implementar seeding automático de dados de desenvolvimento
Dec 12, 2025
3d2b260
feat(aspire): migrar migrations para Aspire AppHost e remover Migrati…
Dec 12, 2025
53943da
feat(providers): implementar integração Providers ↔ ServiceCatalogs
Dec 12, 2025
32f854a
Merge branch 'master' into sprint3-parte2-admin-endpoints
Dec 12, 2025
e334c4d
fix: resolve build errors and code quality issues
Dec 12, 2025
7b37dc6
docs(roadmap): atualizar Sprint 3 como 100% completo
Dec 12, 2025
f4fac92
fix: ajustes finais em testes e cache service
Dec 12, 2025
fe94741
fix: corrigir assinaturas dos métodos de seeding
Dec 12, 2025
fe216b9
fix: corrigir erros de compilação e formatar código
Dec 12, 2025
5c16c0c
fix: implementar tuple (value, isCached) no ICacheService.GetAsync e …
Dec 12, 2025
7c2c0ea
fix: implementar melhorias do PR review
Dec 12, 2025
27e9113
feat: adicionar Bruno Collections completas para ServiceCatalogs
Dec 12, 2025
b09fcc8
fix: resolver feedback de code review e testes de integração
Dec 12, 2025
9bfa947
fix: usar idMap nos services + documentar scripts de coverage
Dec 12, 2025
b0b9470
docs: auditoria completa e documentação de todos os scripts
Dec 13, 2025
ef3aa47
refactor: simplificar scripts - remover redundâncias e over-engineering
Dec 13, 2025
afb3b17
refactor: simplificar infrastructure - remover redundâncias
Dec 13, 2025
18550b0
fix: aplicar correções de code review
Dec 13, 2025
618d193
chore: aplicar dotnet format e remover documento de trabalho
Dec 13, 2025
5b0d74a
fix: corrigir issues de code review
Dec 13, 2025
c5f79b4
fix(ci): corrigir falso positivo no check de formatação
Dec 13, 2025
fa50798
fix(ci): corrigir check de formatação em ambos workflows
Dec 13, 2025
d8bb00d
refactor: resolve SonarQube warnings (S1135 TODOs, S2139 exception ha…
Dec 13, 2025
dd768d0
fix(ci): resolve pr-validation workflow issues - fix trailing whitesp…
Dec 13, 2025
2a349f5
fix: apply code review feedback - improve cancellation handling, cach…
Dec 13, 2025
302c5e0
docs(roadmap): atualizar Sprint 3 como 100% concluída - todas as 4 pa…
Dec 13, 2025
9f7b09a
refactor: remover ExampleSchemaFilter problemático e seus testes
Dec 13, 2025
2559cb8
fix: corrigir erros de compilação críticos
Dec 13, 2025
5543174
fix: aplicar correções do code review
Dec 13, 2025
311a6fe
refactor: remover overengineering - ModuleTagsDocumentFilter e CacheW…
Dec 13, 2025
2e9e085
fix(ci): focar dotnet format apenas em style (não code quality analyz…
Dec 13, 2025
72115b6
fix(tests): UserProfileUpdatedDomainEventHandler deve re-lançar exceç…
Dec 13, 2025
6ca7dcc
fix(tests): Providers event handlers devem re-lançar exceção original
Dec 13, 2025
c76bc62
fix(code-review): apply GitHub bot suggestions - security, robustness…
Dec 13, 2025
4e26c1a
fix(tests): UserProfileUpdatedDomainEventHandler test deve esperar ex…
Dec 13, 2025
da56552
fix(code-review): aplicar sugestões críticas do CodeRabbit
Dec 13, 2025
0a666cf
fix(build): DapperConnection.HandleDapperError deve retornar exceção
Dec 13, 2025
bc2d0e9
fix(warnings): resolve critical SonarQube warnings without pragma sup…
Dec 14, 2025
0fd9c0c
fix(warnings): resolve all actionable warnings without pragma suppres…
Dec 14, 2025
0506176
fix(tests): update AzureBlobStorageService test name to match excepti…
Dec 14, 2025
8f5ff20
fix(editorconfig): add trailing newline to comply with insert_final_n…
Dec 14, 2025
95436e2
fix(code-quality): address security, exception handling, and document…
Dec 14, 2025
15d1263
fix(exceptions): preserve exception types to maintain HTTP status cod…
Dec 14, 2025
3bfede4
fix(security,performance,tests): prevent PII leakage, optimize token …
Dec 14, 2025
ff1fe58
fix(tests): update RequestLoggingMiddleware test for unwrapped except…
Dec 14, 2025
f88b246
fix(warnings,migrations,api): resolve build warnings and environment …
Dec 14, 2025
c9f268c
fix(security,tests): remove PII from logs and update ArgumentExceptio…
Dec 14, 2025
213bf76
fix(auth,tests): use dynamic token expiry and update ArgumentExceptio…
Dec 14, 2025
29543fd
fix(auth,security): correct HybridCache factory signature and prevent…
Dec 14, 2025
8b5d0f4
fix(auth,security): correct HybridCache factory signature and prevent…
Dec 14, 2025
3b67333
refactor(auth): simplify HybridCache factory with async ValueTask lambda
Dec 14, 2025
184ce63
fix(auth): use dynamic token expiration and improve cache factory sig…
Dec 14, 2025
3cbb5f8
fix(auth): remove redundant token fetch before cache check
Dec 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test(locations): adicionar testes unitários e de integração para Allo…
…wedCity

- 55 testes unitários (18 entidade + 22 handlers + 4 queries + 4 gets)
- 18 testes de integração para AllowedCityRepository com TestContainers
- Corrigido AllowedCity: IbgeCode de string para int?, trim antes de validação
- Corrigido UpdateAllowedCityHandler: adicionar chamada UpdateAsync
- Corrigido AllowedCityRepository: alterado de internal para public
- Adicionado suporte para TestContainers.PostgreSql, Respawn, Bogus e AutoFixture no csproj
- Criado GlobalTestConfiguration.cs para collection fixture
- Testes de integração cobrem: CRUD, ordenação, case-insensitive, constraints únicos
  • Loading branch information
Filipe Frigini committed Dec 12, 2025
commit b0cb72f96572efbe5cbc8441d46d1374275e58b7
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace MeAjudaAi.Modules.Locations.Application.Handlers;
/// <summary>
/// Handler responsável por processar o comando de criação de cidade permitida.
/// </summary>
internal sealed class CreateAllowedCityHandler(
public sealed class CreateAllowedCityHandler(
IAllowedCityRepository repository,
IHttpContextAccessor httpContextAccessor) : ICommandHandler<CreateAllowedCityCommand, Guid>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MeAjudaAi.Modules.Locations.Application.Handlers;
/// <summary>
/// Handler responsável por processar o comando de exclusão de cidade permitida.
/// </summary>
internal sealed class DeleteAllowedCityHandler(IAllowedCityRepository repository) : ICommandHandler<DeleteAllowedCityCommand>
public sealed class DeleteAllowedCityHandler(IAllowedCityRepository repository) : ICommandHandler<DeleteAllowedCityCommand>
{
public async Task HandleAsync(DeleteAllowedCityCommand command, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MeAjudaAi.Modules.Locations.Application.Handlers;
/// <summary>
/// Handler responsável por processar a query de listagem de cidades permitidas.
/// </summary>
internal sealed class GetAllAllowedCitiesHandler(IAllowedCityRepository repository)
public sealed class GetAllAllowedCitiesHandler(IAllowedCityRepository repository)
: IQueryHandler<GetAllAllowedCitiesQuery, IReadOnlyList<AllowedCityDto>>
{
public async Task<IReadOnlyList<AllowedCityDto>> HandleAsync(GetAllAllowedCitiesQuery query, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MeAjudaAi.Modules.Locations.Application.Handlers;
/// <summary>
/// Handler responsável por processar a query de busca de cidade permitida por ID.
/// </summary>
internal sealed class GetAllowedCityByIdHandler(IAllowedCityRepository repository)
public sealed class GetAllowedCityByIdHandler(IAllowedCityRepository repository)
: IQueryHandler<GetAllowedCityByIdQuery, AllowedCityDto?>
{
public async Task<AllowedCityDto?> HandleAsync(GetAllowedCityByIdQuery query, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace MeAjudaAi.Modules.Locations.Application.Handlers;
/// <summary>
/// Handler responsável por processar o comando de atualização de cidade permitida.
/// </summary>
internal sealed class UpdateAllowedCityHandler(
public sealed class UpdateAllowedCityHandler(
IAllowedCityRepository repository,
IHttpContextAccessor httpContextAccessor) : ICommandHandler<UpdateAllowedCityCommand>
{
Expand All @@ -36,5 +36,8 @@ public async Task HandleAsync(UpdateAllowedCityCommand command, CancellationToke
command.IbgeCode,
command.IsActive,
currentUser);

// Persistir alterações
await repository.UpdateAsync(city, cancellationToken);
}
}
18 changes: 14 additions & 4 deletions src/Modules/Locations/Domain/Entities/AllowedCity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public AllowedCity(
int? ibgeCode = null,
bool isActive = true)
{
// Trim first
cityName = cityName?.Trim() ?? string.Empty;
stateSigla = stateSigla?.Trim().ToUpperInvariant() ?? string.Empty;
createdBy = createdBy?.Trim() ?? string.Empty;

if (string.IsNullOrWhiteSpace(cityName))
throw new ArgumentException("Nome da cidade não pode ser vazio", nameof(cityName));

Expand All @@ -74,8 +79,8 @@ public AllowedCity(
throw new ArgumentException("CreatedBy não pode ser vazio", nameof(createdBy));

Id = Guid.NewGuid();
CityName = cityName.Trim();
StateSigla = stateSigla.Trim().ToUpperInvariant();
CityName = cityName;
StateSigla = stateSigla;
IbgeCode = ibgeCode;
IsActive = isActive;
CreatedAt = DateTime.UtcNow;
Expand All @@ -84,6 +89,11 @@ public AllowedCity(

public void Update(string cityName, string stateSigla, int? ibgeCode, bool isActive, string updatedBy)
{
// Trim first
cityName = cityName?.Trim() ?? string.Empty;
stateSigla = stateSigla?.Trim().ToUpperInvariant() ?? string.Empty;
updatedBy = updatedBy?.Trim() ?? string.Empty;

if (string.IsNullOrWhiteSpace(cityName))
throw new ArgumentException("Nome da cidade não pode ser vazio", nameof(cityName));

Expand All @@ -96,8 +106,8 @@ public void Update(string cityName, string stateSigla, int? ibgeCode, bool isAct
if (string.IsNullOrWhiteSpace(updatedBy))
throw new ArgumentException("UpdatedBy não pode ser vazio", nameof(updatedBy));

CityName = cityName.Trim();
StateSigla = stateSigla.Trim().ToUpperInvariant();
CityName = cityName;
StateSigla = stateSigla;
IbgeCode = ibgeCode;
IsActive = isActive;
UpdatedAt = DateTime.UtcNow;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using MeAjudaAi.Modules.Locations.Application.Commands;
using MeAjudaAi.Modules.Locations.Application.DTOs;
using MeAjudaAi.Shared.Authorization;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Contracts;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand Down Expand Up @@ -36,11 +34,9 @@ private static async Task<IResult> CreateAsync(
request.IbgeCode,
request.IsActive);

var result = await commandDispatcher.DispatchAsync(command, cancellationToken);
var cityId = await commandDispatcher.SendAsync<CreateAllowedCityCommand, Guid>(command, cancellationToken);

return result.Match(
success => Results.Created($"/api/v1/admin/allowed-cities/{success}", Response.Success(success)),
errors => HandleErrors(errors));
return Results.Created($"/api/v1/admin/allowed-cities/{cityId}", new Response<Guid>(cityId, 201));
}
}

Expand All @@ -50,5 +46,5 @@ private static async Task<IResult> CreateAsync(
public sealed record CreateAllowedCityRequest(
string CityName,
string StateSigla,
int? IbgeCode = null,
int? IbgeCode,
bool IsActive = true);
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Contracts;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -19,8 +18,8 @@ public static void Map(IEndpointRouteBuilder app)
=> app.MapDelete("/api/v1/admin/allowed-cities/{id:guid}", DeleteAsync)
.WithName("DeleteAllowedCity")
.WithSummary("Delete allowed city")
.WithDescription("Deletes an allowed city from the system")
.Produces<Response<Unit>>(StatusCodes.Status200OK)
.WithDescription("Deletes an allowed city")
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status404NotFound)
.RequireAdmin();

Expand All @@ -29,12 +28,10 @@ private static async Task<IResult> DeleteAsync(
ICommandDispatcher commandDispatcher,
CancellationToken cancellationToken)
{
var command = new DeleteAllowedCityCommand(id);
var command = new DeleteAllowedCityCommand { Id = id };

var result = await commandDispatcher.DispatchAsync(command, cancellationToken);
await commandDispatcher.SendAsync(command, cancellationToken);

return result.Match(
success => Results.Ok(Response.Success(success)),
errors => HandleErrors(errors));
return Results.NoContent();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using MeAjudaAi.Modules.Locations.Application.DTOs;
using MeAjudaAi.Modules.Locations.Application.Queries;
using MeAjudaAi.Shared.Authorization;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Contracts;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using MeAjudaAi.Shared.Queries;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -24,16 +24,14 @@ public static void Map(IEndpointRouteBuilder app)
.RequireAdmin();

private static async Task<IResult> GetAllAsync(
ICommandDispatcher commandDispatcher,
bool onlyActive = false,
CancellationToken cancellationToken = default)
bool onlyActive,
IQueryDispatcher queryDispatcher,
CancellationToken cancellationToken)
{
var query = new GetAllAllowedCitiesQuery(onlyActive);
var query = new GetAllAllowedCitiesQuery { OnlyActive = onlyActive };

var result = await commandDispatcher.DispatchAsync(query, cancellationToken);
var result = await queryDispatcher.QueryAsync<GetAllAllowedCitiesQuery, IReadOnlyList<AllowedCityDto>>(query, cancellationToken);

return result.Match(
success => Results.Ok(Response.Success(success)),
errors => HandleErrors(errors));
return Results.Ok(new Response<IReadOnlyList<AllowedCityDto>>(result));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using MeAjudaAi.Modules.Locations.Application.DTOs;
using MeAjudaAi.Modules.Locations.Application.Queries;
using MeAjudaAi.Shared.Authorization;
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Contracts;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using MeAjudaAi.Shared.Queries;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -19,24 +19,22 @@ public static void Map(IEndpointRouteBuilder app)
=> app.MapGet("/api/v1/admin/allowed-cities/{id:guid}", GetByIdAsync)
.WithName("GetAllowedCityById")
.WithSummary("Get allowed city by ID")
.WithDescription("Retrieves an allowed city by its unique identifier")
.WithDescription("Retrieves a specific allowed city by its ID")
.Produces<Response<AllowedCityDto>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.RequireAdmin();

private static async Task<IResult> GetByIdAsync(
Guid id,
ICommandDispatcher commandDispatcher,
IQueryDispatcher queryDispatcher,
CancellationToken cancellationToken)
{
var query = new GetAllowedCityByIdQuery(id);
var query = new GetAllowedCityByIdQuery { Id = id };

var result = await commandDispatcher.DispatchAsync(query, cancellationToken);
var result = await queryDispatcher.QueryAsync<GetAllowedCityByIdQuery, AllowedCityDto?>(query, cancellationToken);

return result.Match(
success => success is null
? Results.NotFound(Response.Error($"Cidade permitida com ID '{id}' não encontrada"))
: Results.Ok(Response.Success(success)),
errors => HandleErrors(errors));
return result is not null
? Results.Ok(new Response<AllowedCityDto>(result))
: Results.NotFound(new Response<AllowedCityDto>(default, 404, "Cidade permitida não encontrada"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using MeAjudaAi.Shared.Commands;
using MeAjudaAi.Shared.Contracts;
using MeAjudaAi.Shared.Endpoints;
using MeAjudaAi.Shared.Functional;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -20,7 +19,7 @@ public static void Map(IEndpointRouteBuilder app)
.WithName("UpdateAllowedCity")
.WithSummary("Update allowed city")
.WithDescription("Updates an existing allowed city")
.Produces<Response<Unit>>(StatusCodes.Status200OK)
.Produces<Response<string>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status400BadRequest)
.RequireAdmin();
Expand All @@ -31,18 +30,18 @@ private static async Task<IResult> UpdateAsync(
ICommandDispatcher commandDispatcher,
CancellationToken cancellationToken)
{
var command = new UpdateAllowedCityCommand(
id,
request.CityName,
request.StateSigla,
request.IbgeCode,
request.IsActive);
var command = new UpdateAllowedCityCommand
{
Id = id,
CityName = request.CityName,
StateSigla = request.StateSigla,
IbgeCode = request.IbgeCode,
IsActive = request.IsActive
};

var result = await commandDispatcher.DispatchAsync(command, cancellationToken);
await commandDispatcher.SendAsync(command, cancellationToken);

return result.Match(
success => Results.Ok(Response.Success(success)),
errors => HandleErrors(errors));
return Results.Ok(new Response<string>("Cidade permitida atualizada com sucesso"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MeAjudaAi.Modules.Locations.Infrastructure.Repositories;
/// <summary>
/// Repository implementation for AllowedCity entity
/// </summary>
internal sealed class AllowedCityRepository(LocationsDbContext context) : IAllowedCityRepository
public sealed class AllowedCityRepository(LocationsDbContext context) : IAllowedCityRepository
{
public async Task<IReadOnlyList<AllowedCity>> GetAllActiveAsync(CancellationToken cancellationToken = default)
{
Expand Down
13 changes: 13 additions & 0 deletions src/Modules/Locations/Tests/GlobalTestConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using MeAjudaAi.Shared.Tests;
using Xunit;

namespace MeAjudaAi.Modules.Locations.Tests;

/// <summary>
/// Collection definition específica para testes de integração do módulo Locations
/// </summary>
[CollectionDefinition("LocationsIntegrationTests")]
public class LocationsIntegrationTestCollection : ICollectionFixture<SharedIntegrationTestFixture>
{
// Esta classe não tem implementação - apenas define a collection específica do módulo Locations
}
Loading