2026-04-17 12:34:35 -03:00
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Http.Headers;
|
|
|
|
|
using System.Net.Http.Json;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using Dapper;
|
|
|
|
|
using Microsoft.Data.SqlClient;
|
|
|
|
|
using SIGCM2.TestSupport;
|
|
|
|
|
|
|
|
|
|
namespace SIGCM2.Api.Tests.Admin;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// ADM-008 B5 — Integration tests for /api/v1/admin/puntos-de-venta.
|
|
|
|
|
/// All endpoints require permission 'administracion:puntos_de_venta:gestionar'.
|
|
|
|
|
/// Tests: T5.3 CRUD, T5.4 concurrencia, T5.5 secuencialidad.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Collection("ApiIntegration")]
|
|
|
|
|
public sealed class PuntosDeVentaControllerTests : IAsyncLifetime
|
|
|
|
|
{
|
2026-04-18 21:44:40 -03:00
|
|
|
private const string TestConnectionString = TestConnectionStrings.ApiTestDb;
|
2026-04-17 12:34:35 -03:00
|
|
|
|
|
|
|
|
private const string Endpoint = "/api/v1/admin/puntos-de-venta";
|
|
|
|
|
private const string MediosEndpoint = "/api/v1/admin/medios";
|
|
|
|
|
private const string AdminUsername = "admin";
|
|
|
|
|
private const string AdminPassword = "@Diego550@";
|
|
|
|
|
|
|
|
|
|
private readonly HttpClient _client;
|
|
|
|
|
|
|
|
|
|
public PuntosDeVentaControllerTests(TestWebAppFactory factory)
|
|
|
|
|
{
|
|
|
|
|
_client = factory.CreateClient();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task InitializeAsync() => Task.CompletedTask;
|
|
|
|
|
public Task DisposeAsync() => Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
private async Task<string> GetAdminTokenAsync()
|
|
|
|
|
{
|
|
|
|
|
var response = await _client.PostAsJsonAsync("/api/v1/auth/login", new
|
|
|
|
|
{
|
|
|
|
|
username = AdminUsername,
|
|
|
|
|
password = AdminPassword
|
|
|
|
|
});
|
|
|
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
|
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
return json.GetProperty("accessToken").GetString()!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<string> GetCajeroTokenAsync(string username)
|
|
|
|
|
{
|
|
|
|
|
var adminToken = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
using var mkUser = BuildRequest(HttpMethod.Post, "/api/v1/users", new
|
|
|
|
|
{
|
|
|
|
|
username,
|
|
|
|
|
password = "Secure1234!",
|
|
|
|
|
nombre = "Cajero",
|
|
|
|
|
apellido = "Test",
|
|
|
|
|
email = (string?)null,
|
|
|
|
|
rol = "cajero"
|
|
|
|
|
}, adminToken);
|
|
|
|
|
var mkResp = await _client.SendAsync(mkUser);
|
|
|
|
|
if (mkResp.StatusCode != HttpStatusCode.Created && mkResp.StatusCode != HttpStatusCode.Conflict)
|
|
|
|
|
Assert.Fail($"Seed cajero failed: {mkResp.StatusCode}");
|
|
|
|
|
|
|
|
|
|
var loginResp = await _client.PostAsJsonAsync("/api/v1/auth/login", new
|
|
|
|
|
{
|
|
|
|
|
username,
|
|
|
|
|
password = "Secure1234!"
|
|
|
|
|
});
|
|
|
|
|
loginResp.EnsureSuccessStatusCode();
|
|
|
|
|
var loginJson = await loginResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
return loginJson.GetProperty("accessToken").GetString()!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HttpRequestMessage BuildRequest(HttpMethod method, string url, object? body = null, string? bearerToken = null)
|
|
|
|
|
{
|
|
|
|
|
var request = new HttpRequestMessage(method, url);
|
|
|
|
|
if (bearerToken is not null)
|
|
|
|
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
|
|
|
|
|
if (body is not null)
|
|
|
|
|
request.Content = JsonContent.Create(body);
|
|
|
|
|
return request;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Creates a Medio via the API and returns its id.</summary>
|
|
|
|
|
private async Task<int> CreateMedioAsync(string codigo, string nombre, string token)
|
|
|
|
|
{
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, MediosEndpoint, new
|
|
|
|
|
{
|
|
|
|
|
codigo,
|
|
|
|
|
nombre,
|
|
|
|
|
tipo = 1
|
|
|
|
|
}, token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
return json.GetProperty("id").GetInt32();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Creates a PuntoDeVenta via the API and returns its id.</summary>
|
|
|
|
|
private async Task<int> CreatePdvAsync(int medioId, short numeroAFIP, string nombre, string token)
|
|
|
|
|
{
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId,
|
|
|
|
|
numeroAFIP,
|
|
|
|
|
nombre,
|
|
|
|
|
descripcion = (string?)null
|
|
|
|
|
}, token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
return json.GetProperty("id").GetInt32();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task DeleteMedioIfExistsAsync(string codigo)
|
|
|
|
|
{
|
|
|
|
|
await using var conn = new SqlConnection(TestConnectionString);
|
|
|
|
|
await conn.OpenAsync();
|
|
|
|
|
|
|
|
|
|
var id = await conn.QuerySingleOrDefaultAsync<int?>(
|
|
|
|
|
"SELECT Id FROM dbo.Medio WHERE Codigo = @Codigo", new { Codigo = codigo });
|
|
|
|
|
if (id is null) return;
|
|
|
|
|
|
|
|
|
|
// Delete dependent PuntosDeVenta (disable versioning to also clear history)
|
|
|
|
|
await conn.ExecuteAsync("ALTER TABLE dbo.PuntoDeVenta SET (SYSTEM_VERSIONING = OFF)");
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.PuntoDeVenta_History WHERE MedioId = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.PuntoDeVenta WHERE MedioId = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync(
|
|
|
|
|
"ALTER TABLE dbo.PuntoDeVenta SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.PuntoDeVenta_History, HISTORY_RETENTION_PERIOD = 10 YEARS))");
|
|
|
|
|
|
|
|
|
|
// Delete dependent Secciones
|
|
|
|
|
await conn.ExecuteAsync("ALTER TABLE dbo.Seccion SET (SYSTEM_VERSIONING = OFF)");
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.Seccion_History WHERE MedioId = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.Seccion WHERE MedioId = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync(
|
|
|
|
|
"ALTER TABLE dbo.Seccion SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Seccion_History, HISTORY_RETENTION_PERIOD = 10 YEARS))");
|
|
|
|
|
|
|
|
|
|
// Delete the medio itself
|
|
|
|
|
await conn.ExecuteAsync("ALTER TABLE dbo.Medio SET (SYSTEM_VERSIONING = OFF)");
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.Medio_History WHERE Id = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.Medio WHERE Id = @id", new { id });
|
|
|
|
|
await conn.ExecuteAsync(
|
|
|
|
|
"ALTER TABLE dbo.Medio SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Medio_History, HISTORY_RETENTION_PERIOD = 10 YEARS))");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task DeleteUsuarioIfExistsAsync(string username)
|
|
|
|
|
{
|
|
|
|
|
await using var conn = new SqlConnection(TestConnectionString);
|
|
|
|
|
await conn.OpenAsync();
|
|
|
|
|
await conn.ExecuteAsync("""
|
|
|
|
|
DELETE rt FROM dbo.RefreshToken rt
|
|
|
|
|
INNER JOIN dbo.Usuario u ON u.Id = rt.UsuarioId
|
|
|
|
|
WHERE u.Username = @Username
|
|
|
|
|
""", new { Username = username });
|
|
|
|
|
await conn.ExecuteAsync("DELETE FROM dbo.Usuario WHERE Username = @Username", new { Username = username });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async Task<int> CountAuditEventsAsync(string action, string targetType, string targetId)
|
|
|
|
|
{
|
|
|
|
|
await using var conn = new SqlConnection(TestConnectionString);
|
|
|
|
|
await conn.OpenAsync();
|
|
|
|
|
return await conn.QuerySingleAsync<int>(
|
|
|
|
|
"SELECT COUNT(*) FROM dbo.AuditEvent WHERE Action = @Action AND TargetType = @TargetType AND TargetId = @TargetId",
|
|
|
|
|
new { Action = action, TargetType = targetType, TargetId = targetId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 401 / 403 guards ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_WithoutAuth_Returns401()
|
|
|
|
|
{
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId = 1,
|
|
|
|
|
numeroAFIP = 1,
|
|
|
|
|
nombre = "PdV Test"
|
|
|
|
|
});
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_WithCajeroRole_Returns403()
|
|
|
|
|
{
|
|
|
|
|
const string username = "adm008_pdv_cajero_403";
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var token = await GetCajeroTokenAsync(username);
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId = 1,
|
|
|
|
|
numeroAFIP = 1,
|
|
|
|
|
nombre = "PdV Test 403"
|
|
|
|
|
}, token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteUsuarioIfExistsAsync(username);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── CREATE ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — Happy path: Create returns 201 + AuditEvent.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_WithAdmin_Returns201AndAuditEvent()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_C201";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Create 201", token);
|
|
|
|
|
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV Central Create",
|
|
|
|
|
descripcion = "Test desc"
|
|
|
|
|
}, token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.Created, resp.StatusCode);
|
|
|
|
|
Assert.NotNull(resp.Headers.Location);
|
|
|
|
|
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.True(json.TryGetProperty("id", out var idEl));
|
|
|
|
|
var pdvId = idEl.GetInt32();
|
|
|
|
|
Assert.True(pdvId > 0);
|
|
|
|
|
Assert.Equal(medioId, json.GetProperty("medioId").GetInt32());
|
|
|
|
|
Assert.Equal(1, json.GetProperty("numeroAFIP").GetInt16());
|
|
|
|
|
Assert.Equal("PdV Central Create", json.GetProperty("nombre").GetString());
|
|
|
|
|
Assert.True(json.GetProperty("activo").GetBoolean());
|
|
|
|
|
|
|
|
|
|
var auditCount = await CountAuditEventsAsync("punto_de_venta.create", "PuntoDeVenta", pdvId.ToString());
|
|
|
|
|
Assert.Equal(1, auditCount);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — 409 medio_inactivo al crear con Medio inactivo.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_WithInactiveMedio_Returns409MedioInactivo()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_INACT";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio Inactivo PDV", token);
|
|
|
|
|
|
|
|
|
|
// Deactivate the medio
|
|
|
|
|
using var deactReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
|
|
|
|
var deactResp = await _client.SendAsync(deactReq);
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, deactResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Try to create PdV in inactive medio
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV en Medio Inactivo"
|
|
|
|
|
}, token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.Conflict, resp.StatusCode);
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — 409 numero_afip_duplicado al violar UNIQUE(MedioId, NumeroAFIP).</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_DuplicateNumeroAFIPInSameMedio_Returns409()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_DUP";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Dup", token);
|
|
|
|
|
|
|
|
|
|
// First create
|
|
|
|
|
using var first = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV Original"
|
|
|
|
|
}, token);
|
|
|
|
|
var firstResp = await _client.SendAsync(first);
|
|
|
|
|
Assert.Equal(HttpStatusCode.Created, firstResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Second with same medioId + numeroAFIP
|
|
|
|
|
using var second = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV Duplicado"
|
|
|
|
|
}, token);
|
|
|
|
|
var secondResp = await _client.SendAsync(second);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.Conflict, secondResp.StatusCode);
|
|
|
|
|
var json = await secondResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("numero_afip_duplicado", json.GetProperty("error").GetString());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — mismo NumeroAFIP en distinto Medio es permitido.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task CreatePdv_SameNumeroAFIPDifferentMedio_Returns201()
|
|
|
|
|
{
|
|
|
|
|
const string medio1Codigo = "ADMS08_M1_MULTI";
|
|
|
|
|
const string medio2Codigo = "ADMS08_M2_MULTI";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId1 = await CreateMedioAsync(medio1Codigo, "Medio Multi 1 PDV", token);
|
|
|
|
|
var medioId2 = await CreateMedioAsync(medio2Codigo, "Medio Multi 2 PDV", token);
|
|
|
|
|
|
|
|
|
|
using var req1 = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId = medioId1,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV Medio 1"
|
|
|
|
|
}, token);
|
|
|
|
|
var resp1 = await _client.SendAsync(req1);
|
|
|
|
|
Assert.Equal(HttpStatusCode.Created, resp1.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Same numeroAFIP in different medio → should succeed
|
|
|
|
|
using var req2 = BuildRequest(HttpMethod.Post, Endpoint, new
|
|
|
|
|
{
|
|
|
|
|
medioId = medioId2,
|
|
|
|
|
numeroAFIP = (short)1,
|
|
|
|
|
nombre = "PdV Medio 2"
|
|
|
|
|
}, token);
|
|
|
|
|
var resp2 = await _client.SendAsync(req2);
|
|
|
|
|
Assert.Equal(HttpStatusCode.Created, resp2.StatusCode);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medio1Codigo);
|
|
|
|
|
await DeleteMedioIfExistsAsync(medio2Codigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── GET BY ID ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — 404 cuando id inexistente.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetPdvById_NotFound_Returns404()
|
|
|
|
|
{
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Get, $"{Endpoint}/999999", bearerToken: token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("punto_de_venta_not_found", json.GetProperty("error").GetString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── LIST ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — List returns 200 with paged result.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListPdvs_WithAdmin_Returns200PagedResult()
|
|
|
|
|
{
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Get, Endpoint, bearerToken: token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.True(json.TryGetProperty("items", out _), "Response must have 'items'");
|
|
|
|
|
Assert.True(json.TryGetProperty("total", out _), "Response must have 'total'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — List filtrado por medioId y activo.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ListPdvs_FilterByMedioAndActivo_ReturnsMatchingItems()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_LIST";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV List", token);
|
|
|
|
|
await CreatePdvAsync(medioId, 1, "PdV Lista 1", token);
|
|
|
|
|
await CreatePdvAsync(medioId, 2, "PdV Lista 2", token);
|
|
|
|
|
|
|
|
|
|
// Filter by medioId
|
|
|
|
|
using var req = BuildRequest(HttpMethod.Get, $"{Endpoint}?medioId={medioId}&activo=true", bearerToken: token);
|
|
|
|
|
var resp = await _client.SendAsync(req);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
|
|
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
var items = json.GetProperty("items").EnumerateArray().ToList();
|
|
|
|
|
Assert.Equal(2, items.Count);
|
|
|
|
|
Assert.All(items, item => Assert.Equal(medioId, item.GetProperty("medioId").GetInt32()));
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── UPDATE ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — Happy path Update returns 200 + AuditEvent.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task UpdatePdv_WithAdmin_Returns200AndAuditEvent()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_UPD";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Update", token);
|
|
|
|
|
var pdvId = await CreatePdvAsync(medioId, 1, "PdV Original", token);
|
|
|
|
|
|
|
|
|
|
using var updateReq = BuildRequest(HttpMethod.Put, $"{Endpoint}/{pdvId}", new
|
|
|
|
|
{
|
|
|
|
|
nombre = "PdV Actualizado",
|
|
|
|
|
numeroAFIP = (short)2,
|
|
|
|
|
descripcion = "Nueva desc"
|
|
|
|
|
}, token);
|
|
|
|
|
var updateResp = await _client.SendAsync(updateReq);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.OK, updateResp.StatusCode);
|
|
|
|
|
var updated = await updateResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("PdV Actualizado", updated.GetProperty("nombre").GetString());
|
|
|
|
|
Assert.Equal(2, updated.GetProperty("numeroAFIP").GetInt16());
|
|
|
|
|
|
|
|
|
|
var auditCount = await CountAuditEventsAsync("punto_de_venta.update", "PuntoDeVenta", pdvId.ToString());
|
|
|
|
|
Assert.Equal(1, auditCount);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — 409 medio_inactivo al actualizar PdV con Medio inactivo.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task UpdatePdv_WhenMedioInactive_Returns409MedioInactivo()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_UPDMI";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Update MedioInact", token);
|
|
|
|
|
var pdvId = await CreatePdvAsync(medioId, 1, "PdV Update Medio Inactivo", token);
|
|
|
|
|
|
|
|
|
|
// Deactivate the medio
|
|
|
|
|
using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
|
|
|
|
var deactMedioResp = await _client.SendAsync(deactMedioReq);
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, deactMedioResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Try to update PdV with inactive medio
|
|
|
|
|
using var updateReq = BuildRequest(HttpMethod.Put, $"{Endpoint}/{pdvId}", new
|
|
|
|
|
{
|
|
|
|
|
nombre = "PdV Medio Inactivo",
|
|
|
|
|
numeroAFIP = (short)1
|
|
|
|
|
}, token);
|
|
|
|
|
var updateResp = await _client.SendAsync(updateReq);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.Conflict, updateResp.StatusCode);
|
|
|
|
|
var json = await updateResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── DEACTIVATE ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — Happy path Deactivate returns 204 + AuditEvent.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task DeactivatePdv_WithAdmin_Returns204AndAuditEvent()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_DEACT";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Deactivate", token);
|
|
|
|
|
var pdvId = await CreatePdvAsync(medioId, 1, "PdV Para Desactivar", token);
|
|
|
|
|
|
|
|
|
|
using var deactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/deactivate", bearerToken: token);
|
|
|
|
|
var deactResp = await _client.SendAsync(deactReq);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, deactResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
var auditCount = await CountAuditEventsAsync("punto_de_venta.deactivate", "PuntoDeVenta", pdvId.ToString());
|
|
|
|
|
Assert.Equal(1, auditCount);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── REACTIVATE ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — Happy path Reactivate returns 204 + AuditEvent.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ReactivatePdv_WithAdmin_Returns204AndAuditEvent()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_REACT";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Reactivate", token);
|
|
|
|
|
var pdvId = await CreatePdvAsync(medioId, 1, "PdV Para Reactivar", token);
|
|
|
|
|
|
|
|
|
|
// Deactivate first
|
|
|
|
|
using var deactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/deactivate", bearerToken: token);
|
|
|
|
|
var deactResp = await _client.SendAsync(deactReq);
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, deactResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Reactivate
|
|
|
|
|
using var reactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/reactivate", bearerToken: token);
|
|
|
|
|
var reactResp = await _client.SendAsync(reactReq);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, reactResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
var auditCount = await CountAuditEventsAsync("punto_de_venta.reactivate", "PuntoDeVenta", pdvId.ToString());
|
|
|
|
|
Assert.Equal(1, auditCount);
|
|
|
|
|
|
|
|
|
|
// Verify it's active again via GET
|
|
|
|
|
using var getReq = BuildRequest(HttpMethod.Get, $"{Endpoint}/{pdvId}", bearerToken: token);
|
|
|
|
|
var getResp = await _client.SendAsync(getReq);
|
|
|
|
|
Assert.Equal(HttpStatusCode.OK, getResp.StatusCode);
|
|
|
|
|
var pdvJson = await getResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.True(pdvJson.GetProperty("activo").GetBoolean());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>T5.3 — 409 medio_inactivo al reactivar con Medio inactivo.</summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task ReactivatePdv_WhenMedioInactive_Returns409MedioInactivo()
|
|
|
|
|
{
|
|
|
|
|
const string medioCodigo = "ADMS08_MED_REACTMI";
|
|
|
|
|
var token = await GetAdminTokenAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var medioId = await CreateMedioAsync(medioCodigo, "Medio PDV Reactivate Inactivo", token);
|
|
|
|
|
var pdvId = await CreatePdvAsync(medioId, 1, "PdV Reactivate Medio Inactivo", token);
|
|
|
|
|
|
|
|
|
|
// Deactivate PdV while medio is active
|
|
|
|
|
using var deactPdvReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/deactivate", bearerToken: token);
|
|
|
|
|
await _client.SendAsync(deactPdvReq);
|
|
|
|
|
|
|
|
|
|
// Deactivate medio
|
|
|
|
|
using var deactMedioReq = BuildRequest(HttpMethod.Post, $"{MediosEndpoint}/{medioId}/deactivate", bearerToken: token);
|
|
|
|
|
var deactMedioResp = await _client.SendAsync(deactMedioReq);
|
|
|
|
|
Assert.Equal(HttpStatusCode.NoContent, deactMedioResp.StatusCode);
|
|
|
|
|
|
|
|
|
|
// Try to reactivate PdV with inactive medio
|
|
|
|
|
using var reactReq = BuildRequest(HttpMethod.Post, $"{Endpoint}/{pdvId}/reactivate", bearerToken: token);
|
|
|
|
|
var reactResp = await _client.SendAsync(reactReq);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(HttpStatusCode.Conflict, reactResp.StatusCode);
|
|
|
|
|
var json = await reactResp.Content.ReadFromJsonAsync<JsonElement>();
|
|
|
|
|
Assert.Equal("medio_inactivo", json.GetProperty("error").GetString());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
await DeleteMedioIfExistsAsync(medioCodigo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|