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