From 3f8f4cee5be36ba558a56aa77eb9306e0faacaed Mon Sep 17 00:00:00 2001 From: Karol Date: Fri, 10 Nov 2023 18:21:51 +0100 Subject: [PATCH] update converters and im-/exporters to accommodate for changes in proforma gem --- .../convert_exercise_to_task.rb | 68 ++++++++++--- .../convert_task_to_exercise.rb | 24 +++-- app/services/proforma_service/export_task.rb | 3 +- app/services/proforma_service/import.rb | 2 +- .../convert_exercise_to_task_spec.rb | 80 ++++++++++----- .../convert_task_to_exercise_spec.rb | 98 ++++++++++++------- .../proforma_service/export_task_spec.rb | 2 +- 7 files changed, 191 insertions(+), 86 deletions(-) diff --git a/app/services/proforma_service/convert_exercise_to_task.rb b/app/services/proforma_service/convert_exercise_to_task.rb index 571bac7c..87a8d21b 100644 --- a/app/services/proforma_service/convert_exercise_to_task.rb +++ b/app/services/proforma_service/convert_exercise_to_task.rb @@ -30,14 +30,35 @@ module ProformaService language: DEFAULT_LANGUAGE, model_solutions:, meta_data: { - CodeOcean: { - public: @exercise.public, - hide_file_tree: @exercise.hide_file_tree, - allow_file_creation: @exercise.allow_file_creation, - allow_auto_completion: @exercise.allow_auto_completion, - expected_difficulty: @exercise.expected_difficulty, - execution_environment_id: @exercise.execution_environment_id, - files: task_files_meta_data, + '@@order' => %w[meta-data], + 'meta-data' => { + '@@order' => %w[CodeOcean:public CodeOcean:hide_file_tree CodeOcean:allow_file_creation CodeOcean:allow_auto_completion CodeOcean:expected_difficulty CodeOcean:execution_environment_id CodeOcean:files], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:public' => { + '@@order' => %w[$1], + '$1' => @exercise.public, + }, + 'CodeOcean:hide_file_tree' => { + '@@order' => %w[$1], + '$1' => @exercise.hide_file_tree, + }, + 'CodeOcean:allow_file_creation' => { + '@@order' => %w[$1], + '$1' => @exercise.allow_file_creation, + }, + 'CodeOcean:allow_auto_completion' => { + '@@order' => %w[$1], + '$1' => @exercise.allow_auto_completion, + }, + 'CodeOcean:expected_difficulty' => { + '@@order' => %w[$1], + '$1' => @exercise.expected_difficulty, + }, + 'CodeOcean:execution_environment_id' => { + '@@order' => %w[$1], + '$1' => @exercise.execution_environment_id, + }, + 'CodeOcean:files' => task_files_meta_data, }, }, }.compact @@ -86,9 +107,18 @@ module ProformaService def test_meta_data(file) { - CodeOcean: { - 'feedback-message': file.feedback_message, - weight: file.weight, + '@@order' => %w[test-meta-data], + 'test-meta-data' => { + '@@order' => %w[CodeOcean:feedback-message CodeOcean:weight], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:feedback-message' => { + '@@order' => %w[$1], + '$1' => file.feedback_message, + }, + 'CodeOcean:weight' => { + '@@order' => %w[$1], + '$1' => file.weight, + }, }, } end @@ -109,10 +139,20 @@ module ProformaService end def task_files_meta_data - exercise_files.to_h do |file| - # added CO- to id, otherwise the key would have CodeOcean as a prefix after export and import (cause unknown) - ["CO-#{file.id}", {role: file.role}] + # TODO: refactor? + task_files_hash = { + '@@order' => exercise_files.map {|file| "CodeOcean:CO-#{file.id}" }, + } + exercise_files.each do |file| + task_files_hash["CodeOcean:CO-#{file.id}"] = { + '@@order' => ['CodeOcean:role'], + 'CodeOcean:role' => { + '@@order' => ['$1'], + '$1' => file.role, + }, + } end + task_files_hash end def task_files diff --git a/app/services/proforma_service/convert_task_to_exercise.rb b/app/services/proforma_service/convert_task_to_exercise.rb index f7d3959d..efa64074 100644 --- a/app/services/proforma_service/convert_task_to_exercise.rb +++ b/app/services/proforma_service/convert_task_to_exercise.rb @@ -21,19 +21,25 @@ module ProformaService user: @user, title: @task.title, description: @task.description, - public: string_to_bool(@task.meta_data[:CodeOcean]&.dig(:public)) || false, - hide_file_tree: string_to_bool(@task.meta_data[:CodeOcean]&.dig(:hide_file_tree)) || false, - allow_file_creation: string_to_bool(@task.meta_data[:CodeOcean]&.dig(:allow_file_creation)) || false, - allow_auto_completion: string_to_bool(@task.meta_data[:CodeOcean]&.dig(:allow_auto_completion)) || false, - expected_difficulty: @task.meta_data[:CodeOcean]&.dig(:expected_difficulty) || 1, + public: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'public')) || false, + hide_file_tree: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'hide_file_tree')) || false, + allow_file_creation: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'allow_file_creation')) || false, + allow_auto_completion: string_to_bool(extract_meta_data(@task.meta_data&.dig('meta-data'), 'allow_auto_completion')) || false, + expected_difficulty: extract_meta_data(@task.meta_data&.dig('meta-data'), 'expected_difficulty') || 1, execution_environment_id:, files: ) end + def extract_meta_data(meta_data, *path) + current_level = meta_data + path.each {|attribute| current_level = current_level&.dig("CodeOcean:#{attribute}") } + current_level&.dig('$1') + end + def execution_environment_id - from_meta_data = @task.meta_data[:CodeOcean]&.dig(:execution_environment_id) + from_meta_data = extract_meta_data(@task.meta_data&.dig('meta-data'), 'execution_environment_id') return from_meta_data if from_meta_data return nil unless @task.proglang @@ -60,8 +66,8 @@ module ProformaService def test_files @task.tests.map do |test_object| task_files.delete(test_object.files.first.id).tap do |file| - file.weight = test_object.meta_data[:CodeOcean]&.dig(:weight) || 1.0 - file.feedback_message = test_object.meta_data[:CodeOcean]&.dig(:'feedback-message').presence || 'Feedback' + file.weight = extract_meta_data(test_object.meta_data&.dig('test-meta-data'), 'weight').presence || 1.0 + file.feedback_message = extract_meta_data(test_object.meta_data&.dig('test-meta-data'), 'feedback-message').presence || 'Feedback' file.role ||= 'teacher_defined_test' end end @@ -89,7 +95,7 @@ module ProformaService hidden: file.visible != 'yes', # hides 'delayed' and 'no' name: File.basename(file.filename, '.*'), read_only: file.usage_by_lms != 'edit', - role: @task.meta_data[:CodeOcean]&.dig(:files)&.dig("CO-#{file.id}".to_sym)&.dig(:role), + role: extract_meta_data(@task.meta_data&.dig('meta-data'), 'files', "CO-#{file.id}", 'role'), path: File.dirname(file.filename).in?(['.', '']) ? nil : File.dirname(file.filename) ) if file.binary diff --git a/app/services/proforma_service/export_task.rb b/app/services/proforma_service/export_task.rb index 467accd0..23a37109 100644 --- a/app/services/proforma_service/export_task.rb +++ b/app/services/proforma_service/export_task.rb @@ -9,8 +9,7 @@ module ProformaService def execute @task = ConvertExerciseToTask.call(exercise: @exercise) - namespaces = [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}] - exporter = ProformaXML::Exporter.new(task: @task, custom_namespaces: namespaces) + exporter = ProformaXML::Exporter.new(task: @task) exporter.perform end end diff --git a/app/services/proforma_service/import.rb b/app/services/proforma_service/import.rb index eaefe0dc..0547f8bc 100644 --- a/app/services/proforma_service/import.rb +++ b/app/services/proforma_service/import.rb @@ -12,7 +12,7 @@ module ProformaService if single_task? importer = ProformaXML::Importer.new(zip: @zip) import_result = importer.perform - @task = import_result[:task] + @task = import_result exercise = base_exercise exercise_files = exercise&.files&.to_a diff --git a/spec/services/proforma_service/convert_exercise_to_task_spec.rb b/spec/services/proforma_service/convert_exercise_to_task_spec.rb index 90d63ae5..915921f2 100644 --- a/spec/services/proforma_service/convert_exercise_to_task_spec.rb +++ b/spec/services/proforma_service/convert_exercise_to_task_spec.rb @@ -34,17 +34,37 @@ RSpec.describe ProformaService::ConvertExerciseToTask do description: exercise.description, uuid: exercise.uuid, language: described_class::DEFAULT_LANGUAGE, - meta_data: { - CodeOcean: { - allow_auto_completion: exercise.allow_auto_completion, - allow_file_creation: exercise.allow_file_creation, - execution_environment_id: exercise.execution_environment_id, - expected_difficulty: exercise.expected_difficulty, - hide_file_tree: exercise.hide_file_tree, - public: exercise.public, - files: {}, + meta_data: + { + '@@order' => ['meta-data'], 'meta-data' => + {'@@order' => %w[CodeOcean:public CodeOcean:hide_file_tree CodeOcean:allow_file_creation CodeOcean:allow_auto_completion CodeOcean:expected_difficulty CodeOcean:execution_environment_id CodeOcean:files], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:allow_auto_completion' => { + '@@order' => ['$1'], + '$1' => exercise.allow_auto_completion, + }, + 'CodeOcean:allow_file_creation' => { + '@@order' => ['$1'], + '$1' => exercise.allow_file_creation, + }, + 'CodeOcean:execution_environment_id' => { + '@@order' => ['$1'], + '$1' => exercise.execution_environment_id, + }, + 'CodeOcean:expected_difficulty' => { + '@@order' => ['$1'], + '$1' => exercise.expected_difficulty, + }, + 'CodeOcean:files' => {'@@order' => []}, + 'CodeOcean:hide_file_tree' => { + '@@order' => ['$1'], + '$1' => exercise.hide_file_tree, + }, + 'CodeOcean:public' => { + '@@order' => ['$1'], + '$1' => exercise.public, + }} }, - }, files: [], tests: [], model_solutions: [] @@ -76,9 +96,17 @@ RSpec.describe ProformaService::ConvertExerciseToTask do it 'adds the file\'s role to the file hash in task-meta_data' do expect(task).to have_attributes( - meta_data: { - CodeOcean: a_hash_including(files: {"CO-#{file.id}" => {role: 'main_file'}}), - } + meta_data: a_hash_including( + 'meta-data' => a_hash_including( + 'CodeOcean:files' => { + '@@order' => ["CodeOcean:CO-#{file.id}"], + "CodeOcean:CO-#{file.id}" => { + '@@order' => ['CodeOcean:role'], + 'CodeOcean:role' => {'$1' => 'main_file', '@@order' => ['$1']}, + }, + } + ) + ) ) end end @@ -104,9 +132,17 @@ RSpec.describe ProformaService::ConvertExerciseToTask do it 'adds the file\'s role to the file hash in task-meta_data' do expect(task).to have_attributes( - meta_data: { - CodeOcean: a_hash_including(files: {"CO-#{file.id}" => {role: 'regular_file'}}), - } + meta_data: a_hash_including( + 'meta-data' => a_hash_including( + 'CodeOcean:files' => { + '@@order' => ["CodeOcean:CO-#{file.id}"], + "CodeOcean:CO-#{file.id}" => { + '@@order' => ['CodeOcean:role'], + 'CodeOcean:role' => {'$1' => 'regular_file', '@@order' => ['$1']}, + }, + } + ) + ) ) end @@ -190,12 +226,12 @@ RSpec.describe ProformaService::ConvertExerciseToTask do id: test_file.id, title: test_file.name, files: have(1).item, - meta_data: { - CodeOcean: { - 'feedback-message': 'feedback_message', - weight: test_file.weight, - }, - } + meta_data: a_hash_including( + 'test-meta-data' => a_hash_including( + 'CodeOcean:feedback-message' => {'$1' => 'feedback_message', '@@order' => ['$1']}, + 'CodeOcean:weight' => {'$1' => test_file.weight, '@@order' => ['$1']} + ) + ) ) end diff --git a/spec/services/proforma_service/convert_task_to_exercise_spec.rb b/spec/services/proforma_service/convert_task_to_exercise_spec.rb index 3fc9b238..fd5ef3a2 100644 --- a/spec/services/proforma_service/convert_task_to_exercise_spec.rb +++ b/spec/services/proforma_service/convert_task_to_exercise_spec.rb @@ -79,15 +79,34 @@ RSpec.describe ProformaService::ConvertTaskToExercise do context 'when meta_data is set' do let(:meta_data) do { - CodeOcean: { - public:, - hide_file_tree:, - allow_file_creation:, - allow_auto_completion:, - expected_difficulty:, - execution_environment_id: execution_environment&.id, - files: files_meta_data, - }, + '@@order' => ['meta-data'], 'meta-data' => + {'@@order' => %w[CodeOcean:public CodeOcean:hide_file_tree CodeOcean:allow_file_creation CodeOcean:allow_auto_completion CodeOcean:expected_difficulty CodeOcean:execution_environment_id CodeOcean:files], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:allow_auto_completion' => { + '@@order' => ['$1'], + '$1' => allow_auto_completion, + }, + 'CodeOcean:allow_file_creation' => { + '@@order' => ['$1'], + '$1' => allow_file_creation, + }, + 'CodeOcean:execution_environment_id' => { + '@@order' => ['$1'], + '$1' => execution_environment.id, + }, + 'CodeOcean:expected_difficulty' => { + '@@order' => ['$1'], + '$1' => expected_difficulty, + }, + 'CodeOcean:files' => {'@@order' => []}, + 'CodeOcean:hide_file_tree' => { + '@@order' => ['$1'], + '$1' => hide_file_tree, + }, + 'CodeOcean:public' => { + '@@order' => ['$1'], + '$1' => public, + }} } end let(:files_meta_data) { {} } @@ -178,12 +197,22 @@ RSpec.describe ProformaService::ConvertTaskToExercise do context 'when file is a main_file' do let(:meta_data) do { - CodeOcean: { - files: files_meta_data, + '@@order' => ['meta-data'], 'meta-data' => { + '@@order' => %w[CodeOcean:files], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:files' => files_meta_data, + } + } + end + let(:files_meta_data) do + { + '@@order' => ["CodeOcean:CO-#{file.id}"], + "CodeOcean:CO-#{file.id}" => { + '@@order' => ['CodeOcean:role'], + 'CodeOcean:role' => {'$1' => 'main_file', '@@order' => ['$1']}, }, } end - let(:files_meta_data) { {"CO-#{file.id}".to_sym => {role: 'main_file'}} } it 'creates an exercise with a file that has the correct attributes' do expect(convert_to_exercise_service.files.first).to have_attributes(role: 'main_file') @@ -331,10 +360,10 @@ RSpec.describe ProformaService::ConvertTaskToExercise do test_type: 'test_type', files: test_files, meta_data: { - CodeOcean: { - 'feedback-message': 'feedback-message', - 'testing-framework': 'testing-framework', - 'testing-framework-version': 'testing-framework-version', + 'test-meta-data' => { + '@@order' => %w[CodeOcean:feedback-message CodeOcean:weight], + 'CodeOcean:feedback-message' => {'$1' => 'feedback-message', '@@order' => ['$1']}, + 'CodeOcean:weight' => {'$1' => '0.7', '@@order' => ['$1']}, }, } ) @@ -361,6 +390,7 @@ RSpec.describe ProformaService::ConvertTaskToExercise do it 'creates an exercise with a test with correct attributes' do expect(convert_to_exercise_service.files.find {|file| file.role == 'teacher_defined_test' }).to have_attributes( feedback_message: 'feedback-message', + weight: 0.7, content: 'testfile-content', name: 'testfile', role: 'teacher_defined_test', @@ -373,12 +403,22 @@ RSpec.describe ProformaService::ConvertTaskToExercise do context 'when test file is a teacher_defined_linter' do let(:meta_data) do { - CodeOcean: { - files: files_meta_data, + '@@order' => ['meta-data'], 'meta-data' => { + '@@order' => %w[CodeOcean:files], + '@xmlns' => {'CodeOcean' => 'codeocean.openhpi.de'}, + 'CodeOcean:files' => files_meta_data, + } + } + end + let(:files_meta_data) do + { + '@@order' => ["CodeOcean:CO-#{test_file.id}"], + "CodeOcean:CO-#{test_file.id}" => { + '@@order' => ['CodeOcean:role'], + 'CodeOcean:role' => {'$1' => 'teacher_defined_linter', '@@order' => ['$1']}, }, } end - let(:files_meta_data) { {"CO-#{test_file.id}".to_sym => {role: 'teacher_defined_linter'}} } it 'creates an exercise with a test' do expect(convert_to_exercise_service.files.select {|file| file.role == 'teacher_defined_linter' }).to have(1).item @@ -388,16 +428,7 @@ RSpec.describe ProformaService::ConvertTaskToExercise do context 'when task has multiple tests' do let(:tests) { [test, test2] } let(:test2) do - ProformaXML::Test.new( - files: test_files2, - meta_data: { - CodeOcean: { - 'feedback-message': 'feedback-message', - 'testing-framework': 'testing-framework', - 'testing-framework-version': 'testing-framework-version', - }, - } - ) + ProformaXML::Test.new(files: test_files2) end let(:test_files2) { [test_file2] } let(:test_file2) do @@ -467,14 +498,7 @@ RSpec.describe ProformaService::ConvertTaskToExercise do title: 'title', description: 'description', test_type: 'test_type', - files: test_files, - meta_data: { - CodeOcean: { - 'feedback-message': 'feedback-message', - 'testing-framework': 'testing-framework', - 'testing-framework-version': 'testing-framework-version', - }, - } + files: test_files ) end let(:test_files) { [test_file] } diff --git a/spec/services/proforma_service/export_task_spec.rb b/spec/services/proforma_service/export_task_spec.rb index 8cddcbb3..497848ad 100644 --- a/spec/services/proforma_service/export_task_spec.rb +++ b/spec/services/proforma_service/export_task_spec.rb @@ -30,7 +30,7 @@ RSpec.describe ProformaService::ExportTask do before do allow(ProformaService::ConvertExerciseToTask).to receive(:call).with(exercise:).and_return(task) - allow(ProformaXML::Exporter).to receive(:new).with(task:, custom_namespaces: [{prefix: 'CodeOcean', uri: 'codeocean.openhpi.de'}]).and_return(exporter) + allow(ProformaXML::Exporter).to receive(:new).with(task:).and_return(exporter) end it do