parser_test.go raw

   1  package bridge
   2  
   3  import (
   4  	"testing"
   5  )
   6  
   7  func TestClassifyDM_Subscribe(t *testing.T) {
   8  	tests := []struct {
   9  		input string
  10  		want  DMCommand
  11  	}{
  12  		{"subscribe", DMCommandSubscribe},
  13  		{"Subscribe", DMCommandSubscribe},
  14  		{"SUBSCRIBE", DMCommandSubscribe},
  15  		{"  subscribe  ", DMCommandSubscribe},
  16  		{"\n subscribe \n", DMCommandSubscribe},
  17  	}
  18  
  19  	for _, tt := range tests {
  20  		got := ClassifyDM(tt.input)
  21  		if got != tt.want {
  22  			t.Errorf("ClassifyDM(%q) = %d, want %d", tt.input, got, tt.want)
  23  		}
  24  	}
  25  }
  26  
  27  func TestClassifyDMFull_SubscribeWithAlias(t *testing.T) {
  28  	tests := []struct {
  29  		input     string
  30  		wantAlias string
  31  	}{
  32  		{"subscribe alice", "alice"},
  33  		{"Subscribe MyAlias", "myalias"},
  34  		{"SUBSCRIBE bob", "bob"},
  35  		{"  subscribe  foo  ", "foo"},
  36  		{"subscribe please", "please"},
  37  	}
  38  
  39  	for _, tt := range tests {
  40  		got := ClassifyDMFull(tt.input)
  41  		if got.Command != DMCommandSubscribe {
  42  			t.Errorf("ClassifyDMFull(%q).Command = %d, want DMCommandSubscribe", tt.input, got.Command)
  43  		}
  44  		if got.Alias != tt.wantAlias {
  45  			t.Errorf("ClassifyDMFull(%q).Alias = %q, want %q", tt.input, got.Alias, tt.wantAlias)
  46  		}
  47  	}
  48  }
  49  
  50  func TestClassifyDMFull_Status(t *testing.T) {
  51  	tests := []string{"status", "Status", "STATUS", "  status  "}
  52  	for _, input := range tests {
  53  		got := ClassifyDMFull(input)
  54  		if got.Command != DMCommandStatus {
  55  			t.Errorf("ClassifyDMFull(%q).Command = %d, want DMCommandStatus", input, got.Command)
  56  		}
  57  	}
  58  }
  59  
  60  func TestClassifyDM_None(t *testing.T) {
  61  	tests := []string{
  62  		"To: alice@example.com\n\nHello",
  63  		"just a regular message",
  64  		"subscriber",
  65  		"unsubscribe",
  66  		"",
  67  	}
  68  
  69  	for _, input := range tests {
  70  		got := ClassifyDM(input)
  71  		if got != DMCommandNone {
  72  			t.Errorf("ClassifyDM(%q) = %d, want DMCommandNone", input, got)
  73  		}
  74  	}
  75  }
  76  
  77  func TestIsOutboundEmail(t *testing.T) {
  78  	tests := []struct {
  79  		input string
  80  		want  bool
  81  	}{
  82  		{"To: alice@example.com\n\nHello", true},
  83  		{"to: alice@example.com\n\nHello", true},
  84  		{"TO: alice@example.com\n\nHello", true},
  85  		{"Subject: Hello\n\nBody", true},
  86  		{"Cc: bob@example.com\n\nBody", true},
  87  		{"Bcc: carol@example.com\n\nBody", true},
  88  		{"Attachment: https://blossom.example/abc#key\n\nBody", true},
  89  		{"just a regular message", false},
  90  		{"From: alice@example.com\n\nHello", false},
  91  		{"", false},
  92  		{"subscribe", false},
  93  	}
  94  
  95  	for _, tt := range tests {
  96  		got := IsOutboundEmail(tt.input)
  97  		if got != tt.want {
  98  			t.Errorf("IsOutboundEmail(%q) = %v, want %v", tt.input, got, tt.want)
  99  		}
 100  	}
 101  }
 102  
 103  func TestParseDMContent_FullMessage(t *testing.T) {
 104  	content := "To: alice@example.com, bob@example.com\nCc: carol@example.com\nSubject: Hello from Nostr\nAttachment: https://blossom.example/abc123#key\n\nMessage body starts here.\nSecond line."
 105  
 106  	dm, err := ParseDMContent(content)
 107  	if err != nil {
 108  		t.Fatalf("ParseDMContent error: %v", err)
 109  	}
 110  
 111  	if len(dm.To) != 2 {
 112  		t.Fatalf("expected 2 To addresses, got %d: %v", len(dm.To), dm.To)
 113  	}
 114  	if dm.To[0] != "alice@example.com" || dm.To[1] != "bob@example.com" {
 115  		t.Errorf("To = %v, want [alice@example.com bob@example.com]", dm.To)
 116  	}
 117  
 118  	if len(dm.Cc) != 1 || dm.Cc[0] != "carol@example.com" {
 119  		t.Errorf("Cc = %v, want [carol@example.com]", dm.Cc)
 120  	}
 121  
 122  	if len(dm.Bcc) != 0 {
 123  		t.Errorf("Bcc = %v, want empty", dm.Bcc)
 124  	}
 125  
 126  	if dm.Subject != "Hello from Nostr" {
 127  		t.Errorf("Subject = %q, want %q", dm.Subject, "Hello from Nostr")
 128  	}
 129  
 130  	if len(dm.Attachments) != 1 || dm.Attachments[0] != "https://blossom.example/abc123#key" {
 131  		t.Errorf("Attachments = %v, want [https://blossom.example/abc123#key]", dm.Attachments)
 132  	}
 133  
 134  	expected := "Message body starts here.\nSecond line."
 135  	if dm.Body != expected {
 136  		t.Errorf("Body = %q, want %q", dm.Body, expected)
 137  	}
 138  }
 139  
 140  func TestParseDMContent_ToOnly(t *testing.T) {
 141  	content := "To: alice@example.com\n\nHello"
 142  
 143  	dm, err := ParseDMContent(content)
 144  	if err != nil {
 145  		t.Fatalf("ParseDMContent error: %v", err)
 146  	}
 147  
 148  	if len(dm.To) != 1 || dm.To[0] != "alice@example.com" {
 149  		t.Errorf("To = %v", dm.To)
 150  	}
 151  	if dm.Body != "Hello" {
 152  		t.Errorf("Body = %q", dm.Body)
 153  	}
 154  }
 155  
 156  func TestParseDMContent_MultipleAttachments(t *testing.T) {
 157  	content := "To: alice@example.com\nAttachment: https://blossom.example/a#k1\nAttachment: https://blossom.example/b#k2\n\nBody"
 158  
 159  	dm, err := ParseDMContent(content)
 160  	if err != nil {
 161  		t.Fatalf("ParseDMContent error: %v", err)
 162  	}
 163  
 164  	if len(dm.Attachments) != 2 {
 165  		t.Fatalf("expected 2 attachments, got %d", len(dm.Attachments))
 166  	}
 167  	if dm.Attachments[0] != "https://blossom.example/a#k1" {
 168  		t.Errorf("Attachments[0] = %q", dm.Attachments[0])
 169  	}
 170  	if dm.Attachments[1] != "https://blossom.example/b#k2" {
 171  		t.Errorf("Attachments[1] = %q", dm.Attachments[1])
 172  	}
 173  }
 174  
 175  func TestParseDMContent_SpaceSeparatedAddresses(t *testing.T) {
 176  	// Per spec: spaces are the real delimiter, commas are decorative
 177  	content := "To: alice@example.com bob@example.com carol@example.com\n\nBody"
 178  
 179  	dm, err := ParseDMContent(content)
 180  	if err != nil {
 181  		t.Fatalf("ParseDMContent error: %v", err)
 182  	}
 183  
 184  	if len(dm.To) != 3 {
 185  		t.Fatalf("expected 3 To addresses, got %d: %v", len(dm.To), dm.To)
 186  	}
 187  }
 188  
 189  func TestParseDMContent_BccHeader(t *testing.T) {
 190  	content := "To: alice@example.com\nBcc: secret@example.com\n\nPrivate"
 191  
 192  	dm, err := ParseDMContent(content)
 193  	if err != nil {
 194  		t.Fatalf("ParseDMContent error: %v", err)
 195  	}
 196  
 197  	if len(dm.Bcc) != 1 || dm.Bcc[0] != "secret@example.com" {
 198  		t.Errorf("Bcc = %v, want [secret@example.com]", dm.Bcc)
 199  	}
 200  }
 201  
 202  func TestParseDMContent_NoBlankLine(t *testing.T) {
 203  	// If there's no blank line and an unrecognized line appears,
 204  	// it becomes the body
 205  	content := "To: alice@example.com\nThis is not a header line"
 206  
 207  	dm, err := ParseDMContent(content)
 208  	if err != nil {
 209  		t.Fatalf("ParseDMContent error: %v", err)
 210  	}
 211  
 212  	if len(dm.To) != 1 {
 213  		t.Errorf("To = %v", dm.To)
 214  	}
 215  	if dm.Body != "This is not a header line" {
 216  		t.Errorf("Body = %q, want %q", dm.Body, "This is not a header line")
 217  	}
 218  }
 219  
 220  func TestParseDMContent_EmptyBody(t *testing.T) {
 221  	content := "To: alice@example.com\nSubject: No body\n\n"
 222  
 223  	dm, err := ParseDMContent(content)
 224  	if err != nil {
 225  		t.Fatalf("ParseDMContent error: %v", err)
 226  	}
 227  
 228  	if dm.Subject != "No body" {
 229  		t.Errorf("Subject = %q", dm.Subject)
 230  	}
 231  	if dm.Body != "" {
 232  		t.Errorf("Body = %q, want empty", dm.Body)
 233  	}
 234  }
 235  
 236  func TestParseDMContent_BodyOnly(t *testing.T) {
 237  	// No headers at all — everything is body
 238  	content := "Just a plain message with no headers"
 239  
 240  	dm, err := ParseDMContent(content)
 241  	if err != nil {
 242  		t.Fatalf("ParseDMContent error: %v", err)
 243  	}
 244  
 245  	if dm.Body != "Just a plain message with no headers" {
 246  		t.Errorf("Body = %q", dm.Body)
 247  	}
 248  	if len(dm.To) != 0 {
 249  		t.Errorf("To = %v, want empty", dm.To)
 250  	}
 251  }
 252  
 253  func TestParseDMContent_EmptyString(t *testing.T) {
 254  	dm, err := ParseDMContent("")
 255  	if err != nil {
 256  		t.Fatalf("ParseDMContent error: %v", err)
 257  	}
 258  
 259  	if dm.Body != "" {
 260  		t.Errorf("Body = %q, want empty", dm.Body)
 261  	}
 262  }
 263  
 264  func TestParseAddressList(t *testing.T) {
 265  	tests := []struct {
 266  		input string
 267  		want  int
 268  	}{
 269  		{"alice@example.com", 1},
 270  		{"alice@example.com, bob@example.com", 2},
 271  		{"alice@example.com bob@example.com", 2},
 272  		{"alice@example.com,bob@example.com,carol@example.com", 3},
 273  		{"alice@example.com , bob@example.com , carol@example.com", 3},
 274  		{"", 0},
 275  		{"not-an-email", 0},
 276  		{"alice@example.com not-an-email bob@example.com", 2},
 277  	}
 278  
 279  	for _, tt := range tests {
 280  		got := parseAddressList(tt.input)
 281  		if len(got) != tt.want {
 282  			t.Errorf("parseAddressList(%q) returned %d addresses, want %d: %v",
 283  				tt.input, len(got), tt.want, got)
 284  		}
 285  	}
 286  }
 287  
 288  func TestIsOutboundEmail_EmptyFirstLine(t *testing.T) {
 289  	// Content starts with a newline — first line is empty
 290  	got := IsOutboundEmail("\nTo: alice@example.com")
 291  	if got {
 292  		t.Error("expected false when first line is empty")
 293  	}
 294  }
 295  
 296  func TestParseDMContent_EmptyAttachmentValue(t *testing.T) {
 297  	content := "To: alice@example.com\nAttachment:\n\nBody"
 298  
 299  	dm, err := ParseDMContent(content)
 300  	if err != nil {
 301  		t.Fatalf("ParseDMContent error: %v", err)
 302  	}
 303  	if len(dm.Attachments) != 0 {
 304  		t.Errorf("expected 0 attachments for empty value, got %d", len(dm.Attachments))
 305  	}
 306  }
 307  
 308  func TestParseDMContent_UnknownHeaderBecomesBody(t *testing.T) {
 309  	// An unknown header key should cause parser to treat rest as body
 310  	content := "To: alice@example.com\nX-Custom: value\nMore text"
 311  
 312  	dm, err := ParseDMContent(content)
 313  	if err != nil {
 314  		t.Fatalf("ParseDMContent error: %v", err)
 315  	}
 316  
 317  	if len(dm.To) != 1 {
 318  		t.Errorf("To = %v", dm.To)
 319  	}
 320  	// X-Custom line and everything after should be body
 321  	if dm.Body != "X-Custom: value\nMore text" {
 322  		t.Errorf("Body = %q, want %q", dm.Body, "X-Custom: value\nMore text")
 323  	}
 324  }
 325