Skip to content

Commit 726d58f

Browse files
committed
Add SchoolImportResultDashboard
1 parent 8636584 commit 726d58f

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
require 'administrate/base_dashboard'
4+
5+
class SchoolImportResultDashboard < Administrate::BaseDashboard
6+
ATTRIBUTE_TYPES = {
7+
id: Field::String,
8+
job_id: StatusField,
9+
user_id: UserInfoField,
10+
results: ResultsSummaryField,
11+
created_at: Field::DateTime,
12+
updated_at: Field::DateTime
13+
}.freeze
14+
15+
COLLECTION_ATTRIBUTES = %i[
16+
job_id
17+
user_id
18+
results
19+
created_at
20+
].freeze
21+
22+
SHOW_PAGE_ATTRIBUTES = %i[
23+
id
24+
job_id
25+
user_id
26+
results
27+
created_at
28+
updated_at
29+
].freeze
30+
31+
FORM_ATTRIBUTES = [].freeze
32+
33+
COLLECTION_FILTERS = {}.freeze
34+
35+
def display_resource(school_import_result)
36+
"Import Job #{school_import_result.job_id}"
37+
end
38+
end
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Admin::SchoolImportResults' do
6+
let(:admin_user) { create(:user, roles: 'editor-admin') }
7+
8+
before do
9+
allow_any_instance_of(Admin::ApplicationController).to receive(:current_user).and_return(admin_user)
10+
11+
# Stub UserInfoApiClient to avoid external API calls
12+
allow(UserInfoApiClient).to receive(:fetch_by_ids).and_return([
13+
{
14+
id: admin_user.id,
15+
name: 'Test Admin',
16+
17+
}
18+
])
19+
end
20+
21+
describe 'GET /admin/school_import_results' do
22+
it 'returns successfully' do
23+
get admin_school_import_results_path
24+
expect(response).to have_http_status(:success)
25+
end
26+
27+
context 'with existing import results' do
28+
let!(:import_result) do
29+
SchoolImportResult.create!(
30+
job_id: SecureRandom.uuid,
31+
user_id: admin_user.id,
32+
results: { 'successful' => [], 'failed' => [] }
33+
)
34+
end
35+
36+
it 'displays the import results' do
37+
get admin_school_import_results_path
38+
expect(response.body).to include('School Import History')
39+
expect(response.body).to include('Test Admin &lt;[email protected]&gt;')
40+
end
41+
end
42+
end
43+
44+
describe 'GET /admin/school_import_results/new' do
45+
it 'returns successfully' do
46+
get new_admin_school_import_result_path
47+
expect(response).to have_http_status(:success)
48+
end
49+
50+
it 'displays the upload form' do
51+
get new_admin_school_import_result_path
52+
expect(response.body).to include('Upload School Import CSV')
53+
expect(response.body).to include('csv_file')
54+
end
55+
end
56+
57+
describe 'GET /admin/school_import_results/:id' do
58+
let(:import_result) do
59+
SchoolImportResult.create!(
60+
job_id: SecureRandom.uuid,
61+
user_id: admin_user.id,
62+
results: {
63+
'successful' => [
64+
{ 'name' => 'Test School', 'code' => '12-34-56', 'id' => SecureRandom.uuid, 'owner_email' => '[email protected]' }
65+
],
66+
'failed' => []
67+
}
68+
)
69+
end
70+
71+
it 'returns successfully' do
72+
get admin_school_import_result_path(import_result)
73+
expect(response).to have_http_status(:success)
74+
end
75+
76+
it 'displays the job details' do
77+
get admin_school_import_result_path(import_result)
78+
expect(response.body).to include(import_result.job_id)
79+
expect(response.body).to include('Test School')
80+
end
81+
82+
it 'displays user name and email' do
83+
get admin_school_import_result_path(import_result)
84+
expect(response.body).to include('Test Admin &lt;[email protected]&gt;')
85+
end
86+
87+
it 'allows downloading results as CSV' do
88+
get admin_school_import_result_path(import_result, format: :csv)
89+
expect(response).to have_http_status(:success)
90+
expect(response.content_type).to include('text/csv')
91+
expect(response.body).to include('Status,School Name,School Code')
92+
expect(response.body).to include('Success,Test School,12-34-56')
93+
end
94+
95+
context 'with failed schools' do
96+
let(:import_result_with_failures) do
97+
SchoolImportResult.create!(
98+
job_id: SecureRandom.uuid,
99+
user_id: admin_user.id,
100+
results: {
101+
'successful' => [
102+
{ 'name' => 'Good School', 'code' => '11-22-33', 'id' => SecureRandom.uuid, 'owner_email' => '[email protected]' }
103+
],
104+
'failed' => [
105+
{ 'name' => 'Bad School', 'owner_email' => '[email protected]', 'error_code' => 'OWNER_NOT_FOUND', 'error' => 'Owner not found' }
106+
]
107+
}
108+
)
109+
end
110+
111+
it 'includes both successful and failed schools in CSV' do
112+
get admin_school_import_result_path(import_result_with_failures, format: :csv)
113+
expect(response.body).to include('Success,Good School,11-22-33')
114+
expect(response.body).to include('Failed,Bad School')
115+
expect(response.body).to include('OWNER_NOT_FOUND,Owner not found')
116+
end
117+
end
118+
end
119+
120+
describe 'POST /admin/school_import_results' do
121+
let(:csv_content) { "name,website,address_line_1,municipality,country_code,owner_email\nTest,https://test.edu,123 Main,City,US,[email protected]" }
122+
let(:csv_file) { Rack::Test::UploadedFile.new(StringIO.new(csv_content), 'text/csv', original_filename: 'test.csv') }
123+
let(:job_id) { SecureRandom.uuid }
124+
125+
before do
126+
allow(School::ImportBatch).to receive(:call).and_return(
127+
OperationResponse.new.tap do |response|
128+
response[:job_id] = job_id
129+
response[:total_schools] = 3
130+
end
131+
)
132+
end
133+
134+
it 'starts an import job' do
135+
post admin_school_import_results_path, params: { csv_file: csv_file }
136+
expect(School::ImportBatch).to have_received(:call)
137+
expect(response).to redirect_to(admin_school_import_results_path)
138+
expect(flash[:notice]).to include(job_id)
139+
end
140+
141+
context 'without a CSV file' do
142+
it 'shows an error' do
143+
post admin_school_import_results_path, params: {}
144+
expect(response).to redirect_to(new_admin_school_import_result_path)
145+
expect(flash[:error]).to include('CSV file is required')
146+
end
147+
end
148+
149+
context 'when import fails' do
150+
before do
151+
allow(School::ImportBatch).to receive(:call).and_return(
152+
OperationResponse.new.tap do |response|
153+
response[:error] = {
154+
message: 'CSV validation failed',
155+
details: {
156+
row_errors: [
157+
{ row: 2, errors: [{ field: 'country_code', message: 'invalid code' }] }
158+
]
159+
}
160+
}
161+
end
162+
)
163+
end
164+
165+
it 'shows an error message with details' do
166+
post admin_school_import_results_path, params: { csv_file: csv_file }
167+
expect(response).to have_http_status(:success)
168+
expect(response.body).to include('CSV validation failed')
169+
expect(response.body).to include('Row Errors')
170+
end
171+
end
172+
end
173+
174+
describe 'authorization' do
175+
let(:non_admin_user) { create(:user, roles: nil) }
176+
177+
before do
178+
allow_any_instance_of(Admin::ApplicationController).to receive(:current_user).and_return(non_admin_user)
179+
end
180+
181+
it 'redirects non-admin users' do
182+
get admin_school_import_results_path
183+
expect(response).to redirect_to('/')
184+
end
185+
end
186+
end

0 commit comments

Comments
 (0)