Skip to content

Commit 1064458

Browse files
authored
Delete feedback (#634)
## Status - Related to RaspberryPiFoundation/digital-editor-issues#948 ## What's changed? - Added route to allow users to delete feedback - Owners can read and delete any feedback within the school - Teachers can only delete feedback for projects within their class
1 parent 09270f8 commit 1064458

7 files changed

Lines changed: 190 additions & 2 deletions

File tree

app/controllers/api/feedback_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ def set_read
4242
end
4343
end
4444

45+
def destroy
46+
result = Feedback::Delete.call(feedback_id: params[:id])
47+
48+
if result.success?
49+
head :no_content
50+
else
51+
render json: { error: result[:error] }, status: :unprocessable_entity
52+
end
53+
end
54+
4555
private
4656

4757
def project

app/models/ability.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def define_school_owner_abilities(school:)
6969
can(%i[read create create_batch update destroy], :school_student)
7070
can(%i[create create_copy], Lesson, school_id: school.id)
7171
can(%i[read update destroy], Lesson, school_id: school.id, visibility: %w[teachers students public])
72+
can(%i[read destroy], Feedback, school_project: { school_id: school.id })
7273
can(%i[exchange_code], :google_auth)
7374
end
7475

@@ -98,7 +99,7 @@ def define_school_teacher_abilities(user:, school:)
9899
).pluck(:id)
99100
can(%i[read], Project, remixed_from_id: teacher_project_ids)
100101
can(%i[show_status unsubmit return complete], SchoolProject, project: { remixed_from_id: teacher_project_ids })
101-
can(%i[read create], Feedback, school_project: { project: { remixed_from_id: teacher_project_ids } })
102+
can(%i[read create destroy], Feedback, school_project: { project: { remixed_from_id: teacher_project_ids } })
102103
can(%i[exchange_code], :google_auth)
103104
end
104105

config/routes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
end
4949
resources :remixes, only: %i[index], controller: 'projects/remixes'
5050
resource :images, only: %i[show create], controller: 'projects/images'
51-
resources :feedback, only: %i[index create], controller: 'feedback' do
51+
resources :feedback, only: %i[index create destroy], controller: 'feedback' do
5252
put :read, on: :member, to: 'feedback#set_read'
5353
end
5454
end

lib/concepts/feedback/delete.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
class Feedback
4+
class Delete
5+
class << self
6+
def call(feedback_id:)
7+
response = OperationResponse.new
8+
delete_feedback(feedback_id)
9+
response
10+
rescue StandardError => e
11+
Sentry.capture_exception(e)
12+
response[:error] = "Error deleting feedback: #{e}"
13+
response
14+
end
15+
16+
private
17+
18+
def delete_feedback(feedback_id)
19+
feedback = Feedback.find(feedback_id)
20+
feedback.destroy!
21+
end
22+
end
23+
end
24+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe Feedback::Delete, type: :unit do
6+
let(:feedback) { create(:feedback) }
7+
8+
describe '.call' do
9+
context 'when deletion is successful' do
10+
let!(:feedback_id) { feedback.id }
11+
12+
it 'returns a successful operation response' do
13+
response = described_class.call(feedback_id:)
14+
expect(response.success?).to be(true)
15+
end
16+
17+
it 'deletes the feedback' do
18+
expect { described_class.call(feedback_id:) }.to change(Feedback, :count).by(-1)
19+
expect(Feedback.where(id: feedback_id)).to be_empty
20+
end
21+
end
22+
23+
context 'when deletion fails' do
24+
let(:feedback_id) { 'does-not-exist' }
25+
26+
before do
27+
allow(Sentry).to receive(:capture_exception)
28+
end
29+
30+
it 'returns a failed operation response' do
31+
response = described_class.call(feedback_id:)
32+
expect(response.success?).to be(false)
33+
end
34+
35+
it 'returns the error message in the operation response' do
36+
response = described_class.call(feedback_id:)
37+
expect(response[:error]).to match(/does-not-exist/)
38+
end
39+
40+
it 'sent the exception to Sentry' do
41+
described_class.call(feedback_id:)
42+
expect(Sentry).to have_received(:capture_exception).with(kind_of(StandardError))
43+
end
44+
end
45+
end
46+
end
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Delete feedback requests', type: :request do
6+
let(:headers) { { Authorization: UserProfileMock::TOKEN } }
7+
let(:school) { create(:school) }
8+
let(:student) { create(:student, school:) }
9+
let(:teacher) { create(:teacher, school:) }
10+
let(:school_class) { create(:school_class, teacher_ids: [teacher.id], school:) }
11+
let(:class_student) { create(:class_student, school_class:, student_id: student.id) }
12+
let(:lesson) { create(:lesson, school:, school_class:, user_id: teacher.id, visibility: 'students') }
13+
let(:teacher_project) { create(:project, user_id: teacher.id, school:, lesson:) }
14+
let(:student_project) { create(:project, user_id: class_student.student_id, school:, parent: teacher_project) }
15+
let!(:feedback) { create(:feedback, school_project: student_project.school_project, user_id: teacher.id, content: 'Excellent work!') }
16+
17+
context 'when logged in as the class teacher' do
18+
before do
19+
authenticated_in_hydra_as(teacher)
20+
end
21+
22+
context 'when deleting feedback on student work' do
23+
before do
24+
delete("/api/projects/#{student_project.identifier}/feedback/#{feedback.id}", headers:)
25+
student_project.reload
26+
end
27+
28+
it 'returns no content response' do
29+
expect(response).to have_http_status(:no_content)
30+
end
31+
32+
it 'removes the feedback from the school project' do
33+
expect(student_project.school_project.feedback.count).to eq(0)
34+
end
35+
end
36+
37+
context 'when attempting to delete non-existent feedback' do
38+
before do
39+
delete("/api/projects/#{student_project.identifier}/feedback/invalid-id", headers:)
40+
end
41+
42+
it 'returns not found response' do
43+
expect(response).to have_http_status(:not_found)
44+
end
45+
46+
it 'returns an error message' do
47+
data = JSON.parse(response.body, symbolize_names: true)
48+
expect(data[:error]).to match(/Couldn't find Feedback with 'id'="invalid-id"/)
49+
end
50+
end
51+
end
52+
53+
context 'when logged in as the school owner' do
54+
let(:owner) { create(:owner, school:) }
55+
56+
before do
57+
authenticated_in_hydra_as(owner)
58+
delete("/api/projects/#{student_project.identifier}/feedback/#{feedback.id}", headers:)
59+
student_project.reload
60+
end
61+
62+
it 'returns no content response' do
63+
expect(response).to have_http_status(:no_content)
64+
end
65+
66+
it 'removes the feedback from the school project' do
67+
expect(student_project.school_project.feedback.count).to eq(0)
68+
end
69+
end
70+
71+
context 'when logged in as a teacher not in the class' do
72+
let(:other_teacher) { create(:teacher, school:) }
73+
74+
before do
75+
authenticated_in_hydra_as(other_teacher)
76+
delete("/api/projects/#{student_project.identifier}/feedback/#{feedback.id}", headers:)
77+
student_project.reload
78+
end
79+
80+
it 'returns forbidden response' do
81+
expect(response).to have_http_status(:forbidden)
82+
end
83+
84+
it 'does not remove the feedback from the school project' do
85+
expect(student_project.school_project.feedback.count).to eq(1)
86+
end
87+
end
88+
89+
context 'when logged in as a school student' do
90+
before do
91+
authenticated_in_hydra_as(student)
92+
delete("/api/projects/#{student_project.identifier}/feedback/#{feedback.id}", headers:)
93+
end
94+
95+
it 'returns forbidden response' do
96+
expect(response).to have_http_status(:forbidden)
97+
end
98+
99+
it 'does not remove the feedback from the school project' do
100+
expect(student_project.school_project.feedback.count).to eq(1)
101+
end
102+
end
103+
end

spec/models/ability_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@
310310
it { is_expected.not_to be_able_to(:create, feedback) }
311311
it { is_expected.to be_able_to(:read, feedback) }
312312
it { is_expected.to be_able_to(:set_read, feedback) }
313+
it { is_expected.not_to be_able_to(:destroy, feedback) }
313314
it { is_expected.to be_able_to(:create, remixed_project) }
314315
it { is_expected.to be_able_to(:update, remixed_project) }
315316
it { is_expected.not_to be_able_to(:destroy, remixed_project) }
@@ -340,6 +341,7 @@
340341
it { is_expected.not_to be_able_to(:create, feedback) }
341342
it { is_expected.not_to be_able_to(:read, feedback) }
342343
it { is_expected.not_to be_able_to(:set_read, feedback) }
344+
it { is_expected.not_to be_able_to(:destroy, feedback) }
343345
it { is_expected.not_to be_able_to(:create, remixed_project) }
344346
it { is_expected.not_to be_able_to(:update, remixed_project) }
345347
it { is_expected.not_to be_able_to(:destroy, remixed_project) }
@@ -357,6 +359,7 @@
357359
it { is_expected.to be_able_to(:create, feedback) }
358360
it { is_expected.to be_able_to(:read, feedback) }
359361
it { is_expected.not_to be_able_to(:set_read, feedback) }
362+
it { is_expected.to be_able_to(:destroy, feedback) }
360363
it { is_expected.not_to be_able_to(:create, remixed_project) }
361364
it { is_expected.not_to be_able_to(:update, remixed_project) }
362365
it { is_expected.not_to be_able_to(:destroy, remixed_project) }
@@ -374,6 +377,7 @@
374377
it { is_expected.to be_able_to(:create, feedback) }
375378
it { is_expected.to be_able_to(:read, feedback) }
376379
it { is_expected.not_to be_able_to(:set_read, feedback) }
380+
it { is_expected.to be_able_to(:destroy, feedback) }
377381
it { is_expected.not_to be_able_to(:create, original_project) }
378382
it { is_expected.to be_able_to(:update, original_project) }
379383

0 commit comments

Comments
 (0)