feat(application): Product commands, DTOs, IProductRepository, validators (PRD-002)

This commit is contained in:
2026-04-19 13:02:42 -03:00
parent 16197cf242
commit 8b555e1f8b
15 changed files with 311 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
using FluentValidation.TestHelper;
using SIGCM2.Application.Products.Create;
using SIGCM2.Application.Products.Update;
namespace SIGCM2.Application.Tests.Products.Validators;
/// <summary>
/// PRD-002 — Validator tests for CreateProductCommand and UpdateProductCommand.
/// </summary>
public class ProductValidatorsTests
{
private readonly CreateProductCommandValidator _createValidator = new();
private readonly UpdateProductCommandValidator _updateValidator = new();
// ── Create: Nombre ────────────────────────────────────────────────────────
[Fact]
public void Create_NombreEmpty_FailsValidation()
{
var cmd = ValidCreate() with { Nombre = "" };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Nombre);
}
[Fact]
public void Create_NombreWhitespace_FailsValidation()
{
var cmd = ValidCreate() with { Nombre = " " };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Nombre);
}
[Fact]
public void Create_NombreOver300Chars_FailsValidation()
{
var cmd = ValidCreate() with { Nombre = new string('X', 301) };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Nombre);
}
// ── Create: MedioId / ProductTypeId ───────────────────────────────────────
[Fact]
public void Create_MedioIdZero_FailsValidation()
{
var cmd = ValidCreate() with { MedioId = 0 };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.MedioId);
}
[Fact]
public void Create_ProductTypeIdZero_FailsValidation()
{
var cmd = ValidCreate() with { ProductTypeId = 0 };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.ProductTypeId);
}
// ── Create: BasePrice ─────────────────────────────────────────────────────
[Fact]
public void Create_NegativeBasePrice_FailsValidation()
{
var cmd = ValidCreate() with { BasePrice = -0.01m };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.BasePrice);
}
[Fact]
public void Create_ZeroBasePrice_Passes()
{
var cmd = ValidCreate() with { BasePrice = 0m };
_createValidator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.BasePrice);
}
// ── Create: PriceDurationDays ──────────────────────────────────────────────
[Fact]
public void Create_PriceDurationDaysZero_FailsValidation()
{
var cmd = ValidCreate() with { PriceDurationDays = 0 };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PriceDurationDays);
}
[Fact]
public void Create_PriceDurationDaysNegative_FailsValidation()
{
var cmd = ValidCreate() with { PriceDurationDays = -1 };
_createValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.PriceDurationDays);
}
[Fact]
public void Create_PriceDurationDaysNull_Passes()
{
var cmd = ValidCreate() with { PriceDurationDays = null };
_createValidator.TestValidate(cmd).ShouldNotHaveValidationErrorFor(x => x.PriceDurationDays);
}
// ── Create: valid ─────────────────────────────────────────────────────────
[Fact]
public void Create_ValidCommand_Passes()
{
_createValidator.TestValidate(ValidCreate()).ShouldNotHaveAnyValidationErrors();
}
// ── Update: Id must be > 0 ────────────────────────────────────────────────
[Fact]
public void Update_IdZero_FailsValidation()
{
var cmd = ValidUpdate() with { Id = 0 };
_updateValidator.TestValidate(cmd).ShouldHaveValidationErrorFor(x => x.Id);
}
[Fact]
public void Update_ValidCommand_Passes()
{
_updateValidator.TestValidate(ValidUpdate()).ShouldNotHaveAnyValidationErrors();
}
// ── Helpers ───────────────────────────────────────────────────────────────
private static CreateProductCommand ValidCreate() => new(
Nombre: "Clasificado Estándar",
MedioId: 1,
ProductTypeId: 2,
RubroId: null,
BasePrice: 100.50m,
PriceDurationDays: null);
private static UpdateProductCommand ValidUpdate() => new(
Id: 1,
Nombre: "Clasificado Estándar",
RubroId: null,
BasePrice: 100.50m,
PriceDurationDays: null);
}