router_test.go raw

   1  package bridge
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"strings"
   7  	"sync"
   8  	"testing"
   9  	"time"
  10  )
  11  
  12  type mockDMSink struct {
  13  	mu   sync.Mutex
  14  	msgs map[string][]string
  15  }
  16  
  17  func newMockDMSink() *mockDMSink {
  18  	return &mockDMSink{msgs: make(map[string][]string)}
  19  }
  20  
  21  func (m *mockDMSink) send(pubkey, content string) error {
  22  	m.mu.Lock()
  23  	defer m.mu.Unlock()
  24  	m.msgs[pubkey] = append(m.msgs[pubkey], content)
  25  	return nil
  26  }
  27  
  28  func (m *mockDMSink) get(pubkey string) []string {
  29  	m.mu.Lock()
  30  	defer m.mu.Unlock()
  31  	return m.msgs[pubkey]
  32  }
  33  
  34  func TestRouter_Subscribe(t *testing.T) {
  35  	sink := newMockDMSink()
  36  
  37  	// No subscription handler — should get "not configured" response
  38  	router := NewRouter(nil, nil, sink.send)
  39  	router.RouteDM(context.Background(), "user1", "subscribe")
  40  
  41  	msgs := sink.get("user1")
  42  	if len(msgs) != 1 {
  43  		t.Fatalf("expected 1 message, got %d", len(msgs))
  44  	}
  45  	if !strings.Contains(msgs[0], "not configured") {
  46  		t.Errorf("expected 'not configured' message, got: %s", msgs[0])
  47  	}
  48  }
  49  
  50  func TestRouter_OutboundEmail(t *testing.T) {
  51  	sink := newMockDMSink()
  52  
  53  	// No outbound processor — should get "not configured" response
  54  	router := NewRouter(nil, nil, sink.send)
  55  	router.RouteDM(context.Background(), "user1", "To: alice@example.com\nSubject: Test\n\nHello")
  56  
  57  	msgs := sink.get("user1")
  58  	if len(msgs) != 1 {
  59  		t.Fatalf("expected 1 message, got %d", len(msgs))
  60  	}
  61  	if !strings.Contains(msgs[0], "not configured") {
  62  		t.Errorf("expected 'not configured' message, got: %s", msgs[0])
  63  	}
  64  }
  65  
  66  func TestRouter_UnrecognizedMessage(t *testing.T) {
  67  	sink := newMockDMSink()
  68  
  69  	router := NewRouter(nil, nil, sink.send)
  70  	router.RouteDM(context.Background(), "user1", "just a regular hello message")
  71  
  72  	msgs := sink.get("user1")
  73  	if len(msgs) != 1 {
  74  		t.Fatalf("expected 1 help message, got %d", len(msgs))
  75  	}
  76  	if !strings.Contains(msgs[0], "Marmot Email Bridge") {
  77  		t.Errorf("expected help text, got: %s", msgs[0])
  78  	}
  79  	if !strings.Contains(msgs[0], "subscribe") {
  80  		t.Error("help text should mention subscribe command")
  81  	}
  82  }
  83  
  84  func TestRouter_WithSubscriptionHandler(t *testing.T) {
  85  	sink := newMockDMSink()
  86  	store := NewMemorySubscriptionStore()
  87  
  88  	// Create handler with nil payment processor — will get "not available" reply
  89  	subHandler := NewSubscriptionHandler(store, nil, sink.send, 2100, nil, 0)
  90  	router := NewRouter(subHandler, nil, sink.send)
  91  
  92  	router.RouteDM(context.Background(), "user1", "subscribe")
  93  
  94  	// HandleSubscribe runs in a goroutine, wait briefly for it to complete
  95  	time.Sleep(50 * time.Millisecond)
  96  
  97  	// Should have been routed to the subscription handler,
  98  	// which sends a "not available" message because payments=nil
  99  	msgs := sink.get("user1")
 100  	if len(msgs) != 1 {
 101  		t.Fatalf("expected 1 message, got %d", len(msgs))
 102  	}
 103  	if !strings.Contains(msgs[0], "not available") {
 104  		t.Errorf("expected 'not available' from sub handler, got: %s", msgs[0])
 105  	}
 106  }
 107  
 108  func TestRouter_OutboundWithProcessor(t *testing.T) {
 109  	sink := newMockDMSink()
 110  
 111  	// Create outbound processor with nil SMTP client — will fail at send time
 112  	outbound := NewOutboundProcessor(nil, nil, nil, "test.example.com", sink.send, nil)
 113  	router := NewRouter(nil, outbound, sink.send)
 114  
 115  	// This will hit the SMTP send which panics because smtpClient is nil.
 116  	defer func() {
 117  		if r := recover(); r != nil {
 118  			t.Logf("expected panic from nil SMTP client: %v", r)
 119  		}
 120  	}()
 121  
 122  	router.RouteDM(context.Background(), "user1", "To: alice@example.com\nSubject: Test\n\nBody")
 123  }
 124  
 125  func TestRouter_Reply_NilSendDM(t *testing.T) {
 126  	router := NewRouter(nil, nil, nil)
 127  	// Should not panic
 128  	router.reply("user1", "test")
 129  }
 130  
 131  func TestRouter_Reply_Error(t *testing.T) {
 132  	errorSend := func(pubkey, content string) error {
 133  		return fmt.Errorf("send failed")
 134  	}
 135  	router := NewRouter(nil, nil, errorSend)
 136  	// Should not panic, just log
 137  	router.reply("user1", "test")
 138  }
 139  
 140  func TestGenerateReplyLink(t *testing.T) {
 141  	link := GenerateReplyLink("https://bridge.example.com/compose", "alice@example.com", "Hello")
 142  	if !strings.Contains(link, "bridge.example.com/compose") {
 143  		t.Errorf("link missing base URL: %s", link)
 144  	}
 145  	if !strings.Contains(link, "#to=alice%40example.com") {
 146  		t.Errorf("link missing to param: %s", link)
 147  	}
 148  	if !strings.Contains(link, "subject=Re%3A+Hello") {
 149  		t.Errorf("link missing subject: %s", link)
 150  	}
 151  }
 152  
 153  func TestGenerateReplyLink_AlreadyRe(t *testing.T) {
 154  	link := GenerateReplyLink("https://example.com/compose", "bob@test.com", "Re: Original")
 155  	if strings.Contains(link, "Re: Re:") {
 156  		t.Errorf("double Re: in link: %s", link)
 157  	}
 158  }
 159