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.
- data/CHANGELOG +59 -0
- data/README +204 -0
- data/Rakefile +27 -0
- data/TODO +36 -0
- data/VERSION.yml +4 -0
- data/lib/mlist/email.rb +69 -0
- data/lib/mlist/email_post.rb +126 -0
- data/lib/mlist/email_server/base.rb +33 -0
- data/lib/mlist/email_server/default.rb +31 -0
- data/lib/mlist/email_server/fake.rb +16 -0
- data/lib/mlist/email_server/pop.rb +28 -0
- data/lib/mlist/email_server/smtp.rb +24 -0
- data/lib/mlist/email_server.rb +2 -0
- data/lib/mlist/email_subscriber.rb +6 -0
- data/lib/mlist/list.rb +183 -0
- data/lib/mlist/mail_list.rb +277 -0
- data/lib/mlist/manager/database.rb +48 -0
- data/lib/mlist/manager/notifier.rb +31 -0
- data/lib/mlist/manager.rb +30 -0
- data/lib/mlist/message.rb +150 -0
- data/lib/mlist/server.rb +62 -0
- data/lib/mlist/thread.rb +98 -0
- data/lib/mlist/util/email_helpers.rb +155 -0
- data/lib/mlist/util/header_sanitizer.rb +71 -0
- data/lib/mlist/util/quoting.rb +70 -0
- data/lib/mlist/util/tmail_builder.rb +42 -0
- data/lib/mlist/util/tmail_methods.rb +138 -0
- data/lib/mlist/util.rb +12 -0
- data/lib/mlist.rb +46 -0
- data/lib/pop_ssl.rb +999 -0
- data/rails/init.rb +22 -0
- data/spec/fixtures/schema.rb +94 -0
- data/spec/integration/date_formats_spec.rb +12 -0
- data/spec/integration/mlist_spec.rb +232 -0
- data/spec/integration/pop_email_server_spec.rb +22 -0
- data/spec/integration/proof_spec.rb +74 -0
- data/spec/matchers/equal_tmail.rb +53 -0
- data/spec/matchers/have_address.rb +48 -0
- data/spec/matchers/have_header.rb +104 -0
- data/spec/models/email_post_spec.rb +100 -0
- data/spec/models/email_server/base_spec.rb +11 -0
- data/spec/models/email_spec.rb +54 -0
- data/spec/models/mail_list_spec.rb +469 -0
- data/spec/models/message_spec.rb +109 -0
- data/spec/models/thread_spec.rb +83 -0
- data/spec/models/util/email_helpers_spec.rb +47 -0
- data/spec/models/util/header_sanitizer_spec.rb +19 -0
- data/spec/models/util/quoting_spec.rb +96 -0
- data/spec/spec_helper.rb +76 -0
- 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
|