mlist 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
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