nutrition field
This commit is contained in:
148
services/nutritionResponse_test.go
Normal file
148
services/nutritionResponse_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user