89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|