1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe EmailsHelper do 6 describe 'closure_reason_text' do 7 context 'when given a MergeRequest' do 8 let(:merge_request) { create(:merge_request) } 9 let(:merge_request_presenter) { merge_request.present } 10 11 context 'when user can read merge request' do 12 let(:user) { create(:user) } 13 14 before do 15 merge_request.project.add_developer(user) 16 self.instance_variable_set(:@recipient, user) 17 self.instance_variable_set(:@project, merge_request.project) 18 end 19 20 context "and format is text" do 21 it "returns plain text" do 22 expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") 23 end 24 end 25 26 context "and format is HTML" do 27 it "returns HTML" do 28 expect(helper.closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}") 29 end 30 end 31 32 context "and format is unknown" do 33 it "returns plain text" do 34 expect(helper.closure_reason_text(merge_request, format: 'unknown')).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})") 35 end 36 end 37 end 38 39 context 'when user cannot read merge request' do 40 it "does not have link to merge request" do 41 expect(helper.closure_reason_text(merge_request)).to be_empty 42 end 43 end 44 end 45 46 context 'when given a String' do 47 let(:user) { create(:user) } 48 let(:project) { create(:project) } 49 let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" } 50 51 context 'when user can read commits' do 52 before do 53 project.add_developer(user) 54 self.instance_variable_set(:@recipient, user) 55 self.instance_variable_set(:@project, project) 56 end 57 58 it "returns plain text" do 59 expect(closure_reason_text(closed_via)).to eq("via #{closed_via}") 60 end 61 end 62 63 context 'when user cannot read commits' do 64 it "returns plain text" do 65 expect(closure_reason_text(closed_via)).to be_empty 66 end 67 end 68 end 69 70 context 'when not given anything' do 71 it "returns empty string" do 72 expect(closure_reason_text(nil)).to eq("") 73 end 74 end 75 end 76 77 describe 'notification_reason_text' do 78 subject { helper.notification_reason_text(reason_code) } 79 80 using RSpec::Parameterized::TableSyntax 81 82 where(:reason_code, :reason_text) do 83 NotificationReason::OWN_ACTIVITY | ' of your activity ' 84 NotificationReason::ASSIGNED | ' you have been assigned an item ' 85 NotificationReason::MENTIONED | ' you have been mentioned ' 86 "" | ' of your account ' 87 nil | ' of your account ' 88 end 89 90 with_them do 91 it { is_expected.to start_with "You're receiving this email because" } 92 93 it { is_expected.to include reason_text } 94 95 it { is_expected.to end_with "on #{Gitlab.config.gitlab.host}." } 96 end 97 end 98 99 describe 'sanitize_name' do 100 context 'when name contains a valid URL string' do 101 it 'returns name with `.` replaced with `_` to prevent mail clients from auto-linking URLs' do 102 expect(sanitize_name('https://about.gitlab.com')).to eq('https://about_gitlab_com') 103 expect(sanitize_name('www.gitlab.com')).to eq('www_gitlab_com') 104 expect(sanitize_name('//about.gitlab.com/handbook/security/#best-practices')).to eq('//about_gitlab_com/handbook/security/#best-practices') 105 end 106 107 it 'returns name as it is when it does not contain a URL' do 108 expect(sanitize_name('Foo Bar')).to eq('Foo Bar') 109 end 110 end 111 end 112 113 describe '#say_hi' do 114 let(:user) { create(:user, name: 'John') } 115 116 it 'returns the greeting message for the given user' do 117 expect(say_hi(user)).to eq('Hi John!') 118 end 119 end 120 121 describe '#say_hello' do 122 let(:user) { build(:user, name: 'John') } 123 124 it 'returns the greeting message for the given user' do 125 expect(say_hello(user)).to eq('Hello, John!') 126 end 127 end 128 129 describe '#two_factor_authentication_disabled_text' do 130 it 'returns the message that 2FA is disabled' do 131 expect(two_factor_authentication_disabled_text).to eq( 132 _('Two-factor authentication has been disabled for your GitLab account.') 133 ) 134 end 135 end 136 137 describe '#re_enable_two_factor_authentication_text' do 138 context 'format is html' do 139 it 'returns HTML' do 140 expect(re_enable_two_factor_authentication_text(format: :html)).to eq( 141 "If you want to re-enable two-factor authentication, visit the " \ 142 "#{link_to('two-factor authentication settings', profile_two_factor_auth_url, target: :_blank, rel: 'noopener noreferrer')} page." 143 ) 144 end 145 end 146 147 context 'format is not specified' do 148 it 'returns text' do 149 expect(re_enable_two_factor_authentication_text).to eq( 150 "If you want to re-enable two-factor authentication, visit #{profile_two_factor_auth_url}" 151 ) 152 end 153 end 154 end 155 156 describe '#admin_changed_password_text' do 157 context 'format is html' do 158 it 'returns HTML' do 159 expect(admin_changed_password_text(format: :html)).to eq( 160 "An administrator changed the password for your GitLab account on " \ 161 "#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url, target: :_blank, rel: 'noopener noreferrer')}." 162 ) 163 end 164 end 165 166 context 'format is not specified' do 167 it 'returns text' do 168 expect(admin_changed_password_text).to eq( 169 "An administrator changed the password for your GitLab account on #{Gitlab.config.gitlab.url}." 170 ) 171 end 172 end 173 end 174 175 describe '#contact_your_administrator_text' do 176 it 'returns the message to contact the administrator' do 177 expect(contact_your_administrator_text).to eq( 178 _('Please contact your administrator with any questions.') 179 ) 180 end 181 end 182 183 describe 'password_reset_token_valid_time' do 184 def validate_time_string(time_limit, expected_string) 185 Devise.reset_password_within = time_limit 186 expect(password_reset_token_valid_time).to eq(expected_string) 187 end 188 189 context 'when time limit is less than 2 hours' do 190 it 'displays the time in hours using a singular unit' do 191 validate_time_string(1.hour, '1 hour') 192 end 193 end 194 195 context 'when time limit is 2 or more hours' do 196 it 'displays the time in hours using a plural unit' do 197 validate_time_string(2.hours, '2 hours') 198 end 199 end 200 201 context 'when time limit contains fractions of an hour' do 202 it 'rounds down to the nearest hour' do 203 validate_time_string(96.minutes, '1 hour') 204 end 205 end 206 207 context 'when time limit is 24 or more hours' do 208 it 'displays the time in days using a singular unit' do 209 validate_time_string(24.hours, '1 day') 210 end 211 end 212 213 context 'when time limit is 2 or more days' do 214 it 'displays the time in days using a plural unit' do 215 validate_time_string(2.days, '2 days') 216 end 217 end 218 219 context 'when time limit contains fractions of a day' do 220 it 'rounds down to the nearest day' do 221 validate_time_string(57.hours, '2 days') 222 end 223 end 224 end 225 226 describe '#header_logo' do 227 context 'there is a brand item with a logo' do 228 it 'returns the brand header logo' do 229 appearance = create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png') 230 231 expect(header_logo).to eq( 232 %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" />} 233 ) 234 end 235 end 236 237 context 'there is a brand item without a logo' do 238 it 'returns the default header logo' do 239 create :appearance, header_logo: nil 240 241 expect(header_logo).to match( 242 %r{<img alt="GitLab" src="/images/mailers/gitlab_header_logo\.(?:gif|png)" width="\d+" height="\d+" />} 243 ) 244 end 245 end 246 247 context 'there is no brand item' do 248 it 'returns the default header logo' do 249 expect(header_logo).to match( 250 %r{<img alt="GitLab" src="/images/mailers/gitlab_header_logo\.(?:gif|png)" width="\d+" height="\d+" />} 251 ) 252 end 253 end 254 end 255 256 describe '#create_list_id_string' do 257 using RSpec::Parameterized::TableSyntax 258 259 where(:full_path, :list_id_path) do 260 "01234" | "01234" 261 "5/0123" | "012.." 262 "45/012" | "012.." 263 "012" | "012" 264 "23/01" | "01.23" 265 "2/01" | "01.2" 266 "234/01" | "01.." 267 "4/2/0" | "0.2.4" 268 "45/2/0" | "0.2.." 269 "5/23/0" | "0.." 270 "0-2/5" | "5.0-2" 271 "0_2/5" | "5.0-2" 272 "0.2/5" | "5.0-2" 273 end 274 275 with_them do 276 it 'ellipcizes different variants' do 277 project = double("project") 278 allow(project).to receive(:full_path).and_return(full_path) 279 allow(project).to receive(:id).and_return(12345) 280 # Set a max length that gives only 5 chars for the project full path 281 max_length = "12345..#{Gitlab.config.gitlab.host}".length + 5 282 list_id = create_list_id_string(project, max_length) 283 284 expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") 285 expect(list_id).to satisfy { |s| s.length <= max_length } 286 end 287 end 288 end 289 290 describe 'Create realistic List-Id identifier' do 291 using RSpec::Parameterized::TableSyntax 292 293 where(:full_path, :list_id_path) do 294 "gitlab-org/gitlab-ce" | "gitlab-ce.gitlab-org" 295 "project-name/subproject_name/my.project" | "my-project.subproject-name.project-name" 296 end 297 298 with_them do 299 it 'produces the right List-Id' do 300 project = double("project") 301 allow(project).to receive(:full_path).and_return(full_path) 302 allow(project).to receive(:id).and_return(12345) 303 list_id = create_list_id_string(project) 304 305 expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") 306 expect(list_id).to satisfy { |s| s.length <= 255 } 307 end 308 end 309 end 310 311 describe 'header and footer messages' do 312 context 'when email_header_and_footer_enabled is enabled' do 313 it 'returns header and footer messages' do 314 create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: true 315 316 aggregate_failures do 317 expect(html_header_message).to eq(%{<div class="header-message" style=""><p>Foo</p></div>}) 318 expect(html_footer_message).to eq(%{<div class="footer-message" style=""><p>Bar</p></div>}) 319 expect(text_header_message).to eq('Foo') 320 expect(text_footer_message).to eq('Bar') 321 end 322 end 323 324 context 'when header and footer messages are empty' do 325 it 'returns nil' do 326 create :appearance, header_message: '', footer_message: '', email_header_and_footer_enabled: true 327 328 aggregate_failures do 329 expect(html_header_message).to eq(nil) 330 expect(html_footer_message).to eq(nil) 331 expect(text_header_message).to eq(nil) 332 expect(text_footer_message).to eq(nil) 333 end 334 end 335 end 336 337 context 'when header and footer messages are nil' do 338 it 'returns nil' do 339 create :appearance, header_message: nil, footer_message: nil, email_header_and_footer_enabled: true 340 341 aggregate_failures do 342 expect(html_header_message).to eq(nil) 343 expect(html_footer_message).to eq(nil) 344 expect(text_header_message).to eq(nil) 345 expect(text_footer_message).to eq(nil) 346 end 347 end 348 end 349 end 350 351 context 'when email_header_and_footer_enabled is disabled' do 352 it 'returns header and footer messages' do 353 create :appearance, header_message: 'Foo', footer_message: 'Bar', email_header_and_footer_enabled: false 354 355 aggregate_failures do 356 expect(html_header_message).to eq(nil) 357 expect(html_footer_message).to eq(nil) 358 expect(text_header_message).to eq(nil) 359 expect(text_footer_message).to eq(nil) 360 end 361 end 362 end 363 end 364 365 describe '#change_reviewer_notification_text' do 366 let(:mary) { build(:user, name: 'Mary') } 367 let(:john) { build(:user, name: 'John') } 368 let(:ted) { build(:user, name: 'Ted') } 369 370 context 'to new reviewers only' do 371 let(:previous_reviewers) { [] } 372 let(:new_reviewers) { [john] } 373 374 context 'with no html tag' do 375 let(:expected_output) do 376 'Reviewer changed to John' 377 end 378 379 it 'returns the expected output' do 380 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers)).to eq(expected_output) 381 end 382 end 383 384 context 'with <strong> tag' do 385 let(:expected_output) do 386 'Reviewer changed to <strong>John</strong>' 387 end 388 389 it 'returns the expected output' do 390 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers, :strong)).to eq(expected_output) 391 end 392 end 393 end 394 395 context 'from previous reviewers to new reviewers' do 396 let(:previous_reviewers) { [john, mary] } 397 let(:new_reviewers) { [ted] } 398 399 context 'with no html tag' do 400 let(:expected_output) do 401 'Reviewer changed from John and Mary to Ted' 402 end 403 404 it 'returns the expected output' do 405 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers)).to eq(expected_output) 406 end 407 end 408 409 context 'with <strong> tag' do 410 let(:expected_output) do 411 'Reviewer changed from <strong>John and Mary</strong> to <strong>Ted</strong>' 412 end 413 414 it 'returns the expected output' do 415 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers, :strong)).to eq(expected_output) 416 end 417 end 418 end 419 420 context 'from previous reviewers to no reviewers' do 421 let(:previous_reviewers) { [john, mary] } 422 let(:new_reviewers) { [] } 423 424 context 'with no html tag' do 425 let(:expected_output) do 426 'Reviewer changed from John and Mary to Unassigned' 427 end 428 429 it 'returns the expected output' do 430 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers)).to eq(expected_output) 431 end 432 end 433 434 context 'with <strong> tag' do 435 let(:expected_output) do 436 'Reviewer changed from <strong>John and Mary</strong> to <strong>Unassigned</strong>' 437 end 438 439 it 'returns the expected output' do 440 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers, :strong)).to eq(expected_output) 441 end 442 end 443 end 444 445 context "with a <script> tag in user's name" do 446 let(:previous_reviewers) { [] } 447 let(:new_reviewers) { [fishy_user] } 448 let(:fishy_user) { build(:user, name: "<script>alert('hi')</script>") } 449 450 let(:expected_output) do 451 'Reviewer changed to <strong><script>alert('hi')</script></strong>' 452 end 453 454 it 'escapes the html tag' do 455 expect(change_reviewer_notification_text(new_reviewers, previous_reviewers, :strong)).to eq(expected_output) 456 end 457 end 458 459 context "with url in user's name" do 460 subject(:email_helper) { Object.new.extend(described_class) } 461 462 let(:previous_reviewers) { [] } 463 let(:new_reviewers) { [fishy_user] } 464 let(:fishy_user) { build(:user, name: "example.com") } 465 466 let(:expected_output) do 467 'Reviewer changed to example_com' 468 end 469 470 it "sanitizes user's name" do 471 expect(email_helper).to receive(:sanitize_name).and_call_original 472 expect(email_helper.change_reviewer_notification_text(new_reviewers, previous_reviewers)).to eq(expected_output) 473 end 474 end 475 end 476end 477