Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/org/settings_handler_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/org/settings_handler_test.go |
| Original SHA-256 | cc221b672832a774a95d43b38882a34ce0f7641a480fb26bf3874631700a2f60 |
| Git blob SHA | 6f545972188c93261cd2b4f779b6af1e9dd9c608 |
| Reading-copy SHA-256 | cc221b672832a774a95d43b38882a34ce0f7641a480fb26bf3874631700a2f60 |
Recovered source transcript
Download complete source text ↓142 lines · 5.7 KB
package org
import (
"encoding/json"
"reflect"
"strings"
"testing"
)
// THE REGRESSION. Every field on the wire must reach the update, and this has now failed TWICE in the same
// way: a settings field was added to the store, to validation and to the SQL, and the handler's request
// struct was not updated, so the API accepted the value, returned 200, and changed nothing. Silent success is
// the worst failure mode a settings screen can have.
//
// Comparing the two structs field by field catches it for every field that will ever be added.
func TestEveryPatchFieldReachesTheUpdate(t *testing.T) {
req := reflect.TypeOf(patchRequest{})
upd := reflect.TypeOf(patchRequest{}.toUpdate())
updFields := map[string]bool{}
for i := 0; i < upd.NumField(); i++ {
updFields[upd.Field(i).Name] = true
}
for i := 0; i < req.NumField(); i++ {
f := req.Field(i)
// UNEXPORTED FIELDS ARE BOOKKEEPING, NOT SETTINGS. encoding/json cannot decode into one, so it can never
// carry a value from the wire and has nothing to reach on the update. defaultBurdenPresent is the case
// this admits: it records whether a KEY appeared, which is how an explicit null is told apart from an
// absent field, and both of those decode to the same nil pointer without it.
if !f.IsExported() {
continue
}
if !updFields[f.Name] {
t.Errorf("patchRequest.%s has no matching field on orgsettings.Update, it can never be applied", f.Name)
}
}
// And the other direction: a field the store can update but the API cannot express is a setting nobody
// can reach.
reqFields := map[string]bool{}
for i := 0; i < req.NumField(); i++ {
reqFields[req.Field(i).Name] = true
}
for i := 0; i < upd.NumField(); i++ {
name := upd.Field(i).Name
if !reqFields[name] {
t.Errorf("orgsettings.Update.%s is not exposed on patchRequest, no caller can ever set it", name)
}
}
}
// A non nil value on the wire must actually arrive on the update. The reflection test above proves the fields
// line up; this proves the mapping function copies them rather than dropping one.
func TestPatchMappingCopiesValues(t *testing.T) {
body := `{"staff_record_scope":"own","manager_record_scope":"team","unowned_records_visible":false,
"rate_visibility":"team","work_visibility":"private","smtp_port":587,
"email_delivery_enabled":true,"time_zone":"UTC","claim_expiry_hours":24}`
var in patchRequest
if err := json.NewDecoder(strings.NewReader(body)).Decode(&in); err != nil {
t.Fatalf("decode: %v", err)
}
u := in.toUpdate()
uv := reflect.ValueOf(u)
rv := reflect.ValueOf(in)
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
name := f.Name
got := rv.Field(i)
// Same rule as above, and it must come before IsNil, which panics on a bool.
if !f.IsExported() {
continue
}
if got.Kind() == reflect.Ptr && got.IsNil() {
continue // absent from the body, nothing to carry
}
target := uv.FieldByName(name)
if !target.IsValid() || target.IsNil() {
t.Errorf("%s was set on the wire and is nil on the update, the value was dropped", name)
continue
}
// DefaultBurdenPct is the one field that deliberately changes TYPE in transit: a decimal string on the
// wire becomes a decimal behind a double pointer on the update, because a wrap must never round trip
// through a JSON float. DeepEqual would compare a string with a number and always disagree, so its
// carrying is proven by name below rather than by structural equality here.
if name == "DefaultBurdenPct" {
continue
}
if !reflect.DeepEqual(got.Elem().Interface(), target.Elem().Interface()) {
t.Errorf("%s changed value in transit: %v became %v", name, got.Elem(), target.Elem())
}
}
}
// THE WRAP HAS THREE STATES ON THE WIRE and they mean different things, so each is pinned. The one that
// matters most is the middle case: a PATCH that does not mention the wrap must leave it alone. The interface
// sends a subset of fields on every save, so getting this wrong would erase an organization's cost basis the
// next time somebody changed an unrelated setting, and every margin in the product would quietly become a
// spread again.
func TestTheWrapTellsAbsentApartFromCleared(t *testing.T) {
decode := func(body string) patchRequest {
var in patchRequest
if err := json.NewDecoder(strings.NewReader(body)).Decode(&in); err != nil {
t.Fatalf("decode %s: %v", body, err)
}
return in
}
// Absent. Leave the wrap alone.
if u := decode(`{"claim_expiry_hours":24}`).toUpdate(); u.DefaultBurdenPct != nil {
t.Error("a patch that never mentioned the wrap would have changed it")
}
// Explicit null. Clear it.
u := decode(`{"default_burden_pct":null}`).toUpdate()
if u.DefaultBurdenPct == nil {
t.Fatal("an explicit null did not reach the update, so the wrap could never be removed")
}
if *u.DefaultBurdenPct != nil {
t.Error("an explicit null did not clear the wrap")
}
// A value. Set it, exactly, with no float in the path.
u = decode(`{"default_burden_pct":"0.3525"}`).toUpdate()
if u.DefaultBurdenPct == nil || *u.DefaultBurdenPct == nil {
t.Fatal("a wrap on the wire did not reach the update")
}
if got := (*u.DefaultBurdenPct).String(); got != "0.3525" {
t.Errorf("wrap = %s, want 0.3525 exactly", got)
}
}
// A wrap that is not a number is REFUSED rather than treated as a clear. burdenUpdate cannot tell a typo from
// an erasure on its own, so the handler asks parseBurden first, and this is the guard on that ordering.
func TestAMalformedWrapIsRefusedNotTreatedAsAClear(t *testing.T) {
var in patchRequest
if err := json.NewDecoder(strings.NewReader(`{"default_burden_pct":"35%"}`)).Decode(&in); err != nil {
t.Fatalf("decode: %v", err)
}
if err := in.parseBurden(); err == nil {
t.Fatal("a wrap of \"35%\" was accepted, and it would have erased the real one")
}
}