Commit 29cfd33d949d21d67f3892473c24d4f0a127dfe6

Authored by Jason Hollingsworth
1 parent d41e404e

Add email aliases for users

Emails are used to associate commits with users. The emails
are not verified and don't have to be valid email addresses. They
are assigned on a first come, first serve basis.

Notifications are sent when an email is added.
app/controllers/profiles/emails_controller.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +class Profiles::EmailsController < ApplicationController
  2 + layout "profile"
  3 +
  4 + def index
  5 + @primary = current_user.email
  6 + @emails = current_user.emails
  7 + end
  8 +
  9 + def create
  10 + @email = current_user.emails.new(params[:email])
  11 +
  12 + flash[:alert] = @email.errors.full_messages.first unless @email.save
  13 +
  14 + redirect_to profile_emails_url
  15 + end
  16 +
  17 + def destroy
  18 + @email = current_user.emails.find(params[:id])
  19 + @email.destroy
  20 +
  21 + respond_to do |format|
  22 + format.html { redirect_to profile_emails_url }
  23 + format.js { render nothing: true }
  24 + end
  25 + end
  26 +end
... ...
app/helpers/commits_helper.rb
... ... @@ -122,17 +122,18 @@ module CommitsHelper
122 122 def commit_person_link(commit, options = {})
123 123 source_name = commit.send "#{options[:source]}_name".to_sym
124 124 source_email = commit.send "#{options[:source]}_email".to_sym
  125 +
  126 + user = User.find_for_commit(source_email, source_name)
  127 + person_name = user.nil? ? source_name : user.name
  128 + person_email = user.nil? ? source_email : user.email
  129 +
125 130 text = if options[:avatar]
126   - avatar = image_tag(avatar_icon(source_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
127   - %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{source_name}</span>}
  131 + avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
  132 + %Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
128 133 else
129   - source_name
  134 + person_name
130 135 end
131 136  
132   - # Prefer email match over name match
133   - user = User.where(email: source_email).first
134   - user ||= User.where(name: source_name).first
135   -
136 137 options = {
137 138 class: "commit-#{options[:source]}-link has_tooltip",
138 139 data: { :'original-title' => sanitize(source_email) }
... ...
app/mailers/emails/profile.rb
... ... @@ -6,6 +6,12 @@ module Emails
6 6 mail(to: @user.email, subject: subject("Account was created for you"))
7 7 end
8 8  
  9 + def new_email_email(email_id)
  10 + @email = Email.find(email_id)
  11 + @user = @email.user
  12 + mail(to: @user.email, subject: subject("Email was added to your account"))
  13 + end
  14 +
9 15 def new_ssh_key_email(key_id)
10 16 @key = Key.find(key_id)
11 17 @user = @key.user
... ...
app/models/email.rb 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: emails
  4 +#
  5 +# id :integer not null, primary key
  6 +# user_id :integer not null
  7 +# email :string not null
  8 +# created_at :datetime not null
  9 +class Email < ActiveRecord::Base
  10 + attr_accessible :email, :user_id
  11 +
  12 + #
  13 + # Relations
  14 + #
  15 + belongs_to :user
  16 +
  17 + #
  18 + # Validations
  19 + #
  20 + validates :user_id, presence: true
  21 + validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
  22 + validate :unique_email, if: ->(email) { email.email_changed? }
  23 +
  24 + before_validation :cleanup_email
  25 +
  26 + def cleanup_email
  27 + self.email = self.email.downcase.strip
  28 + end
  29 +
  30 + def unique_email
  31 + self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
  32 + end
  33 +end
0 34 \ No newline at end of file
... ...
app/models/user.rb
... ... @@ -78,6 +78,7 @@ class User &lt; ActiveRecord::Base
78 78  
79 79 # Profile
80 80 has_many :keys, dependent: :destroy
  81 + has_many :emails, dependent: :destroy
81 82  
82 83 # Groups
83 84 has_many :users_groups, dependent: :destroy
... ... @@ -116,6 +117,7 @@ class User &lt; ActiveRecord::Base
116 117 validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
117 118 validate :namespace_uniq, if: ->(user) { user.username_changed? }
118 119 validate :avatar_type, if: ->(user) { user.avatar_changed? }
  120 + validate :unique_email, if: ->(user) { user.email_changed? }
119 121 validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
120 122  
121 123 before_validation :generate_password, on: :create
... ... @@ -183,6 +185,13 @@ class User &lt; ActiveRecord::Base
183 185 where(conditions).first
184 186 end
185 187 end
  188 +
  189 + def find_for_commit(email, name)
  190 + # Prefer email match over name match
  191 + User.where(email: email).first ||
  192 + User.joins(:emails).where(emails: { email: email }).first ||
  193 + User.where(name: name).first
  194 + end
186 195  
187 196 def filter filter_name
188 197 case filter_name
... ... @@ -250,6 +259,10 @@ class User &lt; ActiveRecord::Base
250 259 end
251 260 end
252 261  
  262 + def unique_email
  263 + self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email)
  264 + end
  265 +
253 266 # Groups user has access to
254 267 def authorized_groups
255 268 @authorized_groups ||= begin
... ...
app/observers/email_observer.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class EmailObserver < BaseObserver
  2 + def after_create(email)
  3 + notification.new_email(email)
  4 + end
  5 +end
... ...
app/services/git_push_service.rb
... ... @@ -188,8 +188,6 @@ class GitPushService
188 188 end
189 189  
190 190 def commit_user commit
191   - User.where(email: commit.author_email).first ||
192   - User.where(name: commit.author_name).first ||
193   - user
  191 + User.find_for_commit(commit.author_email, commit.author_name) || user
194 192 end
195 193 end
... ...
app/services/notification_service.rb
... ... @@ -17,6 +17,13 @@ class NotificationService
17 17 end
18 18 end
19 19  
  20 + # Always notify user about email added to profile
  21 + def new_email(email)
  22 + if email.user
  23 + mailer.new_email_email(email.id)
  24 + end
  25 + end
  26 +
20 27 # When create an issue we should send next emails:
21 28 #
22 29 # * issue assignee if their notification level is not Disabled
... ...
app/views/layouts/nav/_profile.html.haml
... ... @@ -4,6 +4,10 @@
4 4 %i.icon-home
5 5 = nav_link(controller: :accounts) do
6 6 = link_to "Account", profile_account_path
  7 + = nav_link(controller: :emails) do
  8 + = link_to profile_emails_path do
  9 + Emails
  10 + %span.count= current_user.emails.count + 1
7 11 - unless current_user.ldap_user?
8 12 = nav_link(controller: :passwords) do
9 13 = link_to "Password", edit_profile_password_path
... ...
app/views/notify/new_email_email.html.haml 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +%p
  2 + Hi #{@user.name}!
  3 +%p
  4 + A new email was added to your account:
  5 +%p
  6 + email:
  7 + %code= @email.email
  8 +%p
  9 + If this email was added in error, you can remove it here:
  10 + = link_to "Emails", profile_emails_url
... ...
app/views/notify/new_email_email.text.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +Hi <%= @user.name %>!
  2 +
  3 +A new email was added to your account:
  4 +
  5 +email.................. <%= @email.email %>
  6 +
  7 +If this email was added in error, you can remove it here: <%= profile_emails_url %>
... ...
app/views/profiles/emails/index.html.haml 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +%h3.page-title
  2 + My Email Addresses
  3 +%p.light
  4 + Your
  5 + %b Primary Email
  6 + will be used for account notifications, avatar detection and web based operations, such as edits and merges. All email addresses will be used to identify your commits.
  7 +
  8 +.ui-box
  9 + .title
  10 + Emails (#{@emails.count + 1})
  11 + %ul.well-list#emails-table
  12 + %li
  13 + %strong= @primary
  14 + %span.label.label-success Primary Email
  15 + - @emails.each do |email|
  16 + %li
  17 + %strong= email.email
  18 + %span.cgray
  19 + added #{time_ago_with_tooltip(email.created_at)}
  20 + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right'
  21 +
  22 +%h3.page-title Add Email Address
  23 += form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
  24 + .form-group
  25 + = f.label :email, class: 'control-label'
  26 + .col-sm-10
  27 + = f.text_field :email, class: 'form-control'
  28 + .form-actions
  29 + = f.submit 'Add', class: 'btn btn-create'
0 30 \ No newline at end of file
... ...
config/routes.rb
... ... @@ -124,6 +124,7 @@ Gitlab::Application.routes.draw do
124 124 end
125 125 end
126 126 resources :keys
  127 + resources :emails, only: [:index, :create, :destroy]
127 128 resources :groups, only: [:index] do
128 129 member do
129 130 delete :leave
... ...
db/migrate/20140209025651_create_emails.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class CreateEmails < ActiveRecord::Migration
  2 + def change
  3 + create_table :emails do |t|
  4 + t.integer :user_id, null: false
  5 + t.string :email, null: false
  6 +
  7 + t.timestamps
  8 + end
  9 +
  10 + add_index :emails, :user_id
  11 + add_index :emails, :email, unique: true
  12 + end
  13 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(version: 20140127170938) do
  14 +ActiveRecord::Schema.define(version: 20140209025651) do
15 15  
16 16 create_table "broadcast_messages", force: true do |t|
17 17 t.text "message", null: false
... ... @@ -33,6 +33,16 @@ ActiveRecord::Schema.define(version: 20140127170938) do
33 33  
34 34 add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
35 35  
  36 + create_table "emails", force: true do |t|
  37 + t.integer "user_id", null: false
  38 + t.string "email", null: false
  39 + t.datetime "created_at"
  40 + t.datetime "updated_at"
  41 + end
  42 +
  43 + add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
  44 + add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
  45 +
36 46 create_table "events", force: true do |t|
37 47 t.string "target_type"
38 48 t.integer "target_id"
... ... @@ -66,8 +76,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
66 76 t.integer "assignee_id"
67 77 t.integer "author_id"
68 78 t.integer "project_id"
69   - t.datetime "created_at"
70   - t.datetime "updated_at"
  79 + t.datetime "created_at", null: false
  80 + t.datetime "updated_at", null: false
71 81 t.integer "position", default: 0
72 82 t.string "branch_name"
73 83 t.text "description"
... ... @@ -85,8 +95,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
85 95  
86 96 create_table "keys", force: true do |t|
87 97 t.integer "user_id"
88   - t.datetime "created_at"
89   - t.datetime "updated_at"
  98 + t.datetime "created_at", null: false
  99 + t.datetime "updated_at", null: false
90 100 t.text "key"
91 101 t.string "title"
92 102 t.string "type"
... ... @@ -111,8 +121,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
111 121 t.integer "author_id"
112 122 t.integer "assignee_id"
113 123 t.string "title"
114   - t.datetime "created_at"
115   - t.datetime "updated_at"
  124 + t.datetime "created_at", null: false
  125 + t.datetime "updated_at", null: false
116 126 t.integer "milestone_id"
117 127 t.string "state"
118 128 t.string "merge_status"
... ... @@ -164,8 +174,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
164 174 t.text "note"
165 175 t.string "noteable_type"
166 176 t.integer "author_id"
167   - t.datetime "created_at"
168   - t.datetime "updated_at"
  177 + t.datetime "created_at", null: false
  178 + t.datetime "updated_at", null: false
169 179 t.integer "project_id"
170 180 t.string "attachment"
171 181 t.string "line_code"
... ... @@ -187,8 +197,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
187 197 t.string "name"
188 198 t.string "path"
189 199 t.text "description"
190   - t.datetime "created_at"
191   - t.datetime "updated_at"
  200 + t.datetime "created_at", null: false
  201 + t.datetime "updated_at", null: false
192 202 t.integer "creator_id"
193 203 t.boolean "issues_enabled", default: true, null: false
194 204 t.boolean "wall_enabled", default: true, null: false
... ... @@ -239,8 +249,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
239 249 t.text "content", limit: 2147483647
240 250 t.integer "author_id", null: false
241 251 t.integer "project_id"
242   - t.datetime "created_at"
243   - t.datetime "updated_at"
  252 + t.datetime "created_at", null: false
  253 + t.datetime "updated_at", null: false
244 254 t.string "file_name"
245 255 t.datetime "expires_at"
246 256 t.boolean "private", default: true, null: false
... ... @@ -262,42 +272,45 @@ ActiveRecord::Schema.define(version: 20140127170938) do
262 272 t.datetime "created_at"
263 273 end
264 274  
  275 + add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
  276 + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
  277 +
265 278 create_table "tags", force: true do |t|
266 279 t.string "name"
267 280 end
268 281  
269 282 create_table "users", force: true do |t|
270   - t.string "email", default: "", null: false
271   - t.string "encrypted_password", limit: 128, default: "", null: false
  283 + t.string "email", default: "", null: false
  284 + t.string "encrypted_password", default: "", null: false
272 285 t.string "reset_password_token"
273 286 t.datetime "reset_password_sent_at"
274 287 t.datetime "remember_created_at"
275   - t.integer "sign_in_count", default: 0
  288 + t.integer "sign_in_count", default: 0
276 289 t.datetime "current_sign_in_at"
277 290 t.datetime "last_sign_in_at"
278 291 t.string "current_sign_in_ip"
279 292 t.string "last_sign_in_ip"
280   - t.datetime "created_at"
281   - t.datetime "updated_at"
  293 + t.datetime "created_at", null: false
  294 + t.datetime "updated_at", null: false
282 295 t.string "name"
283   - t.boolean "admin", default: false, null: false
284   - t.integer "projects_limit", default: 10
285   - t.string "skype", default: "", null: false
286   - t.string "linkedin", default: "", null: false
287   - t.string "twitter", default: "", null: false
  296 + t.boolean "admin", default: false, null: false
  297 + t.integer "projects_limit", default: 10
  298 + t.string "skype", default: "", null: false
  299 + t.string "linkedin", default: "", null: false
  300 + t.string "twitter", default: "", null: false
288 301 t.string "authentication_token"
289   - t.integer "theme_id", default: 1, null: false
  302 + t.integer "theme_id", default: 1, null: false
290 303 t.string "bio"
291   - t.integer "failed_attempts", default: 0
  304 + t.integer "failed_attempts", default: 0
292 305 t.datetime "locked_at"
293 306 t.string "extern_uid"
294 307 t.string "provider"
295 308 t.string "username"
296   - t.boolean "can_create_group", default: true, null: false
297   - t.boolean "can_create_team", default: true, null: false
  309 + t.boolean "can_create_group", default: true, null: false
  310 + t.boolean "can_create_team", default: true, null: false
298 311 t.string "state"
299   - t.integer "color_scheme_id", default: 1, null: false
300   - t.integer "notification_level", default: 1, null: false
  312 + t.integer "color_scheme_id", default: 1, null: false
  313 + t.integer "notification_level", default: 1, null: false
301 314 t.datetime "password_expires_at"
302 315 t.integer "created_by_id"
303 316 t.string "avatar"
... ... @@ -305,14 +318,15 @@ ActiveRecord::Schema.define(version: 20140127170938) do
305 318 t.datetime "confirmed_at"
306 319 t.datetime "confirmation_sent_at"
307 320 t.string "unconfirmed_email"
308   - t.boolean "hide_no_ssh_key", default: false
309   - t.string "website_url", default: "", null: false
  321 + t.boolean "hide_no_ssh_key", default: false
  322 + t.string "website_url", default: "", null: false
310 323 end
311 324  
312 325 add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
313 326 add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
314 327 add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
315 328 add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
  329 + add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
316 330 add_index "users", ["name"], name: "index_users_on_name", using: :btree
317 331 add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
318 332 add_index "users", ["username"], name: "index_users_on_username", using: :btree
... ... @@ -331,8 +345,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
331 345 create_table "users_projects", force: true do |t|
332 346 t.integer "user_id", null: false
333 347 t.integer "project_id", null: false
334   - t.datetime "created_at"
335   - t.datetime "updated_at"
  348 + t.datetime "created_at", null: false
  349 + t.datetime "updated_at", null: false
336 350 t.integer "project_access", default: 0, null: false
337 351 t.integer "notification_level", default: 3, null: false
338 352 end
... ... @@ -344,8 +358,8 @@ ActiveRecord::Schema.define(version: 20140127170938) do
344 358 create_table "web_hooks", force: true do |t|
345 359 t.string "url"
346 360 t.integer "project_id"
347   - t.datetime "created_at"
348   - t.datetime "updated_at"
  361 + t.datetime "created_at", null: false
  362 + t.datetime "updated_at", null: false
349 363 t.string "type", default: "ProjectHook"
350 364 t.integer "service_id"
351 365 t.boolean "push_events", default: true, null: false
... ...
features/profile/emails.feature 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +Feature: Profile Emails
  2 + Background:
  3 + Given I sign in as a user
  4 + And I visit profile emails page
  5 +
  6 + Scenario: I should see emails
  7 + Then I should see my emails
  8 +
  9 + Scenario: Add new email
  10 + Given I submit new email "my@email.com"
  11 + Then I should see new email "my@email.com"
  12 + And I should see my emails
  13 +
  14 + Scenario: Add duplicate email
  15 + Given I submit duplicate email @user.email
  16 + Then I should not have @user.email added
  17 + And I should see my emails
  18 +
  19 + Scenario: Remove email
  20 + Given I submit new email "my@email.com"
  21 + Then I should see new email "my@email.com"
  22 + And I should see my emails
  23 + Then I click link "Remove" for "my@email.com"
  24 + Then I should not see email "my@email.com"
  25 + And I should see my emails
... ...
features/project/commits/commits_user_lookup.feature 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +Feature: Project Browse Commits User Lookup
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own a project
  5 + And I have the user that authored the commits
  6 + And I visit my project's commits page
  7 +
  8 + Scenario: I browse commit from list
  9 + Given I click on commit link
  10 + Then I see commit info
  11 +
  12 + Scenario: I browse another commit from list
  13 + Given I click on another commit link
  14 + Then I see other commit info
0 15 \ No newline at end of file
... ...
features/steps/profile/profile_emails.rb 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +class ProfileEmails < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 +
  4 + Then 'I visit profile emails page' do
  5 + visit profile_emails_path
  6 + end
  7 +
  8 + Then 'I should see my emails' do
  9 + page.should have_content(@user.email)
  10 + @user.emails.each do |email|
  11 + page.should have_content(email.email)
  12 + end
  13 + end
  14 +
  15 + And 'I submit new email "my@email.com"' do
  16 + fill_in "email_email", with: "my@email.com"
  17 + click_button "Add"
  18 + end
  19 +
  20 + Then 'I should see new email "my@email.com"' do
  21 + email = @user.emails.find_by(email: "my@email.com")
  22 + email.should_not be_nil
  23 + page.should have_content("my@email.com")
  24 + end
  25 +
  26 + Then 'I should not see email "my@email.com"' do
  27 + email = @user.emails.find_by(email: "my@email.com")
  28 + email.should be_nil
  29 + page.should_not have_content("my@email.com")
  30 + end
  31 +
  32 + Then 'I click link "Remove" for "my@email.com"' do
  33 + # there should only be one remove button at this time
  34 + click_link "Remove"
  35 + # force these to reload as they have been cached
  36 + @user.emails.reload
  37 + end
  38 +
  39 + And 'I submit duplicate email @user.email' do
  40 + fill_in "email_email", with: @user.email
  41 + click_button "Add"
  42 + end
  43 +
  44 + Then 'I should not have @user.email added' do
  45 + email = @user.emails.find_by(email: @user.email)
  46 + email.should be_nil
  47 + end
  48 +end
... ...
features/steps/project/project_browse_commits_user_lookup.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedPaths
  5 +
  6 + Given 'I have the user that authored the commits' do
  7 + @user = create(:user, email: 'dmitriy.zaporozhets@gmail.com')
  8 + create(:email, { user: @user, email: 'dzaporozhets@sphereconsultinginc.com' })
  9 + end
  10 +
  11 + Given 'I click on commit link' do
  12 + visit project_commit_path(@project, ValidCommit::ID)
  13 + end
  14 +
  15 + Given 'I click on another commit link' do
  16 + visit project_commit_path(@project, ValidCommitWithAltEmail::ID)
  17 + end
  18 +
  19 + Then 'I see commit info' do
  20 + page.should have_content ValidCommit::MESSAGE
  21 + check_author_link(ValidCommit::AUTHOR_EMAIL)
  22 + end
  23 +
  24 + Then 'I see other commit info' do
  25 + page.should have_content ValidCommitWithAltEmail::MESSAGE
  26 + check_author_link(ValidCommitWithAltEmail::AUTHOR_EMAIL)
  27 + end
  28 +
  29 + def check_author_link(email)
  30 + author_link = find('.commit-author-link')
  31 + author_link['href'].should == user_path(@user)
  32 + author_link['data-original-title'].should == email
  33 + find('.commit-author-name').text.should == @user.name
  34 + end
  35 +end
... ...
features/support/env.rb
... ... @@ -15,7 +15,7 @@ require &#39;spinach/capybara&#39;
15 15 require 'sidekiq/testing/inline'
16 16  
17 17  
18   -%w(valid_commit big_commits select2_helper test_env).each do |f|
  18 +%w(valid_commit valid_commit_with_alt_email big_commits select2_helper test_env).each do |f|
19 19 require Rails.root.join('spec', 'support', f)
20 20 end
21 21  
... ...
spec/factories.rb
... ... @@ -219,6 +219,19 @@ FactoryGirl.define do
219 219 end
220 220 end
221 221 end
  222 +
  223 + factory :email do
  224 + user
  225 + email do
  226 + Faker::Internet.email('alias')
  227 + end
  228 +
  229 + factory :another_email do
  230 + email do
  231 + Faker::Internet.email('another.alias')
  232 + end
  233 + end
  234 + end
222 235  
223 236 factory :milestone do
224 237 title
... ...
spec/mailers/notify_spec.rb
... ... @@ -90,6 +90,28 @@ describe Notify do
90 90 end
91 91 end
92 92  
  93 + describe 'user added email' do
  94 + let(:email) { create(:email) }
  95 +
  96 + subject { Notify.new_email_email(email.id) }
  97 +
  98 + it 'is sent to the new user' do
  99 + should deliver_to email.user.email
  100 + end
  101 +
  102 + it 'has the correct subject' do
  103 + should have_subject /^gitlab \| Email was added to your account$/i
  104 + end
  105 +
  106 + it 'contains the new email address' do
  107 + should have_body_text /#{email.email}/
  108 + end
  109 +
  110 + it 'includes a link to emails page' do
  111 + should have_body_text /#{profile_emails_path}/
  112 + end
  113 + end
  114 +
93 115 context 'for a project' do
94 116 describe 'items that are assignable, the email' do
95 117 let(:assignee) { create(:user, email: 'assignee@example.com') }
... ...
spec/observers/email_observer_spec.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +require 'spec_helper'
  2 +
  3 +describe EmailObserver do
  4 + let(:email) { create(:email) }
  5 +
  6 + before { subject.stub(notification: double('NotificationService').as_null_object) }
  7 +
  8 + subject { EmailObserver.instance }
  9 +
  10 + describe '#after_create' do
  11 + it 'trigger notification to send emails' do
  12 + subject.should_receive(:notification)
  13 +
  14 + subject.after_create(email)
  15 + end
  16 + end
  17 +end
... ...
spec/routing/routing_spec.rb
... ... @@ -183,6 +183,23 @@ describe Profiles::KeysController, &quot;routing&quot; do
183 183 end
184 184 end
185 185  
  186 +# emails GET /emails(.:format) emails#index
  187 +# POST /keys(.:format) emails#create
  188 +# DELETE /keys/:id(.:format) keys#destroy
  189 +describe Profiles::EmailsController, "routing" do
  190 + it "to #index" do
  191 + get("/profile/emails").should route_to('profiles/emails#index')
  192 + end
  193 +
  194 + it "to #create" do
  195 + post("/profile/emails").should route_to('profiles/emails#create')
  196 + end
  197 +
  198 + it "to #destroy" do
  199 + delete("/profile/emails/1").should route_to('profiles/emails#destroy', id: '1')
  200 + end
  201 +end
  202 +
186 203 # profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy
187 204 describe Profiles::AvatarsController, "routing" do
188 205 it "to #destroy" do
... ...
spec/services/notification_service_spec.rb
... ... @@ -16,6 +16,19 @@ describe NotificationService do
16 16 end
17 17 end
18 18  
  19 + describe 'Email' do
  20 + describe :new_email do
  21 + let(:email) { create(:email) }
  22 +
  23 + it { notification.new_email(email).should be_true }
  24 +
  25 + it 'should send email to email owner' do
  26 + Notify.should_receive(:new_email_email).with(email.id)
  27 + notification.new_email(email)
  28 + end
  29 + end
  30 + end
  31 +
19 32 describe 'Notes' do
20 33 context 'issue note' do
21 34 let(:issue) { create(:issue, assignee: create(:user)) }
... ...
spec/support/valid_commit.rb
... ... @@ -2,6 +2,7 @@ module ValidCommit
2 2 ID = "8470d70da67355c9c009e4401746b1d5410af2e3"
3 3 MESSAGE = "notes controller refactored"
4 4 AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
  5 + AUTHOR_EMAIL = "dmitriy.zaporozhets@gmail.com"
5 6  
6 7 FILES = [".foreman", ".gitignore", ".rails_footnotes", ".rspec", ".travis.yml", "CHANGELOG", "Gemfile", "Gemfile.lock", "LICENSE", "Procfile", "Procfile.production", "README.md", "Rakefile", "VERSION", "app", "config.ru", "config", "db", "doc", "lib", "log", "public", "resque.sh", "script", "spec", "vendor"]
7 8 FILES_COUNT = 26
... ...
spec/support/valid_commit_with_alt_email.rb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +module ValidCommitWithAltEmail
  2 + ID = "1e689bfba39525ead225eaf611948cfbe8ac34cf"
  3 + MESSAGE = "fixed notes logic"
  4 + AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
  5 + AUTHOR_EMAIL = "dzaporozhets@sphereconsultinginc.com"
  6 +end
0 7 \ No newline at end of file
... ...