package controllers import ( "encoding/json" "strings" "testing" ) // DM-01: the customer app posts the verification code as "otp"; the server was // written to read "code". req.Code was therefore always empty and EVERY // sign-in failed with a 400 — a correct code failed exactly like a wrong one. // // These pin the alias. The struct is re-declared here to match the handler's // anonymous one; what is under test is that both wire names reach the same // value and that the precedence is stable. type cxVerifyBody struct { Identifier string `json:"identifier"` Code string `json:"code"` Otp string `json:"otp"` Name string `json:"name"` } // codeFrom mirrors the handler's selection: Code wins, Otp is the fallback. func codeFrom(b cxVerifyBody) string { code := strings.TrimSpace(b.Code) if code == "" { code = strings.TrimSpace(b.Otp) } return code } func parseVerify(t *testing.T, raw string) cxVerifyBody { t.Helper() var b cxVerifyBody if err := json.Unmarshal([]byte(raw), &b); err != nil { t.Fatalf("unmarshal %s: %v", raw, err) } return b } // The shape the app actually sends. This is the regression that locked every // customer out of production. func TestVerifyAcceptsTheAppsOtpField(t *testing.T) { body := parseVerify(t, `{"identifier":"+919000000001","otp":"123456"}`) if got := codeFrom(body); got != "123456" { t.Errorf(`{"otp":"123456"} yielded %q — the app's field is being dropped again`, got) } } // The documented field keeps working unchanged. func TestVerifyStillAcceptsCode(t *testing.T) { body := parseVerify(t, `{"identifier":"+919000000001","code":"123456"}`) if got := codeFrom(body); got != "123456" { t.Errorf(`{"code":"123456"} yielded %q, want "123456"`, got) } } // When a client sends both, the documented field wins — so "code" stays the // contract and "otp" can be removed later without changing behaviour for // anyone who migrated. func TestCodeWinsOverOtpWhenBothArePresent(t *testing.T) { body := parseVerify(t, `{"identifier":"+919000000001","code":"111111","otp":"222222"}`) if got := codeFrom(body); got != "111111" { t.Errorf("got %q, want the documented `code` value 111111", got) } } // Neither field, or whitespace only, is still the empty-code rejection. The // alias must not turn a missing code into an accepted one. func TestMissingOrBlankCodeIsStillRejected(t *testing.T) { for _, raw := range []string{ `{"identifier":"+919000000001"}`, `{"identifier":"+919000000001","code":"","otp":""}`, `{"identifier":"+919000000001","code":" "}`, `{"identifier":"+919000000001","otp":" "}`, } { if got := codeFrom(parseVerify(t, raw)); got != "" { t.Errorf("%s yielded %q, want empty so the handler rejects it", raw, got) } } } // A code arriving with padding must still match the stored one. func TestPaddedOtpIsTrimmed(t *testing.T) { if got := codeFrom(parseVerify(t, `{"identifier":"x","otp":" 123456 "}`)); got != "123456" { t.Errorf("got %q, want the trimmed 123456", got) } }