using System.Net; using System.Net.Http.Json; using YesChef.Api.Entities; using YesChef.Api.Features.Units; using YesChef.Api.IntegrationTests.Infrastructure; namespace YesChef.Api.IntegrationTests.Features; public class UnitEndpointsTests : AuthenticatedIntegrationTest { private Task GetFamilyIdAsync() => UseDbAsync(db => db.FamilyMemberships.Where(m => m.UserId == User.Id).Select(m => m.FamilyId).SingleAsync()); [Test] public async Task List_returns_global_units_and_family_customs_merged() { var familyId = await GetFamilyIdAsync(); await UseDbAsync(async db => { db.UnitsOfMeasure.Add(new UnitOfMeasure { Code = "each", SingularName = "each", PluralName = "each", Abbreviation = "ea", Category = UnitCategory.Count, IsBase = true, SortOrder = 0, }); db.FamilyUnitsOfMeasure.Add(new FamilyUnitOfMeasure { FamilyId = familyId, SingularName = "scoop", PluralName = "scoops", Abbreviation = "scp", Category = UnitCategory.Volume, SortOrder = 50, }); await db.SaveChangesAsync(); }); var results = await Client.GetFromJsonAsync>("/api/units"); await Assert.That(results!.Select(r => r.Abbreviation)).IsEquivalentTo(new[] { "ea", "scp" }); var each = results!.Single(r => r.Abbreviation == "ea"); await Assert.That(each.Kind).IsEqualTo(UnitEndpoints.UnitKind.Global); await Assert.That(each.Code).IsEqualTo("each"); var scoop = results!.Single(r => r.Abbreviation == "scp"); await Assert.That(scoop.Kind).IsEqualTo(UnitEndpoints.UnitKind.Family); await Assert.That(scoop.Code).IsNull(); } [Test] public async Task List_does_not_leak_other_family_units() { await UseDbAsync(async db => { var other = new Family { Name = "Other", InviteCode = "other-code" }; db.Families.Add(other); await db.SaveChangesAsync(); db.FamilyUnitsOfMeasure.Add(new FamilyUnitOfMeasure { FamilyId = other.Id, SingularName = "pinch", PluralName = "pinches", Abbreviation = "pn", Category = UnitCategory.Volume, }); await db.SaveChangesAsync(); }); var results = await Client.GetFromJsonAsync>("/api/units"); await Assert.That(results!.Any(r => r.Abbreviation == "pn")).IsFalse(); } [Test] public async Task Create_persists_family_unit_and_returns_201() { var response = await Client.PostAsJsonAsync("/api/units", new UnitEndpoints.CreateUnitRequest("sleeve", "sleeves", "slv", UnitCategory.Packaging)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Created); var dto = await response.Content.ReadFromJsonAsync(); await Assert.That(dto!.Kind).IsEqualTo(UnitEndpoints.UnitKind.Family); await Assert.That(dto.Abbreviation).IsEqualTo("slv"); var persisted = await UseDbAsync(db => db.FamilyUnitsOfMeasure.SingleAsync()); await Assert.That(persisted.SingularName).IsEqualTo("sleeve"); await Assert.That(persisted.Category).IsEqualTo(UnitCategory.Packaging); } [Test] public async Task Create_returns_409_when_abbreviation_collides_with_global() { await UseDbAsync(async db => { db.UnitsOfMeasure.Add(new UnitOfMeasure { Code = "lb", SingularName = "pound", PluralName = "pounds", Abbreviation = "lb", Category = UnitCategory.Weight, }); await db.SaveChangesAsync(); }); var response = await Client.PostAsJsonAsync("/api/units", new UnitEndpoints.CreateUnitRequest("librapound", "librapounds", "lb", UnitCategory.Weight)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Conflict); } [Test] public async Task Create_returns_409_when_abbreviation_collides_within_family() { await Client.PostAsJsonAsync("/api/units", new UnitEndpoints.CreateUnitRequest("sleeve", "sleeves", "slv", UnitCategory.Packaging)); var response = await Client.PostAsJsonAsync("/api/units", new UnitEndpoints.CreateUnitRequest("slab", "slabs", "slv", UnitCategory.Packaging)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Conflict); } [Test] public async Task Create_returns_400_when_required_field_missing() { var response = await Client.PostAsJsonAsync("/api/units", new UnitEndpoints.CreateUnitRequest(" ", "sleeves", "slv", UnitCategory.Packaging)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.BadRequest); } [Test] public async Task Update_family_unit_changes_fields() { var familyId = await GetFamilyIdAsync(); var unit = await UseDbAsync(async db => { var u = new FamilyUnitOfMeasure { FamilyId = familyId, SingularName = "old", PluralName = "olds", Abbreviation = "od", Category = UnitCategory.Count, SortOrder = 1, }; db.FamilyUnitsOfMeasure.Add(u); await db.SaveChangesAsync(); return u; }); var response = await Client.PutAsJsonAsync($"/api/units/family/{unit.Id}", new UnitEndpoints.UpdateUnitRequest("new", "news", "nw", UnitCategory.Weight, 2)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); var refreshed = await UseDbAsync(db => db.FamilyUnitsOfMeasure.SingleAsync(u => u.Id == unit.Id)); await Assert.That(refreshed.SingularName).IsEqualTo("new"); await Assert.That(refreshed.Abbreviation).IsEqualTo("nw"); await Assert.That(refreshed.Category).IsEqualTo(UnitCategory.Weight); await Assert.That(refreshed.SortOrder).IsEqualTo(2); } [Test] public async Task Update_family_unit_404_for_other_family_id() { var foreignId = await UseDbAsync(async db => { var other = new Family { Name = "Other", InviteCode = "other-code" }; db.Families.Add(other); await db.SaveChangesAsync(); var u = new FamilyUnitOfMeasure { FamilyId = other.Id, SingularName = "foreign", PluralName = "foreigns", Abbreviation = "fg", Category = UnitCategory.Count, }; db.FamilyUnitsOfMeasure.Add(u); await db.SaveChangesAsync(); return u.Id; }); var response = await Client.PutAsJsonAsync($"/api/units/family/{foreignId}", new UnitEndpoints.UpdateUnitRequest("x", "xs", "xx", UnitCategory.Count, 0)); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound); } [Test] public async Task Delete_family_unit_removes_row() { var familyId = await GetFamilyIdAsync(); var unit = await UseDbAsync(async db => { var u = new FamilyUnitOfMeasure { FamilyId = familyId, SingularName = "doomed", PluralName = "doomeds", Abbreviation = "dm", Category = UnitCategory.Count, }; db.FamilyUnitsOfMeasure.Add(u); await db.SaveChangesAsync(); return u; }); var response = await Client.DeleteAsync($"/api/units/family/{unit.Id}"); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.NoContent); var remaining = await UseDbAsync(db => db.FamilyUnitsOfMeasure.CountAsync()); await Assert.That(remaining).IsEqualTo(0); } [Test] public async Task Endpoints_require_authentication() { var response = await AnonymousClient.GetAsync("/api/units"); await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Unauthorized); } }