From b48f347a9427a853253b9e023c142520219ad95a Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Sat, 19 Apr 2025 22:18:55 +1200 Subject: [PATCH] Write out FixturesSpecHelper Having this helper generate these records before the test suite runs means they are not included any the standard rspec transactional tests mechanism. This means that if the suite cannot fully complete for any reason, such as a syntax error, or if the number of create/destroy is not perfectly matched in a test then the following test run will fail. Using let(:x) and before is best practise, and shouldn't have a significant effect on test runtimes. --- app/policies/group_policy.rb | 2 +- .../accounts/passwords_controller_spec.rb | 47 ++-- spec/controllers/contests_controller_spec.rb | 20 +- .../controllers/evaluators_controller_spec.rb | 21 +- .../file_attachments_controller_spec.rb | 2 +- spec/controllers/groups_controller_spec.rb | 24 +-- .../problem_sets_controller_spec.rb | 19 +- spec/controllers/problems_controller_spec.rb | 54 ++--- spec/controllers/roles_controller_spec.rb | 19 +- spec/controllers/settings_controller_spec.rb | 21 +- .../submissions_controller_spec.rb | 21 +- spec/controllers/user_controller_spec.rb | 14 +- spec/controllers/users_controller_spec.rb | 15 +- .../invitation_and_join_request_spec.rb | 152 +++++++------ spec/features/submissions_spec.rb | 12 +- spec/models/contest_relation_spec.rb | 45 ++-- spec/models/submission_spec.rb | 47 ++-- spec/policies/authorization_spec.rb | 203 +++++++++++------- spec/spec_helper.rb | 9 - spec/support/controllers_spec_helper.rb | 28 ++- spec/support/fixtures_spec_helper.rb | 30 --- 21 files changed, 396 insertions(+), 409 deletions(-) delete mode 100644 spec/support/fixtures_spec_helper.rb diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 7fe18b882..234d161d4 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -51,7 +51,7 @@ def leave? end def invite? - user.is_admin? or user.owns(record) or member? && [Group::MEMBERSHIP[:open], Group::MEMBERSHIP[:invitation]].include?(record.membership) + user.is_admin? || user.owns(record) || (member? && [Group::MEMBERSHIP[:open], Group::MEMBERSHIP[:invitation]].include?(record.membership)) end def reject? diff --git a/spec/controllers/accounts/passwords_controller_spec.rb b/spec/controllers/accounts/passwords_controller_spec.rb index d220ff549..353e7fdc1 100644 --- a/spec/controllers/accounts/passwords_controller_spec.rb +++ b/spec/controllers/accounts/passwords_controller_spec.rb @@ -1,47 +1,38 @@ require "spec_helper" describe Accounts::PasswordsController do - before(:each) do - @user = users(:user) - end + let(:user) { FactoryBot.create(:user) } - it "can get password reset form" do - get :new - expect(response).to be_success - end + describe "forgot password" do + it "can get password reset form" do + get :new + expect(response).to be_success + end - it "can send password reset email" do - post :create, user: {email: @user.email} + it "can send password reset email" do + post :create, user: {email: user.email} - expect(mail = ActionMailer::Base.deliveries.last).to_not be_nil + expect(mail = ActionMailer::Base.deliveries.last).to_not be_nil - host = ActionMailer::Base.default_url_options[:host] - expect(mail).to have_link("reset_password_token") + host = ActionMailer::Base.default_url_options[:host] + expect(mail).to have_link("reset_password_token") + end end - context "using password reset token" do - before(:all) do - @resetuser = FactoryBot.create(:user) - end - after(:all) do - @resetuser.destroy - end - before(:each) do - User.send_reset_password_instructions email: @resetuser.email - @resetuser.reload - expect(mail = ActionMailer::Base.deliveries.last).to_not be_nil - expect(mail.body.encoded =~ %r{}).to_not be_nil - @reset_token = $1 + describe "using password reset token" do + before do + @token = user.send_reset_password_instructions end it "can edit password" do - get :edit, reset_password_token: @reset_token + get :edit, reset_password_token: @token expect(response).to be_success end it "can update password" do - post :update, user: {reset_password_token: @reset_token, password: "newpassword", password_confirmation: "newpassword"} - expect(@resetuser.reload.valid_password?("newpassword")).to be true + post :update, user: {reset_password_token: @token, password: "newpassword", password_confirmation: "newpassword"} + + expect(user.reload.valid_password?("newpassword")).to be true end end end diff --git a/spec/controllers/contests_controller_spec.rb b/spec/controllers/contests_controller_spec.rb index 3037607ae..7d79db1b1 100644 --- a/spec/controllers/contests_controller_spec.rb +++ b/spec/controllers/contests_controller_spec.rb @@ -1,19 +1,14 @@ require "spec_helper" describe ContestsController do - before(:all) do - @problem_set = FactoryBot.create(:problem_set) - @contest = FactoryBot.create(:contest, problem_set: @problem_set) - end - after(:all) do - @contest.destroy - @problem_set.destroy - end + let(:problem_set) { FactoryBot.create(:problem_set) } + let(:contest) { FactoryBot.create(:contest, problem_set: problem_set) } context "as admin" do - before(:each) do - sign_in users(:admin) + before do + sign_in FactoryBot.create(:admin) end + can_index :contests can_index :contests, params: {filter: "my"} @@ -22,9 +17,10 @@ end context "as an organiser" do - before(:each) do - sign_in users(:organiser) + before do + sign_in FactoryBot.create(:organiser) end + can_index :contests, params: {filter: "my"} end end diff --git a/spec/controllers/evaluators_controller_spec.rb b/spec/controllers/evaluators_controller_spec.rb index 2b3376ea0..232714d82 100644 --- a/spec/controllers/evaluators_controller_spec.rb +++ b/spec/controllers/evaluators_controller_spec.rb @@ -1,25 +1,26 @@ require "spec_helper" describe EvaluatorsController do - before(:all) do - @evaluator = FactoryBot.create(:evaluator) - end - after(:all) do - @evaluator.destroy + let(:evaluator) { FactoryBot.create(:evaluator) } + + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :evaluators can_create :evaluator, attributes: {name: "A unique name", description: "Unique description", source: "special sauce"} can_manage :evaluator, attributes: {name: "A unique name", description: "Unique description", source: "special sauce"} end context "as a normal user" do - before(:each) do - sign_in users(:user) + let(:user) { FactoryBot.create(:user) } + + it "can't be accessed" do + get :index + expect(response).to have_http_status(:forbidden) end end end diff --git a/spec/controllers/file_attachments_controller_spec.rb b/spec/controllers/file_attachments_controller_spec.rb index 80038384a..e4aa9aa00 100644 --- a/spec/controllers/file_attachments_controller_spec.rb +++ b/spec/controllers/file_attachments_controller_spec.rb @@ -3,7 +3,7 @@ RSpec.describe FileAttachmentsController do describe "GET /file_attachments" do before do - sign_in users(:superadmin) + sign_in FactoryBot.create(:superadmin) end can_index :file_attachments diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index f69d927d3..ccdbbb2de 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1,33 +1,29 @@ require "spec_helper" describe GroupsController do - before(:all) do - @group = FactoryBot.create(:group) - end - after(:all) do - @group.destroy + let(:group) { FactoryBot.create(:group) } + + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :groups can_create :group, attributes: {name: "A unique name"} can_manage :group, attributes: {name: "A unique name"} end context "as a normal user" do - before(:each) do - sign_in users(:user) - end + let(:user) { FactoryBot.create(:user) } + can_browse :groups end context "as an organiser" do - before(:each) do - sign_in users(:organiser) - end + let(:user) { FactoryBot.create(:organiser) } + can_index :groups, params: {filter: "my"} end end diff --git a/spec/controllers/problem_sets_controller_spec.rb b/spec/controllers/problem_sets_controller_spec.rb index 939b6d8ab..31ba2ecd1 100644 --- a/spec/controllers/problem_sets_controller_spec.rb +++ b/spec/controllers/problem_sets_controller_spec.rb @@ -1,26 +1,23 @@ require "spec_helper" describe ProblemSetsController do - before(:all) do - @problem_set = FactoryBot.create(:problem_set) - end - after(:all) do - @problem_set.destroy + let(:problem_set) { FactoryBot.create(:problem_set) } + + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :problem_sets can_create :problem_set, attributes: {name: "A unique title"} can_manage :problem_set, attributes: {name: "A unique title"} end context "as an organiser" do - before(:each) do - sign_in users(:organiser) - end + let(:user) { FactoryBot.create(:organiser) } + can_index :problem_sets, params: {filter: "my"} end end diff --git a/spec/controllers/problems_controller_spec.rb b/spec/controllers/problems_controller_spec.rb index 5e1b76713..460dffbbd 100644 --- a/spec/controllers/problems_controller_spec.rb +++ b/spec/controllers/problems_controller_spec.rb @@ -1,62 +1,48 @@ require "spec_helper" describe ProblemsController do - before(:all) do - @group = FactoryBot.create(:group, name: "Special Group", members: [users(:admin), users(:user)]) - @group_set = FactoryBot.create(:problem_set, name: "Set in Group", groups: [@group]) - @group_problem = FactoryBot.create(:adding_problem, problem_sets: [@group_set]) - end - after(:all) do - [@group, @group_set, @group_problem].reverse_each { |object| object.destroy } - end + let(:group) { FactoryBot.create(:group, name: "Special Group", members: [user]) } + let(:group_set) { FactoryBot.create(:problem_set, name: "Set in Group", groups: [group]) } + let(:group_problem) { FactoryBot.create(:adding_problem, problem_sets: [group_set]) } + + let(:owned_problem) { FactoryBot.create(:problem, owner: user) } + let(:unowned_problem) { FactoryBot.create(:problem) } + shared_examples "for any user" do can_index :problems, params: {filter: "my"} can_create :problem, attributes: {name: "A unique title", statement: "A unique statement"} can_manage :owned_problem, resource_name: :problem, attributes: {name: "A unique title", statement: "A unique statement"} end + before do + sign_in user + end + context "as admin" do - before(:all) do - @owned_problem = FactoryBot.create(:problem, owner: users(:admin)) - @unowned_problem = FactoryBot.create(:problem) - end - after(:all) do - @unowned_problem.destroy - @owned_problem.destroy - end - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + include_examples "for any user" + can_manage :unowned_problem, resource_name: :problem, attributes: {name: "A unique title", statement: "A unique statement"} end context "as a normal user" do - before(:all) do - @owned_problem = FactoryBot.create(:problem, owner: users(:user)) - @unowned_problem = FactoryBot.create(:problem) - end - after(:all) do - @unowned_problem.destroy - @owned_problem.destroy - end - before(:each) do - sign_in users(:user) - end + let(:user) { FactoryBot.create(:user) } + include_examples "for any user" it "can get submit for group problem" do - get :submit, id: @group_problem.id + get :submit, id: group_problem.id expect(response).to be_success end it "can post submit for group problem" do expect_any_instance_of(Submission).to receive(:judge) # post multi-part form - post :submit, id: @group_problem.id, submission: {language_id: LanguageGroup.find_by_identifier("c++").current_language, source_file: fixture_file_upload("/files/adding.cpp", "text/plain")} + post :submit, id: group_problem.id, submission: {language_id: LanguageGroup.find_by_identifier("c++").current_language, source_file: fixture_file_upload("/files/adding.cpp", "text/plain")} expect(response).to redirect_to submission_path(assigns(:submission)) - expect(assigns(:submission).problem_id).to eq(@group_problem.id) - expect(assigns(:submission).user_id).to eq(users(:user).id) + expect(assigns(:submission).problem_id).to eq(group_problem.id) + expect(assigns(:submission).user_id).to eq(user.id) expect(assigns(:submission).language.group.identifier).to eq("c++") expect(assigns(:submission).source).not_to be_empty end diff --git a/spec/controllers/roles_controller_spec.rb b/spec/controllers/roles_controller_spec.rb index c9c8e561c..2cbe1e12a 100644 --- a/spec/controllers/roles_controller_spec.rb +++ b/spec/controllers/roles_controller_spec.rb @@ -1,26 +1,23 @@ require "spec_helper" describe RolesController do - before(:all) do - @role = FactoryBot.create(:role) - end - after(:all) do - @role.destroy + let(:role) { FactoryBot.create(:role) } + + before do + sign_in user end context "as superadmin" do - before(:each) do - sign_in users(:superadmin) - end + let(:user) { FactoryBot.create(:superadmin) } + can_index :roles can_create :role, attributes: {name: "A unique name"} can_manage :role, attributes: {name: "A unique name"} end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :roles end end diff --git a/spec/controllers/settings_controller_spec.rb b/spec/controllers/settings_controller_spec.rb index 228c03866..99e67787e 100644 --- a/spec/controllers/settings_controller_spec.rb +++ b/spec/controllers/settings_controller_spec.rb @@ -1,25 +1,26 @@ require "spec_helper" describe SettingsController do - before(:all) do - @setting = FactoryBot.create(:setting) - end - after(:all) do - @setting.destroy + let(:setting) { FactoryBot.create(:setting) } + + before do + sign_in user end context "as superadmin" do - before(:each) do - sign_in users(:superadmin) - end + let(:user) { FactoryBot.create(:superadmin) } + can_index :settings can_create :setting, attributes: {key: "A unique name", value: "Secret value setting"} can_manage :setting, attributes: {key: "A unique name", value: "Secret value setting"} end context "as a normal user" do - before(:each) do - sign_in users(:user) + let(:user) { FactoryBot.create(:user) } + + it "doesn't display" do + get :index + expect(response).to have_http_status(:forbidden) end end end diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index f329744a3..7eeec92ef 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -1,26 +1,25 @@ require "spec_helper" describe SubmissionsController do - before(:all) do - @submission = FactoryBot.create(:submission, problem: problems(:problem), user: users(:superadmin)) - end - after(:all) do - @submission.destroy + let(:problem) { FactoryBot.create(:problem) } + let(:user) { FactoryBot.create(:superadmin) } + let(:submission) { FactoryBot.create(:submission, problem: problem, user: user) } + + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :submissions can_show :submission can_destroy :submission end context "as a normal user" do - before(:each) do - sign_in users(:user) - end + let(:user) { FactoryBot.create(:user) } + can_index :submissions, params: {filter: "my"} end end diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb index d5e3d64ac..b6560b3f7 100644 --- a/spec/controllers/user_controller_spec.rb +++ b/spec/controllers/user_controller_spec.rb @@ -1,23 +1,19 @@ require "spec_helper" describe UserController do - before(:each) do - @user = users(:user) - @superadmin = users(:superadmin) + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:superadmin) } can_show :user can_update :user, attributes: {name: "A unique name"} end context "as a normal user" do - before(:each) do - sign_in users(:user) - end + let(:user) { FactoryBot.create(:user) } + can_show :user end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 45754b04f..959fa9ecc 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -1,22 +1,19 @@ require "spec_helper" describe UsersController do - before(:each) do - @user = users(:user) - @superadmin = users(:superadmin) + before do + sign_in user end context "as admin" do - before(:each) do - sign_in users(:admin) - end + let(:user) { FactoryBot.create(:admin) } + can_index :users end context "as a normal user" do - before(:each) do - sign_in users(:user) - end + let(:user) { FactoryBot.create(:user) } + can_index :users end end diff --git a/spec/features/invitation_and_join_request_spec.rb b/spec/features/invitation_and_join_request_spec.rb index 091e5dc17..866add22e 100644 --- a/spec/features/invitation_and_join_request_spec.rb +++ b/spec/features/invitation_and_join_request_spec.rb @@ -1,89 +1,107 @@ require "spec_helper" feature "invitation and join request" do - scenario "group owner invites a user, and user accepts invitation to group" do - @group = FactoryBot.create(:group, owner: users(:organiser), visibility: Group::VISIBILITY[:private], membership: Group::MEMBERSHIP[:private]) - - login_as users(:organiser), scope: :user - visit invites_members_group_path(@group) - expect do - within "#invite_member_form" do - fill_in "username", with: users(:user).username - click_on "Invite" - end - end.to change { @group.invitations.pending.count }.by(1) - - @invitation = @group.invitations.pending.where(target_id: users(:user)).first - - expect(@group.members.exists?(users(:user).id)).to be false - - login_as users(:user), scope: :user - visit accounts_requests_path - expect do - within ".group_invitations_list" do - accept_link = find :xpath, "//a[@href = '#{accept_members_group_path(@group, @invitation)}']" - accept_link.click - end - end.to change { @group.invitations.pending.count }.by(-1) - - expect(@group.members.exists?(users(:user).id)).to be true + subject(:group) { FactoryBot.create(:group, owner: owner, members: members, visibility: visibility, membership: membership) } + + let(:owner) { FactoryBot.create(:organiser) } + let(:member) { FactoryBot.create(:user) } + let(:members) { [member] } + let(:visibility) { Group::VISIBILITY[:unlisted] } + let(:membership) { Group::MEMBERSHIP[:invitation] } + + let(:uninvited_user) { FactoryBot.create(:user) } + + context "when the group is private and invite-only" do + let(:membership) { Group::MEMBERSHIP[:private] } + let(:visibility) { Group::VISIBILITY[:private] } + + scenario "group owner invites a user, and user accepts invitation to group" do + login_as owner, scope: :user + visit invites_members_group_path(group) + expect do + within "#invite_member_form" do + fill_in "username", with: uninvited_user.username + click_on "Invite" + end + end.to change { group.invitations.pending.count }.by(1) + + invitation = group.invitations.pending.where(target: uninvited_user).first + + expect(group.members.exists?(uninvited_user.id)).to be false + + login_as uninvited_user, scope: :user + visit accounts_requests_path + + expect do + within ".group_invitations_list" do + accept_link = find :xpath, "//a[@href = '#{accept_members_group_path(group, invitation)}']" + accept_link.click + end + end.to change { group.invitations.pending.count }.by(-1) + + expect(group.members.exists?(uninvited_user.id)).to be true + end end - scenario "group member invites a user and cancels the invitation" do - @group = FactoryBot.create(:group, members: [users(:user)], visibility: Group::VISIBILITY[:private], membership: Group::MEMBERSHIP[:invitation]) + context "when the group unlisted and invite + apply" do + let(:membership) { Group::MEMBERSHIP[:invitation] } + let(:visibility) { Group::VISIBILITY[:unlisted] } - login_as users(:user), scope: :user - visit invites_members_group_path(@group) + scenario "group member invites a user and cancels the invitation" do + login_as member, scope: :user + visit invites_members_group_path(group) - expect do - within "#invite_member_form" do - fill_in "username", with: users(:organiser).username - click_on "Invite" - end - end.to change { @group.invitations.pending.count }.by(1) + expect do + within "#invite_member_form" do + fill_in "username", with: uninvited_user.username + click_on "Invite" + end + end.to change { group.invitations.pending.count }.by(1) - @invitation = @group.invitations.pending.where(target_id: users(:organiser)).first + invitation = group.invitations.pending.where(target_id: uninvited_user).first - visit invites_members_group_path(@group) - cancel_link = find :xpath, "//a[@href = '#{cancel_members_group_path(@group, @invitation)}']" - expect { cancel_link.click }.to change { @group.invitations.pending.count }.by(-1) - end - - scenario "user applies to join group, and group member accepts join request" do - @group = FactoryBot.create(:group, owner: users(:organiser), visibility: Group::VISIBILITY[:unlisted], membership: Group::MEMBERSHIP[:invitation]) + visit invites_members_group_path(group) + cancel_link = find :xpath, "//a[@href = '#{cancel_members_group_path(group, invitation)}']" + expect { cancel_link.click }.to change { group.invitations.pending.count }.by(-1) + end - login_as users(:user), scope: :user - visit group_path(@group) + scenario "uninvited user applies to join group, and group owner accepts join request" do + login_as uninvited_user, scope: :user + visit group_path(group) - apply_link = find :xpath, "//a[@href = '#{apply_group_path(@group)}']" - expect { apply_link.click }.to change { @group.join_requests.pending.count }.by(1) + apply_link = find :xpath, "//a[@href = '#{apply_group_path(group)}']" + expect { apply_link.click }.to change { group.join_requests.pending.count }.by(1) - expect(@group.members.exists?(users(:user).id)).to be false - @join_request = @group.join_requests.pending.where(subject_id: users(:user)).first + expect(group.members.exists?(uninvited_user.id)).to be false + join_request = group.join_requests.pending.where(subject_id: uninvited_user).first - login_as users(:organiser) - visit join_requests_members_group_path(@group) - accept_link = find :xpath, "//a[@href = '#{accept_members_group_path(@group, @join_request)}']" - expect { accept_link.click }.to change { @group.join_requests.pending.count }.by(-1) + login_as owner + visit join_requests_members_group_path(group) + accept_link = find :xpath, "//a[@href = '#{accept_members_group_path(group, join_request)}']" + expect { accept_link.click }.to change { group.join_requests.pending.count }.by(-1) - expect(@group.members.exists?(users(:user).id)).to be true + expect(group.members.exists?(uninvited_user.id)).to be true + end end - scenario "user applies to join group, and group owner rejects join request" do - @group = FactoryBot.create(:group, owner: users(:organiser), visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:application]) + context "when the group is public and allows applications" do + let(:visibility) { Group::VISIBILITY[:public] } + let(:membership) { Group::MEMBERSHIP[:application] } - login_as users(:user), scope: :user - visit group_path(@group) - apply_link = find :xpath, "//a[@href = '#{apply_group_path(@group)}']" - expect { apply_link.click }.to change { @group.join_requests.pending.count }.by(1) + scenario "user applies to join group, and group owner rejects join request" do + login_as uninvited_user, scope: :user + visit group_path(group) + apply_link = find :xpath, "//a[@href = '#{apply_group_path(group)}']" + expect { apply_link.click }.to change { group.join_requests.pending.count }.by(1) - @join_request = @group.join_requests.pending.where(subject_id: users(:user)).first + join_request = group.join_requests.pending.where(subject_id: uninvited_user).first - login_as users(:organiser) - visit join_requests_members_group_path(@group) - reject_link = find :xpath, "//a[@href = '#{reject_members_group_path(@group, @join_request)}']" - expect { reject_link.click }.to change { @group.join_requests.pending.count }.by(-1) + login_as owner + visit join_requests_members_group_path(group) + reject_link = find :xpath, "//a[@href = '#{reject_members_group_path(group, join_request)}']" + expect { reject_link.click }.to change { group.join_requests.pending.count }.by(-1) - expect(@group.members.exists?(users(:user).id)).to be false + expect(group.members.exists?(uninvited_user.id)).to be false + end end end diff --git a/spec/features/submissions_spec.rb b/spec/features/submissions_spec.rb index f46f81710..c5367f7b5 100644 --- a/spec/features/submissions_spec.rb +++ b/spec/features/submissions_spec.rb @@ -1,12 +1,14 @@ require "spec_helper" feature "submission" do + let(:problem) { FactoryBot.create(:adding_problem) } + let!(:problem_set) { FactoryBot.create(:problem_set, problems: [problem], group_ids: [0]) } + let(:user) { FactoryBot.create(:user) } + scenario "submit code for a problem" do - @problem = FactoryBot.create(:adding_problem) - @problem_set = FactoryBot.create(:problem_set, problems: [@problem], group_ids: [0]) - login_as users(:user), scope: :user + login_as user, scope: :user - visit submit_problem_path(@problem) + visit submit_problem_path(problem) expect do within "#new_submission" do @@ -14,6 +16,6 @@ attach_file "submission_source_file", Rails.root.join("spec/fixtures/files/adding.cpp") click_on "Submit" end - end.to change { users(:user).submissions.count }.by(1) + end.to change { user.reload.submissions.count }.by(1) end end diff --git a/spec/models/contest_relation_spec.rb b/spec/models/contest_relation_spec.rb index 03885de11..deb6ab852 100644 --- a/spec/models/contest_relation_spec.rb +++ b/spec/models/contest_relation_spec.rb @@ -1,7 +1,6 @@ require "spec_helper" describe ContestRelation do - include FixturesSpecHelper RSpec::Matchers.define :finish_at_correct_time do def expected_time(relation) [relation.started_at.advance(hours: relation.contest.duration), relation.contest.end_time].min @@ -19,34 +18,34 @@ def expected_time(relation) "finish at the earlier of when contest duration runs out or when contest finishes" end end - before(:all) do - @contest = FactoryBot.create(:contest) - @relation = FactoryBot.create(:contest_relation, contest: @contest, user: users(:user)) - end - after(:all) do - @relation.destroy - @contest.destroy - end + + let(:user) { FactoryBot.create(:user) } + let(:contest) { FactoryBot.create(:contest) } + let(:relation) { FactoryBot.create(:contest_relation, contest: contest, user: user) } + it "updates finish_at when relation started_at changes" do - expect(@relation).to finish_at_correct_time - @relation.started_at = @contest.end_time.advance(hours: -1) - expect(@relation).to finish_at_correct_time + expect(relation).to finish_at_correct_time + relation.started_at = contest.end_time.advance(hours: -1) + expect(relation).to finish_at_correct_time end + it "updates finish_at when contest changes" do - @anothercontest = FactoryBot.build(:contest, start_time: @relation.started_at.advance(hours: -1), end_time: @relation.started_at.advance(hours: 1)) - @relation.contest = @anothercontest - expect(@relation).to finish_at_correct_time - @relation.contest_id = @contest.id - expect(@relation).to finish_at_correct_time + anothercontest = FactoryBot.build(:contest, start_time: relation.started_at.advance(hours: -1), end_time: relation.started_at.advance(hours: 1)) + relation.contest = anothercontest + expect(relation).to finish_at_correct_time + relation.contest_id = contest.id + expect(relation).to finish_at_correct_time end + it "updates finish_at when contest end_time changes" do - @contest.update_attributes(end_time: @relation.started_at.advance(hours: 1)) - expect(@relation).to finish_at_correct_time + contest.update_attributes(end_time: relation.started_at.advance(hours: 1)) + expect(relation.reload).to finish_at_correct_time end + it "updates finish_at when contest duration changes" do - @contest.update_attributes(duration: 1.0) - expect(@relation).to finish_at_correct_time - @contest.update_attributes(duration: 5.0) - expect(@relation).to finish_at_correct_time + contest.update_attributes(duration: 1.0) + expect(relation.reload).to finish_at_correct_time + contest.update_attributes(duration: 5.0) + expect(relation.reload).to finish_at_correct_time end end diff --git a/spec/models/submission_spec.rb b/spec/models/submission_spec.rb index 9dc1bf9d8..ca34359f6 100644 --- a/spec/models/submission_spec.rb +++ b/spec/models/submission_spec.rb @@ -1,43 +1,40 @@ require "spec_helper" describe Submission do - # pending "add some examples to (or delete) #{__FILE__}" context 'on "adding" problem' do - before(:all) do - @user = FactoryBot.create(:user) - @problem = FactoryBot.create(:adding_problem) - @submission = FactoryBot.create(:adding_submission, problem: @problem, user: @user) - @char_submission = FactoryBot.create(:adding_char_submission, problem: @problem, user: @user) - @unsigned_submission = FactoryBot.create(:adding_unsigned_submission, problem: @problem, user: @user) - end - after(:all) do - [@user, @problem, @submission, @char_submission, @unsigned_submission].reverse_each { |object| object.destroy } - end + let(:user) { FactoryBot.create(:user) } + let(:problem) { FactoryBot.create(:adding_problem) } + let(:submission) { FactoryBot.create(:adding_submission, problem: problem, user: user) } + let(:char_submission) { FactoryBot.create(:adding_char_submission, problem: problem, user: user) } + let(:unsigned_submission) { FactoryBot.create(:adding_unsigned_submission, problem: problem, user: user) } + it "judges submission" do - expect(@submission.score).to be_nil - @submission.judge - @submission.reload - expect(@submission.evaluation).to eq(1) + expect(submission.score).to be_nil + submission.judge + submission.reload + expect(submission.evaluation).to eq(1) end + it "judges submission on stdio problem" do problem = FactoryBot.create(:adding_problem_stdio) - submission = FactoryBot.create(:adding_submission_stdio, problem: problem, user: @user) + submission = FactoryBot.create(:adding_submission_stdio, problem: problem, user: user) expect(submission.evaluation).to be_nil submission.judge submission.reload expect(submission.evaluation).to eq(1) end + it "judges partially correct submissions correctly" do - expect(@char_submission.score).to be_nil - @char_submission.judge - @char_submission.reload - expect(@char_submission.points).to eq(2) - expect(@char_submission.maximum_points).to eq(4) + expect(char_submission.score).to be_nil + char_submission.judge + char_submission.reload + expect(char_submission.points).to eq(2) + expect(char_submission.maximum_points).to eq(4) - expect(@unsigned_submission.score).to be_nil - @unsigned_submission.judge - @unsigned_submission.reload - expect(@unsigned_submission.evaluation).to eq(0.75) + expect(unsigned_submission.score).to be_nil + unsigned_submission.judge + unsigned_submission.reload + expect(unsigned_submission.evaluation).to eq(0.75) end end end diff --git a/spec/policies/authorization_spec.rb b/spec/policies/authorization_spec.rb index f37e0305f..46e4cc4d0 100644 --- a/spec/policies/authorization_spec.rb +++ b/spec/policies/authorization_spec.rb @@ -1,150 +1,187 @@ require "spec_helper" describe "Authorization" do - include FixturesSpecHelper - before(:all) do - @superadmin = users(:superadmin) - @admin = users(:admin) - @organiser = users(:organiser) - @user = users(:user) - # various objects to test ability on - @member = FactoryBot.create(:user) - @group = FactoryBot.create(:group, members: [users(:user), users(:admin), users(:superadmin), @member]) - @organiser_group = FactoryBot.create(:group, owner: users(:organiser), members: [@member]) - @private_problem = FactoryBot.create(:problem) - @group_set = FactoryBot.create(:problem_set, groups: [@group]) - @everyone_set = FactoryBot.create(:problem_set, group_ids: [0]) - @group_problem = FactoryBot.create(:problem, problem_sets: [@group_set]) - @user_problem = FactoryBot.create(:problem, owner: users(:user)) - @admin_problem = FactoryBot.create(:problem, owner: users(:admin)) - @everyone_problem = FactoryBot.create(:problem, problem_sets: [@everyone_set]) - @contest_set = FactoryBot.create(:problem_set) - @contest = FactoryBot.create(:contest, name: "Contest", groups: [@group], problem_set: @contest_set, duration: 100, start_time: DateTime.now.advance(hours: -100), end_time: DateTime.now.advance(hours: 100)) - @contest_problem = FactoryBot.create(:problem, problem_sets: [@contest_set]) - @private_set = FactoryBot.create(:problem_set) - @private_contest = FactoryBot.create(:contest, problem_set: @contest_set) - @past_contest = FactoryBot.create(:contest, groups: [@group], problem_set: @contest_set, start_time: DateTime.now.advance(hours: -100), end_time: DateTime.now.advance(hours: -50)) - @future_contest = FactoryBot.create(:contest, groups: [@group], problem_set: @contest_set, start_time: DateTime.now.advance(hours: 100), end_time: DateTime.now.advance(hours: 200)) - @everyone_contest = FactoryBot.create(:contest, group_ids: [0], problem_set: @contest_set, start_time: DateTime.now.advance(hours: -100), end_time: DateTime.now.advance(hours: 100)) + let(:superadmin) { FactoryBot.create(:superadmin) } + let(:admin) { FactoryBot.create(:admin) } + let(:user) { FactoryBot.create(:user) } + let(:organiser) { FactoryBot.create(:organiser) } + + let(:group) { FactoryBot.create(:group, members: [user, admin, superadmin]) } + let(:organiser_group) { FactoryBot.create(:group, owner: organiser, members: [user]) } + + let(:group_set) { FactoryBot.create(:problem_set, groups: [group]) } + let(:everyone_set) { FactoryBot.create(:problem_set, group_ids: [0]) } + + let(:private_problem) { FactoryBot.create(:problem) } + let(:group_problem) { FactoryBot.create(:problem, problem_sets: [group_set]) } + let(:user_problem) { FactoryBot.create(:problem, owner: user) } + let(:admin_problem) { FactoryBot.create(:problem, owner: admin) } + let(:everyone_problem) { FactoryBot.create(:problem, problem_sets: [everyone_set]) } + let(:contest_problem) { FactoryBot.create(:problem, problem_sets: [contest_set]) } + + let(:contest_set) { FactoryBot.create(:problem_set) } + let(:private_set) { FactoryBot.create(:problem_set) } + + let(:private_contest) { FactoryBot.create(:contest, problem_set: contest_set) } + + let(:contest) do + FactoryBot.create( + :contest, + groups: [group], + problem_set: contest_set, + start_time: 100.hours.ago, + end_time: 100.hours.from_now + ) + end + + let(:past_contest) do + FactoryBot.create( + :contest, + groups: [group], + problem_set: contest_set, + start_time: 100.hours.ago, + end_time: 50.hours.ago + ) end - after(:all) do - [@member, @group, @organiser_group, @private_problem, @group_set, @everyone_set, @group_problem, @user_problem, @admin_problem, @everyone_problem, @contest_set, @contest, @contest_problem, @private_set, @private_contest, @past_contest, @future_contest, @everyone_contest].reverse_each { |obj| obj.destroy } + + let(:future_contest) do + FactoryBot.create( + :contest, + groups: [group], + problem_set: contest_set, + start_time: 100.hours.from_now, + end_time: 20.hours.from_now + ) + end + + let(:everyone_contest) do + FactoryBot.create( + :contest, + groups: [group], + problem_set: contest_set, + start_time: 100.hours.ago, + end_time: 100.hours.from_now + ) end + describe "on models" do it "superadmin can manage all objects" do - expect(@superadmin).to be_permitted_to :manage, [Problem, Setting, Role, User, Group, Evaluator, Contest] + expect(superadmin).to be_permitted_to :manage, [Problem, Setting, Role, User, Group, Evaluator, Contest] end + it "admin can manage most objects" do - expect(@admin).to be_permitted_to :manage, [Problem, Evaluator, Contest, Problem.new, Group.new, Contest.new, @user, @admin, @group] + expect(admin).to be_permitted_to :manage, [Problem, Evaluator, Contest, Problem.new, Group.new, Contest.new, user, admin, group] end + it "admin cannot manage Role, Setting, group for everyone, and superadmin user" do - expect(@admin).not_to be_permitted_to :manage, [Setting, Role, Group.find(0), @superadmin] + expect(admin).not_to be_permitted_to :manage, [Setting, Role, Group.find(0), superadmin] end + it "user cannot see Role or Setting" do - expect(@user).not_to be_permitted_to [:index, :show, :edit, :new], [Setting, Role] + expect(user).not_to be_permitted_to [:index, :show, :edit, :new], [Setting, Role] end end describe "on problems" do it "admin can :manage all problems" do - expect(@admin).to be_permitted_to [:show, :manage], [@private_problem, @group_problem, @user_problem, @admin_problem, @everyone_problem, @contest_problem] + expect(admin).to be_permitted_to [:show, :manage], [private_problem, group_problem, user_problem, admin_problem, everyone_problem, contest_problem] end it "user can read group or public problems" do - expect(@user).to be_permitted_to :show, @everyone_problem - expect(@user).to be_permitted_to :show, [@group_problem, @everyone_problem] + expect(user).to be_permitted_to :show, everyone_problem + expect(user).to be_permitted_to :show, [group_problem, everyone_problem] end it "user can read/update owned problem" do - expect(@user).to be_permitted_to [:index, :show, :edit, :update], @user_problem + expect(user).to be_permitted_to [:index, :show, :edit, :update], user_problem end it "user cannot read private problems" do - expect(@user).not_to be_permitted_to [:index, :show, :update], [@private_problem, @admin_problem, @contest_problem] + expect(user).not_to be_permitted_to [:index, :show, :update], [private_problem, admin_problem, contest_problem] end it "user can create problem" do - expect(@user).to be_permitted_to [:new, :create], Problem.new(owner_id: users(:user).id) + expect(user).to be_permitted_to [:new, :create], Problem.new(owner_id: user.id) end context "user in contest" do - before(:all) do - @relation = FactoryBot.create(:contest_relation, user_id: users(:user).id, contest_id: @contest.id, started_at: DateTime.now.advance(hours: -1)) - @contest_problem.reload - @contest_user = users(:user).reload - end - after(:all) do - @relation.destroy - @contest_problem.reload - @contest_user.reload + before do + FactoryBot.create(:contest_relation, user_id: user.id, contest_id: contest.id, started_at: DateTime.now.advance(hours: -1)) end + it "can read contest problem" do - expect(@contest_user).to be_permitted_to :show, [@contest_problem] + expect(user).to be_permitted_to :show, [contest_problem] end + it "cannot read/update other problems" do - expect(@contest_user).not_to be_permitted_to [:index, :show, :update], [@private_problem, @admin_problem, @user_problem] + expect(user).not_to be_permitted_to [:index, :show, :update], [private_problem, admin_problem, user_problem] end end end + describe "on problem sets" do it "admin can :manage all problem sets" do - expect(@admin).to be_permitted_to [:show, :manage], [@private_set, @group_set, @contest_set, @everyone_set] + expect(admin).to be_permitted_to [:show, :manage], [private_set, group_set, contest_set, everyone_set] end - # removed feature - # it 'user can read group or public problem sets' do - # expect(@user).to be_permitted_to :show, [@group_set, @everyone_set] - # end + it "user cannot read private or contest problem sets" do - expect(@user).not_to be_permitted_to [:index, :show, :update], [@private_set, @contest_set] + expect(user).not_to be_permitted_to [:index, :show, :update], [private_set, contest_set] end + context "user in contest" do - before(:all) do - @relation = FactoryBot.create(:contest_relation, user_id: users(:user).id, contest_id: @contest.id, started_at: DateTime.now.advance(hours: -1)) - @contest_user = users(:user).reload # refresh .competing? - end - after(:all) do - @relation.destroy - users(:user).reload + before do + FactoryBot.create(:contest_relation, user_id: user.id, contest_id: contest.id, started_at: DateTime.now.advance(hours: -1)) end + it "cannot read/update other problems" do - expect(@contest_user).not_to be_permitted_to [:index, :show, :update], [@private_set, @group_set, @everyone_set] + expect(user.reload).not_to be_permitted_to [:index, :show, :update], [private_set, group_set, everyone_set] end end end describe "on groups" do + let(:new_user) { FactoryBot.create(:user) } + it "members can invite users to open group" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:open]) - expect(@organiser).to be_permitted_to [:invite, :reject], @organiser_group - expect(@member).to be_permitted_to :invite, @organiser_group - expect(@member).not_to be_permitted_to :reject, @organiser_group - expect(@admin).to be_permitted_to [:invite, :reject], @organiser_group + organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:open]) + expect(organiser).to be_permitted_to [:invite, :reject], organiser_group + expect(user).to be_permitted_to :invite, organiser_group + expect(user).not_to be_permitted_to :reject, organiser_group + expect(admin).to be_permitted_to [:invite, :reject], organiser_group end + it "members can invite users if group membership is by invitation" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:invitation]) - expect(@member).to be_permitted_to :invite, @organiser_group - expect(@member).not_to be_permitted_to :reject, @organiser_group + organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:invitation]) + expect(user).to be_permitted_to :invite, organiser_group + expect(user).not_to be_permitted_to :reject, organiser_group end + it "members cannot invite users if group membership is by application" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:application]) - expect(@member).not_to be_permitted_to :invite, @organiser_group + organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:application]) + expect(user).not_to be_permitted_to :invite, organiser_group end - it "user can apply to join if group membership is by invitation" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:invitation]) - expect(@user).to be_permitted_to :apply, @organiser_group + + it "user can invite others to join if group membership is by invitation" do + organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:invitation]) + expect(user).to be_permitted_to :invite, organiser_group end + it "user can apply to join if group membership is by application" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:application]) - expect(@user).to be_permitted_to :apply, @organiser_group + organiser_group.update_attributes(visibility: Group::VISIBILITY[:public], membership: Group::MEMBERSHIP[:application]) + expect(new_user).to be_permitted_to :apply, organiser_group end + it "user cannot see, nor apply to private visibility groups, even if membership settings otherwise allow it" do - @organiser_group.update_attributes(visibility: Group::VISIBILITY[:private], membership: Group::MEMBERSHIP[:invitation]) - expect(@user).not_to be_permitted_to [:show, :apply], @organiser_group + organiser_group.update_attributes(visibility: Group::VISIBILITY[:private], membership: Group::MEMBERSHIP[:invitation]) + expect(new_user).not_to be_permitted_to [:show, :apply], organiser_group end end + describe "on contests" do it "user can index contest in group or for everyone" do - expect(@user).to be_permitted_to :index, [@contest, @everyone_contest, @past_contest, @future_contest] - expect(@user).to be_permitted_to :show, [@contest, @everyone_contest, @past_contest] + expect(user).to be_permitted_to :index, [contest, everyone_contest, past_contest, future_contest] + expect(user).to be_permitted_to :show, [contest, everyone_contest, past_contest] end + it "user cannot index private contests" do - expect(@user).not_to be_permitted_to [:index, :show], @private_contest + expect(user).not_to be_permitted_to [:index, :show], private_contest end + it "user can start active contest" do - expect(@user).to be_permitted_to :start, [@contest, @everyone_contest] + expect(user).to be_permitted_to :start, [contest, everyone_contest] end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 090a52265..f872ada70 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -49,22 +49,13 @@ # config.infer_spec_type_from_file_location! - config.before(:suite) do - FixturesSpecHelper.initialize - end - config.after(:suite) do - FixturesSpecHelper.destroy - end config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::IntegrationHelpers, type: :request - config.include FixturesSpecHelper, type: :controller # supply fixtures variables config.include ControllersSpecHelper, type: :controller # some macros for testing controllers config.render_views # don't stub views when testing controllers - config.include FixturesSpecHelper, type: :feature # supply fixture variables config.include RequestsSpecHelper, type: :feature # use warden to shortcut login - config.include FixturesSpecHelper, type: :presenter, file_path: %r{spec/presenters} # supply fixture variables config.include ActionView::TestCase::Behavior, type: :presenter, file_path: %r{spec/presenters} end diff --git a/spec/support/controllers_spec_helper.rb b/spec/support/controllers_spec_helper.rb index d603c2376..512b682cb 100644 --- a/spec/support/controllers_spec_helper.rb +++ b/spec/support/controllers_spec_helper.rb @@ -17,6 +17,14 @@ def process_hash hash hash end + def subject_object(resource) + if respond_to?(resource) + send(resource) + else + instance_variable_get("@#{resource}") + end + end + module ClassMethods def _process_options options options[:attributes] ||= {} @@ -62,8 +70,9 @@ def can_manage resource, options = {} def can_show resource, options = {} options = process_options resource, options + it "can show #{resource}" do - object = instance_variable_get "@#{resource}" + object = subject_object(resource) get :show, id: object.to_param expect(response).to be_success end @@ -71,14 +80,16 @@ def can_show resource, options = {} def can_update resource, options = {} options = process_options resource, options + it "can edit #{resource}" do - object = instance_variable_get "@#{resource}" + object = subject_object(resource) get :edit, id: object.to_param expect(response).to be_success assigns(options[:resource_name]).instance_of?(object.class) end + it "can update #{resource}" do - object = instance_variable_get "@#{resource}" + object = subject_object(resource) put :update, :id => object.to_param, options[:resource_name] => object.attributes.symbolize_keys.merge(options[:attributes]) expect(response).to redirect_to send "#{options[:resource_name]}_path", assigns(options[:resource_name]) expect(assigns(options[:resource_name])).to have_attributes(options[:attributes]) @@ -87,10 +98,12 @@ def can_update resource, options = {} def can_create resource, options = {} options = process_options resource, options + it "can get new #{resource}" do get :new expect(response).to be_success end + it "can create #{resource}" do expect do post :create, options[:resource_name] => options[:attributes] @@ -102,16 +115,19 @@ def can_create resource, options = {} def can_destroy resource, options = {} options = process_options resource, options + it "can destroy #{resource}" do - object = instance_variable_get "@#{resource}" + object = subject_object(resource) expect do delete :destroy, id: object.to_param - end.to change { object.class.count }.by(-1) + end.to change { object.class.count } + .by(-1) expect(response).to be_redirect end end end - # Instance methods + + # Instance methodss RSpec::Matchers.define :have_attributes do |expected| match do |actual| matching = true diff --git a/spec/support/fixtures_spec_helper.rb b/spec/support/fixtures_spec_helper.rb deleted file mode 100644 index 13aae4be8..000000000 --- a/spec/support/fixtures_spec_helper.rb +++ /dev/null @@ -1,30 +0,0 @@ -module FixturesSpecHelper - @@table_names = [:users, :problems, :test_sets] - - def self.initialize - @@users = { - user: FactoryBot.create(:user), - organiser: FactoryBot.create(:organiser), - admin: FactoryBot.create(:admin), - superadmin: FactoryBot.create(:superadmin) - } - @@problems = {problem: FactoryBot.create(:problem)} - @@test_sets = {test_set: FactoryBot.create(:test_set, problem: @@problems[:problem])} - end - - def self.destroy - @@table_names.each do |table| - (class_variable_get "@@#{table}".to_sym).each { |k, v| - v.clear_association_cache # touching already destroyed association object causes `touch': can not touch on a new record object (ActiveRecord::ActiveRecordError) - v.destroy - } - end - end - - @@table_names.each do |table| - class_variable_set "@@#{table}".to_sym, {} - define_method table do |key| - (self.class.send :class_variable_get, "@@#{table}".to_sym)[key] - end - end -end