Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- Enable zip downloads of test results (#7733)
- Create rake task to remove orphaned end users (#7741)
- Enable scanned assignments the ability to add inactive students (#7737)
- Enable test results downloads through the API (#7754)

### 🐛 Bug fixes
- Fix name column search in graders table (#7693)
Expand Down
25 changes: 25 additions & 0 deletions app/controllers/api/groups_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,31 @@ def annotations
end
end

def test_results
return render_no_grouping_error unless grouping

# Use the existing Assignment#summary_test_results method filtered for this specific group
# This ensures format consistency with the UI download (summary_test_result_json)
group_name = grouping.group.group_name
results = assignment.summary_test_results([group_name])

return render_no_grouping_error if results.blank?

# Group by test_group name to match the summary_test_result_json format
results_by_test_group = results.group_by(&:name)

respond_to do |format|
format.xml { render xml: results_by_test_group.to_xml(root: 'test_results', skip_types: 'true') }
format.json { render json: results_by_test_group }
end
end

def render_no_grouping_error
render 'shared/http_status',
locals: { code: '404', message: 'No test results found for this group' },
status: :not_found
end

def add_annotations
result = self.grouping&.current_result
return page_not_found('No submission exists for that group') if result.nil?
Expand Down
26 changes: 15 additions & 11 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ def summary_json(user)
end

# Generates the summary of the most test results associated with an assignment.
def summary_test_results
def summary_test_results(group_names = nil)
latest_test_run_by_grouping = TestRun.group('grouping_id').select('MAX(created_at) as test_runs_created_at',
'grouping_id')
.where.not(submission_id: nil)
Expand All @@ -682,17 +682,21 @@ def summary_test_results
.select('id', 'test_runs.grouping_id', 'groups.group_name')
.to_sql

self.test_groups.joins(test_group_results: :test_results)
.joins("INNER JOIN (#{latest_test_runs}) latest_test_runs \
query = self.test_groups.joins(test_group_results: :test_results)
.joins("INNER JOIN (#{latest_test_runs}) latest_test_runs \
ON test_group_results.test_run_id = latest_test_runs.id")
.select('test_groups.name',
'test_groups.id as test_groups_id',
'latest_test_runs.group_name',
'test_results.name as test_result_name',
'test_results.status',
'test_results.marks_earned',
'test_results.marks_total',
:output, :extra_info, :error_type)

# Optionally - filters specific groups if provided
query = query.where('latest_test_runs.group_name': group_names) if group_names.present?

query.select('test_groups.name',
'test_groups.id as test_groups_id',
'latest_test_runs.group_name',
'test_results.name as test_result_name',
'test_results.status',
'test_results.marks_earned',
'test_results.marks_total',
:output, :extra_info, :error_type)
end

# Generate a JSON summary of the most recent test results associated with an assignment.
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
put 'remove_tag'
post 'collect_submission'
post 'add_test_run'
get 'test_results'
end
resources :submission_files, only: [:index, :create] do
collection do
Expand Down
154 changes: 154 additions & 0 deletions spec/controllers/api/groups_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1306,5 +1306,159 @@
end
end
end

context 'GET test_results' do
let(:grouping) { create(:grouping_with_inviter, assignment: assignment) }
let(:test_group) { create(:test_group, assignment: assignment) }
let(:submission) { create(:version_used_submission, grouping: grouping) }

context 'when the group has test results' do
let!(:test_run) do
create(:test_run, grouping: grouping, role: instructor, status: :complete, submission: submission)
end
let!(:test_group_result) do
create(:test_group_result, test_run: test_run, test_group: test_group,
marks_earned: 5.0, marks_total: 10.0, time: 1000)
end

before do
create(:test_result, test_group_result: test_group_result, name: 'Test 1',
status: 'pass', marks_earned: 3.0, marks_total: 5.0, position: 1)
end

context 'expecting json response' do
before do
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end

it 'should be successful' do
expect(response).to have_http_status(:ok)
end

it 'should return data grouped by test group name' do
expect(response.parsed_body).to have_key(test_group.name)
end

it 'should return test results for the group' do
test_results = response.parsed_body[test_group.name]
expect(test_results).to be_an(Array)
expect(test_results.first).to include(
'test_result_name' => 'Test 1',
'status' => 'pass',
'marks_earned' => 3.0,
'marks_total' => 5.0
)
end
end

context 'expecting xml response' do
before do
request.env['HTTP_ACCEPT'] = 'application/xml'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end

it 'should be successful' do
expect(response).to have_http_status(:ok)
end

it 'should return xml content' do
xml_data = Hash.from_xml(response.body)
expect(xml_data).to have_key('test_results')
end
end

context 'with multiple test groups' do
let(:test_group_two) { create(:test_group, assignment: assignment, name: 'Group B') }
let!(:test_group_result_two) do
create(:test_group_result, test_run: test_run, test_group: test_group_two)
end

before do
create(:test_result, test_group_result: test_group_result_two, name: 'Test B1',
status: 'pass', marks_earned: 2.0, marks_total: 5.0, position: 1)
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end

it 'should be successful' do
expect(response).to have_http_status(:ok)
end

it 'should return results keyed by each test group name' do
expect(response.parsed_body.keys).to contain_exactly(test_group.name, test_group_two.name)
end

it 'should return correct test results for each group' do
expect(response.parsed_body[test_group.name].first['test_result_name']).to eq('Test 1')
expect(response.parsed_body[test_group_two.name].first['test_result_name']).to eq('Test B1')
end
end
end

context 'authorization check' do
it_behaves_like 'for a different course' do
before do
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end
end
end

context 'when the group has no test results' do
before do
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end

it 'should return 404 status' do
expect(response).to have_http_status(:not_found)
end
end

context 'when the group does not exist' do
before do
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: 999_999, assignment_id: assignment.id, course_id: course.id }
end

it 'should return 404 status' do
expect(response).to have_http_status(:not_found)
end
end

context 'when multiple test runs exist' do
let!(:older_test_run) do
create(:test_run, grouping: grouping, role: instructor, created_at: 2.days.ago, status: :complete,
submission: submission)
end
let!(:newer_test_run) do
create(:test_run, grouping: grouping, role: instructor, created_at: 1.hour.ago, status: :complete,
submission: submission)
end
let!(:older_test_group_result) do
create(:test_group_result, test_run: older_test_run, test_group: test_group)
end
let!(:newer_test_group_result) do
create(:test_group_result, test_run: newer_test_run, test_group: test_group)
end

before do
create(:test_result, test_group_result: older_test_group_result, name: 'Old Test',
marks_earned: 1.0, marks_total: 5.0, status: 'pass', position: 1)
create(:test_result, test_group_result: newer_test_group_result, name: 'New Test',
marks_earned: 4.0, marks_total: 5.0, status: 'pass', position: 1)
request.env['HTTP_ACCEPT'] = 'application/json'
get :test_results, params: { id: grouping.group.id, assignment_id: assignment.id, course_id: course.id }
end

it 'should return only the latest test run results' do
test_results = response.parsed_body[test_group.name]
expect(test_results.length).to eq(1)
expect(test_results.first['test_result_name']).to eq('New Test')
expect(test_results.first['marks_earned']).to eq(4.0)
end
end
end
end
end