1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
|
shared_context 'gitlab email notification' do
set(:project) { create(:project, :repository, name: 'a-known-name') }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:new_user_address) { 'newguy@example.com' }
before do
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}")
end
end
shared_context 'reply-by-email is enabled with incoming address without %{key}' do
before do
stub_incoming_email_setting(enabled: true, address: "reply@#{Gitlab.config.gitlab.host}")
end
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'has the characteristics of an email sent from GitLab' do
sender = subject.header[:from].addrs[0]
reply_to = subject.header[:reply_to].addresses
aggregate_failures do
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email with X-GitLab headers containing IDs' do
it 'has X-GitLab-*-ID header' do
is_expected.to have_header "X-GitLab-#{model.class.name}-ID", "#{model.id}"
end
it 'has X-GitLab-*-IID header if model has iid defined' do
if model.respond_to?(:iid)
is_expected.to have_header "X-GitLab-#{model.class.name}-IID", "#{model.iid}"
else
expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
end
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project headers' do
aggregate_failures do
full_path_as_domain = "#{project.name}.#{project.namespace.path}"
is_expected.to have_header('X-GitLab-Project', /#{project.name}/)
is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/)
is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/)
is_expected.to have_header('List-Id', "#{project.full_path} <#{project.id}.#{full_path_as_domain}.#{Gitlab.config.gitlab.host}>")
end
end
end
shared_examples 'a new thread email with reply-by-email enabled' do
it 'has the characteristics of a threaded email' do
host = Gitlab.config.gitlab.host
route_key = "#{model.class.model_name.singular_route_key}_#{model.id}"
aggregate_failures do
is_expected.to have_header('Message-ID', "<#{route_key}@#{host}>")
is_expected.to have_header('References', /\A<reply\-.*@#{host}>\Z/ )
end
end
end
shared_examples 'a thread answer email with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
include_examples 'an email with X-GitLab headers containing IDs'
it 'has the characteristics of a threaded reply' do
host = Gitlab.config.gitlab.host
route_key = "#{model.class.model_name.singular_route_key}_#{model.id}"
aggregate_failures do
is_expected.to have_header('Message-ID', /\A<.*@#{host}>\Z/)
is_expected.to have_header('In-Reply-To', "<#{route_key}@#{host}>")
is_expected.to have_header('References', /\A<reply\-.*@#{host}> <#{route_key}@#{host}>\Z/ )
is_expected.to have_subject(/^Re: /)
end
end
end
shared_examples 'an email starting a new thread with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
include_examples 'an email with X-GitLab headers containing IDs'
include_examples 'a new thread email with reply-by-email enabled'
it 'includes "Reply to this email directly or <View it on GitLab>"' do
expect(subject.default_part.body).to include(%(Reply to this email directly or <a href="#{Gitlab::UrlBuilder.build(model)}">view it on GitLab</a>.))
end
context 'when reply-by-email is enabled with incoming address with %{key}' do
it 'has a Reply-To header' do
is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/
end
end
context 'when reply-by-email is enabled with incoming address without %{key}' do
include_context 'reply-by-email is enabled with incoming address without %{key}'
include_examples 'a new thread email with reply-by-email enabled'
it 'has a Reply-To header' do
is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/
end
end
end
shared_examples 'an answer to an existing thread with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
include_examples 'an email with X-GitLab headers containing IDs'
include_examples 'a thread answer email with reply-by-email enabled'
context 'when reply-by-email is enabled with incoming address with %{key}' do
it 'has a Reply-To header' do
is_expected.to have_header 'Reply-To', /<reply+(.*)@#{Gitlab.config.gitlab.host}>\Z/
end
end
context 'when reply-by-email is enabled with incoming address without %{key}' do
include_context 'reply-by-email is enabled with incoming address without %{key}'
include_examples 'a thread answer email with reply-by-email enabled'
it 'has a Reply-To header' do
is_expected.to have_header 'Reply-To', /<reply@#{Gitlab.config.gitlab.host}>\Z/
end
end
end
shared_examples 'it should have Gmail Actions links' do
it do
aggregate_failures do
is_expected.to have_body_text('<script type="application/ld+json">')
is_expected.to have_body_text('ViewAction')
end
end
end
shared_examples 'it should not have Gmail Actions links' do
it do
aggregate_failures do
is_expected.not_to have_body_text('<script type="application/ld+json">')
is_expected.not_to have_body_text('ViewAction')
end
end
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text('View Issue') }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text('View Merge request') }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text('View Commit') }
end
shared_examples 'an unsubscribeable thread' do
it_behaves_like 'an unsubscribeable thread with incoming address without %{key}'
it 'has a List-Unsubscribe header in the correct format, and a body link' do
aggregate_failures do
is_expected.to have_header('List-Unsubscribe', /unsubscribe/)
is_expected.to have_header('List-Unsubscribe', /mailto/)
is_expected.to have_header('List-Unsubscribe', /^<.+,.+>$/)
is_expected.to have_body_text('unsubscribe')
end
end
end
shared_examples 'an unsubscribeable thread with incoming address without %{key}' do
include_context 'reply-by-email is enabled with incoming address without %{key}'
it 'has a List-Unsubscribe header in the correct format, and a body link' do
aggregate_failures do
is_expected.to have_header('List-Unsubscribe', /unsubscribe/)
is_expected.not_to have_header('List-Unsubscribe', /mailto/)
is_expected.to have_header('List-Unsubscribe', /^<[^,]+>$/)
is_expected.to have_body_text('unsubscribe')
end
end
end
shared_examples 'a user cannot unsubscribe through footer link' do
it 'does not have a List-Unsubscribe header or a body link' do
aggregate_failures do
is_expected.not_to have_header('List-Unsubscribe', /unsubscribe/)
is_expected.not_to have_body_text('unsubscribe')
end
end
end
shared_examples 'an email with a labels subscriptions link in its footer' do
it { is_expected.to have_body_text('label subscriptions') }
end
shared_examples 'a note email' do
it_behaves_like 'it should have Gmail Actions links'
it 'is sent to the given recipient as the author' do
sender = subject.header[:from].addrs[0]
aggregate_failures do
expect(sender.display_name).to eq(note_author.name)
expect(sender.address).to eq(gitlab_sender)
expect(subject).to deliver_to(recipient.notification_email)
end
end
it 'contains the message from the note' do
is_expected.to have_body_text note.note
end
it 'does not contain note author' do
is_expected.not_to have_body_text note.author_name
end
context 'when enabled email_author_in_body' do
before do
stub_application_setting(email_author_in_body: true)
end
it 'contains a link to note author' do
is_expected.to have_body_text note.author_name
end
end
end
|