-
Notifications
You must be signed in to change notification settings - Fork 650
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a first version for the copy course service, and basic specs for it
- Loading branch information
Showing
2 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# frozen_string_literal: true | ||
|
||
#= Copy course from another server dashboard | ||
class CopyCourse | ||
def initialize(url:) | ||
@url = url | ||
end | ||
|
||
def make_copy | ||
@course_data = retrieve_course_data | ||
copy_main_course_data | ||
add_tracked_wikis | ||
@cat_data = retrieve_categories_data | ||
copy_tracked_categories_data | ||
@users_data = retrieve_users_data | ||
copy_users_data | ||
return { course: @course, error: nil } | ||
rescue StandardError => e | ||
return { course: nil, error: e.message } | ||
rescue ActiveRecord::RecordInvalid => e | ||
return { course: nil, error: "Error copying data to db: #{e.message}" } | ||
end | ||
|
||
private | ||
|
||
def copy_main_course_data | ||
# Extract the attributes we want to copy | ||
params_to_copy = %w[school title term description start end subject slug timeline_start | ||
timeline_end type flags] | ||
copied_data = {} | ||
params_to_copy.each { |p| copied_data[p] = @course_data[p] } | ||
@home_wiki = Wiki.get_or_create(language: @course_data['home_wiki']['language'], | ||
project: @course_data['home_wiki']['project']) | ||
copied_data['home_wiki_id'] = @home_wiki.id | ||
copied_data['passcode'] = 'passcode' # set an arbitrary passcode | ||
if copied_data['flags'].key?('update_logs') | ||
copied_data['flags']['update_logs'] = | ||
fix_update_logs_parsing(copied_data['flags']['update_logs']) | ||
end | ||
# Create the course | ||
@course = Course.create!(copied_data) | ||
end | ||
|
||
# When parsing update_logs from flags, keys are set as strings instead of integers | ||
# This causes problems, so we need to force the keys to be integers. | ||
def fix_update_logs_parsing(update_logs) | ||
update_logs.transform_keys(&:to_i) | ||
end | ||
|
||
def add_tracked_wikis | ||
@course_data['wikis'].each do |wiki_hash| | ||
wiki = Wiki.get_or_create(language: wiki_hash['language'], project: wiki_hash['project']) | ||
next if wiki.id == @home_wiki.id # home wiki was automatically added already | ||
@course.wikis << wiki | ||
end | ||
end | ||
|
||
def copy_tracked_categories_data | ||
@cat_data.each do |cat_hash| | ||
wiki = Wiki.get_or_create(language: cat_hash['wiki']['language'], | ||
project: cat_hash['wiki']['project']) | ||
cat = Category.find_or_create_by!( | ||
depth: cat_hash['depth'], | ||
source: cat_hash['source'], | ||
name: cat_hash['name'], | ||
wiki: | ||
) | ||
@course.categories << cat | ||
end | ||
end | ||
|
||
def copy_users_data | ||
@users_data.each do |user_hash| | ||
user = User.find_or_create_by!(username: user_hash['username']) | ||
CoursesUsers.create!(user_id: user.id, role: user_hash['role'], course_id: @course.id) | ||
end | ||
end | ||
|
||
def get_request(path) | ||
uri = URI(@url + path) | ||
response = Net::HTTP.get_response(uri) | ||
raise "Error getting data from #{uri}" unless response.is_a?(Net::HTTPSuccess) | ||
response | ||
end | ||
|
||
def retrieve_course_data | ||
response = get_request('/course.json') | ||
JSON.parse(response.body)['course'] | ||
end | ||
|
||
def retrieve_categories_data | ||
response = get_request('/categories.json') | ||
JSON.parse(response.body)['course']['categories'] | ||
end | ||
|
||
def retrieve_users_data | ||
response = get_request('/users.json') | ||
JSON.parse(response.body)['course']['users'] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# frozen_string_literal: true | ||
require 'rails_helper' | ||
|
||
describe CopyCourse do | ||
let(:url_base) { 'https://dashboard.wikiedu.org/courses/' } | ||
let(:existent_prod_course_slug) do | ||
'University_of_South_Carolina/Invertebrate_Zoology_(Spring_2022)' | ||
end | ||
let(:course_url) { url_base + existent_prod_course_slug + '/course.json' } | ||
let(:categories_url) { url_base + existent_prod_course_slug + '/categories.json' } | ||
let(:users_url) { url_base + existent_prod_course_slug + '/users.json' } | ||
let(:course_response_body) do | ||
'{ | ||
"course": { | ||
"id": 15907, | ||
"slug": "University_of_South_Carolina/Invertebrate_Zoology_(Spring_2022)", | ||
"school": "University of South Carolina", | ||
"title": "Invertebrate Zoology", | ||
"term": "Spring 2022", | ||
"start": "2022-01-11T00:00:00.000Z", | ||
"end": "2022-04-30T23:59:59.000Z", | ||
"type": "ClassroomProgramCourse", | ||
"home_wiki": { | ||
"id": 1, | ||
"language": "en", | ||
"project": "wikipedia" | ||
}, | ||
"flags": { | ||
"update_logs": { | ||
"28763": { | ||
"start_time": "2022-05-26T16:13:42.177+00:00", | ||
"end_time": "2022-05-26T16:13:46.648+00:00", | ||
"sentry_tag_uuid": "4c0a1b87-da5a-4e82-8f40-3d560690cdb2", | ||
"error_count": 0 | ||
} | ||
} | ||
}, | ||
"wikis": [ | ||
{ | ||
"language": "en", | ||
"project": "wikipedia" | ||
} | ||
] | ||
} | ||
}' | ||
end | ||
let(:categories_response_body) do | ||
'{ | ||
"course": { | ||
"categories": [ | ||
{ | ||
"name": "Category 0", | ||
"depth": 0, | ||
"source": "Source 0", | ||
"wiki": { | ||
"id": 1, | ||
"language": "en", | ||
"project": "wikipedia" | ||
} | ||
} | ||
] | ||
} | ||
}' | ||
end | ||
let(:users_response_body) do | ||
'{ | ||
"course": { | ||
"users": [ | ||
{ | ||
"role": 1, | ||
"id": 28451264, | ||
"username": "Joshua Stone" | ||
}, | ||
{ | ||
"role": 4, | ||
"id": 22694295, | ||
"username": "Helaine (Wiki Ed)" | ||
}, | ||
{ | ||
"role": 0, | ||
"id": 28515697, | ||
"username": "CharlieJ385" | ||
}, | ||
{ | ||
"role": 0, | ||
"id": 28515751, | ||
"username": "Diqi Yan" | ||
} | ||
] | ||
} | ||
}' | ||
end | ||
let(:subject) do | ||
service = described_class.new(url: url_base + existent_prod_course_slug) | ||
service.make_copy | ||
end | ||
|
||
describe '#make_copy' do | ||
it 'returns an error if /course.json request fails' do | ||
stub_request(:get, course_url) | ||
.to_return(status: 404, body: '', headers: {}) | ||
|
||
result = subject | ||
expect(result[:error]).to eq("Error getting data from #{course_url}") | ||
expect(result[:course]).to be_nil | ||
end | ||
|
||
it 'returns an error if /categories.json request fails' do | ||
# Stub the response to the course request | ||
stub_request(:get, course_url) | ||
.to_return(status: 200, body: course_response_body, headers: {}) | ||
# Stub the response to the categories request | ||
stub_request(:get, categories_url) | ||
.to_return(status: 404, body: '', headers: {}) | ||
|
||
result = subject | ||
expect(result[:error]).to eq("Error getting data from #{categories_url}") | ||
expect(result[:course]).to be_nil | ||
end | ||
|
||
it 'returns an error if /users.json request fails' do | ||
# Stub the response to the course request | ||
stub_request(:get, course_url) | ||
.to_return(status: 200, body: course_response_body, headers: {}) | ||
|
||
# Stub the response to the categories request | ||
stub_request(:get, categories_url) | ||
.to_return(status: 200, body: categories_response_body, headers: {}) | ||
|
||
# Stub the response to the users request | ||
stub_request(:get, users_url) | ||
.to_return(status: 404, body: '', headers: {}) | ||
|
||
result = subject | ||
expect(result[:error]).to eq("Error getting data from #{users_url}") | ||
expect(result[:course]).to be_nil | ||
end | ||
|
||
it 'course, categories, and users are created if no error' do | ||
# Stub the response to the course request | ||
stub_request(:get, course_url) | ||
.to_return(status: 200, body: course_response_body, headers: {}) | ||
|
||
# Stub the response to the categories request | ||
stub_request(:get, categories_url) | ||
.to_return(status: 200, body: categories_response_body, headers: {}) | ||
|
||
# Stub the response to the users request | ||
stub_request(:get, users_url) | ||
.to_return(status: 200, body: users_response_body, headers: {}) | ||
result = subject | ||
|
||
# No error was returned | ||
expect(result[:error]).to be_nil | ||
# Course returned is not nil | ||
expect(result[:course]).not_to be_nil | ||
|
||
# The course was created | ||
expect(Course.exists?(slug: existent_prod_course_slug)).to eq(true) | ||
|
||
# Course users were created | ||
course = Course.find_by(slug: existent_prod_course_slug) | ||
|
||
expect(course.instructors.length).to eq(1) | ||
expect(course.instructors.first.username).to eq('Joshua Stone') | ||
|
||
expect(course.staff.length).to eq(1) | ||
expect(course.staff.first.username).to eq('Helaine (Wiki Ed)') | ||
|
||
expect(course.students.length).to eq(2) | ||
expect(course.students.first.username).to eq('CharlieJ385') | ||
expect(course.students.second.username).to eq('Diqi Yan') | ||
|
||
# Wiki exists | ||
expect(Wiki.exists?(language: 'en', project: 'wikipedia')).to eq(true) | ||
|
||
# Category was created | ||
expect(Category.exists?(name: 'Category 0', depth: 0, source: 'Source 0', | ||
wiki: 1)).to eq(true) | ||
|
||
# Category course was created | ||
expect(course.categories.length).to eq(1) | ||
|
||
# Users were created | ||
expect(User.exists?(username: 'Joshua Stone')).to eq(true) | ||
expect(User.exists?(username: 'Helaine (Wiki Ed)')).to eq(true) | ||
expect(User.exists?(username: 'CharlieJ385')).to eq(true) | ||
expect(User.exists?(username: 'Diqi Yan')).to eq(true) | ||
|
||
# Update logs were correctly created | ||
course.flags['update_logs'].each do |key, _value| | ||
expect(key).to be_a(Integer) | ||
end | ||
end | ||
end | ||
end |