Pre-fill list section on product pick; tighten backend warnings
- Adds GET /api/products/{kind}/{id}/section?storeId=... exposing the
per-store memory the list page mirrors when a product is picked, so the
section dropdown reflects what the backend would auto-assign on POST.
- Treats backend warnings as errors via Directory.Build.props; fixes the
surfaced warnings (obsolete PostgreSqlBuilder ctor, nullable string[]
in IsEquivalentTo, redundant nullable flow).
- Annotates wire-exposed enums (ProductKind, UnitKind, UnitCategory,
UnitCategoryFlags) with JsonStringEnumConverter so they round-trip as
strings regardless of caller options. Unblocks the integration tests
that deserialize DTOs via GetFromJsonAsync without the global converter.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using YesChef.Api.Entities;
|
||||
using YesChef.Api.Features.Products;
|
||||
using YesChef.Api.IntegrationTests.Infrastructure;
|
||||
@@ -343,9 +344,9 @@ public class ProductEndpointsTests : AuthenticatedIntegrationTest
|
||||
await db.SaveChangesAsync();
|
||||
});
|
||||
|
||||
var results = await Client.GetFromJsonAsync<List<ProductEndpoints.ProductDto>>("/api/products?q=");
|
||||
var results = (await Client.GetFromJsonAsync<List<ProductEndpoints.ProductDto>>("/api/products?q="))!;
|
||||
|
||||
var milk = results!.Single(r => r.Name == "Milk");
|
||||
var milk = results.Single(r => r.Name == "Milk");
|
||||
await Assert.That(milk.AllowedUnitCategories).IsEqualTo(UnitCategoryFlags.Volume);
|
||||
|
||||
var eggs = results.Single(r => r.Name == "Eggs");
|
||||
@@ -362,4 +363,132 @@ public class ProductEndpointsTests : AuthenticatedIntegrationTest
|
||||
var response = await AnonymousClient.GetAsync("/api/products");
|
||||
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_global_product_section_returns_remembered_section()
|
||||
{
|
||||
var familyId = await GetFamilyIdAsync();
|
||||
var store = await Data.CreateStoreAsync();
|
||||
var (product, section) = await UseDbAsync(async db =>
|
||||
{
|
||||
var p = new Product { Name = "Bananas-Lookup" };
|
||||
var s = new StoreSection { FamilyId = familyId, StoreId = store.Id, Name = "Produce", SortOrder = 1 };
|
||||
db.Products.Add(p);
|
||||
db.StoreSections.Add(s);
|
||||
await db.SaveChangesAsync();
|
||||
db.ProductStoreSections.Add(new ProductStoreSection
|
||||
{
|
||||
FamilyId = familyId,
|
||||
StoreId = store.Id,
|
||||
ProductId = p.Id,
|
||||
StoreSectionId = s.Id,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return (p, s);
|
||||
});
|
||||
|
||||
var body = await Client.GetFromJsonAsync<JsonElement>(
|
||||
$"/api/products/global/{product.Id}/section?storeId={store.Id}");
|
||||
|
||||
await Assert.That(body.GetProperty("sectionId").GetInt32()).IsEqualTo(section.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_global_product_section_returns_null_when_no_memory()
|
||||
{
|
||||
var store = await Data.CreateStoreAsync();
|
||||
var product = await UseDbAsync(async db =>
|
||||
{
|
||||
var p = new Product { Name = "Unknown-Lookup" };
|
||||
db.Products.Add(p);
|
||||
await db.SaveChangesAsync();
|
||||
return p;
|
||||
});
|
||||
|
||||
var body = await Client.GetFromJsonAsync<JsonElement>(
|
||||
$"/api/products/global/{product.Id}/section?storeId={store.Id}");
|
||||
|
||||
await Assert.That(body.GetProperty("sectionId").ValueKind).IsEqualTo(JsonValueKind.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_global_product_section_is_scoped_to_store()
|
||||
{
|
||||
var familyId = await GetFamilyIdAsync();
|
||||
var storeA = await Data.CreateStoreAsync(b => b.Named("A"));
|
||||
var storeB = await Data.CreateStoreAsync(b => b.Named("B"));
|
||||
var product = await UseDbAsync(async db =>
|
||||
{
|
||||
var p = new Product { Name = "Apples-Lookup" };
|
||||
var sectionA = new StoreSection { FamilyId = familyId, StoreId = storeA.Id, Name = "Produce A", SortOrder = 1 };
|
||||
db.Products.Add(p);
|
||||
db.StoreSections.Add(sectionA);
|
||||
await db.SaveChangesAsync();
|
||||
db.ProductStoreSections.Add(new ProductStoreSection
|
||||
{
|
||||
FamilyId = familyId,
|
||||
StoreId = storeA.Id,
|
||||
ProductId = p.Id,
|
||||
StoreSectionId = sectionA.Id,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return p;
|
||||
});
|
||||
|
||||
// Lookup at store B — no memory there even though store A has one.
|
||||
var body = await Client.GetFromJsonAsync<JsonElement>(
|
||||
$"/api/products/global/{product.Id}/section?storeId={storeB.Id}");
|
||||
|
||||
await Assert.That(body.GetProperty("sectionId").ValueKind).IsEqualTo(JsonValueKind.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_family_product_section_returns_remembered_section()
|
||||
{
|
||||
var familyId = await GetFamilyIdAsync();
|
||||
var store = await Data.CreateStoreAsync();
|
||||
var (product, section) = await UseDbAsync(async db =>
|
||||
{
|
||||
var p = new FamilyProduct { FamilyId = familyId, Name = "House Bread" };
|
||||
var s = new StoreSection { FamilyId = familyId, StoreId = store.Id, Name = "Bakery", SortOrder = 1 };
|
||||
db.FamilyProducts.Add(p);
|
||||
db.StoreSections.Add(s);
|
||||
await db.SaveChangesAsync();
|
||||
db.ProductStoreSections.Add(new ProductStoreSection
|
||||
{
|
||||
FamilyId = familyId,
|
||||
StoreId = store.Id,
|
||||
FamilyProductId = p.Id,
|
||||
StoreSectionId = s.Id,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return (p, s);
|
||||
});
|
||||
|
||||
var body = await Client.GetFromJsonAsync<JsonElement>(
|
||||
$"/api/products/family/{product.Id}/section?storeId={store.Id}");
|
||||
|
||||
await Assert.That(body.GetProperty("sectionId").GetInt32()).IsEqualTo(section.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Get_family_product_section_returns_404_for_other_family_product()
|
||||
{
|
||||
var store = await Data.CreateStoreAsync();
|
||||
var otherFamilyProductId = await UseDbAsync(async db =>
|
||||
{
|
||||
var otherFamily = new Family { Name = "Other", InviteCode = "other-code" };
|
||||
db.Families.Add(otherFamily);
|
||||
await db.SaveChangesAsync();
|
||||
var p = new FamilyProduct { FamilyId = otherFamily.Id, Name = "Their Bread" };
|
||||
db.FamilyProducts.Add(p);
|
||||
await db.SaveChangesAsync();
|
||||
return p.Id;
|
||||
});
|
||||
|
||||
var response = await Client.GetAsync(
|
||||
$"/api/products/family/{otherFamilyProductId}/section?storeId={store.Id}");
|
||||
|
||||
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user