From 729404e05314c20034b6f61546063af5b02ac0c1 Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Mon, 21 Mar 2016 18:46:06 +0200 Subject: [PATCH] rails_apps_composer: set up database --- Gemfile.lock | 3 - app/models/user.rb | 2 +- app/services/create_admin_service.rb | 10 +++ config/environments/test.rb | 2 +- config/initializers/devise.rb | 2 +- config/secrets.yml | 9 +++ ...321164557_devise_invitable_add_to_users.rb | 23 +++++++ db/schema.rb | 14 ++++- db/seeds.rb | 2 + spec/factories/users.rb | 10 ++- spec/features/users/sign_in_spec.rb | 49 +++++++++++++++ spec/features/users/sign_out_spec.rb | 21 +++++++ spec/features/users/user_delete_spec.rb | 32 ++++++++++ spec/features/users/user_edit_spec.rb | 42 +++++++++++++ spec/features/users/user_index_spec.rb | 25 ++++++++ spec/features/users/user_show_spec.rb | 39 ++++++++++++ spec/features/visitors/sign_up_spec.rb | 62 +++++++++++++++++++ spec/models/user_spec.rb | 14 ++++- spec/policies/user_policy_spec.rb | 47 ++++++++++++++ spec/support/devise.rb | 3 + spec/support/helpers.rb | 4 ++ spec/support/helpers/session_helpers.rb | 18 ++++++ spec/support/pundit.rb | 1 + 23 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 app/services/create_admin_service.rb create mode 100644 db/migrate/20160321164557_devise_invitable_add_to_users.rb create mode 100644 spec/features/users/sign_in_spec.rb create mode 100644 spec/features/users/sign_out_spec.rb create mode 100644 spec/features/users/user_delete_spec.rb create mode 100644 spec/features/users/user_edit_spec.rb create mode 100644 spec/features/users/user_index_spec.rb create mode 100644 spec/features/users/user_show_spec.rb create mode 100644 spec/features/visitors/sign_up_spec.rb create mode 100644 spec/policies/user_policy_spec.rb create mode 100644 spec/support/devise.rb create mode 100644 spec/support/helpers.rb create mode 100644 spec/support/helpers/session_helpers.rb create mode 100644 spec/support/pundit.rb diff --git a/Gemfile.lock b/Gemfile.lock index 9bbee26..84d1a05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -312,8 +312,6 @@ GEM thor (0.19.1) thread_safe (0.3.5) tilt (2.0.2) - turbolinks (2.5.3) - coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) uglifier (2.7.2) @@ -377,7 +375,6 @@ DEPENDENCIES spring spring-commands-rspec sqlite3 - turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) diff --git a/app/models/user.rb b/app/models/user.rb index 9977b48..32ab5ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,6 +8,6 @@ class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable - devise :database_authenticatable, :registerable, :confirmable, + devise :invitable, :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable end diff --git a/app/services/create_admin_service.rb b/app/services/create_admin_service.rb new file mode 100644 index 0000000..9078b31 --- /dev/null +++ b/app/services/create_admin_service.rb @@ -0,0 +1,10 @@ +class CreateAdminService + def call + user = User.find_or_create_by!(email: Rails.application.secrets.admin_email) do |user| + user.password = Rails.application.secrets.admin_password + user.password_confirmation = Rails.application.secrets.admin_password + user.confirm! + user.admin! + end + end +end diff --git a/config/environments/test.rb b/config/environments/test.rb index 1c19f08..be8d32e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,7 +30,7 @@ Rails.application.configure do # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - + config.action_mailer.default_url_options = { :host => Rails.application.secrets.domain_name } # Randomize the order test cases are executed. config.active_support.test_order = :random diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index d8c4cc6..cf9fe3c 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -12,7 +12,7 @@ Devise.setup do |config| # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class # with default "from" parameter. - config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + config.mailer_sender = 'no-reply@' + Rails.application.secrets.domain_name # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' diff --git a/config/secrets.yml b/config/secrets.yml index ffdfc02..52285d8 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,12 +11,21 @@ # if you're sharing your code publicly. development: + admin_name: First User + admin_email: user@example.com + admin_password: changeme + domain_name: example.com secret_key_base: 6fbc7972b587359c086a6c6738dca91a5cd53f635bdfba31c7b23721a27e1d89bf8c490dfc3ebe773bcdb98d2add0946ea924321bad66499b893d88fc827b883 test: + domain_name: example.com secret_key_base: a0a18747d0172bfadf4187e6c01be490f452539aeaa4d74bada88ef851d7a2dfabcde91552d3689e9eb71caf9062e5d8b28fbeb55c6f429d92d462655c25d60c # Do not keep production secrets in the repository, # instead read values from the environment. production: + admin_name: <%= ENV["ADMIN_NAME"] %> + admin_email: <%= ENV["ADMIN_EMAIL"] %> + admin_password: <%= ENV["ADMIN_PASSWORD"] %> + domain_name: <%= ENV["DOMAIN_NAME"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/db/migrate/20160321164557_devise_invitable_add_to_users.rb b/db/migrate/20160321164557_devise_invitable_add_to_users.rb new file mode 100644 index 0000000..788529a --- /dev/null +++ b/db/migrate/20160321164557_devise_invitable_add_to_users.rb @@ -0,0 +1,23 @@ +class DeviseInvitableAddToUsers < ActiveRecord::Migration + def up + change_table :users do |t| + t.string :invitation_token + t.datetime :invitation_created_at + t.datetime :invitation_sent_at + t.datetime :invitation_accepted_at + t.integer :invitation_limit + t.references :invited_by, polymorphic: true + t.integer :invitations_count, default: 0 + t.index :invitations_count + t.index :invitation_token, unique: true # for invitable + t.index :invited_by_id + end + end + + def down + change_table :users do |t| + t.remove_references :invited_by, polymorphic: true + t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 17198de..7e8df0e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160321164456) do +ActiveRecord::Schema.define(version: 20160321164557) do create_table "users", force: :cascade do |t| t.string "email", default: "", null: false @@ -31,9 +31,21 @@ ActiveRecord::Schema.define(version: 20160321164456) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" + t.integer "role" + t.string "invitation_token" + t.datetime "invitation_created_at" + t.datetime "invitation_sent_at" + t.datetime "invitation_accepted_at" + t.integer "invitation_limit" + t.integer "invited_by_id" + t.string "invited_by_type" + t.integer "invitations_count", default: 0 end add_index "users", ["email"], name: "index_users_on_email", unique: true + add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true + add_index "users", ["invitations_count"], name: "index_users_on_invitations_count" + add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id" add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..997068d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,5 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +user = CreateAdminService.new.call +puts 'CREATED ADMIN USER: ' << user.email diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 96b2b0c..250e115 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,5 +1,13 @@ FactoryGirl.define do factory :user do - + confirmed_at Time.now + name "Test User" + email "test@example.com" + password "please123" + + trait :admin do + role 'admin' + end + end end diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb new file mode 100644 index 0000000..57d4d93 --- /dev/null +++ b/spec/features/users/sign_in_spec.rb @@ -0,0 +1,49 @@ +# Feature: Sign in +# As a user +# I want to sign in +# So I can visit protected areas of the site +feature 'Sign in', :devise do + + # Scenario: User cannot sign in if not registered + # Given I do not exist as a user + # When I sign in with valid credentials + # Then I see an invalid credentials message + scenario 'user cannot sign in if not registered' do + signin('test@example.com', 'please123') + expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' + end + + # Scenario: User can sign in with valid credentials + # Given I exist as a user + # And I am not signed in + # When I sign in with valid credentials + # Then I see a success message + scenario 'user can sign in with valid credentials' do + user = FactoryGirl.create(:user) + signin(user.email, user.password) + expect(page).to have_content I18n.t 'devise.sessions.signed_in' + end + + # Scenario: User cannot sign in with wrong email + # Given I exist as a user + # And I am not signed in + # When I sign in with a wrong email + # Then I see an invalid email message + scenario 'user cannot sign in with wrong email' do + user = FactoryGirl.create(:user) + signin('invalid@email.com', user.password) + expect(page).to have_content I18n.t 'devise.failure.not_found_in_database', authentication_keys: 'email' + end + + # Scenario: User cannot sign in with wrong password + # Given I exist as a user + # And I am not signed in + # When I sign in with a wrong password + # Then I see an invalid password message + scenario 'user cannot sign in with wrong password' do + user = FactoryGirl.create(:user) + signin(user.email, 'invalidpass') + expect(page).to have_content I18n.t 'devise.failure.invalid', authentication_keys: 'email' + end + +end diff --git a/spec/features/users/sign_out_spec.rb b/spec/features/users/sign_out_spec.rb new file mode 100644 index 0000000..3f906b9 --- /dev/null +++ b/spec/features/users/sign_out_spec.rb @@ -0,0 +1,21 @@ +# Feature: Sign out +# As a user +# I want to sign out +# So I can protect my account from unauthorized access +feature 'Sign out', :devise do + + # Scenario: User signs out successfully + # Given I am signed in + # When I sign out + # Then I see a signed out message + scenario 'user signs out successfully' do + user = FactoryGirl.create(:user) + signin(user.email, user.password) + expect(page).to have_content I18n.t 'devise.sessions.signed_in' + click_link 'Sign out' + expect(page).to have_content I18n.t 'devise.sessions.signed_out' + end + +end + + diff --git a/spec/features/users/user_delete_spec.rb b/spec/features/users/user_delete_spec.rb new file mode 100644 index 0000000..7cd1c97 --- /dev/null +++ b/spec/features/users/user_delete_spec.rb @@ -0,0 +1,32 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User delete +# As a user +# I want to delete my user profile +# So I can close my account +feature 'User delete', :devise, :js do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User can delete own account + # Given I am signed in + # When I delete my account + # Then I should see an account deleted message + scenario 'user can delete own account' do + skip 'skip a slow test' + user = FactoryGirl.create(:user) + login_as(user, :scope => :user) + visit edit_user_registration_path(user) + click_button 'Cancel my account' + page.driver.browser.switch_to.alert.accept + expect(page).to have_content I18n.t 'devise.registrations.destroyed' + end + +end + + + + diff --git a/spec/features/users/user_edit_spec.rb b/spec/features/users/user_edit_spec.rb new file mode 100644 index 0000000..f4acd38 --- /dev/null +++ b/spec/features/users/user_edit_spec.rb @@ -0,0 +1,42 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User edit +# As a user +# I want to edit my user profile +# So I can change my email address +feature 'User edit', :devise do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User changes email address + # Given I am signed in + # When I change my email address + # Then I see an account updated message + scenario 'user changes email address' do + user = FactoryGirl.create(:user) + login_as(user, :scope => :user) + visit edit_user_registration_path(user) + fill_in 'Email', :with => 'newemail@example.com' + fill_in 'Current password', :with => user.password + click_button 'Update' + txts = [I18n.t( 'devise.registrations.updated'), I18n.t( 'devise.registrations.update_needs_confirmation')] + expect(page).to have_content(/.*#{txts[0]}.*|.*#{txts[1]}.*/) + end + + # Scenario: User cannot edit another user's profile + # Given I am signed in + # When I try to edit another user's profile + # Then I see my own 'edit profile' page + scenario "user cannot cannot edit another user's profile", :me do + me = FactoryGirl.create(:user) + other = FactoryGirl.create(:user, email: 'other@example.com') + login_as(me, :scope => :user) + visit edit_user_registration_path(other) + expect(page).to have_content 'Edit User' + expect(page).to have_field('Email', with: me.email) + end + +end diff --git a/spec/features/users/user_index_spec.rb b/spec/features/users/user_index_spec.rb new file mode 100644 index 0000000..d786db6 --- /dev/null +++ b/spec/features/users/user_index_spec.rb @@ -0,0 +1,25 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User index page +# As a user +# I want to see a list of users +# So I can see who has registered +feature 'User index page', :devise do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User listed on index page + # Given I am signed in + # When I visit the user index page + # Then I see my own email address + scenario 'user sees own email address' do + user = FactoryGirl.create(:user, :admin) + login_as(user, scope: :user) + visit users_path + expect(page).to have_content user.email + end + +end diff --git a/spec/features/users/user_show_spec.rb b/spec/features/users/user_show_spec.rb new file mode 100644 index 0000000..aeee7d6 --- /dev/null +++ b/spec/features/users/user_show_spec.rb @@ -0,0 +1,39 @@ +include Warden::Test::Helpers +Warden.test_mode! + +# Feature: User profile page +# As a user +# I want to visit my user profile page +# So I can see my personal account data +feature 'User profile page', :devise do + + after(:each) do + Warden.test_reset! + end + + # Scenario: User sees own profile + # Given I am signed in + # When I visit the user profile page + # Then I see my own email address + scenario 'user sees own profile' do + user = FactoryGirl.create(:user) + login_as(user, :scope => :user) + visit user_path(user) + expect(page).to have_content 'User' + expect(page).to have_content user.email + end + + # Scenario: User cannot see another user's profile + # Given I am signed in + # When I visit another user's profile + # Then I see an 'access denied' message + scenario "user cannot see another user's profile" do + me = FactoryGirl.create(:user) + other = FactoryGirl.create(:user, email: 'other@example.com') + login_as(me, :scope => :user) + Capybara.current_session.driver.header 'Referer', root_path + visit user_path(other) + expect(page).to have_content 'Access denied.' + end + +end diff --git a/spec/features/visitors/sign_up_spec.rb b/spec/features/visitors/sign_up_spec.rb new file mode 100644 index 0000000..fc50163 --- /dev/null +++ b/spec/features/visitors/sign_up_spec.rb @@ -0,0 +1,62 @@ +# Feature: Sign up +# As a visitor +# I want to sign up +# So I can visit protected areas of the site +feature 'Sign Up', :devise do + + # Scenario: Visitor can sign up with valid email address and password + # Given I am not signed in + # When I sign up with a valid email address and password + # Then I see a successful sign up message + scenario 'visitor can sign up with valid email address and password' do + sign_up_with('test@example.com', 'please123', 'please123') + txts = [I18n.t( 'devise.registrations.signed_up'), I18n.t( 'devise.registrations.signed_up_but_unconfirmed')] + expect(page).to have_content(/.*#{txts[0]}.*|.*#{txts[1]}.*/) + end + + # Scenario: Visitor cannot sign up with invalid email address + # Given I am not signed in + # When I sign up with an invalid email address + # Then I see an invalid email message + scenario 'visitor cannot sign up with invalid email address' do + sign_up_with('bogus', 'please123', 'please123') + expect(page).to have_content 'Email is invalid' + end + + # Scenario: Visitor cannot sign up without password + # Given I am not signed in + # When I sign up without a password + # Then I see a missing password message + scenario 'visitor cannot sign up without password' do + sign_up_with('test@example.com', '', '') + expect(page).to have_content "Password can't be blank" + end + + # Scenario: Visitor cannot sign up with a short password + # Given I am not signed in + # When I sign up with a short password + # Then I see a 'too short password' message + scenario 'visitor cannot sign up with a short password' do + sign_up_with('test@example.com', 'please', 'please') + expect(page).to have_content "Password is too short" + end + + # Scenario: Visitor cannot sign up without password confirmation + # Given I am not signed in + # When I sign up without a password confirmation + # Then I see a missing password confirmation message + scenario 'visitor cannot sign up without password confirmation' do + sign_up_with('test@example.com', 'please123', '') + expect(page).to have_content "Password confirmation doesn't match" + end + + # Scenario: Visitor cannot sign up with mismatched password and confirmation + # Given I am not signed in + # When I sign up with a mismatched password confirmation + # Then I should see a mismatched password message + scenario 'visitor cannot sign up with mismatched password and confirmation' do + sign_up_with('test@example.com', 'please123', 'mismatch') + expect(page).to have_content "Password confirmation doesn't match" + end + +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 47a31bb..9b17618 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,5 +1,13 @@ -require 'rails_helper' +describe User do + + before(:each) { @user = User.new(email: 'user@example.com') } + + subject { @user } + + it { should respond_to(:email) } + + it "#email returns a string" do + expect(@user.email).to match 'user@example.com' + end -RSpec.describe User, type: :model do - pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb new file mode 100644 index 0000000..37579cc --- /dev/null +++ b/spec/policies/user_policy_spec.rb @@ -0,0 +1,47 @@ +describe UserPolicy do + subject { UserPolicy } + + let (:current_user) { FactoryGirl.build_stubbed :user } + let (:other_user) { FactoryGirl.build_stubbed :user } + let (:admin) { FactoryGirl.build_stubbed :user, :admin } + + permissions :index? do + it "denies access if not an admin" do + expect(UserPolicy).not_to permit(current_user) + end + it "allows access for an admin" do + expect(UserPolicy).to permit(admin) + end + end + + permissions :show? do + it "prevents other users from seeing your profile" do + expect(subject).not_to permit(current_user, other_user) + end + it "allows you to see your own profile" do + expect(subject).to permit(current_user, current_user) + end + it "allows an admin to see any profile" do + expect(subject).to permit(admin) + end + end + + permissions :update? do + it "prevents updates if not an admin" do + expect(subject).not_to permit(current_user) + end + it "allows an admin to make updates" do + expect(subject).to permit(admin) + end + end + + permissions :destroy? do + it "prevents deleting yourself" do + expect(subject).not_to permit(current_user, current_user) + end + it "allows an admin to delete any user" do + expect(subject).to permit(admin, other_user) + end + end + +end diff --git a/spec/support/devise.rb b/spec/support/devise.rb new file mode 100644 index 0000000..3552bea --- /dev/null +++ b/spec/support/devise.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include Devise::TestHelpers, :type => :controller +end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb new file mode 100644 index 0000000..5e1beca --- /dev/null +++ b/spec/support/helpers.rb @@ -0,0 +1,4 @@ +require 'support/helpers/session_helpers' +RSpec.configure do |config| + config.include Features::SessionHelpers, type: :feature +end diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb new file mode 100644 index 0000000..cb6b66f --- /dev/null +++ b/spec/support/helpers/session_helpers.rb @@ -0,0 +1,18 @@ +module Features + module SessionHelpers + def sign_up_with(email, password, confirmation) + visit new_user_registration_path + fill_in 'Email', with: email + fill_in 'Password', with: password + fill_in 'Password confirmation', :with => confirmation + click_button 'Sign up' + end + + def signin(email, password) + visit new_user_session_path + fill_in 'Email', with: email + fill_in 'Password', with: password + click_button 'Sign in' + end + end +end diff --git a/spec/support/pundit.rb b/spec/support/pundit.rb new file mode 100644 index 0000000..1fd8e29 --- /dev/null +++ b/spec/support/pundit.rb @@ -0,0 +1 @@ +require 'pundit/rspec'