diff --git a/app/controllers/conference_registrations_controller.rb b/app/controllers/conference_registrations_controller.rb index 95a699fb..f3edaa5f 100644 --- a/app/controllers/conference_registrations_controller.rb +++ b/app/controllers/conference_registrations_controller.rb @@ -1,10 +1,13 @@ class ConferenceRegistrationsController < ApplicationController before_filter :authenticate_user!, except: [:new, :create] load_resource :conference, find_by: :short_title - authorize_resource :conference_registrations, class: Registration + authorize_resource :conference_registrations, class: Registration, except: [:new, :create] before_action :set_registration, only: [:edit, :update, :destroy, :show] def new + @registration = Registration.new(conference_id: @conference.id) + authorize! :new, @registration + # Redirect to registration edit when user is already registered if @conference.user_registered?(current_user) redirect_to edit_conference_conference_registrations_path(@conference.short_title) @@ -15,18 +18,11 @@ class ConferenceRegistrationsController < ApplicationController return end - # avoid openid sign_in to redirect to register/new when the sign_in user had already a registration - if current_user && @conference.user_registered?(current_user) - redirect_to edit_conference_conference_registrations_path(@conference.short_title) - end - if @conference.registration_limit_exceeded? redirect_to root_path, error: "Sorry, registration limit exceeded for #{@conference.title}" return end - @registration = Registration.new - # @user variable needs to be set so that _sign_up_form_embedded works properly @user = @registration.build_user end @@ -50,6 +46,7 @@ class ConferenceRegistrationsController < ApplicationController end @registration.user = @user + authorize! :create, @registration if @registration.save # Trigger ahoy event @@ -90,9 +87,9 @@ class ConferenceRegistrationsController < ApplicationController redirect_to root_path, notice: "You are not registered for #{@conference.title} anymore!" else - redirect_to root_path, - error: "Could not update your registration for #{@conference.title}: "\ - "#{@registration.errors.full_messages.join('. ')}." + redirect_to conference_conference_registrations_path(@conference.short_title), + flash: { error: "Could not delete your registration for #{@conference.title}: "\ + "#{@registration.errors.full_messages.join('. ')}." } end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 16313da9..dd80ef6b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -46,9 +46,13 @@ class Ability can :show, Commercial, commercialable_type: 'Event', commercialable_id: Event.where(state: 'confirmed').pluck(:id) can :show, User unless CONFIG['authentication']['ichain']['enabled'] - can [:show, :create], Registration do |registration| + can :show, Registration do |registration| registration.new_record? end + + can [:new, :create], Registration do |registration| + registration.conference.registration_open? && registration.new_record? + end can :show, Event do |event| event.new_record? end @@ -68,6 +72,10 @@ class Ability can :manage, Registration, user_id: user.id + can [:new, :create], Registration do |registration| + registration.conference.registration_open? + end + can :index, Ticket can :manage, TicketPurchase, user_id: user.id diff --git a/spec/controllers/conference_registration_controller_spec.rb b/spec/controllers/conference_registration_controller_spec.rb new file mode 100644 index 00000000..e00e37af --- /dev/null +++ b/spec/controllers/conference_registration_controller_spec.rb @@ -0,0 +1,335 @@ +require 'spec_helper' + +describe ConferenceRegistrationsController, type: :controller do + let!(:first_user) { create(:user) } + let(:conference) { create(:conference) } + let(:conference_with_open_registration) { create(:conference) } + let!(:open_registration_period) { create(:registration_period, conference: conference_with_open_registration, start_date: Date.current - 6.days) } + let(:user) { create(:user) } + + context 'user is not signed in' do + describe 'GET #new' do + context 'ichain is disabled' do + before do + get :new, conference_id: conference_with_open_registration.short_title + end + + it 'assigns conference, registration and user variable' do + expect(assigns(:conference)).to eq conference_with_open_registration + expect(assigns(:registration)).to be_instance_of Registration + expect(assigns(:user)).to be_instance_of User + end + + it 'renders the new template' do + expect(response).to render_template('new') + end + end + + context 'ichain is enabled' do + before do + CONFIG['authentication']['ichain']['enabled'] = true + get :new, conference_id: conference_with_open_registration.short_title + end + + after { CONFIG['authentication']['ichain']['enabled'] = false } + + it 'redirects to root path' do + expect(response).to redirect_to root_path + end + + it 'shows error in flash message' do + expect(flash[:alert]).to match 'You are not authorized to access this page. Maybe you need to sign in?' + end + end + end + + describe 'POST #create' do + before do + post :create, user: attributes_for(:user), + registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + end + + it 'assigns user variable' do + expect(assigns(:user)).not_to be_nil + end + + it 'signs in registration user' do + expect(controller.current_user).not_to be_nil + end + + it 'shows success message in flash notice' do + expect(flash[:notice]).to match('You are now registered and will be receiving E-Mail notifications.') + end + + it 'redirects to registration show path' do + expect(response).to redirect_to conference_conference_registrations_path(conference_with_open_registration.short_title) + end + + it 'creates a new registration' do + expect(Registration.count).to eq 1 + end + end + end + + context 'user is signed in' do + before { sign_in(user) } + + describe 'GET #new' do + context 'successful request' do + before do + get :new, conference_id: conference_with_open_registration.short_title + end + + it 'assigns registration and user variables' do + expect(assigns(:registration)).to be_instance_of(Registration) + expect(assigns(:user)).to be_instance_of(User) + end + + it 'renders the new template' do + expect(response).to render_template('new') + end + end + + context 'user is registered to conference' do + before do + create(:registration, user: user, conference: conference_with_open_registration) + get :new, conference_id: conference_with_open_registration.short_title + end + + it 'redirects to edit registration page' do + expect(response).to redirect_to edit_conference_conference_registrations_path(conference_with_open_registration.short_title) + end + end + + context 'registration limit has reached' do + before do + conference_with_open_registration.update_attributes(registration_limit: 1) + create(:registration, conference: conference_with_open_registration) + get :new, conference_id: conference_with_open_registration.short_title + end + + it 'redirects to root path' do + expect(response).to redirect_to root_path + end + + it 'shows error in flash message' do + expect(flash[:alert]).to match "Sorry, registration limit exceeded for #{conference_with_open_registration.title}" + end + end + end + + describe 'GET #show' do + before do + @registration = create(:registration, conference: conference_with_open_registration, user: user) + end + + context 'successful request' do + before do + get :show, conference_id: conference_with_open_registration.short_title + end + + it 'assigns conference, registration and workshops variables' do + expect(assigns(:conference)).to eq conference_with_open_registration + expect(assigns(:registration)).to eq @registration + expect(assigns(:workshops)).to eq @registration.workshops + end + + it 'renders the show template' do + expect(response).to render_template('show') + end + end + + context 'user has purchased a ticket' do + before do + @ticket = create(:ticket, conference: conference_with_open_registration) + @purchased_ticket = create(:ticket_purchase, conference: conference_with_open_registration, + user: user, + ticket: @ticket) + get :show, conference_id: conference_with_open_registration.short_title + end + + it 'assigns price of purchased tickets to total_price and purchased tickets to tickets' do + expect(assigns(:total_price)).to eq Money.new(10000, 'USD') + expect(assigns(:tickets)).to match_array [@purchased_ticket] + end + end + + context 'user has not purchased any ticket' do + before do + get :show, conference_id: conference_with_open_registration.short_title + end + + it 'assigns 0 dollars to total_price and empty array to tickets variables' do + expect(assigns(:total_price)).to eq Money.new(0, 'USD') + expect(assigns(:tickets)).to match_array [] + end + end + end + + describe 'GET #edit' do + before do + @registration = create(:registration, conference: conference_with_open_registration, user: user) + get :edit, conference_id: conference_with_open_registration.short_title + end + + it 'assigns conference and registration variable' do + expect(assigns(:conference)).to eq conference_with_open_registration + expect(assigns(:registration)).to eq @registration + end + + it 'renders the edit template' do + expect(response).to render_template('edit') + end + end + + describe 'POST #create' do + context 'successfully registers' do + it 'assigns user variable' do + post :create, registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + expect(assigns(:user)).to eq user + end + + it 'creates a new registration' do + expect do + post :create, registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + end.to change{ Registration.count }.by 1 + end + end + + context 'tickets are available' do + before do + create(:ticket, conference: conference_with_open_registration) + post :create, registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + end + + it 'redirects to conference tickets path' do + expect(response).to redirect_to conference_tickets_path(conference_with_open_registration.short_title) + end + end + + context 'tickets are not available' do + before do + post :create, registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + end + + it 'redirects to registration show path' do + expect(response).to redirect_to conference_conference_registrations_path(conference_with_open_registration.short_title) + end + end + + context 'registration save fails' do + before do + allow_any_instance_of(Registration).to receive(:save).and_return(false) + post :create, registration: attributes_for(:registration), + conference_id: conference_with_open_registration.short_title + end + + it 'renders the new template' do + expect(response).to render_template('new') + end + + it 'does not create registration' do + expect(Registration.count).to eq 0 + end + end + end + + describe 'PATCH #update' do + before do + @registration = create(:registration, + conference: conference_with_open_registration, + user: user, + arrival: Date.new(2014, 04, 25)) + end + + context 'updates successfully' do + before do + patch :update, registration: attributes_for(:registration, arrival: Date.new(2014, 04, 29)), + conference_id: conference_with_open_registration.short_title + end + + it 'redirects to registration show path' do + expect(response).to redirect_to conference_conference_registrations_path(conference_with_open_registration.short_title) + end + + it 'shows success message in flash notice' do + expect(flash[:notice]).to match('Registration was successfully updated.') + end + + it 'updates the registration' do + @registration.reload + expect(@registration.arrival).to eq Date.new(2014, 04, 29) + end + end + + context 'update fails' do + before do + allow_any_instance_of(Registration).to receive(:update_attributes).and_return(false) + patch :update, registration: attributes_for(:registration, arrival: Date.new(2014, 04, 27)), + conference_id: conference_with_open_registration.short_title + end + + it 'renders edit template' do + expect(response).to render_template('edit') + end + + it 'shows error in flash message' do + expect(flash[:error]).to match "Could not update your registration for The dog and pony show: #{@registration.errors.full_messages.join('. ')}." + end + + it 'does not update the registration' do + @registration.reload + expect(@registration.arrival).to eq Date.new(2014, 04, 25) + end + end + end + + describe 'DELETE #destroy' do + before do + @registration = create(:registration, conference: conference_with_open_registration, user: user) + end + + context 'deletes successfully' do + before do + delete :destroy, conference_id: conference_with_open_registration.short_title + end + + it 'redirects to root path' do + expect(response).to redirect_to root_path + end + + it 'shows success message in flash notice' do + expect(flash[:notice]).to match('You are not registered for The dog and pony show anymore!') + end + + it 'deletes the registration' do + expect(Registration.count).to eq 0 + end + end + + context 'delete fails' do + before do + allow_any_instance_of(Registration).to receive(:destroy).and_return(false) + delete :destroy, conference_id: conference_with_open_registration.short_title + end + + it 'redirects to registration show path' do + expect(response).to redirect_to conference_conference_registrations_path(conference_with_open_registration.short_title) + end + + it 'shows error in flash message' do + expect(flash[:error]).to match "Could not delete your registration for The dog and pony show: #{@registration.errors.full_messages.join('. ')}." + end + + it 'does not delete the registration' do + expect(assigns(:registration)).to eq @registration + end + end + end + end +end diff --git a/spec/factories/conferences.rb b/spec/factories/conferences.rb index a63dd0d5..7d35fc6c 100644 --- a/spec/factories/conferences.rb +++ b/spec/factories/conferences.rb @@ -7,7 +7,7 @@ FactoryGirl.define do timezone 'Amsterdam' start_date { Date.today } end_date { 6.days.from_now } - registration_limit 0 + registration_limit 10 after(:create) do |conference| Role.where(name: 'organizer', resource: conference).first_or_create(description: 'For the organizers of the conference (who shall have full access)') diff --git a/spec/factories/registration.rb b/spec/factories/registration.rb index fb79cb4d..6debd8ae 100644 --- a/spec/factories/registration.rb +++ b/spec/factories/registration.rb @@ -4,5 +4,6 @@ FactoryGirl.define do factory :registration do user conference + arrival 3.days.ago end end diff --git a/spec/features/conference_registration_spec.rb b/spec/features/conference_registration_spec.rb index b1ab5f08..ff7f000d 100644 --- a/spec/features/conference_registration_spec.rb +++ b/spec/features/conference_registration_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' feature Registration do + let!(:first_user) { create(:user) } # first user is admin let!(:conference) { create(:conference, registration_period: create(:registration_period, start_date: 3.days.ago)) } let!(:participant) { create(:user) } @@ -28,13 +29,29 @@ feature Registration do expect(conference.user_registered?(participant)).to be(true) end - scenario 'unregisters for a conference', feature: true, js: true do - visit root_path - click_link 'My Registration' - expect(current_path).to eq(conference_conference_registrations_path(conference.short_title)) + context 'unregisters for a conference', feature: true, js: true do + before do + visit root_path + click_link 'My Registration' + end - click_link 'Unregister' - expect(conference.user_registered?(participant)).to be(false) + scenario 'deletion of registration is successful' do + expect(current_path).to eq(conference_conference_registrations_path(conference.short_title)) + + click_link 'Unregister' + expect(conference.user_registered?(participant)).to be(false) + expect(current_path).to eq root_path + expect(flash).to eq 'You are not registered for The dog and pony show anymore!' + end + + scenario 'deletion of registration is unsuccessful' do + allow_any_instance_of(Registration).to receive(:destroy).and_return(false) + + click_link 'Unregister' + expect(conference.user_registered?(participant)).to be(true) + expect(current_path).to eq conference_conference_registrations_path(conference.short_title) + expect(flash).to eq "Could not delete your registration for #{conference.title}: #{registration.errors.full_messages.join('. ')}." + end end end @@ -49,5 +66,15 @@ feature Registration do expect(conference.user_registered?(participant)).to be(true) end end + + context 'registration is closed' do + let(:conference_with_closed_registration) { create(:conference, registration_period: create(:registration_period)) } + + scenario 'registers for a conference', feature: true do + visit new_conference_conference_registrations_path(conference_with_closed_registration.short_title) + expect(current_path).to eq(root_path) + expect(flash).to eq 'You are not authorized to access this page.' + end + end end end diff --git a/spec/features/proposal_spec.rb b/spec/features/proposal_spec.rb index 720a1bc3..55c8ebb5 100644 --- a/spec/features/proposal_spec.rb +++ b/spec/features/proposal_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature Event do let!(:conference) { create(:conference) } + let!(:registration_period) { create(:registration_period, conference: conference, start_date: Date.current) } let!(:cfp) { create(:cfp, program_id: conference.program.id) } let!(:organizer_role) { Role.find_by(name: 'organizer', resource: conference) } let!(:organizer) { create(:user, email: 'admin@example.com', role_ids: [organizer_role.id]) } @@ -134,6 +135,7 @@ feature Event do click_link "confirm_proposal_#{@event.id}" expect(flash). to eq('The proposal was confirmed. Please register to attend the conference.') + expect(current_path).to eq(new_conference_conference_registrations_path(conference.short_title)) @event.reload expect(@event.state).to eq('confirmed') end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index fab5f857..366d9659 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -32,6 +32,11 @@ describe 'User' do let(:registration) { create(:registration) } + let(:conference_with_open_registration) { create(:conference) } + let!(:open_registration_period) { create(:registration_period, conference: conference_with_open_registration, start_date: Date.current - 6.days) } + let(:conference_with_closed_registration) { create(:conference) } + let!(:closed_registration_period) { create(:registration_period, conference: conference_with_closed_registration, start_date: Date.current - 6.days, end_date: Date.current - 6.days) } + # Test abilities for not signed in users context 'when user is not signed in' do it{ should be_able_to(:index, Conference)} @@ -54,8 +59,11 @@ describe 'User' do it{ should be_able_to(:show, User)} - it{ should be_able_to(:create, Registration)} it{ should be_able_to(:show, Registration.new)} + it{ should be_able_to(:create, Registration.new(conference_id: conference_with_open_registration.id))} + it{ should be_able_to(:new, Registration.new(conference_id: conference_with_open_registration.id))} + it{ should_not be_able_to(:new, Registration.new(conference_id: conference_with_closed_registration.id))} + it{ should_not be_able_to(:create, Registration.new(conference_id: conference_with_closed_registration.id))} it{ should_not be_able_to(:manage, registration)} it{ should be_able_to(:create, Event)} @@ -79,6 +87,8 @@ describe 'User' do it{ should be_able_to(:manage, registration_public) } it{ should be_able_to(:manage, registration_not_public) } + it{ should_not be_able_to(:new, Registration.new(conference_id: conference_with_closed_registration.id))} + it{ should_not be_able_to(:create, Registration.new(conference_id: conference_with_closed_registration.id))} it{ should be_able_to(:index, Ticket) } it{ should be_able_to(:manage, TicketPurchase.new(user_id: user.id)) } diff --git a/spec/models/registration_spec.rb b/spec/models/registration_spec.rb index 1bf9e88f..cf87466b 100644 --- a/spec/models/registration_spec.rb +++ b/spec/models/registration_spec.rb @@ -1,24 +1,79 @@ -#!/bin/env ruby -# encoding: utf-8 require 'spec_helper' -describe 'Registration' do - describe 'validations' do +describe Registration do + subject { create(:registration) } + + describe 'validation' do + it 'has a valid factory' do expect(build(:registration)).to be_valid end + it { should validate_presence_of(:user) } + + it 'validates uniqueness of user in scope of conference' do + expect(build(:registration, conference: subject.conference, user: subject.user)).not_to be_valid + end + describe 'registration_limit_not_exceed' do - it 'is not valid when limit exceeded' do - conference = build(:conference) - conference.registration_limit = 1 - registration1 = build(:registration, conference: conference) - registration1.save - registration2 = build(:registration, conference: conference) - registration2.save - expect(conference.registrations.size).to be 1 - expect(registration2.valid?).to be false - expect(registration2.errors.full_messages).to eq(['Registration limit exceeded']) + context 'registration_limit has exceeded' do + before do + conference = create(:conference, registration_limit: 1) + create(:registration, conference: conference) + @second_registration = build(:registration, conference: conference) + end + + it 'is not valid factory' do + expect(@second_registration.valid?).to be false + expect(@second_registration.errors.full_messages).to eq(['Registration limit exceeded']) + end + end + end + end + + describe 'association' do + it { should belong_to(:user) } + it { should belong_to(:conference) } + it { should belong_to(:dietary_choice) } + it { should have_and_belong_to_many(:social_events) } + it { should have_and_belong_to_many(:events) } + it { should have_and_belong_to_many(:qanswers) } + it { should have_and_belong_to_many(:vchoices) } + it { should have_many(:events_registrations) } + it { should have_many(:workshops) } + end + + describe 'callback' do + after { subject.run_callbacks(:create) } + + # set_week and subscribe_to_conference are private methods + describe '#set_week' do + before { subject.created_at = Time.utc(2014, 5, 10) } + + it 'sets week of registration' do + expect(subject).to receive(:set_week) + expect(subject.week).to eq 18 + end + end + + describe '#subscribe_to_conference' do + it 'subscribes to conference' do + expect(subject).to receive(:subscribe_to_conference) + expect(subject.user.subscribed?(subject.conference)).to be true + end + end + + it 'sends registrations mail' do + expect(subject).to receive(:send_registration_mail) + end + end + + describe 'method' do + describe '#week' do + before { subject.created_at = Date.new(2014, 06, 30) } + + it 'returns week number of created_at' do + expect(subject.week).to eq(26) end end end