feat(api): migrar controllers admin a RequirePermission [UDT-006]

This commit is contained in:
2026-04-15 16:34:32 -03:00
parent 4866c4f21f
commit 0218d8d371
10 changed files with 238 additions and 20 deletions

View File

@@ -130,7 +130,7 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
// ── GET /api/v1/permisos — catalog ───────────────────────────────────────
[Fact]
public async Task GetPermisos_WithAdmin_Returns200With18Items()
public async Task GetPermisos_WithAdmin_Returns200With21Items()
{
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
using var req = BuildRequest(HttpMethod.Get, "/api/v1/permisos", bearerToken: token);
@@ -138,7 +138,8 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(18, list.GetArrayLength());
// V007 (UDT-006) adds 3 new admin permisos → 21 total
Assert.Equal(21, list.GetArrayLength());
}
[Fact]
@@ -181,7 +182,7 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
// ── GET /api/v1/roles/{codigo}/permisos ──────────────────────────────────
[Fact]
public async Task GetRolPermisos_AdminRol_Returns200With18Items()
public async Task GetRolPermisos_AdminRol_Returns200With21Items()
{
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/admin/permisos", bearerToken: token);
@@ -189,7 +190,8 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(18, list.GetArrayLength());
// V007 (UDT-006) adds 3 new admin permisos → 21 total
Assert.Equal(21, list.GetArrayLength());
}
[Fact]
@@ -424,4 +426,63 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
await DeleteUsuarioIfExistsAsync(username);
}
}
// ── UDT-006: 403 ProblemDetails shape ─────────────────────────────────────
[Fact]
public async Task GetPermisos_WithCajeroToken_Returns403WithProblemDetailsShape()
{
const string username = "udt006_permisos_403_cajero";
try
{
var token = await CreateNonAdminUserAndGetTokenAsync(username);
using var req = BuildRequest(HttpMethod.Get, "/api/v1/permisos", bearerToken: token);
var resp = await _client.SendAsync(req);
Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode);
Assert.Contains("problem+json", resp.Content.Headers.ContentType?.MediaType ?? "");
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(403, json.GetProperty("status").GetInt32());
Assert.Equal("Acceso denegado", json.GetProperty("title").GetString());
Assert.True(json.TryGetProperty("permisoRequerido", out var perm),
"Response must contain 'permisoRequerido'");
// GET /permisos migra a administracion:permisos:ver
Assert.Equal("administracion:permisos:ver", perm.GetString());
}
finally
{
await DeleteUsuarioIfExistsAsync(username);
}
}
[Fact]
public async Task PutRolPermisos_WithCajeroToken_Returns403WithProblemDetailsShape()
{
const string username = "udt006_put_permisos_403";
try
{
var token = await CreateNonAdminUserAndGetTokenAsync(username);
using var req = BuildRequest(
HttpMethod.Put,
"/api/v1/roles/cajero/permisos",
new { codigos = new[] { "ventas:contado:crear" } },
token);
var resp = await _client.SendAsync(req);
Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode);
Assert.Contains("problem+json", resp.Content.Headers.ContentType?.MediaType ?? "");
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(403, json.GetProperty("status").GetInt32());
Assert.True(json.TryGetProperty("permisoRequerido", out var perm),
"Response must contain 'permisoRequerido'");
// PUT /roles/{c}/permisos migra a administracion:roles_permisos:gestionar
Assert.Equal("administracion:roles_permisos:gestionar", perm.GetString());
}
finally
{
await DeleteUsuarioIfExistsAsync(username);
}
}
}

View File

@@ -350,4 +350,51 @@ public sealed class RolesEndpointTests : IAsyncLifetime
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
}
// ── UDT-006: 403 ProblemDetails shape ────────────────────────────────────
[Fact]
public async Task GetRoles_WithCajeroToken_Returns403WithProblemDetailsShape()
{
const string username = "udt006_roles_403_cajero";
try
{
var token = await CreateCajeroTokenAsync(username);
var resp = await _client.SendAsync(BuildRequest(HttpMethod.Get, Endpoint, bearerToken: token));
Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode);
Assert.Contains("problem+json", resp.Content.Headers.ContentType?.MediaType ?? "");
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(403, json.GetProperty("status").GetInt32());
Assert.Equal("Acceso denegado", json.GetProperty("title").GetString());
Assert.True(json.TryGetProperty("permisoRequerido", out var perm),
"Response must contain 'permisoRequerido'");
// RolesController migra a administracion:roles:gestionar
Assert.Equal("administracion:roles:gestionar", perm.GetString());
}
finally
{
await DeleteUsuarioIfExistsAsync(username);
}
}
// Helper: create cajero user via SQL and return token
private async Task<string> CreateCajeroTokenAsync(string username)
{
var adminToken = await GetBearerTokenAsync(AdminUsername, AdminPassword);
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}");
return await GetBearerTokenAsync(username, "Secure1234!");
}
}

View File

@@ -388,6 +388,56 @@ public sealed class CreateUsuarioEndpointTests : IAsyncLifetime
}
}
// ---------------------------------------------------------------------------
// UDT-006 Scenario: 403 con ProblemDetails shape — token cajero sin permiso administracion:usuarios:gestionar
// ---------------------------------------------------------------------------
[Fact]
public async Task CreateUsuario_WithCajeroRole_Returns403WithProblemDetailsShape()
{
const string username = "udt006_403_shape_test";
try
{
var token = await CreateCajeroTokenAsync(username);
using var request = BuildRequest(HttpMethod.Post, Endpoint, ValidCreateBody("shape_target"), token);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
// Content-Type must be application/problem+json
Assert.Contains("problem+json", response.Content.Headers.ContentType?.MediaType ?? "");
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
Assert.Equal(403, json.GetProperty("status").GetInt32());
Assert.Equal("Acceso denegado", json.GetProperty("title").GetString());
Assert.True(json.TryGetProperty("permisoRequerido", out var perm),
"Response must contain 'permisoRequerido'");
Assert.Equal("administracion:usuarios:gestionar", perm.GetString());
}
finally
{
await DeleteUsuarioAsync(username);
}
}
// Helper: create a cajero user and return its token
private async Task<string> CreateCajeroTokenAsync(string username)
{
var adminToken = await GetBearerTokenAsync(AdminUsername, AdminPassword);
using var mkUser = BuildRequest(HttpMethod.Post, Endpoint, 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}");
return await GetBearerTokenAsync(username, "Secure1234!");
}
// ---------------------------------------------------------------------------
// Scenario 7 (UDT-004 Phase 5.3): 400 — rol existe pero está inactivo
// ---------------------------------------------------------------------------