package sms import ( "context" "fmt" "sync" ) // MockSender records calls and returns configurable results (for tests and local dev). type MockSender struct { name string sort int mu sync.Mutex calls []*Message Err error MessageID string SendHook func(ctx context.Context, msg *Message) (string, error) } type MockSenderOption func(*MockSender) func WithMockName(name string) MockSenderOption { return func(m *MockSender) { m.name = name } } func WithMockSort(sort int) MockSenderOption { return func(m *MockSender) { m.sort = sort } } func WithMockError(err error) MockSenderOption { return func(m *MockSender) { m.Err = err } } func WithMockMessageID(id string) MockSenderOption { return func(m *MockSender) { m.MessageID = id } } func NewMockSender(opts ...MockSenderOption) *MockSender { m := &MockSender{ name: "mock", sort: 0, MessageID: "mock-sms-id", } for _, opt := range opts { opt(m) } return m } func (m *MockSender) Name() string { return m.name } func (m *MockSender) Sort() int { return m.sort } func (m *MockSender) Send(ctx context.Context, msg *Message) (string, error) { m.mu.Lock() m.calls = append(m.calls, msg) m.mu.Unlock() if m.SendHook != nil { return m.SendHook(ctx, msg) } if m.Err != nil { return "", m.Err } return m.MessageID, nil } func (m *MockSender) Calls() []*Message { m.mu.Lock() defer m.mu.Unlock() out := make([]*Message, len(m.calls)) copy(out, m.calls) return out } func (m *MockSender) Reset() { m.mu.Lock() defer m.mu.Unlock() m.calls = nil } // ErrSender is a Sender that always fails (helper for tests). func ErrSender(name string, sort int, err error) Sender { return &staticErrSender{name: name, sort: sort, err: err} } type staticErrSender struct { name string sort int err error } func (s *staticErrSender) Name() string { return s.name } func (s *staticErrSender) Sort() int { return s.sort } func (s *staticErrSender) Send(context.Context, *Message) (string, error) { if s.err != nil { return "", s.err } return "", fmt.Errorf("%s: send failed", s.name) }