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) } }