feat: PRD-001 ProductType (flags + multimedia) #38
184
src/api/SIGCM2.Api/Controllers/ProductTypesController.cs
Normal file
184
src/api/SIGCM2.Api/Controllers/ProductTypesController.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SIGCM2.Api.Authorization;
|
||||
using SIGCM2.Application.Abstractions;
|
||||
using SIGCM2.Application.Common;
|
||||
using SIGCM2.Application.ProductTypes.Create;
|
||||
using SIGCM2.Application.ProductTypes.Deactivate;
|
||||
using SIGCM2.Application.ProductTypes.GetById;
|
||||
using SIGCM2.Application.ProductTypes.List;
|
||||
using SIGCM2.Application.ProductTypes.Update;
|
||||
|
||||
namespace SIGCM2.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// PRD-001: ProductType catalog management.
|
||||
/// Read endpoints at /api/v1/product-types — require authentication (any role).
|
||||
/// Write endpoints at /api/v1/admin/product-types — require 'catalogo:tipos:gestionar'.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
public sealed class ProductTypesController : ControllerBase
|
||||
{
|
||||
private readonly IDispatcher _dispatcher;
|
||||
private readonly IValidator<CreateProductTypeCommand> _createValidator;
|
||||
private readonly IValidator<UpdateProductTypeCommand> _updateValidator;
|
||||
|
||||
public ProductTypesController(
|
||||
IDispatcher dispatcher,
|
||||
IValidator<CreateProductTypeCommand> createValidator,
|
||||
IValidator<UpdateProductTypeCommand> updateValidator)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
// ── READ endpoints ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Returns a paginated list of ProductTypes. Requires authentication.</summary>
|
||||
[HttpGet("api/v1/product-types")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(typeof(PagedResult<ProductTypeListItemDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> ListProductTypes(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] bool? activo = true,
|
||||
[FromQuery] string? search = null)
|
||||
{
|
||||
var query = new ListProductTypesQuery(page, pageSize, activo, search);
|
||||
var result = await _dispatcher.Send<ListProductTypesQuery, PagedResult<ProductTypeListItemDto>>(query);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>Returns a single ProductType by id. Requires authentication.</summary>
|
||||
[HttpGet("api/v1/product-types/{id:int}")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(typeof(ProductTypeDetailDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetProductTypeById([FromRoute] int id)
|
||||
{
|
||||
var query = new GetProductTypeByIdQuery(id);
|
||||
var result = await _dispatcher.Send<GetProductTypeByIdQuery, ProductTypeDetailDto>(query);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// ── WRITE endpoints ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Creates a new ProductType. Requires catalogo:tipos:gestionar.</summary>
|
||||
[HttpPost("api/v1/admin/product-types")]
|
||||
[RequirePermission("catalogo:tipos:gestionar")]
|
||||
[ProducesResponseType(typeof(ProductTypeCreatedDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<IActionResult> CreateProductType([FromBody] CreateProductTypeRequest request)
|
||||
{
|
||||
var command = new CreateProductTypeCommand(
|
||||
Nombre: request.Nombre ?? string.Empty,
|
||||
HasDuration: request.HasDuration,
|
||||
RequiresText: request.RequiresText,
|
||||
RequiresCategory: request.RequiresCategory,
|
||||
IsBundle: request.IsBundle,
|
||||
AllowImages: request.AllowImages,
|
||||
MaxImages: request.MaxImages,
|
||||
MaxImageSizeMB: request.MaxImageSizeMB,
|
||||
MaxImageWidth: request.MaxImageWidth,
|
||||
MaxImageHeight: request.MaxImageHeight);
|
||||
|
||||
var validation = await _createValidator.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<CreateProductTypeCommand, ProductTypeCreatedDto>(command);
|
||||
return CreatedAtAction(nameof(GetProductTypeById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
/// <summary>Updates a ProductType. Requires catalogo:tipos:gestionar.</summary>
|
||||
[HttpPut("api/v1/admin/product-types/{id:int}")]
|
||||
[RequirePermission("catalogo:tipos:gestionar")]
|
||||
[ProducesResponseType(typeof(ProductTypeUpdatedDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<IActionResult> UpdateProductType([FromRoute] int id, [FromBody] UpdateProductTypeRequest request)
|
||||
{
|
||||
var command = new UpdateProductTypeCommand(
|
||||
Id: id,
|
||||
Nombre: request.Nombre ?? string.Empty,
|
||||
HasDuration: request.HasDuration,
|
||||
RequiresText: request.RequiresText,
|
||||
RequiresCategory: request.RequiresCategory,
|
||||
IsBundle: request.IsBundle,
|
||||
AllowImages: request.AllowImages,
|
||||
MaxImages: request.MaxImages,
|
||||
MaxImageSizeMB: request.MaxImageSizeMB,
|
||||
MaxImageWidth: request.MaxImageWidth,
|
||||
MaxImageHeight: request.MaxImageHeight);
|
||||
|
||||
var validation = await _updateValidator.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<UpdateProductTypeCommand, ProductTypeUpdatedDto>(command);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>Soft-deletes (deactivates) a ProductType. Requires catalogo:tipos:gestionar.</summary>
|
||||
[HttpDelete("api/v1/admin/product-types/{id:int}")]
|
||||
[RequirePermission("catalogo:tipos:gestionar")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<IActionResult> DeactivateProductType([FromRoute] int id)
|
||||
{
|
||||
var command = new DeactivateProductTypeCommand(id);
|
||||
await _dispatcher.Send<DeactivateProductTypeCommand, ProductTypeStatusDto>(command);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Request body records ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>PRD-001: Create ProductType request body.</summary>
|
||||
public sealed record CreateProductTypeRequest(
|
||||
string? Nombre,
|
||||
bool HasDuration = false,
|
||||
bool RequiresText = false,
|
||||
bool RequiresCategory = false,
|
||||
bool IsBundle = false,
|
||||
bool AllowImages = false,
|
||||
int? MaxImages = null,
|
||||
decimal? MaxImageSizeMB = null,
|
||||
int? MaxImageWidth = null,
|
||||
int? MaxImageHeight = null);
|
||||
|
||||
/// <summary>PRD-001: Update ProductType request body.</summary>
|
||||
public sealed record UpdateProductTypeRequest(
|
||||
string? Nombre,
|
||||
bool HasDuration = false,
|
||||
bool RequiresText = false,
|
||||
bool RequiresCategory = false,
|
||||
bool IsBundle = false,
|
||||
bool AllowImages = false,
|
||||
int? MaxImages = null,
|
||||
decimal? MaxImageSizeMB = null,
|
||||
int? MaxImageWidth = null,
|
||||
int? MaxImageHeight = null);
|
||||
@@ -414,6 +414,55 @@ public sealed class ExceptionFilter : IExceptionFilter
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
// PRD-001: ProductType exceptions
|
||||
case ProductTypeNotFoundException productTypeNotFoundEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
error = "product_type_not_found",
|
||||
message = productTypeNotFoundEx.Message
|
||||
})
|
||||
{
|
||||
StatusCode = StatusCodes.Status404NotFound
|
||||
};
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
case ProductTypeNombreDuplicadoException productTypeDupEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
error = "product_type_nombre_duplicado",
|
||||
message = productTypeDupEx.Message
|
||||
})
|
||||
{
|
||||
StatusCode = StatusCodes.Status409Conflict
|
||||
};
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
case ProductTypeEnUsoException productTypeEnUsoEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
error = "product_type_en_uso",
|
||||
message = productTypeEnUsoEx.Message
|
||||
})
|
||||
{
|
||||
StatusCode = StatusCodes.Status409Conflict
|
||||
};
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
case ProductTypeFlagsIncoherentesException productTypeFlagsEx:
|
||||
context.Result = new ObjectResult(new
|
||||
{
|
||||
error = "product_type_flags_incoherentes",
|
||||
message = productTypeFlagsEx.Message
|
||||
})
|
||||
{
|
||||
StatusCode = StatusCodes.Status422UnprocessableEntity
|
||||
};
|
||||
context.ExceptionHandled = true;
|
||||
break;
|
||||
|
||||
// ADM-008: PuntoDeVenta exceptions
|
||||
case PuntoDeVentaNotFoundException puntoDeVentaNotFoundEx:
|
||||
context.Result = new ObjectResult(new
|
||||
|
||||
@@ -50,8 +50,9 @@ public class AuthControllerTests
|
||||
// V011 (ADM-001) adds 'administracion:secciones:gestionar' → 22
|
||||
// V013 (ADM-008) adds 'administracion:puntos_de_venta:gestionar' → 23
|
||||
// V014 (ADM-009) adds 'administracion:fiscal:gestionar' → 24
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25 total
|
||||
Assert.Equal(25, permisos.GetArrayLength());
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25
|
||||
// V017 (PRD-001) adds 'catalogo:tipos:gestionar' → 26 total
|
||||
Assert.Equal(26, permisos.GetArrayLength());
|
||||
}
|
||||
|
||||
// Scenario: invalid credentials return 401 with opaque error
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
|
||||
// ── GET /api/v1/permisos — catalog ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetPermisos_WithAdmin_Returns200With25Items()
|
||||
public async Task GetPermisos_WithAdmin_Returns200With26Items()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/permisos", bearerToken: token);
|
||||
@@ -140,8 +140,9 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
|
||||
// V011 (ADM-001) adds 'administracion:secciones:gestionar' → 22
|
||||
// V013 (ADM-008) adds 'administracion:puntos_de_venta:gestionar' → 23
|
||||
// V014 (ADM-009) adds 'administracion:fiscal:gestionar' → 24
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25 total
|
||||
Assert.Equal(25, list.GetArrayLength());
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25
|
||||
// V017 (PRD-001) adds 'catalogo:tipos:gestionar' → 26 total
|
||||
Assert.Equal(26, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -184,7 +185,7 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
|
||||
// ── GET /api/v1/roles/{codigo}/permisos ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetRolPermisos_AdminRol_Returns200With25Items()
|
||||
public async Task GetRolPermisos_AdminRol_Returns200With26Items()
|
||||
{
|
||||
var token = await GetBearerTokenAsync(AdminUsername, AdminPassword);
|
||||
using var req = BuildRequest(HttpMethod.Get, "/api/v1/roles/admin/permisos", bearerToken: token);
|
||||
@@ -195,8 +196,9 @@ public sealed class PermisosEndpointTests : IAsyncLifetime
|
||||
// V011 (ADM-001) adds 'administracion:secciones:gestionar' → 22
|
||||
// V013 (ADM-008) adds 'administracion:puntos_de_venta:gestionar' → 23
|
||||
// V014 (ADM-009) adds 'administracion:fiscal:gestionar' → 24
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25 total
|
||||
Assert.Equal(25, list.GetArrayLength());
|
||||
// V016 (CAT-001) adds 'catalogo:rubros:gestionar' → 25
|
||||
// V017 (PRD-001) adds 'catalogo:tipos:gestionar' → 26 total
|
||||
Assert.Equal(26, list.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using SIGCM2.TestSupport;
|
||||
|
||||
namespace SIGCM2.Api.Tests.ProductTypes;
|
||||
|
||||
/// <summary>
|
||||
/// PRD-001 — Integration tests for /api/v1/product-types and /api/v1/admin/product-types.
|
||||
/// Read endpoints require authentication (any role).
|
||||
/// Write endpoints require permission 'catalogo:tipos:gestionar'.
|
||||
/// Verifies HTTP status codes, response shapes, and ExceptionFilter mappings.
|
||||
/// </summary>
|
||||
[Collection("ApiIntegration")]
|
||||
public sealed class ProductTypesControllerTests : IAsyncLifetime
|
||||
{
|
||||
private const string ReadEndpoint = "/api/v1/product-types";
|
||||
private const string AdminEndpoint = "/api/v1/admin/product-types";
|
||||
private const string AdminUsername = "admin";
|
||||
private const string AdminPassword = "@Diego550@";
|
||||
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public ProductTypesControllerTests(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 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;
|
||||
}
|
||||
|
||||
// ── 401 guards on READ endpoints ───────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task List_WithoutAuth_Returns401()
|
||||
{
|
||||
using var req = BuildRequest(HttpMethod.Get, ReadEndpoint);
|
||||
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 Create_WithoutAuth_Returns401()
|
||||
{
|
||||
using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new { nombre = "Test" });
|
||||
var resp = await _client.SendAsync(req);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
|
||||
}
|
||||
|
||||
// ── POST /api/v1/admin/product-types ──────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Create_WithAdmin_Returns201WithId()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
var uniqueName = $"Tipo_Create_{Guid.NewGuid():N}";
|
||||
|
||||
using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = uniqueName,
|
||||
hasDuration = true,
|
||||
requiresText = false,
|
||||
requiresCategory = false,
|
||||
isBundle = false,
|
||||
allowImages = true,
|
||||
maxImages = 3,
|
||||
maxImageSizeMB = 1.5,
|
||||
maxImageWidth = (int?)null,
|
||||
maxImageHeight = (int?)null
|
||||
}, token);
|
||||
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Created, resp.StatusCode);
|
||||
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.True(json.GetProperty("id").GetInt32() > 0);
|
||||
Assert.Equal(uniqueName, json.GetProperty("nombre").GetString());
|
||||
Assert.True(json.GetProperty("isActive").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_DuplicateNombre_Returns409()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
var uniqueName = $"DupNombre_{Guid.NewGuid():N}";
|
||||
|
||||
using var req1 = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = uniqueName,
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
var resp1 = await _client.SendAsync(req1);
|
||||
Assert.Equal(HttpStatusCode.Created, resp1.StatusCode);
|
||||
|
||||
using var req2 = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = uniqueName,
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
var resp2 = await _client.SendAsync(req2);
|
||||
|
||||
Assert.Equal(HttpStatusCode.Conflict, resp2.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_InvalidBody_Returns400()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
using var req = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = string.Empty, // invalid
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
|
||||
}
|
||||
|
||||
// ── GET /api/v1/product-types ─────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task List_WithAdmin_Returns200WithPaginatedResult()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
|
||||
using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}?page=1&pageSize=10", 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 _));
|
||||
Assert.True(json.TryGetProperty("total", out _));
|
||||
}
|
||||
|
||||
// ── GET /api/v1/product-types/{id} ────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetById_NotFound_Returns404()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/999999999", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetById_ExistingId_Returns200()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
var uniqueName = $"Tipo_GetById_{Guid.NewGuid():N}";
|
||||
|
||||
using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = uniqueName,
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
var createResp = await _client.SendAsync(createReq);
|
||||
Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
|
||||
var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var id = created.GetProperty("id").GetInt32();
|
||||
|
||||
using var req = BuildRequest(HttpMethod.Get, $"{ReadEndpoint}/{id}", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
|
||||
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(id, json.GetProperty("id").GetInt32());
|
||||
Assert.Equal(uniqueName, json.GetProperty("nombre").GetString());
|
||||
}
|
||||
|
||||
// ── PUT /api/v1/admin/product-types/{id} ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Update_NotFound_Returns404()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
using var req = BuildRequest(HttpMethod.Put, $"{AdminEndpoint}/999999999", new
|
||||
{
|
||||
nombre = "No Existe",
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
||||
}
|
||||
|
||||
// ── DELETE /api/v1/admin/product-types/{id} ───────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task Deactivate_NotFound_Returns404()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
using var req = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/999999999", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Deactivate_ExistingActive_Returns204()
|
||||
{
|
||||
var token = await GetAdminTokenAsync();
|
||||
var uniqueName = $"Tipo_Deactivate_{Guid.NewGuid():N}";
|
||||
|
||||
using var createReq = BuildRequest(HttpMethod.Post, AdminEndpoint, new
|
||||
{
|
||||
nombre = uniqueName,
|
||||
hasDuration = false, requiresText = false, requiresCategory = false, isBundle = false,
|
||||
allowImages = false
|
||||
}, token);
|
||||
var createResp = await _client.SendAsync(createReq);
|
||||
Assert.Equal(HttpStatusCode.Created, createResp.StatusCode);
|
||||
var created = await createResp.Content.ReadFromJsonAsync<JsonElement>();
|
||||
var id = created.GetProperty("id").GetInt32();
|
||||
|
||||
using var req = BuildRequest(HttpMethod.Delete, $"{AdminEndpoint}/{id}", bearerToken: token);
|
||||
var resp = await _client.SendAsync(req);
|
||||
|
||||
Assert.Equal(HttpStatusCode.NoContent, resp.StatusCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user