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.Rubros; /// /// CAT-001 — Integration tests for /api/v1/rubros and /api/v1/admin/rubros. /// Read endpoints require authentication (any role). /// Write endpoints require permission 'catalogo:rubros:gestionar'. /// Verifies audit events after each mutating operation. /// [Collection("ApiIntegration")] public sealed class RubrosControllerTests : IAsyncLifetime { private const string TestConnectionString = "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; private const string ReadEndpoint = "/api/v1/rubros"; private const string AdminEndpoint = "/api/v1/admin/rubros"; private const string AdminUsername = "admin"; private const string AdminPassword = "@Diego550@"; private readonly HttpClient _client; public RubrosControllerTests(TestWebAppFactory factory) { _client = factory.CreateClient(); } public Task InitializeAsync() => Task.CompletedTask; public Task DisposeAsync() => Task.CompletedTask; // ── Helpers ─────────────────────────────────────────────────────────────── private async Task GetAdminTokenAsync() { var response = await _client.PostAsJsonAsync("/api/v1/auth/login", new { username = AdminUsername, password = AdminPassword }); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadFromJsonAsync(); return json.GetProperty("accessToken").GetString()!; } private async Task 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(); 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; } private static async Task CountAuditEventsAsync(string action, string targetType, string targetId) { await using var conn = new SqlConnection(TestConnectionString); await conn.OpenAsync(); return await conn.QuerySingleAsync( "SELECT COUNT(*) FROM dbo.AuditEvent WHERE Action = @Action AND TargetType = @TargetType AND TargetId = @TargetId", new { Action = action, TargetType = targetType, TargetId = targetId }); } private static async Task DeleteRubroIfExistsAsync(int id) { await using var conn = new SqlConnection(TestConnectionString); await conn.OpenAsync(); // Need to disable system versioning to delete from history + main table await conn.ExecuteAsync("ALTER TABLE dbo.Rubro SET (SYSTEM_VERSIONING = OFF)"); await conn.ExecuteAsync("DELETE FROM dbo.Rubro_History WHERE Id = @Id", new { Id = id }); // Delete children first (recursive), then the target await conn.ExecuteAsync(""" WITH ToDelete AS ( SELECT Id FROM dbo.Rubro WHERE Id = @Id UNION ALL SELECT r.Id FROM dbo.Rubro r INNER JOIN ToDelete t ON r.ParentId = t.Id ) DELETE r FROM dbo.Rubro r INNER JOIN ToDelete td ON r.Id = td.Id """, new { Id = id }); await conn.ExecuteAsync( "ALTER TABLE dbo.Rubro SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Rubro_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 }); } // ── 401 / 403 guards on READ endpoints ──────────────────────────────────── [Fact] public async Task GetTree_WithoutAuth_Returns401() { using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/tree"); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode); } [Fact] public async Task GetById_WithoutAuth_Returns401() { using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/999999"); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode); } // ── 401 / 403 guards on WRITE endpoints ─────────────────────────────────── [Fact] public async Task CreateRubro_WithoutAuth_Returns401() { using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Test", parentId = (int?)null }); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode); } [Fact] public async Task CreateRubro_WithCajeroRole_Returns403() { const string username = "cat001_rubro_cajero_403"; try { var token = await GetCajeroTokenAsync(username); using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Test403", parentId = (int?)null }, token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.Forbidden, resp.StatusCode); } finally { await DeleteUsuarioIfExistsAsync(username); } } // ── GET /api/v1/rubros/tree ──────────────────────────────────────────────── [Fact] public async Task GetTree_WithAdmin_Returns200WithTree() { var token = await GetAdminTokenAsync(); // Create a root rubro for the tree using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "TreeRoot_GetTree", parentId = (int?)null, tarifarioBaseId = (int?)null }, token); var createResp = await _client.SendAsync(createReq); Assert.Equal(HttpStatusCode.Created, createResp.StatusCode); var created = await createResp.Content.ReadFromJsonAsync(); var rootId = created.GetProperty("id").GetInt32(); try { using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/tree", bearerToken: token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.OK, resp.StatusCode); var json = await resp.Content.ReadFromJsonAsync(); Assert.Equal(JsonValueKind.Array, json.ValueKind); // Should contain our created root var nombres = json.EnumerateArray().Select(n => n.GetProperty("nombre").GetString()).ToList(); Assert.Contains("TreeRoot_GetTree", nombres); } finally { await DeleteRubroIfExistsAsync(rootId); } } [Fact] public async Task GetTree_IncluirInactivosTrue_IncludesInactivos() { var token = await GetAdminTokenAsync(); // Create then deactivate a rubro using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "RubroInactivo_GetTree", parentId = (int?)null, }, token); var createResp = await _client.SendAsync(createReq); Assert.Equal(HttpStatusCode.Created, createResp.StatusCode); var created = await createResp.Content.ReadFromJsonAsync(); var rubroId = created.GetProperty("id").GetInt32(); try { // Deactivate it using var deleteReq = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/{rubroId}", bearerToken: token); await _client.SendAsync(deleteReq); // Without incluirInactivos → should not appear using var req1 = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/tree", bearerToken: token); var resp1 = await _client.SendAsync(req1); var json1 = await resp1.Content.ReadFromJsonAsync(); var nombres1 = json1.EnumerateArray().Select(n => n.GetProperty("nombre").GetString()).ToList(); Assert.DoesNotContain("RubroInactivo_GetTree", nombres1); // With incluirInactivos=true → should appear using var req2 = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/tree?incluirInactivos=true", bearerToken: token); var resp2 = await _client.SendAsync(req2); var json2 = await resp2.Content.ReadFromJsonAsync(); var nombres2 = json2.EnumerateArray().Select(n => n.GetProperty("nombre").GetString()).ToList(); Assert.Contains("RubroInactivo_GetTree", nombres2); } finally { await DeleteRubroIfExistsAsync(rubroId); } } // ── GET /api/v1/rubros/{id} ──────────────────────────────────────────────── [Fact] public async Task GetById_ExistingRubro_Returns200() { var token = await GetAdminTokenAsync(); using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "RubroGetById", parentId = (int?)null, }, token); var createResp = await _client.SendAsync(createReq); Assert.Equal(HttpStatusCode.Created, createResp.StatusCode); var created = await createResp.Content.ReadFromJsonAsync(); var rubroId = created.GetProperty("id").GetInt32(); try { using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/{rubroId}", bearerToken: token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.OK, resp.StatusCode); var json = await resp.Content.ReadFromJsonAsync(); Assert.Equal("RubroGetById", json.GetProperty("nombre").GetString()); Assert.Equal(rubroId, json.GetProperty("id").GetInt32()); } finally { await DeleteRubroIfExistsAsync(rubroId); } } [Fact] public async Task GetById_NotFound_Returns404() { var token = await GetAdminTokenAsync(); using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/999999", bearerToken: token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode); var json = await resp.Content.ReadFromJsonAsync(); Assert.Equal("rubro_not_found", json.GetProperty("error").GetString()); } // ── POST /api/v1/admin/rubros ────────────────────────────────────────────── [Fact] public async Task CreateRubro_Root_Returns201WithAuditEvent() { var token = await GetAdminTokenAsync(); using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "RubroCreate201", parentId = (int?)null, tarifarioBaseId = (int?)null }, token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.Created, resp.StatusCode); Assert.NotNull(resp.Headers.Location); var json = await resp.Content.ReadFromJsonAsync(); var id = json.GetProperty("id").GetInt32(); Assert.True(id > 0); Assert.Equal("RubroCreate201", json.GetProperty("nombre").GetString()); Assert.True(json.GetProperty("activo").GetBoolean()); try { var auditCount = await CountAuditEventsAsync("rubro.created", "Rubro", id.ToString()); Assert.Equal(1, auditCount); } finally { await DeleteRubroIfExistsAsync(id); } } [Fact] public async Task CreateRubro_Child_Returns201() { var token = await GetAdminTokenAsync(); using var parentReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ParentCreate", parentId = (int?)null }, token); var parentResp = await _client.SendAsync(parentReq); Assert.Equal(HttpStatusCode.Created, parentResp.StatusCode); var parentJson = await parentResp.Content.ReadFromJsonAsync(); var parentId = parentJson.GetProperty("id").GetInt32(); try { using var childReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ChildCreate", parentId }, token); var childResp = await _client.SendAsync(childReq); Assert.Equal(HttpStatusCode.Created, childResp.StatusCode); var childJson = await childResp.Content.ReadFromJsonAsync(); Assert.Equal(parentId, childJson.GetProperty("parentId").GetInt32()); } finally { await DeleteRubroIfExistsAsync(parentId); } } [Fact] public async Task CreateRubro_DuplicateNombreUnderParent_Returns409() { var token = await GetAdminTokenAsync(); using var parentReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ParentDup409", parentId = (int?)null }, token); var parentResp = await _client.SendAsync(parentReq); Assert.Equal(HttpStatusCode.Created, parentResp.StatusCode); var parentJson = await parentResp.Content.ReadFromJsonAsync(); var parentId = parentJson.GetProperty("id").GetInt32(); try { // First child using var child1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Duplicado", parentId }, token); var r1 = await _client.SendAsync(child1); Assert.Equal(HttpStatusCode.Created, r1.StatusCode); // Second child with same nombre using var child2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Duplicado", parentId }, token); var r2 = await _client.SendAsync(child2); Assert.Equal(HttpStatusCode.Conflict, r2.StatusCode); var json = await r2.Content.ReadFromJsonAsync(); Assert.Equal("rubro_nombre_duplicado", json.GetProperty("error").GetString()); } finally { await DeleteRubroIfExistsAsync(parentId); } } [Fact] public async Task CreateRubro_ParentNotFound_Returns404() { var token = await GetAdminTokenAsync(); using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "OrphanChild", parentId = 999999 }, token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode); } // ── PUT /api/v1/admin/rubros/{id} ───────────────────────────────────────── [Fact] public async Task UpdateRubro_Returns200WithAuditEvent() { var token = await GetAdminTokenAsync(); using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "OriginalNombre", parentId = (int?)null }, token); var createResp = await _client.SendAsync(createReq); Assert.Equal(HttpStatusCode.Created, createResp.StatusCode); var created = await createResp.Content.ReadFromJsonAsync(); var id = created.GetProperty("id").GetInt32(); try { using var updateReq = BuildRequest(HttpMethod.Put, $"{AdminEndpoint}/{id}", new { nombre = "NombreActualizado" }, token); var updateResp = await _client.SendAsync(updateReq); Assert.Equal(HttpStatusCode.OK, updateResp.StatusCode); var updated = await updateResp.Content.ReadFromJsonAsync(); Assert.Equal("NombreActualizado", updated.GetProperty("nombre").GetString()); var auditCount = await CountAuditEventsAsync("rubro.updated", "Rubro", id.ToString()); Assert.Equal(1, auditCount); // Verify Rubro_History row await using var conn = new SqlConnection(TestConnectionString); await conn.OpenAsync(); var histCount = await conn.QuerySingleAsync( "SELECT COUNT(*) FROM dbo.Rubro_History WHERE Id = @Id", new { Id = id }); Assert.True(histCount >= 1, "Should have ≥1 row in Rubro_History after update"); } finally { await DeleteRubroIfExistsAsync(id); } } [Fact] public async Task UpdateRubro_NotFound_Returns404() { var token = await GetAdminTokenAsync(); using var req = BuildRequest(HttpMethod.Put, $"{AdminEndpoint}/999999", new { nombre = "Test" }, token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode); } [Fact] public async Task UpdateRubro_DuplicateNombreSibling_Returns409() { var token = await GetAdminTokenAsync(); using var parent = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ParentUpdate409", parentId = (int?)null }, token); var parentResp = await _client.SendAsync(parent); var parentJson = await parentResp.Content.ReadFromJsonAsync(); var parentId = parentJson.GetProperty("id").GetInt32(); try { using var c1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Sibling1", parentId }, token); var r1 = await _client.SendAsync(c1); var j1 = await r1.Content.ReadFromJsonAsync(); var id1 = j1.GetProperty("id").GetInt32(); using var c2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Sibling2", parentId }, token); var r2 = await _client.SendAsync(c2); var j2 = await r2.Content.ReadFromJsonAsync(); // Try to rename Sibling1 → Sibling2 (conflict) using var updateReq = BuildRequest(HttpMethod.Put, $"{AdminEndpoint}/{id1}", new { nombre = "Sibling2" }, token); var updateResp = await _client.SendAsync(updateReq); Assert.Equal(HttpStatusCode.Conflict, updateResp.StatusCode); } finally { await DeleteRubroIfExistsAsync(parentId); } } // ── DELETE /api/v1/admin/rubros/{id} ────────────────────────────────────── [Fact] public async Task DeleteRubro_LeafRubro_Returns204WithAuditEvent() { var token = await GetAdminTokenAsync(); using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "RubroToDelete", parentId = (int?)null }, token); var createResp = await _client.SendAsync(createReq); Assert.Equal(HttpStatusCode.Created, createResp.StatusCode); var created = await createResp.Content.ReadFromJsonAsync(); var id = created.GetProperty("id").GetInt32(); try { using var deleteReq = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/{id}", bearerToken: token); var deleteResp = await _client.SendAsync(deleteReq); Assert.Equal(HttpStatusCode.NoContent, deleteResp.StatusCode); // Verify audit event (handler uses "rubro.deleted") var auditCount = await CountAuditEventsAsync("rubro.deleted", "Rubro", id.ToString()); Assert.Equal(1, auditCount); } finally { await DeleteRubroIfExistsAsync(id); } } [Fact] public async Task DeleteRubro_WithActiveChildren_Returns409() { var token = await GetAdminTokenAsync(); using var parentReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ParentWithChildren", parentId = (int?)null }, token); var parentResp = await _client.SendAsync(parentReq); var parentJson = await parentResp.Content.ReadFromJsonAsync(); var parentId = parentJson.GetProperty("id").GetInt32(); try { // Add a child using var childReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "ChildActive", parentId }, token); await _client.SendAsync(childReq); // Try to delete parent (has active children → 409) using var deleteReq = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/{parentId}", bearerToken: token); var deleteResp = await _client.SendAsync(deleteReq); Assert.Equal(HttpStatusCode.Conflict, deleteResp.StatusCode); var json = await deleteResp.Content.ReadFromJsonAsync(); Assert.Equal("rubro_tiene_hijos_activos", json.GetProperty("error").GetString()); } finally { await DeleteRubroIfExistsAsync(parentId); } } [Fact] public async Task DeleteRubro_NotFound_Returns404() { var token = await GetAdminTokenAsync(); using var req = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/999999", bearerToken: token); var resp = await _client.SendAsync(req); Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode); } // ── PATCH /api/v1/admin/rubros/{id}/mover ───────────────────────────────── [Fact] public async Task MoveRubro_Returns200WithAuditEvent() { var token = await GetAdminTokenAsync(); using var p1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "MoveParent1", parentId = (int?)null }, token); var p1Resp = await _client.SendAsync(p1); var p1Json = await p1Resp.Content.ReadFromJsonAsync(); var parent1Id = p1Json.GetProperty("id").GetInt32(); try { using var p2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "MoveParent2", parentId = (int?)null }, token); var p2Resp = await _client.SendAsync(p2); var p2Json = await p2Resp.Content.ReadFromJsonAsync(); var parent2Id = p2Json.GetProperty("id").GetInt32(); using var childReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "MoveChild", parentId = parent1Id }, token); var childResp = await _client.SendAsync(childReq); var childJson = await childResp.Content.ReadFromJsonAsync(); var childId = childJson.GetProperty("id").GetInt32(); // Move child from parent1 to parent2 using var moveReq = BuildRequest(HttpMethod.Patch, $"{AdminEndpoint}/{childId}/mover", new { nuevoParentId = parent2Id, nuevoOrden = 0 }, token); var moveResp = await _client.SendAsync(moveReq); Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode); var moved = await moveResp.Content.ReadFromJsonAsync(); Assert.Equal(parent2Id, moved.GetProperty("parentId").GetInt32()); var auditCount = await CountAuditEventsAsync("rubro.moved", "Rubro", childId.ToString()); Assert.Equal(1, auditCount); } finally { await DeleteRubroIfExistsAsync(parent1Id); } } [Fact] public async Task MoveRubro_CycleDetected_Returns400() { var token = await GetAdminTokenAsync(); using var rootReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "CycleRoot", parentId = (int?)null }, token); var rootResp = await _client.SendAsync(rootReq); var rootJson = await rootResp.Content.ReadFromJsonAsync(); var rootId = rootJson.GetProperty("id").GetInt32(); try { using var childReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "CycleChild", parentId = rootId }, token); var childResp = await _client.SendAsync(childReq); var childJson = await childResp.Content.ReadFromJsonAsync(); var childId = childJson.GetProperty("id").GetInt32(); // Try to move root under its own child → cycle using var moveReq = BuildRequest(HttpMethod.Patch, $"{AdminEndpoint}/{rootId}/mover", new { nuevoParentId = childId, nuevoOrden = 0 }, token); var moveResp = await _client.SendAsync(moveReq); Assert.Equal(HttpStatusCode.BadRequest, moveResp.StatusCode); var json = await moveResp.Content.ReadFromJsonAsync(); Assert.Equal("rubro_cycle_detected", json.GetProperty("error").GetString()); } finally { await DeleteRubroIfExistsAsync(rootId); } } [Fact] public async Task MoveRubro_DuplicateNombreUnderNewParent_Returns409() { var token = await GetAdminTokenAsync(); using var p1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "MoveDupParent1", parentId = (int?)null }, token); var p1Resp = await _client.SendAsync(p1); var p1Json = await p1Resp.Content.ReadFromJsonAsync(); var parent1Id = p1Json.GetProperty("id").GetInt32(); try { using var p2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "MoveDupParent2", parentId = (int?)null }, token); var p2Resp = await _client.SendAsync(p2); var p2Json = await p2Resp.Content.ReadFromJsonAsync(); var parent2Id = p2Json.GetProperty("id").GetInt32(); // Add "SameName" under parent1 using var c1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "SameName", parentId = parent1Id }, token); var c1Resp = await _client.SendAsync(c1); var c1Json = await c1Resp.Content.ReadFromJsonAsync(); var c1Id = c1Json.GetProperty("id").GetInt32(); // Add "SameName" under parent2 already using var c2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "SameName", parentId = parent2Id }, token); await _client.SendAsync(c2); // Try to move c1 (SameName) under parent2 → duplicate using var moveReq = BuildRequest(HttpMethod.Patch, $"{AdminEndpoint}/{c1Id}/mover", new { nuevoParentId = parent2Id, nuevoOrden = 0 }, token); var moveResp = await _client.SendAsync(moveReq); Assert.Equal(HttpStatusCode.Conflict, moveResp.StatusCode); } finally { await DeleteRubroIfExistsAsync(parent1Id); } } }