refactor(tests): TestWebAppFactory.CreateClientWithOverrides para DI override por test (closes #36)
Agrega helper CreateClientWithOverrides en TestWebAppFactory que envuelve WithWebHostBuilder+ConfigureTestServices para inyectar stubs por test sin tocar la fábrica compartida. Usa el patrón para agregar 2 tests e2e: Deactivate_WhenProductQueryReturnsInUse_Returns409WithErrorCode (PRD-001/PRD-002) y CreateRubro_WhenParentHasAvisos_Returns409WithErrorCode (CAT-002). Remueve el comentario TODO PRD-002. 287 Api tests verdes.
This commit is contained in:
@@ -5,8 +5,11 @@ using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SIGCM2.Api.Filters;
|
||||
using SIGCM2.Application.Abstractions.Persistence;
|
||||
using SIGCM2.Domain.Exceptions;
|
||||
using SIGCM2.TestSupport;
|
||||
|
||||
@@ -17,12 +20,7 @@ namespace SIGCM2.Api.Tests.Rubros;
|
||||
///
|
||||
/// Unit tests: ExceptionFilter mapping for new 409 cases (no DB needed).
|
||||
/// Integration: GET /arbol returns tieneAvisos field per node (stub = false).
|
||||
///
|
||||
/// Design note: the 409 guard behavior is fully covered by unit tests in
|
||||
/// SIGCM2.Application.Tests (CreateRubroCommandHandlerTests, MoveRubroCommandHandlerTests).
|
||||
/// e2e 409 verification via a separate factory is skipped here because the shared
|
||||
/// ApiIntegration singleton factory cannot be safely augmented with per-test DI overrides
|
||||
/// (RSA key singleton issue documented in ApiIntegrationCollection.cs).
|
||||
/// Integration: POST child under leaf with avisos → 409 (via per-test DI override, issue #36).
|
||||
/// </summary>
|
||||
[Collection("ApiIntegration")]
|
||||
public sealed class RubrosReglaDeOroTests : IAsyncLifetime
|
||||
@@ -32,10 +30,12 @@ public sealed class RubrosReglaDeOroTests : IAsyncLifetime
|
||||
private const string AdminUsername = "admin";
|
||||
private const string AdminPassword = "@Diego550@";
|
||||
|
||||
private readonly TestWebAppFactory _factory;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public RubrosReglaDeOroTests(TestWebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = factory.CreateClient();
|
||||
}
|
||||
|
||||
@@ -167,11 +167,82 @@ public sealed class RubrosReglaDeOroTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
// ── Integration: POST returns 409 message format (guard path) ─────────────
|
||||
// NOTE: these tests rely on the unit-tested handler behavior. The 409 is proven by:
|
||||
// - CreateRubroCommandHandlerTests.Handle_ParentTieneAvisos_Throws_RubroPadreEsHojaConAvisosException
|
||||
// - ExceptionFilter_MapsRubroPadreEsHojaConAvisos_To409 (above)
|
||||
// The combined e2e 409 test is omitted here because it requires per-factory DI override
|
||||
// which conflicts with the shared ApiIntegration RSA singleton pattern.
|
||||
// See: ApiIntegrationCollection.cs for the rationale.
|
||||
// ── Integration: POST child under leaf with avisos → 409 (DI override) ─────
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the 409 guard path end-to-end via a per-test DI override: injects a stub
|
||||
/// <see cref="IAvisoQueryRepository"/> that reports the parent Rubro has 1 aviso,
|
||||
/// so no real Aviso row needs to exist in the database.
|
||||
///
|
||||
/// Uses <see cref="TestWebAppFactory.CreateClientWithOverrides"/> — the pattern enabled
|
||||
/// by fixing issue #36 (RSA singleton / per-test DI override).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CreateRubro_WhenParentHasAvisos_Returns409WithErrorCode()
|
||||
{
|
||||
// Arrange: get token from shared client (same DB)
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
// Create a real parent Rubro to have a valid parentId
|
||||
using var createParentReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = $"Parent_Hoja_CAT002_{Guid.NewGuid():N}"[..30],
|
||||
parentId = (int?)null,
|
||||
}, token);
|
||||
var parentResp = await _client.SendAsync(createParentReq);
|
||||
Assert.Equal(HttpStatusCode.Created, parentResp.StatusCode);
|
||||
var parentJson = await parentResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var parentId = parentJson.GetProperty("id").GetInt32();
|
||||
|
||||
try
|
||||
{
|
||||
// Child client with stub that reports parentId has 1 aviso
|
||||
using var client = _factory.CreateClientWithOverrides(services =>
|
||||
{
|
||||
services.RemoveAll<IAvisoQueryRepository>();
|
||||
services.AddScoped<IAvisoQueryRepository>(_ =>
|
||||
new AlwaysHasAvisosQueryRepository());
|
||||
});
|
||||
|
||||
// Act: attempt to create a child under the "leaf with avisos" parent
|
||||
var childReq = new System.Net.Http.HttpRequestMessage(HttpMethod.Post, AdminEndpoint);
|
||||
childReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
childReq.Content = System.Net.Http.Json.JsonContent.Create(new
|
||||
{
|
||||
nombre = $"Child_CAT002_{Guid.NewGuid():N}"[..30],
|
||||
parentId,
|
||||
});
|
||||
var resp = await client.SendAsync(childReq);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
|
||||
var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal("rubro_padre_es_hoja_con_avisos", body.GetProperty("error").GetString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteRubroIfExistsAsync(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub: always reports that a Rubro has 1 aviso (single query) and a non-empty batch dictionary.
|
||||
/// Used by <see cref="RubrosReglaDeOroTests.CreateRubro_WhenParentHasAvisos_Returns409WithErrorCode"/>
|
||||
/// via <see cref="TestWebAppFactory.CreateClientWithOverrides"/> to verify the 409 guard
|
||||
/// without seeding real Aviso rows in the database (issue #36 DI override pattern).
|
||||
/// </summary>
|
||||
file sealed class AlwaysHasAvisosQueryRepository : IAvisoQueryRepository
|
||||
{
|
||||
public Task<int> CountAvisosEnRubroAsync(int rubroId, CancellationToken ct = default)
|
||||
=> Task.FromResult(1);
|
||||
|
||||
public Task<IReadOnlyDictionary<int, int>> CountAvisosBatchAsync(
|
||||
IReadOnlyCollection<int> rubroIds,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
IReadOnlyDictionary<int, int> result = rubroIds.ToDictionary(id => id, _ => 1);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user