package sms import ( "os" "strings" "testing" ) // StagingCode is a permanent skeleton key for every account on the platform if // it ever reaches production. The guard against that is the only thing standing // between a convenience for QA and a total auth bypass, so it is tested rather // than trusted. func setEnv(t *testing.T, key, value string) { t.Helper() previous, had := os.LookupEnv(key) if value == "" { _ = os.Unsetenv(key) } else { _ = os.Setenv(key, value) } t.Cleanup(func() { if had { _ = os.Setenv(key, previous) } else { _ = os.Unsetenv(key) } }) } // A fixed OTP is refused in production however the environment is spelt. func TestStagingCodeIsRefusedInProduction(t *testing.T) { for _, env := range []string{"production", "PRODUCTION", "Production", " production "} { setEnv(t, "CX_STAGING_OTP", "1234") setEnv(t, "ENV", env) if got := StagingCode(); got != "" { t.Errorf("ENV=%q returned the fixed code %q — that is a skeleton key "+ "for every account on the platform", env, got) } } } // And is available everywhere else, which is what unblocks automated sign-in. func TestStagingCodeIsAvailableOutsideProduction(t *testing.T) { for _, env := range []string{"development", "staging", ""} { setEnv(t, "CX_STAGING_OTP", "1234") setEnv(t, "ENV", env) if got := StagingCode(); got != "1234" { t.Errorf("ENV=%q returned %q, want the configured staging code", env, got) } } } // Unset means unset — no accidental default. func TestStagingCodeIsEmptyWhenNotConfigured(t *testing.T) { setEnv(t, "ENV", "development") setEnv(t, "CX_STAGING_OTP", "") if got := StagingCode(); got != "" { t.Errorf("StagingCode() = %q with nothing configured, want empty", got) } } // Until a gateway is registered, Configured() must report false. Shipping while // this quietly said true would mean nobody noticed OTP codes were only reaching // the application log. func TestTransportReportsThatNoGatewayIsWired(t *testing.T) { if Configured() { t.Error("Configured() = true with no gateway registered — " + "the log sink must never claim to be a real transport") } if Transport() != "log" { t.Errorf("Transport() = %q, want \"log\"", Transport()) } } // Registering a gateway flips both, and Register(nil) is ignored rather than // silently disabling delivery. func TestRegisterInstallsAGatewayAndIgnoresNil(t *testing.T) { original := active t.Cleanup(func() { active = original }) Register(nil) if Transport() != "log" { t.Errorf("Register(nil) changed the transport to %q", Transport()) } fake := &recordingSender{} Register(fake) if !Configured() || Transport() != "test" { t.Fatalf("after Register: configured=%v transport=%q", Configured(), Transport()) } if err := SendOTP("+919876543210", "4821"); err != nil { t.Fatalf("SendOTP: %v", err) } if fake.phone != "+919876543210" { t.Errorf("phone = %q, want the number passed in", fake.phone) } if !strings.Contains(fake.message, "4821") { t.Errorf("message %q does not carry the code", fake.message) } if !strings.Contains(fake.message, "Do not share") { t.Errorf("message %q is missing the do-not-share warning", fake.message) } } // An empty number is refused rather than handed to a gateway that will bill for // it and fail. func TestSendOTPRefusesAnEmptyNumber(t *testing.T) { if err := SendOTP(" ", "4821"); err == nil { t.Error("SendOTP accepted an empty phone number") } } // The log sink masks the number. An OTP in a log file is already bad enough // without the number it belongs to sitting beside it. func TestMaskPhoneHidesTheSubscriberDigits(t *testing.T) { got := maskPhone("+919876543210") if strings.Contains(got, "9876543") { t.Errorf("maskPhone = %q, still exposes the subscriber digits", got) } if !strings.HasSuffix(got, "10") { t.Errorf("maskPhone = %q, should keep the last two digits so a support "+ "call can be matched", got) } } type recordingSender struct { phone string message string } func (r *recordingSender) Name() string { return "test" } func (r *recordingSender) Send(phone, message string) error { r.phone, r.message = phone, message return nil }