141 lines
4.1 KiB
Go
141 lines
4.1 KiB
Go
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
|
|
}
|