Files
backend_fiesta/services/nutritionResponse_test.go
2026-09-29 17:27:48 +05:30

149 lines
5.0 KiB
Go

package services
import (
"encoding/json"
"testing"
"nearle/models"
)
/*
What the product screen receives.
The app renders a nutrition panel off `getproductbyvariant`. These are about the
contract that screen is built on — the exact field names, and what happens for
the overwhelming majority of products, which have no structured nutrition at all
and are not going to for a while.
*/
func TestTheStructuredPanelIsUsedWhereItExists(t *testing.T) {
facts := `{"nutrition":{"per":"100g","servingsize":"30g","items":[
{"name":"Energy","value":520,"unit":"kcal"},
{"name":"Protein","value":6.5,"unit":"g"}]}}`
panel := nutritionFromFacts(facts)
if !panel.HasValues() {
t.Fatal("no panel")
}
if panel.Per != "100g" || panel.Servingsize != "30g" {
t.Fatalf("basis lost: per=%q servingsize=%q", panel.Per, panel.Servingsize)
}
if len(panel.Items) != 2 || panel.Items[1].Value != 6.5 {
t.Fatalf("items wrong: %+v", panel.Items)
}
}
func TestTheOldNutrientLinesStillProduceAPanel(t *testing.T) {
// This is the case that actually matters on day one: every product on the
// platform carries display lines and nothing else. Without this the app
// ships a nutrition screen that is empty for every product in every shop.
panel := nutritionFromFacts(`{"nutrients":["Energy 350kcal","Protein 7g"]}`)
if !panel.HasValues() {
t.Fatal("the lines were not used")
}
if panel.Items[0].Name != "Energy" || panel.Items[0].Value != 350 {
t.Fatalf("got %+v", panel.Items[0])
}
}
func TestStructuredBeatsLinesWhenBothArePresent(t *testing.T) {
// The import keeps both — the console shows the lines, the app shows the
// panel. The structured one is the agent team's work and carries a basis,
// so it wins.
facts := `{"nutrients":["Energy 350kcal"],
"nutrition":{"per":"100g","items":[{"name":"Energy","value":520,"unit":"kcal"}]}}`
panel := nutritionFromFacts(facts)
if panel.Per != "100g" || panel.Items[0].Value != 520 {
t.Fatalf("the derived panel won: %+v", panel)
}
}
func TestNothingKnownMeansNoPanel(t *testing.T) {
for _, facts := range []string{"", " ", "{}", `{"highlights":["Crunchy"]}`} {
if p := nutritionFromFacts(facts); p != nil {
t.Errorf("%q produced a panel: %+v", facts, p)
}
}
}
func TestAMalformedSnapshotCostsThePanelAndNothingElse(t *testing.T) {
// One bad blob must not fail the response for the variant group it is in —
// the shopper tapped a product and is owed the screen.
if p := nutritionFromFacts(`{"nutrition":{"items":`); p != nil {
t.Fatalf("read a panel out of broken JSON: %+v", p)
}
}
func TestAPanelWithNoRowsIsNotSent(t *testing.T) {
// `items: []` renders as an empty box, which a shopper reads as "this food
// has no nutrition" rather than "we do not know".
if p := nutritionFromFacts(`{"nutrition":{"per":"100g","items":[]}}`); p != nil {
t.Fatalf("sent an empty panel: %+v", p)
}
}
func TestEveryRowInAVariantGroupIsDecorated(t *testing.T) {
// A shopper switching from 500ml to 1L must not watch the nutrition vanish.
rows := []models.Products{
{Productid: 1, Cataloguefacts: `{"nutrients":["Energy 350kcal"]}`},
{Productid: 2, Cataloguefacts: `{"nutrition":{"per":"100g","items":[{"name":"Energy","value":700,"unit":"kcal"}]}}`},
{Productid: 3},
}
decorateNutrition(rows)
if !rows[0].Nutrition.HasValues() || !rows[1].Nutrition.HasValues() {
t.Fatal("a sibling was left without its panel")
}
if rows[2].Nutrition != nil {
t.Fatal("invented a panel for a product with no facts")
}
}
func TestTheWireNamesAreTheOnesTheAppAskedFor(t *testing.T) {
// An API is a promise to a client already written against it. `servingsize`
// is not how the rest of this file would spell it, and it is what was asked
// for, which outranks house style.
rows := []models.Products{{
Productid: 7093,
Cataloguefacts: `{"nutrition":{"per":"100g","servingsize":"30g","items":[{"name":"Energy","value":520,"unit":"kcal"}]}}`,
}}
decorateNutrition(rows)
encoded, err := json.Marshal(rows[0])
if err != nil {
t.Fatalf("marshal: %v", err)
}
var out map[string]any
if err := json.Unmarshal(encoded, &out); err != nil {
t.Fatalf("unmarshal: %v", err)
}
nutrition, ok := out["nutrition"].(map[string]any)
if !ok {
t.Fatalf("no `nutrition` key on the product: %s", encoded)
}
for _, key := range []string{"per", "servingsize", "items"} {
if _, ok := nutrition[key]; !ok {
t.Errorf("missing %q: %v", key, nutrition)
}
}
item := nutrition["items"].([]any)[0].(map[string]any)
for _, key := range []string{"name", "value", "unit"} {
if _, ok := item[key]; !ok {
t.Errorf("item missing %q: %v", key, item)
}
}
}
func TestAProductWithNoNutritionHasNoKeyAtAll(t *testing.T) {
// Not `"nutrition": null`. Most products have none, and a null on every row
// of a mobile response is payload spent saying nothing.
encoded, _ := json.Marshal(models.Products{Productid: 1})
var out map[string]any
_ = json.Unmarshal(encoded, &out)
if _, present := out["nutrition"]; present {
t.Fatalf("an absent panel was sent as a key: %s", encoded)
}
}