Files
codeocean/spec/policies/exercise_policy_spec.rb
kiragrammel 175c8933f3 Automatically submit LTI grade on each score run
With this commit, we refactor the overall score handling of CodeOcean. Previously, "Score" and "Submit" were two distinct actions, requiring users to confirm the LTI transmission of their score (after assessing their submission). This yielded many questions and was unnecessary, since LTI parameters are no longer expiring after each use. Therefore, we can now transmit the current grade on each score run with the very same LTI parameters. As a consequence, the LTI consumer gets a more detailed history of the scores, enabling further analytical insights.

For users, the previous "Submit" button got replaced with a notification that is shown as soon as the full score got reached. Then, learners can decide to "finalize" their work on the given exercise, which will initiate a redirect to a follow-up action (as defined in the RedirectBehavior). This RedirectBehavior has also been unified and simplified for better readability.

As part of this refactoring, we rephrased the notifications and UX workflow of a) the LTI transmission, b) the finalization of an exercise (measured by reaching the full score) and c) the deadline handling (on time, within grace period, too late). Those information are now separately shown, potentially resulting in multiple notifications. As a side effect, they are much better maintainable, and the LTI transmission is more decoupled from this notification handling.
2023-11-23 14:42:10 +01:00

239 lines
7.2 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ExercisePolicy do
subject(:policy) { described_class }
let(:exercise) { build(:dummy, public: true) }
%i[batch_update? programming_groups_for_exercise?].each do |action|
permissions(action) do
it 'grants access to admins only' do
expect(policy).to permit(build(:admin), exercise)
%i[external_user teacher].each do |factory_name|
expect(policy).not_to permit(create(factory_name), exercise)
end
end
end
end
%i[create? index? new? statistics? feedback? rfcs_for_exercise?].each do |action|
permissions(action) do
it 'grants access to admins' do
expect(policy).to permit(build(:admin), exercise)
end
it 'grants access to teachers' do
expect(policy).to permit(create(:teacher), exercise)
end
it 'does not grant access to external users' do
expect(policy).not_to permit(build(:external_user), exercise)
end
end
end
%i[clone? destroy? edit? update?].each do |action|
permissions(action) do
it 'grants access to admins' do
expect(policy).to permit(build(:admin), exercise)
end
it 'grants access to authors' do
expect(policy).to permit(exercise.author, exercise)
end
it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name|
expect(policy).not_to permit(create(factory_name), exercise)
end
end
end
end
%i[export_external_check? export_external_confirm?].each do |action|
permissions(action) do
context 'when user is author' do
let(:user) { exercise.author }
it 'does not grant access' do
expect(policy).not_to permit(user, exercise)
end
context 'when user has codeharbor_link' do
before { user.codeharbor_link = build(:codeharbor_link) }
it 'grants access' do
expect(policy).to permit(user, exercise)
end
end
end
context 'when user is admin' do
let(:user) { build(:admin) }
it 'does not grant access' do
expect(policy).not_to permit(user, exercise)
end
context 'when user has codeharbor_link' do
before { user.codeharbor_link = build(:codeharbor_link) }
it 'grants access' do
expect(policy).to permit(user, exercise)
end
end
end
%i[external_user teacher].each do |factory_name|
context "when user is #{factory_name}" do
let(:user) { create(factory_name) }
it 'does not grant access' do
expect(policy).not_to permit(user, exercise)
end
context 'when user has codeharbor_link' do
before { user.codeharbor_link = build(:codeharbor_link) }
it 'does not grant access' do
expect(policy).not_to permit(user, exercise)
end
end
end
end
end
end
permissions :show? do
let(:teacher) { create(:teacher) }
let(:exercise_not_public) { build(:dummy, public: false) }
it 'does not grant access to external users' do
expect(policy).not_to permit(build(:external_user), exercise_not_public)
end
context 'when a teacher is not a member in the same study group as the exercise author' do
it 'not grants access to the user' do
expect(policy).not_to permit(teacher, exercise_not_public)
end
end
context "when a teacher is only a member of type 'learner' in the same study group as the exercise author" do
it 'not grants access to the user' do
exercise_not_public.author.study_groups << teacher.study_groups.first
expect(policy).not_to permit(teacher, exercise_not_public)
end
end
context 'when a teacher and the exercise author are teaching team members of the same study group' do
it 'grants access to the user' do
exercise_not_public.author.study_groups << teacher.study_groups.first
exercise_not_public.author.study_group_memberships.last.update(role: 'teacher')
expect(policy).to permit(teacher, exercise_not_public)
end
end
end
%i[implement? working_times? intervention? reload?].each do |action|
permissions(action) do
context 'when the exercise has no visible files' do
let(:exercise) { create(:dummy) }
it 'does not grant access to anyone' do
%i[admin external_user teacher].each do |factory_name|
expect(policy).not_to permit(create(factory_name), exercise)
end
end
end
context 'when the exercise has visible files' do
let(:exercise) { create(:fibonacci) }
it 'grants access to anyone' do
%i[admin external_user teacher].each do |factory_name|
expect(policy).to permit(create(factory_name), exercise)
end
end
end
context 'when the exercise is published' do
let(:exercise) { create(:fibonacci, unpublished: false) }
it 'grants access to anyone' do
%i[admin external_user teacher].each do |factory_name|
expect(policy).to permit(create(factory_name), exercise)
end
end
end
context 'when the exercise is unpublished' do
let(:exercise) { create(:fibonacci, unpublished: true) }
it 'grants access to admins' do
expect(policy).to permit(build(:admin), exercise)
end
it 'grants access to the author' do
expect(policy).to permit(exercise.author, exercise)
end
it 'does not grant access to everyone' do
%i[external_user teacher].each do |factory_name|
expect(policy).not_to permit(create(factory_name), exercise)
end
end
end
end
end
describe ExercisePolicy::Scope do
describe '#resolve' do
let(:admin) { create(:admin) }
let(:external_user) { create(:external_user) }
let(:teacher) { create(:teacher) }
before do
[admin, teacher].each do |user|
[true, false].each do |public|
create(:dummy, public:, user:)
end
end
end
context 'when being an admin' do
let(:scope) { Pundit.policy_scope!(admin, Exercise) }
it 'returns all exercises' do
expect(scope.map(&:id)).to include(*Exercise.all.map(&:id))
end
end
context 'when being an external users' do
let(:scope) { Pundit.policy_scope!(external_user, Exercise) }
it 'returns nothing' do
expect(scope.count).to be 0
end
end
context 'when being a teacher' do
let(:scope) { Pundit.policy_scope!(teacher, Exercise) }
it 'includes all public exercises' do
expect(scope.map(&:id)).to include(*Exercise.where(public: true).map(&:id))
end
it 'includes all authored non-public exercises' do
expect(scope.map(&:id)).to include(*Exercise.where(public: false, user: teacher).map(&:id))
end
it "does not include other authors' non-public exercises" do
expect(scope.map(&:id)).not_to include(*Exercise.where(public: false).where.not(user: teacher).map(&:id))
end
end
end
end
end