feat(api): BATCH 5 - PermisosController + tests HTTP [UDT-005]
This commit is contained in:
83
src/api/SIGCM2.Api/Controllers/PermisosController.cs
Normal file
83
src/api/SIGCM2.Api/Controllers/PermisosController.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SIGCM2.Application.Abstractions;
|
||||
using SIGCM2.Application.Permisos.Assign;
|
||||
using SIGCM2.Application.Permisos.Dtos;
|
||||
using SIGCM2.Application.Permisos.GetByRol;
|
||||
using SIGCM2.Application.Permisos.List;
|
||||
|
||||
namespace SIGCM2.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1")]
|
||||
[Authorize(Roles = "admin")]
|
||||
public sealed class PermisosController : ControllerBase
|
||||
{
|
||||
private readonly IDispatcher _dispatcher;
|
||||
private readonly IValidator<AssignPermisosToRolCommand> _assignValidator;
|
||||
|
||||
public PermisosController(
|
||||
IDispatcher dispatcher,
|
||||
IValidator<AssignPermisosToRolCommand> assignValidator)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
_assignValidator = assignValidator;
|
||||
}
|
||||
|
||||
/// <summary>Lists all permisos in the canonical catalog. Requires admin role.</summary>
|
||||
[HttpGet("permisos")]
|
||||
[ProducesResponseType(typeof(IReadOnlyList<PermisoDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> ListPermisos()
|
||||
{
|
||||
var result = await _dispatcher.Send<ListPermisosQuery, IReadOnlyList<PermisoDto>>(new ListPermisosQuery());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>Gets all permisos assigned to a rol. Requires admin role.</summary>
|
||||
[HttpGet("roles/{codigo}/permisos")]
|
||||
[ProducesResponseType(typeof(IReadOnlyList<PermisoDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetRolPermisos(string codigo)
|
||||
{
|
||||
var result = await _dispatcher.Send<GetRolPermisosQuery, IReadOnlyList<PermisoDto>>(
|
||||
new GetRolPermisosQuery(codigo));
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace-set: replaces the full permiso assignment for a rol.
|
||||
/// Returns the updated permiso set (200). Requires admin role.
|
||||
/// </summary>
|
||||
[HttpPut("roles/{codigo}/permisos")]
|
||||
[ProducesResponseType(typeof(IReadOnlyList<PermisoDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> AssignPermisos(string codigo, [FromBody] AssignPermisosRequest request)
|
||||
{
|
||||
var codigos = request.Codigos ?? [];
|
||||
var command = new AssignPermisosToRolCommand(
|
||||
RolCodigo: codigo,
|
||||
Codigos: codigos);
|
||||
|
||||
var validation = await _assignValidator.ValidateAsync(command);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
var errors = validation.Errors
|
||||
.GroupBy(e => e.PropertyName)
|
||||
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
|
||||
return BadRequest(new { errors });
|
||||
}
|
||||
|
||||
var result = await _dispatcher.Send<AssignPermisosToRolCommand, IReadOnlyList<PermisoDto>>(command);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record AssignPermisosRequest(IReadOnlyList<string>? Codigos);
|
||||
@@ -83,6 +83,18 @@ public sealed class ExceptionFilter : IExceptionFilter
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
case PermisoNotFoundException permisoNotFoundEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
error = "permiso_not_found",
|
||||
message = permisoNotFoundEx.Message
|
||||
})
|
||||
{
|
||||
StatusCode = StatusCodes.Status404NotFound
|
||||
};
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
case RolAlreadyExistsException rolExistsEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
|
||||
416
tests/SIGCM2.Api.Tests/Permisos/PermisosEndpointTests.cs
Normal file
416
tests/SIGCM2.Api.Tests/Permisos/PermisosEndpointTests.cs
Normal file
@@ -0,0 +1,416 @@
|
||||
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.Permisos;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for /api/v1/permisos and /api/v1/roles/{codigo}/permisos (UDT-005).
|
||||
/// RED: written before PermisosController exists.
|
||||
/// </summary>
|
||||
[Collection("ApiIntegration")]
|
||||
public sealed class PermisosEndpointTests : IAsyncLifetime
|
||||
{
|
||||
private const string TestConnectionString =
|
||||
"Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;";
|
||||
|
||||
private const string AdminUsername = "admin";
|
||||
private const string AdminPassword = "@Diego550@";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public PermisosEndpointTests(TestWebAppFactory factory)
|
||||
{
|
||||
_client = factory.CreateClient();
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task<string> GetBearerTokenAsync(string username, string password)
|
||||
{
|
||||
var response = await _client.PostAsJsonAsync("/api/v1/auth/login", new { username, password });
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
throw new InvalidOperationException($"Login failed ({(int)response.StatusCode}): {body}");
|
||||
}
|
||||
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
return json.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;
|
||||
}
|
||||
|
||||
private static async Task RestoreCajeroPermisosAsync()
|
||||
{
|
||||
await using var conn = new SqlConnection(TestConnectionString);
|
||||
await conn.OpenAsync();
|
||||
|
||||
// Remove any test-added permisos from cajero
|
||||
await conn.ExecuteAsync("""
|
||||
DELETE rp FROM dbo.RolPermiso rp
|
||||
JOIN dbo.Rol r ON r.Id = rp.RolId
|
||||
JOIN dbo.Permiso p ON p.Id = rp.PermisoId
|
||||
WHERE r.Codigo = 'cajero'
|
||||
AND p.Codigo NOT IN (
|
||||
'ventas:contado:crear','ventas:contado:modificar',
|
||||
'ventas:contado:cobrar','ventas:contado:facturar'
|
||||
);
|
||||
""");
|
||||
|
||||
// Re-add missing canonical cajero permisos
|
||||
await conn.ExecuteAsync("""
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
MERGE dbo.RolPermiso AS t
|
||||
USING (
|
||||
SELECT r.Id AS RolId, p.Id AS PermisoId
|
||||
FROM (VALUES
|
||||
('cajero','ventas:contado:crear'),
|
||||
('cajero','ventas:contado:modificar'),
|
||||
('cajero','ventas:contado:cobrar'),
|
||||
('cajero','ventas:contado:facturar')
|
||||
) AS x (RolCodigo, PermisoCodigo)
|
||||
JOIN dbo.Rol r ON r.Codigo = x.RolCodigo
|
||||
JOIN dbo.Permiso p ON p.Codigo = x.PermisoCodigo
|
||||
) AS s ON t.RolId = s.RolId AND t.PermisoId = s.PermisoId
|
||||
WHEN NOT MATCHED BY TARGET THEN
|
||||
INSERT (RolId, PermisoId) VALUES (s.RolId, s.PermisoId);
|
||||
""");
|
||||
}
|
||||
|
||||
private async Task<string> CreateNonAdminUserAndGetTokenAsync(string username, string rol = "cajero")
|
||||
{
|
||||
var adminToken = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
// Create non-admin user via API
|
||||
using var mkUser = BuildRequest(HttpMethod.Post, "/api/v1/users", new
|
||||
{
|
||||
username,
|
||||
password = "Secure1234!",
|
||||
nombre = "Non",
|
||||
apellido = "Admin",
|
||||
email = (string?)null,
|
||||
rol
|
||||
}, adminToken);
|
||||
var mkUserResp = await _client.SendAsync(mkUser);
|
||||
if (mkUserResp.StatusCode != HttpStatusCode.Created && mkUserResp.StatusCode != HttpStatusCode.Conflict)
|
||||
Assert.Fail($"Seed non-admin user failed: {mkUserResp.StatusCode}");
|
||||
|
||||
return await GetBearerTokenAsync(username, "Secure1234!");
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
// ── GET /api/v1/permisos — catalog ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetPermisos_WithAdmin_Returns200With18Items()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(18, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPermisos_WithoutToken_Returns401()
|
||||
{
|
||||
var resp = await _client.SendAsync(BuildRequest(HttpMethod.Get, "/api/v1/permisos"));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPermisos_WithNonAdminToken_Returns403()
|
||||
{
|
||||
const string username = "perm_nonadmin_list";
|
||||
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);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteUsuarioIfExistsAsync(username);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPermisos_ResponseContainsCodigoNombreFields()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var first = list.EnumerateArray().First();
|
||||
Assert.True(first.TryGetProperty("codigo", out _), "Response item missing 'codigo' field");
|
||||
Assert.True(first.TryGetProperty("nombre", out _), "Response item missing 'nombre' field");
|
||||
}
|
||||
|
||||
// ── GET /api/v1/roles/{codigo}/permisos ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_AdminRol_Returns200With18Items()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/admin/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(18, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_CajeroRol_Returns200With4Items()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/cajero/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(4, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_ReportesRol_Returns200WithEmptyArray()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/reportes/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(0, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_InexistentRol_Returns404()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/rol_inexistente_xyz/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_WithoutToken_Returns401()
|
||||
{
|
||||
var resp = await _client.SendAsync(BuildRequest(HttpMethod.Get, "/api/v1/roles/admin/permisos"));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_WithNonAdminToken_Returns403()
|
||||
{
|
||||
const string username = "perm_nonadmin_getRol";
|
||||
try
|
||||
{
|
||||
var token = await CreateNonAdminUserAndGetTokenAsync(username);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/admin/permisos", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteUsuarioIfExistsAsync(username);
|
||||
}
|
||||
}
|
||||
|
||||
// ── PUT /api/v1/roles/{codigo}/permisos ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_ValidAssignment_Returns200WithUpdatedSet()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
try
|
||||
{
|
||||
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.OK, resp.StatusCode);
|
||||
var list = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(1, list.GetArrayLength());
|
||||
Assert.Equal("ventas:contado:crear", list[0].GetProperty("codigo").GetString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
await RestoreCajeroPermisosAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_ThenGet_ReturnsUpdatedSet()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
try
|
||||
{
|
||||
// Assign 1 permiso to cajero
|
||||
using var putReq = BuildRequest(
|
||||
HttpMethod.Put,
|
||||
"/api/v1/roles/cajero/permisos",
|
||||
new { codigos = new[] { "textos:editar" } },
|
||||
token);
|
||||
var putResp = await _client.SendAsync(putReq);
|
||||
Assert.Equal(HttpStatusCode.OK, putResp.StatusCode);
|
||||
|
||||
// GET should now return 1 item
|
||||
using var getReq = BuildRequest(HttpMethod.Get, "/api/v1/roles/cajero/permisos", bearerToken: token);
|
||||
var getResp = await _client.SendAsync(getReq);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, getResp.StatusCode);
|
||||
var list = await getResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(1, list.GetArrayLength());
|
||||
Assert.Equal("textos:editar", list[0].GetProperty("codigo").GetString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
await RestoreCajeroPermisosAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_Idempotent_TwoCallsSameResult()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
try
|
||||
{
|
||||
var body = new { codigos = new[] { "ventas:contado:crear", "textos:editar" } };
|
||||
|
||||
using var req1 = BuildRequest(HttpMethod.Put, "/api/v1/roles/cajero/permisos", body, token);
|
||||
var resp1 = await _client.SendAsync(req1);
|
||||
Assert.Equal(HttpStatusCode.OK, resp1.StatusCode);
|
||||
|
||||
using var req2 = BuildRequest(HttpMethod.Put, "/api/v1/roles/cajero/permisos", body, token);
|
||||
var resp2 = await _client.SendAsync(req2);
|
||||
Assert.Equal(HttpStatusCode.OK, resp2.StatusCode);
|
||||
|
||||
var list2 = await resp2.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(2, list2.GetArrayLength());
|
||||
}
|
||||
finally
|
||||
{
|
||||
await RestoreCajeroPermisosAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_AdminWithEmptyList_Returns400()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
using var req = BuildRequest(
|
||||
HttpMethod.Put,
|
||||
"/api/v1/roles/admin/permisos",
|
||||
new { codigos = Array.Empty<string>() },
|
||||
token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_NonExistentPermiso_Returns404()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
using var req = BuildRequest(
|
||||
HttpMethod.Put,
|
||||
"/api/v1/roles/cajero/permisos",
|
||||
new { codigos = new[] { "permiso:no:existe" } },
|
||||
token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
// Validator rejects unknown codes with 400 (not in catalog) before handler can 404
|
||||
// The validator checks Permiso.Todos — if code not in static catalog → 400
|
||||
Assert.True(
|
||||
resp.StatusCode == HttpStatusCode.BadRequest || resp.StatusCode == HttpStatusCode.NotFound,
|
||||
$"Expected 400 or 404 but got {resp.StatusCode}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_InexistentRol_Returns404()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
|
||||
using var req = BuildRequest(
|
||||
HttpMethod.Put,
|
||||
"/api/v1/roles/rol_inexistente_xyz/permisos",
|
||||
new { codigos = new[] { "ventas:contado:crear" } },
|
||||
token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_WithoutToken_Returns401()
|
||||
{
|
||||
var resp = await _client.SendAsync(BuildRequest(
|
||||
HttpMethod.Put,
|
||||
"/api/v1/roles/cajero/permisos",
|
||||
new { codigos = new[] { "ventas:contado:crear" } }));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PutRolPermisos_WithNonAdminToken_Returns403()
|
||||
{
|
||||
const string username = "perm_nonadmin_put";
|
||||
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);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteUsuarioIfExistsAsync(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user