Skip to content
Draft
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
31 changes: 29 additions & 2 deletions lib/concepts/lesson/operations/create_copy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ class CreateCopy
class << self
def call(lesson:, lesson_params:)
response = OperationResponse.new
response[:lesson] = build_copy(lesson, lesson_params)
response[:lesson].save!

Lesson.transaction do
response[:lesson] = build_copy(lesson, lesson_params)
response[:lesson].save!
copy_scratch_assets(lesson.project, response[:lesson].project)
end

response
rescue StandardError => e
Sentry.capture_exception(e)
Expand Down Expand Up @@ -39,8 +44,30 @@ def build_project_copy(project, project_params)
project_copy.components.build({ name: component.name, extension: component.extension, content: component.content })
end

copy_scratch_component(project, project_copy)

project_copy
end

def copy_scratch_component(project, project_copy)
return unless project.scratch_project? && project.scratch_component

project_copy.build_scratch_component(content: project.scratch_component.content.deep_dup)
end

def copy_scratch_assets(project, project_copy)
return unless project.scratch_project?

project.scratch_assets.where(uploaded_user_id: project.user_id).find_each do |scratch_asset|
next unless scratch_asset.file.attached?

scratch_asset_copy = project_copy.scratch_assets.create!(
filename: scratch_asset.filename,
uploaded_user_id: project_copy.user_id
)
scratch_asset_copy.file.attach(scratch_asset.file.blob)
end
end
end
end
end
78 changes: 78 additions & 0 deletions spec/concepts/lesson/create_copy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,78 @@
expect(copied_component.content).to eq(original_component.content)
end

context 'when the project is a Scratch project' do
let(:copied_teacher_id) { SecureRandom.uuid }
let(:lesson_params) { { user_id: copied_teacher_id } }
let(:scratch_content) do
{
targets: [
{
isStage: true,
costumes: [
{
assetId: 'teacher_asset',
md5ext: 'teacher_asset.png',
dataFormat: 'png'
}
]
}
],
monitors: [],
extensions: [],
meta: {}
}
end

before do
lesson.project.update!(project_type: Project::Types::CODE_EDITOR_SCRATCH, locale: nil)
create(:scratch_component, project: lesson.project, content: scratch_content)
end

it 'copies the Scratch component content' do
response = described_class.call(lesson:, lesson_params:)
copied_project = response[:lesson].reload.project

expect(copied_project.scratch_component.content.to_h)
.to eq(scratch_content.deep_stringify_keys)
end

it 'copies only teacher-owned Scratch assets to the new project owner' do
create_scratch_asset(
filename: 'teacher_asset.png',
project: lesson.project,
uploaded_user_id: teacher_id,
body: 'teacher-body'
)
create_scratch_asset(
filename: 'student_asset.png',
project: lesson.project,
uploaded_user_id: SecureRandom.uuid,
body: 'student-body'
)

response = described_class.call(lesson:, lesson_params:)
copied_project = response[:lesson].reload.project
copied_asset = ScratchAsset.find_by!(
filename: 'teacher_asset.png',
project: copied_project,
uploaded_user_id: copied_teacher_id
)

visible_asset = ScratchAsset.find_visible_to_project(
project: copied_project,
user: User.new(id: SecureRandom.uuid),
filename: 'teacher_asset.png'
)

expect(copied_asset.file.download).to eq('teacher-body')
expect(visible_asset).to eq(copied_asset)
expect(
ScratchAsset.find_by(filename: 'student_asset.png', project: copied_project)
).to be_nil
end
end

context 'when creating a copy fails' do
let(:lesson_params) { { name: ' ' } }

Expand All @@ -135,4 +207,10 @@
expect(Sentry).to have_received(:capture_exception).with(kind_of(StandardError))
end
end

def create_scratch_asset(filename:, project:, uploaded_user_id:, body:)
ScratchAsset.create!(filename:, project:, uploaded_user_id:).tap do |asset|
asset.file.attach(io: StringIO.new(body), filename:, content_type: 'image/png')
end
end
end
Loading