diff --git a/app/controllers/api/experience_cs_project_migrations_controller.rb b/app/controllers/api/experience_cs_project_migrations_controller.rb index 67f8aba92..4a6feeedc 100644 --- a/app/controllers/api/experience_cs_project_migrations_controller.rb +++ b/app/controllers/api/experience_cs_project_migrations_controller.rb @@ -29,7 +29,8 @@ def migrate_project! authorize! :migrate_from_experience_cs, @project @project.update!( attributes.slice(:name, :instructions).merge( - project_type: Project::Types::CODE_EDITOR_SCRATCH + project_type: Project::Types::CODE_EDITOR_SCRATCH, + origin: Project::Origins::EXPERIENCE_CS ) ) scratch_component = @project.scratch_component || @project.build_scratch_component diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index 9da91fdd9..76cd82a67 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -43,7 +43,7 @@ def create end def update - result = Project::Update.call(project: @project, update_hash: project_params) + result = Project::Update.call(project: @project, update_hash: project_params, current_user:) if result.success? track_project_event('Project - Saved', @project) diff --git a/lib/concepts/project/operations/update.rb b/lib/concepts/project/operations/update.rb index 9f30cd330..90caf65a4 100644 --- a/lib/concepts/project/operations/update.rb +++ b/lib/concepts/project/operations/update.rb @@ -3,11 +3,11 @@ class Project class Update class << self - def call(project:, update_hash:) + def call(project:, update_hash:, current_user: nil) response = setup_response(project) setup_deletions(response, update_hash) - update_project_attributes(response, update_hash) + update_project_attributes(response, update_hash, current_user) update_component_attributes(response, update_hash) update_scratch_component_attributes(response, update_hash) persist_changes(response) @@ -43,10 +43,12 @@ def validate_deletions(response) response[:error] = I18n.t 'errors.project.editing.delete_default_component' end - def update_project_attributes(response, update_hash) + def update_project_attributes(response, update_hash, current_user) return if response.failure? - response[:project].assign_attributes(update_hash.slice(:name, :instructions, :project_type)) + attributes = update_hash.slice(:name, :instructions, :project_type) + attributes[:origin] = Project::Origins::EXPERIENCE_CS if current_user&.experience_cs_admin? + response[:project].assign_attributes(attributes) end def update_component_attributes(response, update_hash) diff --git a/spec/concepts/project/update_spec.rb b/spec/concepts/project/update_spec.rb index 193ee150c..178455443 100644 --- a/spec/concepts/project/update_spec.rb +++ b/spec/concepts/project/update_spec.rb @@ -132,6 +132,32 @@ expect { update }.to change { project.reload.instructions }.to('new instructions') end end + + context 'when the current user is an Experience CS admin' do + subject(:update_as_admin) { described_class.call(project:, update_hash:, current_user:) } + + let(:current_user) { create(:experience_cs_admin_user) } + let(:update_hash) do + { + name: 'updated project name', + components: component_hash, + instructions: + } + end + + it 'returns success? true' do + expect(update_as_admin.success?).to be(true) + end + + it 'sets the project origin to experience_cs' do + expect(update_as_admin[:project].origin).to eq(Project::Origins::EXPERIENCE_CS) + end + + it 'succeeds when the origin is already experience_cs' do + project.update(origin: Project::Origins::EXPERIENCE_CS) + expect(update_as_admin.success?).to be(true) + end + end end def component_properties_hash(component) diff --git a/spec/requests/experience_cs_project_migrations/update_spec.rb b/spec/requests/experience_cs_project_migrations/update_spec.rb index d887c25ff..26dfaa03a 100644 --- a/spec/requests/experience_cs_project_migrations/update_spec.rb +++ b/spec/requests/experience_cs_project_migrations/update_spec.rb @@ -47,7 +47,8 @@ school_id: school.id, name: 'Migrated project', instructions: [{ 'markdown_content' => 'Make the sprite move.' }], - project_type: Project::Types::CODE_EDITOR_SCRATCH + project_type: Project::Types::CODE_EDITOR_SCRATCH, + origin: Project::Origins::EXPERIENCE_CS ) expect(project.scratch_component.content.to_h).to eq(scratch_data.deep_stringify_keys) end diff --git a/spec/requests/projects/update_spec.rb b/spec/requests/projects/update_spec.rb index 876983cee..249891a28 100644 --- a/spec/requests/projects/update_spec.rb +++ b/spec/requests/projects/update_spec.rb @@ -43,12 +43,19 @@ expect(response.body).to include('updated component content') end - it 'calls update operation' do + it 'calls update operation with the current user' do mock_response = instance_double(OperationResponse) allow(mock_response).to receive(:success?).and_return(true) allow(Project::Update).to receive(:call).and_return(mock_response) put("/api/projects/#{project.identifier}", params:, headers:) - expect(Project::Update).to have_received(:call) + expect(Project::Update).to have_received(:call).with( + a_hash_including(project:, current_user: owner) + ) + end + + it 'does not set the project origin' do + put("/api/projects/#{project.identifier}", params:, headers:) + expect(project.reload.origin).to be_nil end context 'when no components specified' do @@ -181,6 +188,28 @@ expect(project.scratch_component.reload.content.to_h).to eq(scratch_data.deep_stringify_keys) end + it 'sets the project origin to experience_cs' do + put('/api/projects/experience-cs-project?locale=fr', params:, headers:, as: :json) + + expect(project.reload.origin).to eq(Project::Origins::EXPERIENCE_CS) + end + + it 'sets the origin when overwriting an existing template project in the en locale' do + project.update!(locale: 'en', origin: nil) + + put('/api/projects/experience-cs-project?locale=en', params:, headers:, as: :json) + + expect(project.reload.origin).to eq(Project::Origins::EXPERIENCE_CS) + end + + it 'leaves the origin as experience_cs when re-syncing a project' do + project.update!(origin: Project::Origins::EXPERIENCE_CS) + + put('/api/projects/experience-cs-project?locale=fr', params:, headers:, as: :json) + + expect(project.reload.origin).to eq(Project::Origins::EXPERIENCE_CS) + end + context 'when authenticated with the Experience CS service API key' do let(:headers) { { ExperienceCsServiceAuthenticator::HEADER => 'service-api-key' } } @@ -242,6 +271,12 @@ expect(response).to have_http_status(:forbidden) end + + it 'sets the project origin to experience_cs' do + put('/api/projects/experience-cs-project?locale=fr', params:, headers:, as: :json) + + expect(project.reload.origin).to eq(Project::Origins::EXPERIENCE_CS) + end end end