From f67cbad79a8c4ba680c533914a779d4116a2bfcc Mon Sep 17 00:00:00 2001 From: abhishek Date: Tue, 6 Oct 2026 14:58:17 +0530 Subject: [PATCH] api for health score toggle test reverse --- models/product.go | 26 ++++++++++++- models/showHealthScoreTag_test.go | 64 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 models/showHealthScoreTag_test.go diff --git a/models/product.go b/models/product.go index 610ea0b..5747b2e 100644 --- a/models/product.go +++ b/models/product.go @@ -214,7 +214,18 @@ type Products struct { // The PLATFORM console ignores this: Nearle staff see every score on every // product, because the decision being made there is whether the data is good // enough to publish at all. - Showhealthscore bool `json:"showhealthscore" gorm:"column:showhealthscore;default:true"` + // + // NO `default` IN THE GORM TAG, and that is not an oversight. GORM skips a + // zero-value field whose tag names a default, so `false` was never written: + // the INSERT omitted the column, the database default of true applied, and a + // product imported with the score switched off came back switched on. It + // shipped that way and was found by importing one and reading it back. + // + // The DEFAULT lives on the column instead, set by the migration in main.go. + // That still covers what it is for — rows that predate the column, and any + // INSERT that genuinely omits it — without teaching GORM to drop a + // deliberate false on the way past. + Showhealthscore bool `json:"showhealthscore" gorm:"column:showhealthscore"` Productdesc string `json:"productdesc,omitempty"` Productsku string `json:"productsku,omitempty"` @@ -331,6 +342,19 @@ type Locationproducts struct { Diffpercent float64 `json:"diffpercent,omitempty"` Othercost float64 `json:"othercost,omitempty"` Approve int `json:"approve" gorm:"default:0"` + // Whether this product's health score is shown to shoppers. + // + // It has to be HERE and not only on Products, because this is the struct the + // admin catalogue reads. Without it the console received no value at all, + // the drawer's switch rendered "on" for every product including the ones + // that were off, and a product already hidden showed no panel and so no way + // to turn it back on. The column was being written correctly the whole time + // and simply never read back — which also made every "it did not save" + // reading taken from this endpoint meaningless. + // + // No `omitempty`: a false has to survive the trip, and omitempty would drop + // exactly the value this field exists to carry. + Showhealthscore bool `json:"showhealthscore"` // Productstatus string `json:"productstatus" gorm:"default:available"` Status string `json:"status" gorm:"default:outofstock"` diff --git a/models/showHealthScoreTag_test.go b/models/showHealthScoreTag_test.go new file mode 100644 index 0000000..2da177c --- /dev/null +++ b/models/showHealthScoreTag_test.go @@ -0,0 +1,64 @@ +package models + +import ( + "reflect" + "strings" + "testing" +) + +/* +`showhealthscore` must not carry a GORM default. + +GORM skips a zero-value field whose tag names a default — the documented +behaviour is that a `false`, `0` or `""` is left out of the INSERT so the +database default applies. For a boolean whose whole purpose is being set to +false, that means the one value anybody would set it to is the one that cannot +be written. + +It shipped that way. A product imported with the health score switched off came +back switched on, and it took importing one and reading it back to find out, +because every layer above reported success: the console sent `false`, the +request carried `false`, the handler read `false`, GORM dropped it, and the +column default wrote `true`. + +The DEFAULT belongs on the column, set by the migration in main.go. That covers +rows predating the column and any INSERT that genuinely omits it, without +teaching the ORM to discard a deliberate false on the way past. +*/ +func TestShowHealthScoreCarriesNoGormDefault(t *testing.T) { + field, ok := reflect.TypeFor[Products]().FieldByName("Showhealthscore") + if !ok { + t.Fatal("Products.Showhealthscore has moved or been renamed") + } + + tag := field.Tag.Get("gorm") + if strings.Contains(strings.ToLower(tag), "default") { + t.Fatalf( + "gorm tag %q names a default. GORM then skips this field when it is false, "+ + "so a product whose health score is switched off is written as switched on. "+ + "The column default is set by the migration in main.go instead.", + tag, + ) + } + + // The column still has to be named, since the Go field is one word and the + // column is too but GORM's default naming would make it `show_health_score`. + if !strings.Contains(tag, "column:showhealthscore") { + t.Errorf("gorm tag %q no longer names the column", tag) + } +} + +func TestShowHealthScoreIsAPlainBoolOnTheWire(t *testing.T) { + // Not a pointer, and not `omitempty`. Every product says what it is: the + // console reads it to set the switch, and an absent key would be + // indistinguishable from false on a screen that has to show one or the + // other. + field, _ := reflect.TypeFor[Products]().FieldByName("Showhealthscore") + + if field.Type.Kind() != reflect.Bool { + t.Errorf("Showhealthscore is %s, want bool", field.Type.Kind()) + } + if tag := field.Tag.Get("json"); tag != "showhealthscore" { + t.Errorf("json tag is %q — an omitempty here would hide every `false`", tag) + } +}