mlist 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/CHANGELOG +59 -0
  2. data/README +204 -0
  3. data/Rakefile +27 -0
  4. data/TODO +36 -0
  5. data/VERSION.yml +4 -0
  6. data/lib/mlist/email.rb +69 -0
  7. data/lib/mlist/email_post.rb +126 -0
  8. data/lib/mlist/email_server/base.rb +33 -0
  9. data/lib/mlist/email_server/default.rb +31 -0
  10. data/lib/mlist/email_server/fake.rb +16 -0
  11. data/lib/mlist/email_server/pop.rb +28 -0
  12. data/lib/mlist/email_server/smtp.rb +24 -0
  13. data/lib/mlist/email_server.rb +2 -0
  14. data/lib/mlist/email_subscriber.rb +6 -0
  15. data/lib/mlist/list.rb +183 -0
  16. data/lib/mlist/mail_list.rb +277 -0
  17. data/lib/mlist/manager/database.rb +48 -0
  18. data/lib/mlist/manager/notifier.rb +31 -0
  19. data/lib/mlist/manager.rb +30 -0
  20. data/lib/mlist/message.rb +150 -0
  21. data/lib/mlist/server.rb +62 -0
  22. data/lib/mlist/thread.rb +98 -0
  23. data/lib/mlist/util/email_helpers.rb +155 -0
  24. data/lib/mlist/util/header_sanitizer.rb +71 -0
  25. data/lib/mlist/util/quoting.rb +70 -0
  26. data/lib/mlist/util/tmail_builder.rb +42 -0
  27. data/lib/mlist/util/tmail_methods.rb +138 -0
  28. data/lib/mlist/util.rb +12 -0
  29. data/lib/mlist.rb +46 -0
  30. data/lib/pop_ssl.rb +999 -0
  31. data/rails/init.rb +22 -0
  32. data/spec/fixtures/schema.rb +94 -0
  33. data/spec/integration/date_formats_spec.rb +12 -0
  34. data/spec/integration/mlist_spec.rb +232 -0
  35. data/spec/integration/pop_email_server_spec.rb +22 -0
  36. data/spec/integration/proof_spec.rb +74 -0
  37. data/spec/matchers/equal_tmail.rb +53 -0
  38. data/spec/matchers/have_address.rb +48 -0
  39. data/spec/matchers/have_header.rb +104 -0
  40. data/spec/models/email_post_spec.rb +100 -0
  41. data/spec/models/email_server/base_spec.rb +11 -0
  42. data/spec/models/email_spec.rb +54 -0
  43. data/spec/models/mail_list_spec.rb +469 -0
  44. data/spec/models/message_spec.rb +109 -0
  45. data/spec/models/thread_spec.rb +83 -0
  46. data/spec/models/util/email_helpers_spec.rb +47 -0
  47. data/spec/models/util/header_sanitizer_spec.rb +19 -0
  48. data/spec/models/util/quoting_spec.rb +96 -0
  49. data/spec/spec_helper.rb +76 -0
  50. metadata +103 -0
@@ -0,0 +1,100 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MList::EmailPost do
4
+ before do
5
+ @subscriber = MList::EmailSubscriber.new('john@example.com')
6
+ @post = MList::EmailPost.new({
7
+ :subscriber => @subscriber,
8
+ :subject => "I'm a Program!",
9
+ :text => "My simple message that isn't too short"
10
+ })
11
+ end
12
+
13
+ it 'should include html and text when both provided' do
14
+ @post.html = '<p>My simple message</p>'
15
+
16
+ tmail = @post.to_tmail
17
+ tmail.content_type.should == 'multipart/alternative'
18
+ tmail.parts.size.should == 2
19
+
20
+ # The first part should be the least desirable...
21
+ part = tmail.parts.first
22
+ part.content_type.should == 'text/plain'
23
+
24
+ # And the last should be the best alternative.
25
+ part = tmail.parts.last
26
+ part.content_type.should == 'text/html'
27
+ end
28
+
29
+ it "should default the mailer to 'MList Client Application'" do
30
+ @post.to_tmail['x-mailer'].to_s.should == 'MList Client Application'
31
+ end
32
+
33
+ it 'should use the given mailer' do
34
+ @post.mailer = 'My Program'
35
+ @post.to_tmail['x-mailer'].to_s.should == 'My Program'
36
+ end
37
+
38
+ it 'should use the given subject' do
39
+ @post.subject.should == "I'm a Program!"
40
+ @post.to_tmail.subject.should == "I'm a Program!"
41
+
42
+ @post.subject = 'My Program'
43
+ @post.subject.should == 'My Program'
44
+ @post.to_tmail.subject.should == 'My Program'
45
+ end
46
+
47
+ it 'should assign the identifier it is in-reply-to and references' do
48
+ message = MList::Message.new
49
+ stub(message).identifier { 'blahblah@example.com' }
50
+ @post.reply_to_message = message
51
+ @post.to_tmail.in_reply_to.should == ["<blahblah@example.com>"]
52
+ @post.to_tmail.references.should == ["<blahblah@example.com>"]
53
+ end
54
+
55
+ it 'should include the subscriber display name in the from address if subscriber supports it' do
56
+ stub(@subscriber).display_name { 'Johnny' }
57
+ @post.to_tmail['from'].to_s.should == "Johnny <john@example.com>"
58
+ end
59
+ end
60
+
61
+ describe MList::EmailPost, 'validations' do
62
+ before do
63
+ subscriber = MList::EmailSubscriber.new('john@example.com')
64
+ @post = MList::EmailPost.new({
65
+ :subscriber => subscriber,
66
+ :subject => "I'm a Program!",
67
+ :text => "My simple message that isn't too short"
68
+ })
69
+ end
70
+
71
+ it 'should be valid with subject and a few words' do
72
+ @post.should be_valid
73
+ end
74
+
75
+ it 'should require text' do
76
+ @post.text = ''
77
+ @post.should_not be_valid
78
+ @post.errors[:text].should_not be_nil
79
+ end
80
+
81
+ it 'should require at least 25 characters of text' do
82
+ @post.text = 'A' * 24
83
+ @post.should_not be_valid
84
+ @post.errors[:text].should_not be_nil
85
+
86
+ @post.text = 'A' * 25
87
+ @post.should be_valid
88
+ @post.errors[:text].should be_nil
89
+ end
90
+
91
+ it 'should require subject' do
92
+ @post.subject = ''
93
+ @post.should_not be_valid
94
+ @post.errors[:subject].should_not be_nil
95
+ end
96
+
97
+ it 'should not fail to work when asked to generate error messages' do
98
+ @post.errors.add(:text)
99
+ end
100
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe MList::EmailServer::Base do
4
+ before do
5
+ @email_server = MList::EmailServer::Fake.new(:domain => 'test.host')
6
+ end
7
+
8
+ it 'should provide unique message id generator' do
9
+ @email_server.generate_message_id.should match(/([a-f0-9]+-){4,}[a-f0-9]+@test.host/)
10
+ end
11
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MList::Email do
4
+ before do
5
+ @tmail = tmail_fixture('single_list')
6
+ @email = MList::Email.new(:tmail => @tmail)
7
+ end
8
+
9
+ it 'should downcase the from address' do
10
+ @tmail.from = 'ALL_Down_CaSe@NOmail.NET'
11
+ @email.from_address.should == 'all_down_case@nomail.net'
12
+ end
13
+
14
+ it 'should downcase list addresses' do
15
+ @tmail.to = 'ALL_Down_CaSe@NOmail.NET, ALL_Down_CaSe@YESmail.NET'
16
+ @email.list_addresses.should == %w(all_down_case@nomail.net all_down_case@yesmail.net)
17
+ end
18
+
19
+ it 'should handle @ in label of addresses' do
20
+ @tmail = tmail_fixture('at_in_address_label')
21
+ @email = MList::Email.new(:tmail => @tmail)
22
+ @email.list_addresses.should == %w(whatever@yesmail.net)
23
+ end
24
+
25
+ it 'should include the cc field in list addresses, no duplicates' do
26
+ @tmail.to = 'list_one@example.com'
27
+ @tmail.cc = 'list_one@example.com, list_two@example.com'
28
+ @email.list_addresses.should == %w(list_one@example.com list_two@example.com)
29
+ end
30
+
31
+ it 'should answer the subject of the email' do
32
+ @email.subject.should == 'Test'
33
+ end
34
+
35
+ it 'should be careful to save true source of email' do
36
+ @email = MList::Email.new(:tmail => tmail_fixture('embedded_content'))
37
+ @email.save!
38
+ @email.reload.source.should == email_fixture('embedded_content')
39
+ end
40
+
41
+ it 'should answer the Date of the email, created_at otherwise' do
42
+ @email.date.should == Time.parse('Mon, 15 Dec 2008 00:38:31 -0500')
43
+
44
+ @tmail['date'] = nil
45
+ stub(Time).now { Time.local(2009,1,1) }
46
+ @email.date.should == Time.local(2009,1,1)
47
+
48
+ stub(Time).now { Time.local(2009,3,1) }
49
+ @email.date.should == Time.local(2009,1,1)
50
+
51
+ @email.created_at = Time.local(2009,2,1)
52
+ @email.date.should == Time.local(2009,2,1)
53
+ end
54
+ end
@@ -0,0 +1,469 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MList::MailList do
4
+ class ManagerList
5
+ include MList::List
6
+ end
7
+
8
+ before do
9
+ @manager_list = ManagerList.new
10
+ stub(@manager_list).label {'Discussions'}
11
+ stub(@manager_list).address {'list_one@example.com'}
12
+ stub(@manager_list).list_id {'list_one@example.com'}
13
+
14
+ @subscriber_one = MList::EmailSubscriber.new('adam@nomail.net')
15
+ @subscriber_two = MList::EmailSubscriber.new('john@example.com')
16
+ stub(@manager_list).subscribers {[@subscriber_one, @subscriber_two]}
17
+
18
+ @outgoing_server = MList::EmailServer::Fake.new
19
+ @mail_list = MList::MailList.create!(
20
+ :manager_list => @manager_list,
21
+ :outgoing_server => @outgoing_server)
22
+ end
23
+
24
+ it 'should not require the manager list be an ActiveRecord type' do
25
+ @mail_list.list.should == @manager_list
26
+ @mail_list.manager_list.should be_nil
27
+ end
28
+
29
+ it 'should have messages counted' do
30
+ MList::Message.reflect_on_association(:mail_list).counter_cache_column.should == :messages_count
31
+ MList::MailList.column_names.should include('messages_count')
32
+ end
33
+
34
+ it 'should have threads counted' do
35
+ MList::Thread.reflect_on_association(:mail_list).counter_cache_column.should == :threads_count
36
+ MList::MailList.column_names.should include('threads_count')
37
+ end
38
+
39
+ it 'should delete email not referenced by other lists' do
40
+ email_in_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
41
+ email_not_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
42
+ lambda do
43
+ MList::Message.create!(:mail_list_id => 2342342, :email => email_in_other)
44
+ @mail_list.process_email(email_in_other, @subscriber_one)
45
+ @mail_list.process_email(email_not_other, @subscriber_one)
46
+ end.should change(MList::Message, :count).by(3)
47
+ @mail_list.destroy
48
+ MList::Email.exists?(email_in_other).should be_true
49
+ MList::Email.exists?(email_not_other).should be_false
50
+ end
51
+
52
+ describe 'post' do
53
+ it 'should allow posting a new message to the list' do
54
+ lambda do
55
+ lambda do
56
+ @mail_list.post(
57
+ :subscriber => @subscriber_one,
58
+ :subject => "I'm a Program!",
59
+ :text => 'Are you a programmer or what?'
60
+ )
61
+ end.should change(MList::Message, :count).by(1)
62
+ end.should change(MList::Thread, :count).by(1)
63
+
64
+ tmail = @outgoing_server.deliveries.last
65
+ tmail.subject.should =~ /I'm a Program!/
66
+ tmail.from.should == ['adam@nomail.net']
67
+ end
68
+
69
+ it 'should answer the message for use by the application' do
70
+ @mail_list.post(
71
+ :subscriber => @subscriber_one,
72
+ :subject => "I'm a Program!",
73
+ :text => 'Are you a programmer or what?'
74
+ ).should be_instance_of(MList::Message)
75
+ end
76
+
77
+ it 'should allow posting a reply to an existing message' do
78
+ @mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
79
+ existing_message = @mail_list.messages.last
80
+ lambda do
81
+ lambda do
82
+ @mail_list.post(
83
+ :reply_to_message => existing_message,
84
+ :subscriber => @subscriber_one,
85
+ :text => 'I am a programmer too, dude!'
86
+ )
87
+ end.should change(MList::Message, :count).by(1)
88
+ end.should_not change(MList::Thread, :count)
89
+ new_message = MList::Message.last
90
+ new_message.subject.should == "Re: Test"
91
+ end
92
+
93
+ it 'should not associate a posting to a parent if not reply' do
94
+ @mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
95
+ lambda do
96
+ lambda do
97
+ @mail_list.post(
98
+ :subscriber => @subscriber_one,
99
+ :subject => 'Test',
100
+ :text => 'It is up to the application to provide reply_to'
101
+ )
102
+ end.should change(MList::Message, :count).by(1)
103
+ end.should change(MList::Thread, :count).by(1)
104
+ message = MList::Message.last
105
+ message.parent.should be_nil
106
+ message.parent_identifier.should be_nil
107
+ end
108
+
109
+ it 'should capture the message-id of delivered email' do
110
+ message = @mail_list.post(
111
+ :subscriber => @subscriber_one,
112
+ :subject => 'Test',
113
+ :text => 'Email must have a message id for threading')
114
+ message.reload.identifier.should_not be_nil
115
+ end
116
+
117
+ it 'should copy the subscriber if desired' do
118
+ @mail_list.post(
119
+ :subscriber => @subscriber_one,
120
+ :subject => 'Copy Me',
121
+ :text => 'Email should be sent to subscriber if desired',
122
+ :copy_sender => true)
123
+
124
+ tmail = @outgoing_server.deliveries.last
125
+ tmail.bcc.should include(@subscriber_one.email_address)
126
+ end
127
+
128
+ it 'should not copy the subscriber if undesired and list includes the subscriber' do
129
+ # The MList::List implementor may include the sending subscriber
130
+ stub(@manager_list).recipients {[@subscriber_one, @subscriber_two]}
131
+
132
+ @mail_list.post(
133
+ :subscriber => @subscriber_one,
134
+ :subject => 'Do Not Copy Me',
135
+ :text => 'Email should not be sent to subscriber if undesired',
136
+ :copy_sender => false)
137
+
138
+ tmail = @outgoing_server.deliveries.last
139
+ tmail.bcc.should_not include(@subscriber_one.email_address)
140
+ end
141
+ end
142
+
143
+ describe 'message storage' do
144
+ def process_post
145
+ @mail_list.process_email(MList::Email.new(:tmail => @post_tmail), @subscriber)
146
+ MList::Message.last
147
+ end
148
+
149
+ before do
150
+ @post_tmail = tmail_fixture('single_list')
151
+ @subscriber = @subscriber_one
152
+ end
153
+
154
+ it 'should not include list label in subject' do
155
+ @post_tmail.subject = '[Discussions] Test'
156
+ process_post.subject.should == 'Test'
157
+ end
158
+
159
+ it 'should not include list label in reply subject' do
160
+ @post_tmail.subject = 'Re: [Discussions] Test'
161
+ process_post.subject.should == 'Re: Test'
162
+ end
163
+
164
+ it 'should not bother labels it does not understand in subject' do
165
+ @post_tmail.subject = '[Ann] Test'
166
+ process_post.subject.should == '[Ann] Test'
167
+ end
168
+
169
+ it 'should not bother labels it does not understand in reply subject' do
170
+ @post_tmail.subject = 'Re: [Ann] Test'
171
+ process_post.subject.should == 'Re: [Ann] Test'
172
+ end
173
+
174
+ it 'should be careful of multiple re:' do
175
+ @post_tmail.subject = 'Re: [Ann] RE: Test'
176
+ process_post.subject.should == 'Re: [Ann] Test'
177
+ end
178
+ end
179
+
180
+ describe 'finding parent message' do
181
+ def email(path)
182
+ MList::Email.new(:tmail => tmail_fixture(path))
183
+ end
184
+
185
+ before do
186
+ @parent_message = MList::Message.new
187
+ end
188
+
189
+ it 'should be nil if none found' do
190
+ do_not_call(@mail_list.messages).find
191
+ @mail_list.find_parent_message(email('single_list')).should be_nil
192
+ end
193
+
194
+ it 'should use in-reply-to field when present' do
195
+ mock(@mail_list.messages).find(:first, :conditions => [
196
+ 'identifier = ?', 'F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative.com'
197
+ ]) { @parent_message }
198
+ @mail_list.find_parent_message(email('single_list_reply')).should == @parent_message
199
+ end
200
+
201
+ it 'should be references field if present and no in-reply-to' do
202
+ tmail = tmail_fixture('single_list_reply')
203
+ tmail['in-reply-to'] = nil
204
+ mock(@mail_list.messages).find(:first,
205
+ :conditions => ['identifier in (?)', ['F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative.com']],
206
+ :order => 'created_at desc') { @parent_message }
207
+ @mail_list.find_parent_message(MList::Email.new(:tmail => tmail)).should == @parent_message
208
+ end
209
+
210
+ describe 'by subject' do
211
+ def search_subject(subject = nil)
212
+ simple_matcher("search by the subject '#{subject}'") do |email|
213
+ if subject
214
+ mock(@mail_list.messages).find(
215
+ :first,
216
+ :conditions => ['subject = ?', subject],
217
+ :order => 'created_at asc'
218
+ ) {@parent_message}
219
+ else
220
+ do_not_call(@mail_list.messages).find
221
+ end
222
+ @mail_list.find_parent_message(email)
223
+ !subject.nil?
224
+ end
225
+ end
226
+
227
+ before do
228
+ @parent_message = MList::Message.new
229
+ @mail_list = MList::MailList.new
230
+ stub(@mail_list).label { 'list name' }
231
+ @reply_tmail = tmail_fixture('single_list')
232
+ @reply_email = MList::Email.new(:tmail => @reply_tmail)
233
+ end
234
+
235
+ it 'should be employed if it has "re:" in it' do
236
+ @reply_tmail.subject = "Re: Test"
237
+ @reply_email.should search_subject('Test')
238
+ end
239
+
240
+ it 'should not be employed when no "re:"' do
241
+ @reply_email.should_not search_subject
242
+ end
243
+
244
+ ['RE: [list name] Re: Test', 'Re: [list name] Re: [list name] Test', '[list name] Re: Test'].each do |subject|
245
+ it "should handle '#{subject}'" do
246
+ @reply_tmail.subject = subject
247
+ @reply_email.should search_subject('Test')
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ describe 'delivery' do
254
+ include MList::Util::EmailHelpers
255
+
256
+ def process_post
257
+ @mail_list.process_email(MList::Email.new(:source => @post_tmail.to_s), @subscriber)
258
+ @outgoing_server.deliveries.last
259
+ end
260
+
261
+ before do
262
+ @post_tmail = tmail_fixture('single_list')
263
+ @subscriber = @subscriber_one
264
+ end
265
+
266
+ it 'should be blind copied to recipients' do
267
+ mock.proxy(@mail_list.messages).build(anything) do |message|
268
+ mock(message.delivery).bcc=(%w(john@example.com))
269
+ message
270
+ end
271
+ process_post
272
+ end
273
+
274
+ it 'should not deliver to addresses found in the to header' do
275
+ @post_tmail.to = ['john@example.com', 'list_one@example.com']
276
+ mock.proxy(@mail_list.messages).build(anything) do |message|
277
+ mock(message.delivery).bcc=([])
278
+ message
279
+ end
280
+ process_post
281
+ end
282
+
283
+ it 'should not deliver to addresses found in the cc header' do
284
+ @post_tmail.cc = ['john@example.com']
285
+ mock.proxy(@mail_list.messages).build(anything) do |message|
286
+ mock(message.delivery).bcc=([])
287
+ message
288
+ end
289
+ process_post
290
+ end
291
+
292
+ it 'should use list address as reply-to by default' do
293
+ process_post.should have_header('reply-to', 'Discussions <list_one@example.com>')
294
+ end
295
+
296
+ it 'should use subscriber address as reply-to if list says to not use address' do
297
+ mock(@manager_list).reply_to_list? { false }
298
+ process_post.should have_header('reply-to', 'adam@nomail.net')
299
+ end
300
+
301
+ it 'should use the reply-to already in an email - should not override it' do
302
+ @post_tmail['reply-to'] = 'theotheradam@nomail.net'
303
+ process_post.should have_header('reply-to', 'theotheradam@nomail.net')
304
+ end
305
+
306
+ it 'should set x-beenthere on emails it delivers to keep from re-posting them' do
307
+ process_post.should have_header('x-beenthere', 'list_one@example.com')
308
+ end
309
+
310
+ it 'should not remove any existing x-beenthere headers' do
311
+ @post_tmail['x-beenthere'] = 'somewhere@nomail.net'
312
+ process_post.should have_header('x-beenthere', %w(list_one@example.com somewhere@nomail.net))
313
+ end
314
+
315
+ it 'should not modify existing headers' do
316
+ @post_tmail['x-something-custom'] = 'existing'
317
+ process_post.should have_header('x-something-custom', 'existing')
318
+ end
319
+
320
+ it 'should delete Return-Receipt-To headers since they cause clients to spam the list (the sender address)' do
321
+ @post_tmail['return-receipt-to'] = 'somewhere@nomail.net'
322
+ process_post.should_not have_header('return-receipt-to')
323
+ end
324
+
325
+ it 'should not have any cc addresses' do
326
+ @post_tmail['cc'] = 'billybob@anywhere.com'
327
+ process_post.should_not have_header('cc')
328
+ end
329
+
330
+ it 'should prefix the list label to the subject of messages' do
331
+ process_post.subject.should == '[Discussions] Test'
332
+ end
333
+
334
+ it 'should move the list label to the front of subjects that already include the label' do
335
+ @post_tmail.subject = 'Re: [Discussions] Test'
336
+ process_post.subject.should == 'Re: [Discussions] Test'
337
+ end
338
+
339
+ it 'should remove multiple occurrences of Re:' do
340
+ @post_tmail.subject = 'Re: [Discussions] Re: Test'
341
+ process_post.subject.should == 'Re: [Discussions] Test'
342
+ end
343
+
344
+ it 'should remove DomainKey-Signature headers so that we can sign the redistribution' do
345
+ @post_tmail['DomainKey-Signature'] = "a whole bunch of junk"
346
+ process_post.should_not have_header('domainkey-signature')
347
+ end
348
+
349
+ it 'should remove DKIM-Signature headers so that we can sign the redistribution' do
350
+ @post_tmail['DKIM-Signature'] = "a whole bunch of junk"
351
+ process_post.should_not have_header('dkim-signature')
352
+ end
353
+
354
+ it 'should capture the new message-ids' do
355
+ delivered = process_post
356
+ delivered.header_string('message-id').should_not be_blank
357
+ MList::Message.last.identifier.should == remove_brackets(delivered.header_string('message-id'))
358
+ delivered.header_string('message-id').should_not match(/F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative\.com/)
359
+ end
360
+
361
+ it 'should maintain the content-id part headers (inline images, etc)' do
362
+ @post_tmail = tmail_fixture('embedded_content')
363
+ process_post.parts[1].parts[1]['content-id'].to_s.should == "<CF68EC17-F8ED-478A-A4A1-AEBF165A8830/bg_pattern.jpg>"
364
+ end
365
+
366
+ it 'should add standard list headers when they are available' do
367
+ stub(@manager_list).help_url {'http://list_manager.example.com/help'}
368
+ stub(@manager_list).subscribe_url {'http://list_manager.example.com/subscribe'}
369
+ stub(@manager_list).unsubscribe_url {'http://list_manager.example.com/unsubscribe'}
370
+ stub(@manager_list).owner_url {"<mailto:list_manager@example.com>\n(Jimmy Fish)"}
371
+ stub(@manager_list).archive_url {'http://list_manager.example.com/archive'}
372
+
373
+ tmail = process_post
374
+ tmail.should have_headers(
375
+ 'list-id' => "<list_one@example.com>",
376
+ 'list-help' => "<http://list_manager.example.com/help>",
377
+ 'list-subscribe' => "<http://list_manager.example.com/subscribe>",
378
+ 'list-unsubscribe' => "<http://list_manager.example.com/unsubscribe>",
379
+ 'list-post' => "<list_one@example.com>",
380
+ 'list-owner' => '<mailto:list_manager@example.com>(Jimmy Fish)',
381
+ 'list-archive' => "<http://list_manager.example.com/archive>",
382
+ 'errors-to' => '"Discussions" <mlist-list_one@example.com>',
383
+ # I couldn't get tmail to quote 'Discussions', so apostrophe's would break smtp
384
+ 'sender' => 'mlist-list_one@example.com'
385
+ )
386
+ tmail.header_string('x-mlist-version').should =~ /\d+\.\d+\.\d+/
387
+ end
388
+
389
+ it 'should not add list headers that are not available or nil' do
390
+ stub(@manager_list).help_url {nil}
391
+ delivery = process_post
392
+ delivery.should_not have_header('list-help')
393
+ delivery.should_not have_header('list-subscribe')
394
+ end
395
+
396
+ it 'should append the list footer to text/plain emails' do
397
+ @post_tmail.body = "My Email\n\n\n\n\n"
398
+ mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
399
+ process_post.body.should == "My Email\n\n\n\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
400
+ end
401
+
402
+ it 'should append the list footer to multipart/alternative, text/plain part of emails' do
403
+ @post_tmail = tmail_fixture('content_types/multipart_alternative_simple')
404
+ mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
405
+ process_post.parts[0].body.should match(/#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}/)
406
+ end
407
+
408
+ it 'should handle whitespace well when appending footer' do
409
+ @post_tmail.body = "My Email"
410
+ mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
411
+ process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
412
+ end
413
+
414
+ it 'should strip out any existing footers from the list' do
415
+ mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
416
+ @post_tmail.body = %{My Email
417
+
418
+ > > #{MList::MailList::FOOTER_BLOCK_START}
419
+ > > content at front shouldn't matter
420
+ > > #{MList::MailList::FOOTER_BLOCK_END}
421
+
422
+ >> #{MList::MailList::FOOTER_BLOCK_START}
423
+ >> this is fine to be removed
424
+ >> #{MList::MailList::FOOTER_BLOCK_END}
425
+
426
+ #{MList::MailList::FOOTER_BLOCK_START}
427
+ this is without any in front
428
+ #{MList::MailList::FOOTER_BLOCK_END}
429
+ }
430
+ process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
431
+ end
432
+
433
+ describe 'time' do
434
+ include TMail::TextUtils
435
+
436
+ before do
437
+ @old_zone_default = Time.zone_default
438
+ @system_time = Time.parse('Thu, 2 Apr 2009 15:22:04')
439
+ mock(Time).now.times(any_times) { @system_time }
440
+ end
441
+
442
+ after do
443
+ Time.zone_default = @old_zone_default
444
+ end
445
+
446
+ it 'should keep date of email post' do
447
+ @post_tmail['date'] = 'Thu, 2 Apr 2009 15:22:04 -0400'
448
+ process_post.header_string('date').should == 'Thu, 2 Apr 2009 15:22:04 -0400'
449
+ end
450
+
451
+ it 'should store the delivery time as created_at of message record' do
452
+ Time.zone_default = 'Pacific Time (US & Canada)'
453
+ @post_tmail['date'] = 'Wed, 1 Apr 2009 15:22:04 -0400'
454
+ process_post.header_string('date').should == 'Wed, 1 Apr 2009 15:22:04 -0400'
455
+ MList::Message.last.created_at.should == @system_time
456
+ end
457
+
458
+ # I think that what TMail is doing is evil, but it's reference to
459
+ # a ruby-talk discussion leads to Japanese, which I cannot read.
460
+ # I'd prefer that it leave the problem of timezones up to the client,
461
+ # especially since ActiveSupport does an EXCELLENT job of making
462
+ # time zones not hurt so much.
463
+ it 'should use the Time.now (zone of the machine) for date header' do
464
+ @post_tmail['date'] = nil
465
+ process_post.header_string('date').should == time2str(@system_time)
466
+ end
467
+ end
468
+ end
469
+ end