Files
backend_fiesta/services/tenantProfile_test.go
2026-09-02 12:30:05 +05:30

133 lines
4.5 KiB
Go

package services
import "testing"
/*
The tenants table had no write path at all, so a merchant could never change
their own shop's photo, licence number or contact — measured across 200 tenants:
18 had an image, none had a licence.
Adding one is where the risk is. `tenants` carries platform-controlled columns
next to merchant-owned ones — `approved`, `status`, `partnerid`, `partneruserid`,
`moduleid`, `configid`, `tenanttoken` — and the obvious implementation, handing
the parsed body to GORM, would let anyone reaching the endpoint approve
themselves onto the platform or move themselves under a different partner. A UI
would never send those fields, which is what makes the hole easy to leave open.
*/
func TestAMerchantCannotApproveThemselves(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{
"tenantimage": "https://example.com/shop.jpg",
"approved": 1,
"status": "Active",
})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
for _, forbidden := range []string{"approved", "status"} {
if _, present := clean[forbidden]; present {
t.Errorf("%q survived the allowlist", forbidden)
}
}
if clean["tenantimage"] != "https://example.com/shop.jpg" {
t.Errorf("the legitimate field was dropped: %+v", clean)
}
}
func TestOwnershipAndBillingColumnsAreNotEditable(t *testing.T) {
_, err := TenantProfileUpdate(map[string]any{
"partnerid": 9,
"partneruserid": 9,
"moduleid": 3,
"configid": 2,
"tenanttoken": "stolen",
"tenantid": 1,
})
// Every field was refused, so nothing is left to write — and that must be
// an error, not a silent success.
if err == nil {
t.Fatal("a request of nothing but forbidden fields was accepted")
}
}
/*
A profile form sends every field it renders on every save. If a blank meant
"erase", opening the form and saving one change would wipe everything the form
does not show — an address typed last month, a licence added by somebody else.
*/
func TestABlankFieldIsNotAnInstructionToErase(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{
"licenseno": "12345678901234",
"address": " ",
"city": "",
})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
if _, present := clean["address"]; present {
t.Error("a whitespace-only value was treated as a change")
}
if _, present := clean["city"]; present {
t.Error("an empty value was treated as a change")
}
if clean["licenseno"] != "12345678901234" {
t.Errorf("the real change was lost: %+v", clean)
}
}
// Zero is a real minimum order and a real subcategory id is not a string, so
// the blank rule must apply to text only.
func TestANumericZeroIsStillAChange(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{"minorder": 0})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
if value, present := clean["minorder"]; !present || value != 0 {
t.Errorf("a minimum order of zero was dropped: %+v", clean)
}
}
// Case and stray whitespace in a key are a client's problem, not a reason to
// silently ignore a field the merchant filled in.
func TestFieldNamesAreMatchedLeniently(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{" TenantImage ": "x.jpg"})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
if clean["tenantimage"] != "x.jpg" {
t.Errorf("want the field normalised onto its column, got %+v", clean)
}
}
// Store setup marks Store type REQUIRED and sends it on save. It was missing
// from the allowlist, so the merchant answered a required question, saw the
// step succeed, and the answer was dropped on the floor — measured on local
// tenant 9002, which reached the end of setup with `tenanttype` still empty.
func TestTheKindOfShopIsTheMerchantsToSet(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{"tenanttype": "grocery"})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
if clean["tenanttype"] != "grocery" {
t.Errorf("tenanttype did not survive the allowlist: %#v", clean)
}
}
// Letting the shop describe itself must not have opened a door next to it.
func TestAllowingTheShopTypeDidNotAllowItsStanding(t *testing.T) {
clean, err := TenantProfileUpdate(map[string]any{
"tenanttype": "pharmacy",
"tenanttoken": "stolen",
"partnerid": 99,
"configid": 7,
})
if err != nil {
t.Fatalf("TenantProfileUpdate: %v", err)
}
for _, forbidden := range []string{"tenanttoken", "partnerid", "configid"} {
if _, present := clean[forbidden]; present {
t.Errorf("%q survived the allowlist", forbidden)
}
}
}