Commit 29cfd33d949d21d67f3892473c24d4f0a127dfe6
1 parent
d41e404e
Exists in
spb-stable
and in
3 other branches
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.
Showing
27 changed files
with
424 additions
and
46 deletions
Show diff stats
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 < 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 < 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 < 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 < 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/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 | ... | ... |
... | ... | @@ -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
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 'spinach/capybara' |
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') } | ... | ... |
... | ... | @@ -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, "routing" 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 | ... | ... |