merge master
This commit is contained in:
@ -48,7 +48,8 @@ describe FileParameters do
|
||||
end
|
||||
|
||||
it 'non existent file' do
|
||||
non_existent_file = build(:file, context: hello_world, id: 42)
|
||||
# Ensure to use an invalid id for the file.
|
||||
non_existent_file = build(:file, context: hello_world, id: -1)
|
||||
expect(file_accepted?(non_existent_file)).to be false
|
||||
end
|
||||
end
|
||||
|
@ -33,7 +33,7 @@ describe Lti do
|
||||
let(:last_name) { 'Doe' }
|
||||
let(:full_name) { 'John Doe' }
|
||||
let(:provider) { double }
|
||||
let(:provider_full) { instance_double('IMS::LTI::ToolProvider', lis_person_name_full: full_name) }
|
||||
let(:provider_full) { instance_double(IMS::LTI::ToolProvider, lis_person_name_full: full_name) }
|
||||
|
||||
context 'when a full name is provided' do
|
||||
it 'returns the full name' do
|
||||
@ -62,7 +62,7 @@ describe Lti do
|
||||
|
||||
describe '#return_to_consumer' do
|
||||
context 'with a return URL' do
|
||||
let(:consumer_return_url) { 'http://example.org' }
|
||||
let(:consumer_return_url) { 'https://example.org' }
|
||||
|
||||
before { allow(controller).to receive(:params).and_return(launch_presentation_return_url: consumer_return_url) }
|
||||
|
||||
@ -81,21 +81,23 @@ describe Lti do
|
||||
context 'without a return URL' do
|
||||
before do
|
||||
allow(controller).to receive(:params).and_return({})
|
||||
allow(controller).to receive(:redirect_to).with(:root)
|
||||
end
|
||||
|
||||
it 'redirects to the root URL' do
|
||||
expect(controller).to receive(:redirect_to).with(:root)
|
||||
controller.send(:return_to_consumer)
|
||||
end
|
||||
|
||||
it 'displays alerts' do
|
||||
message = I18n.t('sessions.oauth.failure')
|
||||
controller.send(:return_to_consumer, lti_errormsg: message)
|
||||
expect(controller.instance_variable_get(:@flash)[:danger]).to eq(obtain_message(message))
|
||||
end
|
||||
|
||||
it 'displays notices' do
|
||||
message = I18n.t('sessions.oauth.success')
|
||||
message = I18n.t('sessions.destroy_through_lti.success_without_outcome')
|
||||
controller.send(:return_to_consumer, lti_msg: message)
|
||||
expect(controller.instance_variable_get(:@flash)[:info]).to eq(obtain_message(message))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,7 +9,7 @@ describe Admin::DashboardController do
|
||||
describe 'with format HTML' do
|
||||
before { get :show }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -17,7 +17,7 @@ describe Admin::DashboardController do
|
||||
before { get :show, format: :json }
|
||||
|
||||
expect_json
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -43,14 +43,14 @@ describe ApplicationController do
|
||||
|
||||
context "when using the 'custom_locale' parameter" do
|
||||
it 'overwrites the session' do
|
||||
expect(session).to receive(:[]=).with(:locale, locale.to_s)
|
||||
expect(session).to receive(:[]=).with(:locale, locale)
|
||||
get :welcome, params: {custom_locale: locale}
|
||||
end
|
||||
end
|
||||
|
||||
context "when using the 'locale' parameter" do
|
||||
it 'overwrites the session' do
|
||||
expect(session).to receive(:[]=).with(:locale, locale.to_s)
|
||||
expect(session).to receive(:[]=).with(:locale, locale)
|
||||
get :welcome, params: {locale: locale}
|
||||
end
|
||||
end
|
||||
@ -78,7 +78,7 @@ describe ApplicationController do
|
||||
describe 'GET #welcome' do
|
||||
before { get :welcome }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:welcome)
|
||||
end
|
||||
end
|
||||
|
@ -25,7 +25,7 @@ describe CodeOcean::FilesController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(201)
|
||||
expect_http_status(:created)
|
||||
end
|
||||
|
||||
context 'with an invalid file' do
|
||||
@ -36,7 +36,7 @@ describe CodeOcean::FilesController do
|
||||
|
||||
expect_assigns(file: CodeOcean::File)
|
||||
expect_json
|
||||
expect_status(422)
|
||||
expect_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -6,7 +6,7 @@ describe CodeharborLinksController do
|
||||
let(:user) { create(:teacher) }
|
||||
|
||||
let(:codeocean_config) { instance_double(CodeOcean::Config) }
|
||||
let(:codeharbor_config) { {codeharbor: {enabled: true, url: 'http://test.url'}} }
|
||||
let(:codeharbor_config) { {codeharbor: {enabled: true, url: 'https://test.url'}} }
|
||||
|
||||
before do
|
||||
allow(CodeOcean::Config).to receive(:new).with(:code_ocean).and_return(codeocean_config)
|
||||
@ -19,7 +19,7 @@ describe CodeharborLinksController do
|
||||
get :new
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
describe 'GET #edit' do
|
||||
@ -27,12 +27,12 @@ describe CodeharborLinksController do
|
||||
|
||||
before { get :edit, params: {id: codeharbor_link.id} }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:post_request) { post :create, params: {codeharbor_link: params} }
|
||||
let(:params) { {push_url: 'http://foo.bar/push', check_uuid_url: 'http://foo.bar/check', api_key: 'api_key'} }
|
||||
let(:params) { {push_url: 'https://foo.bar/push', check_uuid_url: 'https://foo.bar/check', api_key: 'api_key'} }
|
||||
|
||||
it 'creates a codeharbor_link' do
|
||||
expect { post_request }.to change(CodeharborLink, :count).by(1)
|
||||
@ -59,14 +59,14 @@ describe CodeharborLinksController do
|
||||
describe 'PUT #update' do
|
||||
let(:codeharbor_link) { create(:codeharbor_link, user: user) }
|
||||
let(:put_request) { patch :update, params: {id: codeharbor_link.id, codeharbor_link: params} }
|
||||
let(:params) { {push_url: 'http://foo.bar/push', check_uuid_url: 'http://foo.bar/check', api_key: 'api_key'} }
|
||||
let(:params) { {push_url: 'https://foo.bar/push', check_uuid_url: 'https://foo.bar/check', api_key: 'api_key'} }
|
||||
|
||||
it 'updates push_url' do
|
||||
expect { put_request }.to change { codeharbor_link.reload.push_url }.to('http://foo.bar/push')
|
||||
expect { put_request }.to change { codeharbor_link.reload.push_url }.to('https://foo.bar/push')
|
||||
end
|
||||
|
||||
it 'updates check_uuid_url' do
|
||||
expect { put_request }.to change { codeharbor_link.reload.check_uuid_url }.to('http://foo.bar/check')
|
||||
expect { put_request }.to change { codeharbor_link.reload.check_uuid_url }.to('https://foo.bar/check')
|
||||
end
|
||||
|
||||
it 'updates api_key' do
|
||||
|
42
spec/controllers/comments_controller_spec.rb
Normal file
42
spec/controllers/comments_controller_spec.rb
Normal file
@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe CommentsController do
|
||||
let(:user) { create(:learner) }
|
||||
let(:rfc_with_comment) { create(:rfc_with_comment, user: user) }
|
||||
let(:comment) { rfc_with_comment.comments.first }
|
||||
let(:updated_comment) { comment.reload }
|
||||
let(:perform_request) { proc { put :update, format: :json, params: {id: comment.id, comment: comment_params} } }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:current_user).and_return(user)
|
||||
perform_request.call
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
context 'with valid params' do
|
||||
let(:comment_params) { {text: 'test100'} }
|
||||
|
||||
it 'saves the permitted changes' do
|
||||
expect(updated_comment.text).to eq('test100')
|
||||
end
|
||||
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'with additional params' do
|
||||
let(:comment_params) { {text: 'test100', row: 5, file_id: 50} }
|
||||
|
||||
it 'applies the permitted changes' do
|
||||
expect(updated_comment.row).not_to eq(5)
|
||||
expect(updated_comment.file_id).not_to eq(50)
|
||||
expect(updated_comment.row).to eq(1)
|
||||
expect(updated_comment.file_id).to eq(comment.file_id)
|
||||
expect(updated_comment.text).to eq('test100')
|
||||
end
|
||||
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
@ -27,7 +27,7 @@ describe ConsumersController do
|
||||
before { post :create, params: {consumer: {}} }
|
||||
|
||||
expect_assigns(consumer: Consumer)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
end
|
||||
@ -49,7 +49,7 @@ describe ConsumersController do
|
||||
before { get :edit, params: {id: consumer.id} }
|
||||
|
||||
expect_assigns(consumer: Consumer)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
|
||||
@ -60,7 +60,7 @@ describe ConsumersController do
|
||||
end
|
||||
|
||||
expect_assigns(consumers: Consumer.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -68,7 +68,7 @@ describe ConsumersController do
|
||||
before { get :new }
|
||||
|
||||
expect_assigns(consumer: Consumer)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
@ -76,7 +76,7 @@ describe ConsumersController do
|
||||
before { get :show, params: {id: consumer.id} }
|
||||
|
||||
expect_assigns(consumer: :consumer)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -92,7 +92,7 @@ describe ConsumersController do
|
||||
before { put :update, params: {consumer: {name: ''}, id: consumer.id} }
|
||||
|
||||
expect_assigns(consumer: Consumer)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
end
|
||||
|
@ -10,13 +10,13 @@ describe ErrorTemplateAttributesController do
|
||||
|
||||
it 'gets index' do
|
||||
get :index
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(assigns(:error_template_attributes)).not_to be_nil
|
||||
end
|
||||
|
||||
it 'gets new' do
|
||||
get :new
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'creates error_template_attribute' do
|
||||
@ -26,12 +26,12 @@ describe ErrorTemplateAttributesController do
|
||||
|
||||
it 'shows error_template_attribute' do
|
||||
get :show, params: {id: error_template_attribute}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'gets edit' do
|
||||
get :edit, params: {id: error_template_attribute}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'updates error_template_attribute' do
|
||||
|
@ -10,13 +10,13 @@ describe ErrorTemplatesController do
|
||||
|
||||
it 'gets index' do
|
||||
get :index
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(assigns(:error_templates)).not_to be_nil
|
||||
end
|
||||
|
||||
it 'gets new' do
|
||||
get :new
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'creates error_template' do
|
||||
@ -26,12 +26,12 @@ describe ErrorTemplatesController do
|
||||
|
||||
it 'shows error_template' do
|
||||
get :show, params: {id: error_template}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'gets edit' do
|
||||
get :edit, params: {id: error_template}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'updates error_template' do
|
||||
|
@ -20,20 +20,20 @@ describe EventsController do
|
||||
expect { perform_request.call }.to change(Event, :count).by(1)
|
||||
end
|
||||
|
||||
expect_status(201)
|
||||
expect_http_status(:created)
|
||||
end
|
||||
|
||||
context 'with an invalid event' do
|
||||
before { post :create, params: {event: {exercise_id: 847_482}} }
|
||||
|
||||
expect_assigns(event: Event)
|
||||
expect_status(422)
|
||||
expect_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
context 'with no event' do
|
||||
before { post :create }
|
||||
|
||||
expect_status(422)
|
||||
expect_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ describe ExecutionEnvironmentsController do
|
||||
before do
|
||||
allow(Rails.env).to receive(:test?).and_return(false, true)
|
||||
allow(Runner.strategy_class).to receive(:sync_environment).and_return(true)
|
||||
runner = instance_double 'runner'
|
||||
runner = instance_double Runner
|
||||
allow(Runner).to receive(:for).and_return(runner)
|
||||
allow(runner).to receive(:execute_command).and_return({})
|
||||
perform_request.call
|
||||
@ -46,7 +46,7 @@ describe ExecutionEnvironmentsController do
|
||||
end
|
||||
|
||||
expect_assigns(execution_environment: ExecutionEnvironment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
|
||||
it 'does not register the execution environment with the runner management' do
|
||||
@ -82,7 +82,7 @@ describe ExecutionEnvironmentsController do
|
||||
|
||||
expect_assigns(docker_images: Array)
|
||||
expect_assigns(execution_environment: :execution_environment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
|
||||
@ -90,7 +90,7 @@ describe ExecutionEnvironmentsController do
|
||||
let(:command) { 'which ruby' }
|
||||
|
||||
before do
|
||||
runner = instance_double 'runner'
|
||||
runner = instance_double Runner
|
||||
allow(Runner).to receive(:for).with(user, execution_environment).and_return runner
|
||||
allow(runner).to receive(:execute_command).and_return({})
|
||||
post :execute_command, params: {command: command, id: execution_environment.id}
|
||||
@ -98,7 +98,7 @@ describe ExecutionEnvironmentsController do
|
||||
|
||||
expect_assigns(execution_environment: :execution_environment)
|
||||
expect_json
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
@ -108,7 +108,7 @@ describe ExecutionEnvironmentsController do
|
||||
end
|
||||
|
||||
expect_assigns(execution_environments: ExecutionEnvironment.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -119,7 +119,7 @@ describe ExecutionEnvironmentsController do
|
||||
|
||||
expect_assigns(docker_images: Array)
|
||||
expect_assigns(execution_environment: ExecutionEnvironment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
@ -158,7 +158,7 @@ describe ExecutionEnvironmentsController do
|
||||
before { get :shell, params: {id: execution_environment.id} }
|
||||
|
||||
expect_assigns(execution_environment: :execution_environment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:shell)
|
||||
end
|
||||
|
||||
@ -166,7 +166,7 @@ describe ExecutionEnvironmentsController do
|
||||
before { get :statistics, params: {id: execution_environment.id} }
|
||||
|
||||
expect_assigns(execution_environment: :execution_environment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:statistics)
|
||||
end
|
||||
|
||||
@ -174,7 +174,7 @@ describe ExecutionEnvironmentsController do
|
||||
before { get :show, params: {id: execution_environment.id} }
|
||||
|
||||
expect_assigns(execution_environment: :execution_environment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -183,7 +183,7 @@ describe ExecutionEnvironmentsController do
|
||||
before do
|
||||
allow(Rails.env).to receive(:test?).and_return(false, true)
|
||||
allow(Runner.strategy_class).to receive(:sync_environment).and_return(true)
|
||||
runner = instance_double 'runner'
|
||||
runner = instance_double Runner
|
||||
allow(Runner).to receive(:for).and_return(runner)
|
||||
allow(runner).to receive(:execute_command).and_return({})
|
||||
put :update, params: {execution_environment: attributes_for(:ruby, pool_size: 1), id: execution_environment.id}
|
||||
@ -206,7 +206,7 @@ describe ExecutionEnvironmentsController do
|
||||
end
|
||||
|
||||
expect_assigns(execution_environment: ExecutionEnvironment)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
|
||||
it 'does not update the execution environment at the runner management' do
|
||||
|
@ -20,7 +20,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
describe 'POST #clone' do
|
||||
@ -122,7 +122,7 @@ describe ExercisesController do
|
||||
before { post :create, params: {exercise: {}} }
|
||||
|
||||
expect_assigns(exercise: Exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
end
|
||||
@ -144,7 +144,7 @@ describe ExercisesController do
|
||||
before { get :edit, params: {id: exercise.id} }
|
||||
|
||||
expect_assigns(exercise: :exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
|
||||
@ -173,7 +173,7 @@ describe ExercisesController do
|
||||
end
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:implement)
|
||||
end
|
||||
|
||||
@ -195,7 +195,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
expect_assigns(exercises: :scope)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -204,7 +204,7 @@ describe ExercisesController do
|
||||
|
||||
expect_assigns(execution_environments: ExecutionEnvironment.all, exercise: Exercise)
|
||||
expect_assigns(exercise: Exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
@ -213,7 +213,7 @@ describe ExercisesController do
|
||||
before { get :show, params: {id: exercise.id} }
|
||||
|
||||
expect_assigns(exercise: :exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
end
|
||||
@ -223,7 +223,7 @@ describe ExercisesController do
|
||||
before { get :reload, format: :json, params: {id: exercise.id} }
|
||||
|
||||
expect_assigns(exercise: :exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:reload)
|
||||
end
|
||||
end
|
||||
@ -232,10 +232,44 @@ describe ExercisesController do
|
||||
before { get :statistics, params: {id: exercise.id} }
|
||||
|
||||
expect_assigns(exercise: :exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:statistics)
|
||||
end
|
||||
|
||||
describe 'GET #external_user_statistics' do
|
||||
let(:perform_request) { get :external_user_statistics, params: params }
|
||||
let(:params) { {id: exercise.id, external_user_id: external_user.id} }
|
||||
let(:external_user) { create(:external_user) }
|
||||
|
||||
before do
|
||||
2.times { create(:submission, cause: 'autosave', user: external_user, exercise: exercise) }
|
||||
2.times { create(:submission, cause: 'run', user: external_user, exercise: exercise) }
|
||||
create(:submission, cause: 'assess', user: external_user, exercise: exercise)
|
||||
end
|
||||
|
||||
context 'when viewing the default submission statistics page without a parameter' do
|
||||
it 'does not list autosaved submissions' do
|
||||
perform_request
|
||||
expect(assigns(:all_events).filter {|event| event.is_a? Submission }).to match_array [
|
||||
an_object_having_attributes(cause: 'run', user_id: external_user.id),
|
||||
an_object_having_attributes(cause: 'assess', user_id: external_user.id),
|
||||
an_object_having_attributes(cause: 'run', user_id: external_user.id),
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when including autosaved submissions via the query parameter' do
|
||||
let(:params) { super().merge(show_autosaves: 'true') }
|
||||
|
||||
it 'lists all submissions, including autosaved submissions' do
|
||||
perform_request
|
||||
submissions = assigns(:all_events).filter {|event| event.is_a? Submission }
|
||||
expect(submissions).to match_array Submission.all
|
||||
expect(submissions).to include an_object_having_attributes(cause: 'autosave', user_id: external_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #submit' do
|
||||
let(:output) { {} }
|
||||
let(:perform_request) { post :submit, format: :json, params: {id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id}} }
|
||||
@ -285,7 +319,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when the score transmission fails' do
|
||||
@ -301,7 +335,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(503)
|
||||
expect_http_status(:service_unavailable)
|
||||
end
|
||||
end
|
||||
|
||||
@ -322,7 +356,7 @@ describe ExercisesController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
@ -340,7 +374,7 @@ describe ExercisesController do
|
||||
before { put :update, params: {exercise: {title: ''}, id: exercise.id} }
|
||||
|
||||
expect_assigns(exercise: Exercise)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
end
|
||||
|
@ -12,7 +12,7 @@ describe ExternalUsersController do
|
||||
before { get :index }
|
||||
|
||||
expect_assigns(users: ExternalUser.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -20,7 +20,7 @@ describe ExternalUsersController do
|
||||
before { get :show, params: {id: users.first.id} }
|
||||
|
||||
expect_assigns(user: ExternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ describe FileTypesController do
|
||||
|
||||
expect_assigns(editor_modes: Array)
|
||||
expect_assigns(file_type: FileType)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
end
|
||||
@ -52,7 +52,7 @@ describe FileTypesController do
|
||||
|
||||
expect_assigns(editor_modes: Array)
|
||||
expect_assigns(file_type: FileType)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
|
||||
@ -63,7 +63,7 @@ describe FileTypesController do
|
||||
end
|
||||
|
||||
expect_assigns(file_types: FileType.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -72,7 +72,7 @@ describe FileTypesController do
|
||||
|
||||
expect_assigns(editor_modes: Array)
|
||||
expect_assigns(file_type: FileType)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
@ -80,7 +80,7 @@ describe FileTypesController do
|
||||
before { get :show, params: {id: file_type.id} }
|
||||
|
||||
expect_assigns(file_type: :file_type)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -98,7 +98,7 @@ describe FileTypesController do
|
||||
|
||||
expect_assigns(editor_modes: Array)
|
||||
expect_assigns(file_type: FileType)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
end
|
||||
|
@ -33,7 +33,7 @@ describe InternalUsersController do
|
||||
before { get :activate, params: {id: user.id, token: user.activation_token} }
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:activate)
|
||||
end
|
||||
end
|
||||
@ -135,10 +135,10 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
context 'with an invalid internal user' do
|
||||
before { post :create, params: {internal_user: {}} }
|
||||
before { post :create, params: {internal_user: {invalid_attribute: 'a string'}} }
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
end
|
||||
@ -165,7 +165,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
|
||||
@ -178,7 +178,7 @@ describe InternalUsersController do
|
||||
get :forgot_password
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:forgot_password)
|
||||
end
|
||||
|
||||
@ -213,7 +213,7 @@ describe InternalUsersController do
|
||||
context 'without an email address' do
|
||||
before { post :forgot_password }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:forgot_password)
|
||||
end
|
||||
end
|
||||
@ -225,7 +225,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(users: InternalUser.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -236,7 +236,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
@ -256,7 +256,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(user: :user)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:reset_password)
|
||||
end
|
||||
end
|
||||
@ -295,7 +295,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(user: :user)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:reset_password)
|
||||
end
|
||||
end
|
||||
@ -308,7 +308,7 @@ describe InternalUsersController do
|
||||
end
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -326,7 +326,7 @@ describe InternalUsersController do
|
||||
before { put :update, params: {internal_user: {email: ''}, id: users.first.id} }
|
||||
|
||||
expect_assigns(user: InternalUser)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:edit)
|
||||
end
|
||||
end
|
||||
|
@ -35,14 +35,14 @@ describe RequestForCommentsController do
|
||||
describe 'GET #my_comment_requests' do
|
||||
before { get :my_comment_requests }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
describe 'GET #rfcs_with_my_comments' do
|
||||
before { get :rfcs_with_my_comments }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -52,7 +52,7 @@ describe RequestForCommentsController do
|
||||
get :rfcs_for_exercise, params: {exercise_id: exercise.id}
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
end
|
||||
|
@ -86,9 +86,9 @@ describe SessionsController do
|
||||
|
||||
it 'sets the specified locale' do
|
||||
expect(controller).to receive(:switch_locale).and_call_original
|
||||
i18n = instance_double 'i18n', locale: locale.to_s
|
||||
i18n = class_double I18n, locale: locale
|
||||
allow(I18n).to receive(:locale=).with(I18n.default_locale).and_call_original
|
||||
allow(I18n).to receive(:locale=).with(locale.to_s).and_return(i18n)
|
||||
allow(I18n).to receive(:locale=).with(locale).and_return(i18n)
|
||||
perform_request
|
||||
expect(i18n.locale.to_sym).to eq(locale)
|
||||
end
|
||||
@ -201,13 +201,14 @@ describe SessionsController do
|
||||
end
|
||||
|
||||
describe 'GET #destroy_through_lti' do
|
||||
let(:perform_request) { proc { get :destroy_through_lti, params: {consumer_id: consumer.id, submission_id: submission.id} } }
|
||||
let(:perform_request) { proc { get :destroy_through_lti, params: {submission_id: submission.id} } }
|
||||
let(:submission) { create(:submission, exercise: create(:dummy)) }
|
||||
|
||||
before do
|
||||
# Todo replace session with lti_parameter
|
||||
# Todo create LtiParameter Object
|
||||
# session[:lti_parameters] = {}
|
||||
allow(controller).to receive(:current_user).and_return(submission.user)
|
||||
perform_request.call
|
||||
end
|
||||
|
||||
@ -217,7 +218,7 @@ describe SessionsController do
|
||||
perform_request.call
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:destroy_through_lti)
|
||||
end
|
||||
|
||||
@ -230,7 +231,7 @@ describe SessionsController do
|
||||
get :new
|
||||
end
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:new)
|
||||
end
|
||||
|
||||
|
@ -11,7 +11,7 @@ describe StatisticsController do
|
||||
describe "GET ##{route}" do
|
||||
before { get route }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(route)
|
||||
end
|
||||
end
|
||||
@ -20,7 +20,7 @@ describe StatisticsController do
|
||||
describe "GET ##{route}" do
|
||||
before { get route }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:activity_history)
|
||||
end
|
||||
end
|
||||
@ -29,7 +29,7 @@ describe StatisticsController do
|
||||
describe "GET ##{route}.json" do
|
||||
before { get route, format: :json }
|
||||
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_json
|
||||
end
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ describe SubmissionsController do
|
||||
end
|
||||
|
||||
expect_json
|
||||
expect_status(201)
|
||||
expect_http_status(:created)
|
||||
end
|
||||
|
||||
context 'with an invalid submission' do
|
||||
@ -34,7 +34,7 @@ describe SubmissionsController do
|
||||
|
||||
expect_assigns(submission: Submission)
|
||||
expect_json
|
||||
expect_status(422)
|
||||
expect_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
@ -42,7 +42,7 @@ describe SubmissionsController do
|
||||
context 'with an invalid filename' do
|
||||
before { get :download_file, params: {filename: SecureRandom.hex, id: submission.id} }
|
||||
|
||||
expect_status(404)
|
||||
expect_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'with a valid binary filename' do
|
||||
@ -56,7 +56,7 @@ describe SubmissionsController do
|
||||
expect_assigns(file: :file)
|
||||
expect_assigns(submission: :submission)
|
||||
expect_content_type('application/octet-stream')
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
it 'sets the correct filename' do
|
||||
expect(response.headers['Content-Disposition']).to include("attachment; filename=\"#{file.name_with_extension}\"")
|
||||
@ -75,7 +75,7 @@ describe SubmissionsController do
|
||||
expect_assigns(file: :file)
|
||||
expect_assigns(submission: :submission)
|
||||
expect_content_type('video/mp4')
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
it 'sets the correct filename' do
|
||||
expect(response.headers['Content-Disposition']).to include("attachment; filename=\"#{file.name_with_extension}\"")
|
||||
@ -88,7 +88,7 @@ describe SubmissionsController do
|
||||
expect_assigns(file: :file)
|
||||
expect_assigns(submission: :submission)
|
||||
expect_content_type('text/javascript')
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
it 'sets the correct filename' do
|
||||
expect(response.headers['Content-Disposition']).to include("attachment; filename=\"#{file.name_with_extension}\"")
|
||||
@ -104,7 +104,7 @@ describe SubmissionsController do
|
||||
end
|
||||
|
||||
expect_assigns(submissions: Submission.all)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:index)
|
||||
end
|
||||
|
||||
@ -114,7 +114,7 @@ describe SubmissionsController do
|
||||
context 'with an invalid filename' do
|
||||
before { get :render_file, params: {filename: SecureRandom.hex, id: submission.id} }
|
||||
|
||||
expect_status(404)
|
||||
expect_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'with a valid filename' do
|
||||
@ -128,10 +128,10 @@ describe SubmissionsController do
|
||||
expect_assigns(file: :file)
|
||||
expect_assigns(submission: :submission)
|
||||
expect_content_type('video/mp4')
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
it 'renders the file content' do
|
||||
expect(response.body).to eq(file.native_file.read)
|
||||
expect(response.body).to eq(file.read)
|
||||
end
|
||||
end
|
||||
|
||||
@ -141,7 +141,7 @@ describe SubmissionsController do
|
||||
expect_assigns(file: :file)
|
||||
expect_assigns(submission: :submission)
|
||||
expect_content_type('text/javascript')
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
it 'renders the file content' do
|
||||
expect(response.body).to eq(file.content)
|
||||
@ -151,15 +151,20 @@ describe SubmissionsController do
|
||||
end
|
||||
|
||||
describe 'GET #run' do
|
||||
let(:filename) { submission.collect_files.detect(&:main_file?).name_with_extension }
|
||||
let(:perform_request) { get :run, params: {filename: filename, id: submission.id} }
|
||||
let(:file) { submission.collect_files.detect(&:main_file?) }
|
||||
let(:perform_request) { get :run, format: :json, params: {filename: file.filepath, id: submission.id} }
|
||||
|
||||
context 'when no errors occur during execution' do
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:hijack)
|
||||
allow_any_instance_of(Submission).to receive(:run).and_return({})
|
||||
allow_any_instance_of(described_class).to receive(:save_testrun_output)
|
||||
perform_request
|
||||
end
|
||||
|
||||
pending('todo')
|
||||
expect_assigns(submission: :submission)
|
||||
expect_assigns(file: :file)
|
||||
expect_http_status(204)
|
||||
end
|
||||
end
|
||||
|
||||
@ -167,7 +172,7 @@ describe SubmissionsController do
|
||||
before { get :show, params: {id: submission.id} }
|
||||
|
||||
expect_assigns(submission: :submission)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
expect_template(:show)
|
||||
end
|
||||
|
||||
@ -179,7 +184,7 @@ describe SubmissionsController do
|
||||
before { get :show, params: {id: submission.id}, format: :json }
|
||||
|
||||
expect_assigns(submission: :submission)
|
||||
expect_status(200)
|
||||
expect_http_status(:ok)
|
||||
|
||||
%i[render run test].each do |action|
|
||||
describe "##{action}_url" do
|
||||
@ -206,21 +211,29 @@ describe SubmissionsController do
|
||||
end
|
||||
|
||||
describe 'GET #score' do
|
||||
let(:perform_request) { proc { get :score, params: {id: submission.id} } }
|
||||
let(:perform_request) { proc { get :score, format: :json, params: {id: submission.id} } }
|
||||
|
||||
before { perform_request.call }
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:hijack)
|
||||
perform_request.call
|
||||
end
|
||||
|
||||
pending('todo: mock puma webserver or encapsulate tubesock call (Tubesock::HijackNotAvailable)')
|
||||
expect_assigns(submission: :submission)
|
||||
expect_http_status(204)
|
||||
end
|
||||
|
||||
describe 'GET #test' do
|
||||
let(:filename) { submission.collect_files.detect(&:teacher_defined_assessment?).name_with_extension }
|
||||
let(:file) { submission.collect_files.detect(&:teacher_defined_assessment?) }
|
||||
let(:output) { {} }
|
||||
|
||||
before do
|
||||
get :test, params: {filename: filename, id: submission.id}
|
||||
file.update(hidden: false)
|
||||
allow_any_instance_of(described_class).to receive(:hijack)
|
||||
get :test, params: {filename: "#{file.filepath}.json", id: submission.id}
|
||||
end
|
||||
|
||||
pending('todo')
|
||||
expect_assigns(submission: :submission)
|
||||
expect_assigns(file: :file)
|
||||
expect_http_status(204)
|
||||
end
|
||||
end
|
||||
|
13
spec/factories/authentication_token.rb
Normal file
13
spec/factories/authentication_token.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :authentication_token, class: 'AuthenticationToken' do
|
||||
created_by_external_user
|
||||
shared_secret { SecureRandom.hex(32) }
|
||||
expire_at { 7.days.from_now }
|
||||
|
||||
trait :invalid do
|
||||
expire_at { 8.days.ago }
|
||||
end
|
||||
end
|
||||
end
|
@ -3,8 +3,8 @@
|
||||
FactoryBot.define do
|
||||
factory :codeharbor_link do
|
||||
user { build(:teacher) }
|
||||
push_url { 'http://push.url' }
|
||||
check_uuid_url { 'http://check-uuid.url' }
|
||||
push_url { 'https://push.url' }
|
||||
check_uuid_url { 'https://check-uuid.url' }
|
||||
sequence(:api_key) {|n| "api_key#{n}" }
|
||||
end
|
||||
end
|
||||
|
@ -3,8 +3,8 @@
|
||||
FactoryBot.define do
|
||||
lti_params = {
|
||||
lis_result_sourcedid: 'c2db0c7c-4411-4b27-a52b-ddfc3dc32065',
|
||||
lis_outcome_service_url: 'http://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_grading',
|
||||
launch_presentation_return_url: 'http://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_return',
|
||||
lis_outcome_service_url: 'https://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_grading',
|
||||
launch_presentation_return_url: 'https://172.16.54.235:3000/courses/0132156a-9afb-434d-83cc-704780104105/sections/21c6c6f4-1fb6-43b4-af3c-04fdc098879e/items/999b1fe6-d4b6-47b7-a577-ea2b4b1041ec/tool_return',
|
||||
}.freeze
|
||||
|
||||
factory :lti_parameter do
|
||||
|
@ -13,7 +13,7 @@ FactoryBot.define do
|
||||
factory :rfc_with_comment, class: 'RequestForComment' do
|
||||
after(:create) do |rfc|
|
||||
rfc.file = rfc.submission.files.first
|
||||
Comment.create(file: rfc.file, user: rfc.user, text: "comment for rfc #{rfc.question}")
|
||||
Comment.create(file: rfc.file, user: rfc.user, row: 1, text: "comment for rfc #{rfc.question}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -32,6 +32,53 @@ describe 'Authentication' do
|
||||
expect(page).to have_content(I18n.t('sessions.create.failure'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with no authentication token' do
|
||||
let(:request_for_comment) { create(:rfc_with_comment, user: user) }
|
||||
let(:rfc_path) { request_for_comment_url(request_for_comment) }
|
||||
|
||||
it 'denies access to the request for comment' do
|
||||
visit(rfc_path)
|
||||
expect(page).not_to have_current_path(rfc_path)
|
||||
expect(page).not_to have_content(request_for_comment.exercise.title)
|
||||
expect(page).to have_current_path(root_path)
|
||||
expect(page).to have_content(I18n.t('application.not_authorized'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an authentication token' do
|
||||
let(:user) { create(:learner) }
|
||||
let(:request_for_comment) { create(:rfc_with_comment, user: user) }
|
||||
let(:commenting_user) { InternalUser.create(attributes_for(:teacher)) }
|
||||
let(:mail) { UserMailer.got_new_comment(request_for_comment.comments.first, request_for_comment, commenting_user) }
|
||||
let(:rfc_link) { request_for_comment_url(request_for_comment, token: token.shared_secret) }
|
||||
|
||||
before { allow(AuthenticationToken).to receive(:generate!).with(user).and_return(token).once }
|
||||
|
||||
context 'when the token is valid' do
|
||||
let(:token) { create(:authentication_token, user: user) }
|
||||
|
||||
it 'allows access to the request for comment' do
|
||||
mail.deliver_now
|
||||
visit(rfc_link)
|
||||
expect(page).to have_current_path(rfc_link)
|
||||
expect(page).to have_content(request_for_comment.exercise.title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an expired authentication token' do
|
||||
let(:token) { create(:authentication_token, :invalid, user: user) }
|
||||
|
||||
it 'denies access to the request for comment' do
|
||||
mail.deliver_now
|
||||
visit(rfc_link)
|
||||
expect(page).not_to have_current_path(rfc_link)
|
||||
expect(page).not_to have_content(request_for_comment.exercise.title)
|
||||
expect(page).to have_current_path(root_path)
|
||||
expect(page).to have_content(I18n.t('application.not_authorized'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
|
45
spec/features/external_user_statistics_spec.rb
Normal file
45
spec/features/external_user_statistics_spec.rb
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'ExternalUserStatistics', js: true do
|
||||
let(:learner) { create(:external_user) }
|
||||
let(:exercise) { create(:dummy, user: user) }
|
||||
let(:study_group) { create(:study_group) }
|
||||
let(:password) { 'password123456' }
|
||||
|
||||
before do
|
||||
2.times { create(:submission, cause: 'autosave', user: learner, exercise: exercise, study_group: study_group) }
|
||||
2.times { create(:submission, cause: 'run', user: learner, exercise: exercise, study_group: study_group) }
|
||||
create(:submission, cause: 'assess', user: learner, exercise: exercise, study_group: study_group)
|
||||
create(:submission, cause: 'submit', user: learner, exercise: exercise, study_group: study_group)
|
||||
|
||||
study_group.external_users << learner
|
||||
study_group.internal_users << user
|
||||
study_group.save
|
||||
|
||||
visit(sign_in_path)
|
||||
fill_in('email', with: user.email)
|
||||
fill_in('password', with: password)
|
||||
click_button(I18n.t('sessions.new.link'))
|
||||
allow_any_instance_of(LtiHelper).to receive(:lti_outcome_service?).and_return(true)
|
||||
visit(statistics_external_user_exercise_path(id: exercise.id, external_user_id: learner.id))
|
||||
end
|
||||
|
||||
context 'when a admin accesses the page' do
|
||||
let(:user) { create(:admin, password: password) }
|
||||
|
||||
it 'does display the option to enable autosaves' do
|
||||
expect(page).to have_content(I18n.t('exercises.external_users.statistics.toggle_status_on')).or have_content(I18n.t('exercises.external_users.statistics.toggle_status_off'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a teacher accesses the page' do
|
||||
let(:user) { create(:teacher, password: password) }
|
||||
|
||||
it 'does not display the option to enable autosaves' do
|
||||
expect(page).not_to have_content(I18n.t('exercises.external_users.statistics.toggle_status_on'))
|
||||
expect(page).not_to have_content(I18n.t('exercises.external_users.statistics.toggle_status_off'))
|
||||
end
|
||||
end
|
||||
end
|
@ -12,7 +12,7 @@ describe Admin::DashboardHelper do
|
||||
describe '#docker_data' do
|
||||
before do
|
||||
create(:ruby)
|
||||
dcp = instance_double 'docker_container_pool'
|
||||
dcp = class_double Runner::Strategy::DockerContainerPool
|
||||
allow(Runner).to receive(:strategy_class).and_return dcp
|
||||
allow(dcp).to receive(:pool_size).and_return({})
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ describe ApplicationHelper do
|
||||
|
||||
describe '#empty' do
|
||||
it "builds an 'i' tag" do
|
||||
expect(empty).to have_css('i.empty.fa.fa-minus')
|
||||
expect(empty).to have_css('i.empty.fa-solid.fa-minus')
|
||||
end
|
||||
end
|
||||
|
||||
@ -39,7 +39,7 @@ describe ApplicationHelper do
|
||||
|
||||
describe '#no' do
|
||||
it "builds an 'i' tag" do
|
||||
expect(no).to have_css('i.fa.fa-times')
|
||||
expect(no).to have_css('i.fa-solid.fa-xmark')
|
||||
end
|
||||
end
|
||||
|
||||
@ -83,7 +83,7 @@ describe ApplicationHelper do
|
||||
let(:html) { row(label: 'foo', value: 42) }
|
||||
|
||||
it "builds nested 'div' tags" do
|
||||
expect(html).to have_css('div.attribute-row.row div.col-sm-3 + div.col-sm-9')
|
||||
expect(html).to have_css('div.attribute-row.row div.col-md-3 + div.col-md-9')
|
||||
end
|
||||
end
|
||||
|
||||
@ -141,7 +141,7 @@ describe ApplicationHelper do
|
||||
|
||||
describe '#yes' do
|
||||
it "builds an 'i' tag" do
|
||||
expect(yes).to have_css('i.fa.fa-check')
|
||||
expect(yes).to have_css('i.fa-solid.fa-check')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,14 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'find'
|
||||
require 'yaml'
|
||||
require 'active_support'
|
||||
require 'rails'
|
||||
|
||||
describe 'yaml config files' do
|
||||
Find.find(__dir__, 'config') do |path|
|
||||
next unless /.*.\.yml/.match?(path)
|
||||
|
||||
before do
|
||||
allow(Rails).to receive(:root).and_return(Pathname.new('/tmp'))
|
||||
|
||||
app = instance_double Rails::Application
|
||||
allow(Rails).to receive(:application).and_return app
|
||||
allow(app).to receive(:credentials).and_return({})
|
||||
end
|
||||
|
||||
it "loads #{path} without syntax error" do
|
||||
expect { YAML.load_file(path) }.not_to raise_error
|
||||
expect { ActiveSupport::ConfigurationFile.parse(path) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,442 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require 'seeds_helper'
|
||||
|
||||
WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test')
|
||||
|
||||
describe DockerClient do
|
||||
let(:command) { 'whoami' }
|
||||
let(:docker_client) { described_class.new(execution_environment: build(:java), user: build(:admin)) }
|
||||
let(:execution_environment) { build(:java) }
|
||||
let(:image) { double }
|
||||
let(:submission) { create(:submission) }
|
||||
let(:workspace_path) { WORKSPACE_PATH }
|
||||
|
||||
before do
|
||||
docker_image = Docker::Image.new(Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex, 'RepoTags' => [attributes_for(:java)[:docker_image]])
|
||||
allow(described_class).to receive(:find_image_by_tag).and_return(docker_image)
|
||||
described_class.initialize_environment
|
||||
allow(described_class).to receive(:container_creation_options).and_wrap_original do |original_method, *args, &block|
|
||||
result = original_method.call(*args, &block)
|
||||
result['NanoCPUs'] = 2 * 1_000_000_000 # CPU quota in units of 10^-9 CPUs.
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
describe '.check_availability!' do
|
||||
context 'when a socket error occurs' do
|
||||
it 'raises an error' do
|
||||
allow(Docker).to receive(:version).and_raise(Excon::Errors::SocketError.new(StandardError.new))
|
||||
expect { described_class.check_availability! }.to raise_error(DockerClient::Error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a timeout occurs' do
|
||||
it 'raises an error' do
|
||||
allow(Docker).to receive(:version).and_raise(Timeout::Error)
|
||||
expect { described_class.check_availability! }.to raise_error(DockerClient::Error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.container_creation_options' do
|
||||
let(:container_creation_options) { described_class.container_creation_options(execution_environment, workspace_path) }
|
||||
|
||||
it 'specifies the Docker image' do
|
||||
expect(container_creation_options).to include('Image' => described_class.find_image_by_tag(execution_environment.docker_image).info['RepoTags'].first)
|
||||
end
|
||||
|
||||
it 'specifies the memory limit' do
|
||||
expect(container_creation_options).to include('Memory' => execution_environment.memory_limit.megabytes)
|
||||
end
|
||||
|
||||
it 'specifies whether network access is enabled' do
|
||||
expect(container_creation_options).to include('NetworkDisabled' => !execution_environment.network_enabled?)
|
||||
end
|
||||
|
||||
it 'specifies to open the standard input stream once' do
|
||||
expect(container_creation_options).to include('OpenStdin' => true, 'StdinOnce' => true)
|
||||
end
|
||||
|
||||
it 'specifies mapped directories' do
|
||||
expect(container_creation_options).to include('Binds' => kind_of(Array))
|
||||
end
|
||||
|
||||
it 'specifies mapped ports' do
|
||||
expect(container_creation_options).to include('PortBindings' => kind_of(Hash))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.create_container' do
|
||||
let(:create_container) { described_class.create_container(execution_environment) }
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(workspace_path)
|
||||
end
|
||||
|
||||
it 'uses the correct Docker image' do
|
||||
expect(described_class).to receive(:find_image_by_tag).with(execution_environment.docker_image).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'creates a unique directory' do
|
||||
expect(described_class).to receive(:generate_local_workspace_path).and_call_original
|
||||
expect(FileUtils).to receive(:mkdir).with(kind_of(String)).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'creates a container' do
|
||||
local_workspace_path = File.join(workspace_path, 'example').to_s
|
||||
FileUtils.mkdir_p(workspace_path)
|
||||
allow(described_class).to receive(:generate_local_workspace_path).and_return(local_workspace_path)
|
||||
expect(described_class).to receive(:container_creation_options).with(execution_environment, local_workspace_path)
|
||||
.and_wrap_original do |original_method, *args, &block|
|
||||
result = original_method.call(*args, &block)
|
||||
result['NanoCPUs'] = 2 * 1_000_000_000 # CPU quota in units of 10^-9 CPUs.
|
||||
result
|
||||
end
|
||||
expect(Docker::Container).to receive(:create).with(kind_of(Hash)).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'starts the container' do
|
||||
expect_any_instance_of(Docker::Container).to receive(:start).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'configures mapped directories' do
|
||||
expect(described_class).to receive(:mapped_directories).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'configures mapped ports' do
|
||||
expect(described_class).to receive(:mapped_ports).with(execution_environment).and_call_original
|
||||
container = create_container
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
context 'when an error occurs' do
|
||||
let(:error) { Docker::Error::NotFoundError.new }
|
||||
|
||||
context 'when retries are left' do
|
||||
before do
|
||||
allow(described_class).to receive(:mapped_directories).and_raise(error).and_call_original
|
||||
end
|
||||
|
||||
it 'retries to create a container' do
|
||||
container = create_container
|
||||
expect(container).to be_a(Docker::Container)
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no retries are left' do
|
||||
before do
|
||||
allow(described_class).to receive(:mapped_directories).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error)
|
||||
end
|
||||
|
||||
it 'raises the error' do
|
||||
pending('RETRY COUNT is disabled')
|
||||
expect { create_container }.to raise_error(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_workspace_files' do
|
||||
let(:container) { double }
|
||||
|
||||
before do
|
||||
allow(container).to receive(:binds).at_least(:once).and_return(["#{workspace_path}:#{DockerClient::CONTAINER_WORKSPACE_PATH}"])
|
||||
end
|
||||
|
||||
after { docker_client.send(:create_workspace_files, container, submission) }
|
||||
|
||||
it 'creates submission-specific directories' do
|
||||
expect(Dir).to receive(:mkdir).at_least(:once).and_call_original
|
||||
end
|
||||
|
||||
it 'copies binary files' do
|
||||
submission.collect_files.select {|file| file.file_type.binary? }.each do |file|
|
||||
expect(docker_client).to receive(:copy_file_to_workspace).with(container: container, file: file)
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates non-binary files' do
|
||||
submission.collect_files.reject {|file| file.file_type.binary? }.each do |file|
|
||||
expect(docker_client).to receive(:create_workspace_file).with(container: container, file: file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_workspace_file' do
|
||||
let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) }
|
||||
let(:file) { build(:file, content: 'puts 42') }
|
||||
let(:file_path) { File.join(workspace_path, file.name_with_extension) }
|
||||
|
||||
after { File.delete(file_path) }
|
||||
|
||||
it 'creates a file' do
|
||||
expect(described_class).to receive(:local_workspace_path).at_least(:once).and_return(workspace_path)
|
||||
FileUtils.mkdir_p(workspace_path)
|
||||
docker_client.send(:create_workspace_file, container: container, file: file)
|
||||
expect(File.exist?(file_path)).to be true
|
||||
expect(File.new(file_path, 'r').read).to eq(file.content)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.destroy_container' do
|
||||
let(:container) { described_class.create_container(execution_environment) }
|
||||
|
||||
after { described_class.destroy_container(container) }
|
||||
|
||||
it 'kills running processes' do
|
||||
allow(container).to receive(:kill).and_return(container)
|
||||
end
|
||||
|
||||
it 'releases allocated ports' do
|
||||
allow(container).to receive(:port_bindings).at_least(:once).and_return(foo: [{'HostPort' => '42'}])
|
||||
expect(PortPool).to receive(:release)
|
||||
end
|
||||
|
||||
it 'removes the mapped directory' do
|
||||
expect(described_class).to receive(:local_workspace_path).at_least(:once).and_return(workspace_path)
|
||||
# !TODO Fix this
|
||||
# expect(PathName).to receive(:rmtree).with(workspace_path)
|
||||
end
|
||||
|
||||
it 'deletes the container' do
|
||||
expect(container).to receive(:delete).with(force: true, v: true).and_call_original
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute_arbitrary_command' do
|
||||
let(:execute_arbitrary_command) { docker_client.execute_arbitrary_command(command) }
|
||||
|
||||
after { described_class.destroy_container(docker_client.container) }
|
||||
|
||||
it 'creates a new container' do
|
||||
expect(described_class).to receive(:create_container).and_call_original
|
||||
execute_arbitrary_command
|
||||
end
|
||||
|
||||
it 'sends the command' do
|
||||
allow(docker_client).to receive(:send_command).with(command, kind_of(Docker::Container)).and_return({})
|
||||
execute_arbitrary_command
|
||||
end
|
||||
|
||||
context 'when a socket error occurs' do
|
||||
let(:error) { Excon::Errors::SocketError.new(SocketError.new) }
|
||||
|
||||
context 'when retries are left' do
|
||||
let(:result) { {status: 'ok', stdout: 42} }
|
||||
|
||||
before do
|
||||
allow(docker_client).to receive(:send_command).and_raise(error).and_return(result)
|
||||
end
|
||||
|
||||
it 'retries to execute the command' do
|
||||
expect(execute_arbitrary_command[:stdout]).to eq(result[:stdout])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no retries are left' do
|
||||
before do
|
||||
allow(docker_client).to receive(:send_command).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error)
|
||||
end
|
||||
|
||||
it 'raises the error' do
|
||||
pending('retries are disabled')
|
||||
# TODO: Retries is disabled
|
||||
expect { execute_arbitrary_command }.to raise_error(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute_run_command' do
|
||||
let(:filename) { submission.exercise.files.detect {|file| file.role == 'main_file' }.name_with_extension }
|
||||
|
||||
after do
|
||||
docker_client.send(:execute_run_command, submission, filename)
|
||||
described_class.destroy_container(docker_client.container)
|
||||
end
|
||||
|
||||
it 'creates a new container' do
|
||||
expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original
|
||||
end
|
||||
|
||||
it 'creates the workspace files' do
|
||||
expect(docker_client).to receive(:create_workspace_files)
|
||||
end
|
||||
|
||||
it 'executes the run command' do
|
||||
pending('todo in the future')
|
||||
expect(submission.execution_environment).to receive(:run_command).and_call_original
|
||||
expect(docker_client).to receive(:send_command).with(kind_of(String), kind_of(Docker::Container))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute_test_command' do
|
||||
let(:filename) { submission.exercise.files.detect {|file| file.role == 'teacher_defined_test' || file.role == 'teacher_defined_linter' }.name_with_extension }
|
||||
|
||||
after do
|
||||
docker_client.send(:execute_test_command, submission, filename)
|
||||
described_class.destroy_container(docker_client.container)
|
||||
end
|
||||
|
||||
it 'creates a new container' do
|
||||
expect(described_class).to receive(:create_container).with(submission.execution_environment).and_call_original
|
||||
end
|
||||
|
||||
it 'creates the workspace files' do
|
||||
expect(docker_client).to receive(:create_workspace_files)
|
||||
end
|
||||
|
||||
it 'executes the test command' do
|
||||
expect(submission.execution_environment).to receive(:test_command).and_call_original
|
||||
allow(docker_client).to receive(:send_command).with(kind_of(String), kind_of(Docker::Container)).and_return({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '.generate_local_workspace_path' do
|
||||
it 'includes the correct workspace root' do
|
||||
expect(described_class.generate_local_workspace_path.to_s).to start_with(DockerClient::LOCAL_WORKSPACE_ROOT.to_s)
|
||||
end
|
||||
|
||||
it 'includes a UUID' do
|
||||
expect(SecureRandom).to receive(:uuid).and_call_original
|
||||
described_class.generate_local_workspace_path
|
||||
end
|
||||
end
|
||||
|
||||
describe '.initialize_environment' do
|
||||
context 'with complete configuration' do
|
||||
it 'creates the file directory' do
|
||||
expect(FileUtils).to receive(:mkdir_p).with(DockerClient::LOCAL_WORKSPACE_ROOT)
|
||||
described_class.initialize_environment
|
||||
end
|
||||
end
|
||||
|
||||
context 'with incomplete configuration' do
|
||||
before { allow(described_class).to receive(:config).at_least(:once).and_return({}) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { described_class.initialize_environment }.to raise_error(DockerClient::Error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.local_workspace_path' do
|
||||
let(:container) { described_class.create_container(execution_environment) }
|
||||
let(:local_workspace_path) { described_class.local_workspace_path(container) }
|
||||
|
||||
after { described_class.destroy_container(container) }
|
||||
|
||||
it 'returns a path' do
|
||||
expect(local_workspace_path).to be_a(Pathname)
|
||||
end
|
||||
|
||||
it 'includes the correct workspace root' do
|
||||
expect(local_workspace_path.to_s).to start_with(DockerClient::LOCAL_WORKSPACE_ROOT.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.mapped_directories' do
|
||||
it 'returns a unique mapping' do
|
||||
mapping = described_class.mapped_directories(workspace_path).first
|
||||
expect(mapping).to start_with(workspace_path.to_s)
|
||||
expect(mapping).to end_with(DockerClient::CONTAINER_WORKSPACE_PATH)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.mapped_ports' do
|
||||
context 'with exposed ports' do
|
||||
before { execution_environment.exposed_ports = [3000] }
|
||||
|
||||
it 'returns a mapping' do
|
||||
expect(described_class.mapped_ports(execution_environment)).to be_a(Hash)
|
||||
expect(described_class.mapped_ports(execution_environment).length).to eq(1)
|
||||
end
|
||||
|
||||
it 'retrieves available ports' do
|
||||
expect(PortPool).to receive(:available_port)
|
||||
described_class.mapped_ports(execution_environment)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without exposed ports' do
|
||||
it 'returns an empty mapping' do
|
||||
expect(described_class.mapped_ports(execution_environment)).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_command' do
|
||||
let(:block) { proc {} }
|
||||
let(:container) { described_class.create_container(execution_environment) }
|
||||
let(:send_command) { docker_client.send(:send_command, command, container, &block) }
|
||||
|
||||
after do
|
||||
send_command
|
||||
described_class.destroy_container(container)
|
||||
end
|
||||
|
||||
it 'limits the execution time' do
|
||||
expect(Timeout).to receive(:timeout).at_least(:once).with(kind_of(Numeric)).and_call_original
|
||||
end
|
||||
|
||||
it 'provides the command to be executed as input' do
|
||||
pending('we are currently not using attach but rather exec.')
|
||||
expect(container).to receive(:attach).with(stdin: kind_of(StringIO))
|
||||
end
|
||||
|
||||
it 'calls the block' do
|
||||
pending('block is no longer called, see revision 4cbf9970b13362efd4588392cafe4f7fd7cb31c3 to get information how it was done before.')
|
||||
expect(block).to receive(:call)
|
||||
end
|
||||
|
||||
context 'when a timeout occurs' do
|
||||
before do
|
||||
exec_called = 0
|
||||
allow(container).to receive(:exec) do
|
||||
exec_called += 1
|
||||
raise Timeout::Error if exec_called == 1
|
||||
|
||||
[[], []]
|
||||
end
|
||||
end
|
||||
|
||||
it 'destroys the container asynchronously' do
|
||||
pending('Container is destroyed, but not as expected in this test. ToDo update this test.')
|
||||
expect(Concurrent::Future).to receive(:execute)
|
||||
end
|
||||
|
||||
it 'returns a corresponding status' do
|
||||
expect(send_command[:status]).to eq(:timeout)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the container terminates timely' do
|
||||
it 'destroys the container asynchronously' do
|
||||
pending('Container is destroyed, but not as expected in this test. ToDo update this test.')
|
||||
expect(Concurrent::Future).to receive(:execute)
|
||||
end
|
||||
|
||||
it "returns the container's output" do
|
||||
expect(send_command[:stderr]).to be_blank
|
||||
expect(send_command[:stdout]).to start_with('user')
|
||||
end
|
||||
|
||||
it 'returns a corresponding status' do
|
||||
expect(send_command[:status]).to eq(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,34 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe DockerContainerMixin do
|
||||
let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) }
|
||||
|
||||
describe '#binds' do
|
||||
let(:binds) { [] }
|
||||
|
||||
it 'is defined for Docker::Container' do
|
||||
expect(Docker::Container.instance_methods).to include(:binds)
|
||||
end
|
||||
|
||||
it 'returns the correct information' do
|
||||
allow(container).to receive(:json).and_return('HostConfig' => {'Binds' => binds})
|
||||
expect(container.binds).to eq(binds)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#port_bindings' do
|
||||
let(:port) { 1234 }
|
||||
let(:port_bindings) { {"#{port}/tcp" => [{'HostIp' => '', 'HostPort' => port.to_s}]} }
|
||||
|
||||
it 'is defined for Docker::Container' do
|
||||
expect(Docker::Container.instance_methods).to include(:port_bindings)
|
||||
end
|
||||
|
||||
it 'returns the correct information' do
|
||||
allow(container).to receive(:json).and_return('HostConfig' => {'PortBindings' => port_bindings})
|
||||
expect(container.port_bindings).to eq(port => port)
|
||||
end
|
||||
end
|
||||
end
|
@ -13,7 +13,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_mp3)) }
|
||||
|
||||
it 'is an audio file icon' do
|
||||
expect(file_icon).to include('fa-file-audio-o')
|
||||
expect(file_icon).to include('fa-file-audio')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
|
||||
@ -21,7 +22,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_jpg)) }
|
||||
|
||||
it 'is an image file icon' do
|
||||
expect(file_icon).to include('fa-file-image-o')
|
||||
expect(file_icon).to include('fa-file-image')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
|
||||
@ -29,7 +31,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_mp4)) }
|
||||
|
||||
it 'is a video file icon' do
|
||||
expect(file_icon).to include('fa-file-video-o')
|
||||
expect(file_icon).to include('fa-file-video')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -40,6 +43,7 @@ describe FileTree do
|
||||
|
||||
it 'is a lock icon' do
|
||||
expect(file_icon).to include('fa-lock')
|
||||
expect(file_icon).to include('fa-solid')
|
||||
end
|
||||
end
|
||||
|
||||
@ -47,7 +51,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_py)) }
|
||||
|
||||
it 'is a code file icon' do
|
||||
expect(file_icon).to include('fa-file-code-o')
|
||||
expect(file_icon).to include('fa-file-code')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
|
||||
@ -55,7 +60,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_svg)) }
|
||||
|
||||
it 'is a text file icon' do
|
||||
expect(file_icon).to include('fa-file-text-o')
|
||||
expect(file_icon).to include('fa-file-text')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
|
||||
@ -63,7 +69,8 @@ describe FileTree do
|
||||
let(:file) { build(:file, file_type: build(:dot_md)) }
|
||||
|
||||
it 'is a generic file icon' do
|
||||
expect(file_icon).to include('fa-file-o')
|
||||
expect(file_icon).to include('fa-file')
|
||||
expect(file_icon).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -71,7 +78,8 @@ describe FileTree do
|
||||
|
||||
describe '#folder_icon' do
|
||||
it 'is a folder icon' do
|
||||
expect(file_tree.send(:folder_icon)).to include('fa-folder-o')
|
||||
expect(file_tree.send(:folder_icon)).to include('fa-folder')
|
||||
expect(file_tree.send(:folder_icon)).to include('fa-regular')
|
||||
end
|
||||
end
|
||||
|
||||
@ -90,7 +98,7 @@ describe FileTree do
|
||||
end
|
||||
|
||||
it 'creates tree nodes for intermediary path segments' do
|
||||
expect(file_tree.instance_variable_get(:@root).reject(&:content).reject(&:is_root?).map(&:name)).to eq(files.first.path.split('/'))
|
||||
expect(file_tree.instance_variable_get(:@root).reject(&:content).reject(&:root?).map(&:name)).to eq(files.first.path.split('/'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe PortPool do
|
||||
describe '.available_port' do
|
||||
it 'is synchronized' do
|
||||
expect(described_class.instance_variable_get(:@mutex)).to receive(:synchronize)
|
||||
described_class.available_port
|
||||
end
|
||||
|
||||
context 'when a port is available' do
|
||||
it 'returns the port' do
|
||||
expect(described_class.available_port).to be_a(Numeric)
|
||||
end
|
||||
|
||||
it 'removes the port from the list of available ports' do
|
||||
port = described_class.available_port
|
||||
expect(described_class.instance_variable_get(:@available_ports)).not_to include(port)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no port is available' do
|
||||
it 'returns the port' do
|
||||
available_ports = described_class.instance_variable_get(:@available_ports)
|
||||
described_class.instance_variable_set(:@available_ports, [])
|
||||
expect(described_class.available_port).to be nil
|
||||
described_class.instance_variable_set(:@available_ports, available_ports)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.release' do
|
||||
context 'when the port has been obtained earlier' do
|
||||
it 'adds the port to the list of available ports' do
|
||||
port = described_class.available_port
|
||||
expect(described_class.instance_variable_get(:@available_ports)).not_to include(port)
|
||||
described_class.release(port)
|
||||
expect(described_class.instance_variable_get(:@available_ports)).to include(port)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the port has not been obtained earlier' do
|
||||
it 'does not add the port to the list of available ports' do
|
||||
port = described_class.instance_variable_get(:@available_ports).sample
|
||||
expect { described_class.release(port) }.not_to change { described_class.instance_variable_get(:@available_ports).length }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the port is not included in the port range' do
|
||||
it 'does not add the port to the list of available ports' do
|
||||
port = nil
|
||||
expect { described_class.release(port) }.not_to change { described_class.instance_variable_get(:@available_ports).length }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -7,7 +7,7 @@ describe Runner::Strategy::DockerContainerPool do
|
||||
let(:runner_id) { attributes_for(:runner)[:runner_id] }
|
||||
let(:execution_environment) { create :ruby }
|
||||
let(:container_pool) { described_class.new(runner_id, execution_environment) }
|
||||
let(:docker_container_pool_url) { 'http://localhost:1234' }
|
||||
let(:docker_container_pool_url) { 'https://localhost:1234' }
|
||||
let(:config) { {url: docker_container_pool_url, unused_runner_expiration_time: 180} }
|
||||
let(:container) { instance_double(Docker::Container) }
|
||||
|
||||
@ -160,7 +160,7 @@ describe Runner::Strategy::DockerContainerPool do
|
||||
let(:files) { [build(:file, :image)] }
|
||||
|
||||
it 'copies the file inside the workspace' do
|
||||
expect(FileUtils).to receive(:cp).with(files.first.native_file.path, local_path.join(files.first.filepath))
|
||||
expect(File).to receive(:write).with(local_path.join(files.first.filepath), files.first.read)
|
||||
container_pool.copy_files(files)
|
||||
end
|
||||
end
|
||||
@ -182,7 +182,7 @@ describe Runner::Strategy::DockerContainerPool do
|
||||
|
||||
it 'returns the local part of the mount binding' do
|
||||
local_path = 'tmp/container20'
|
||||
allow(container).to receive(:binds).and_return(["#{local_path}:/workspace"])
|
||||
allow(container).to receive(:json).and_return({HostConfig: {Binds: ["#{local_path}:/workspace"]}}.as_json)
|
||||
expect(container_pool.send(:local_workspace_path)).to eq(Pathname.new(local_path))
|
||||
end
|
||||
end
|
||||
|
@ -118,7 +118,7 @@ describe Runner::Strategy::Poseidon do
|
||||
let(:response_status) { -1 }
|
||||
|
||||
it 'raises an error' do
|
||||
faraday_connection = instance_double 'Faraday::Connection'
|
||||
faraday_connection = instance_double Faraday::Connection
|
||||
allow(described_class).to receive(:http_connection).and_return(faraday_connection)
|
||||
%i[post patch delete].each {|message| allow(faraday_connection).to receive(message).and_raise(Faraday::TimeoutError) }
|
||||
expect { action.call }.to raise_error(Runner::Error::FaradayError)
|
||||
@ -131,7 +131,7 @@ describe Runner::Strategy::Poseidon do
|
||||
let(:execution_environment) { create(:ruby) }
|
||||
|
||||
it 'makes the correct request to Poseidon' do
|
||||
faraday_connection = instance_double 'Faraday::Connection'
|
||||
faraday_connection = instance_double Faraday::Connection
|
||||
allow(described_class).to receive(:http_connection).and_return(faraday_connection)
|
||||
allow(faraday_connection).to receive(:put).and_return(Faraday::Response.new(status: 201))
|
||||
action.call
|
||||
@ -143,7 +143,7 @@ describe Runner::Strategy::Poseidon do
|
||||
|
||||
shared_examples 'returns true when the api request was successful' do |status|
|
||||
it "returns true on status #{status}" do
|
||||
faraday_connection = instance_double 'Faraday::Connection'
|
||||
faraday_connection = instance_double Faraday::Connection
|
||||
allow(described_class).to receive(:http_connection).and_return(faraday_connection)
|
||||
allow(faraday_connection).to receive(:put).and_return(Faraday::Response.new(status: status))
|
||||
expect(action.call).to be_truthy
|
||||
@ -152,7 +152,7 @@ describe Runner::Strategy::Poseidon do
|
||||
|
||||
shared_examples 'returns false when the api request failed' do |status|
|
||||
it "raises an exception on status #{status}" do
|
||||
faraday_connection = instance_double 'Faraday::Connection'
|
||||
faraday_connection = instance_double Faraday::Connection
|
||||
allow(described_class).to receive(:http_connection).and_return(faraday_connection)
|
||||
allow(faraday_connection).to receive(:put).and_return(Faraday::Response.new(status: status))
|
||||
expect { action.call }.to raise_exception Runner::Error::UnexpectedResponse
|
||||
@ -168,7 +168,7 @@ describe Runner::Strategy::Poseidon do
|
||||
end
|
||||
|
||||
it 'raises an exception if Faraday raises an error' do
|
||||
faraday_connection = instance_double 'Faraday::Connection'
|
||||
faraday_connection = instance_double Faraday::Connection
|
||||
allow(described_class).to receive(:http_connection).and_return(faraday_connection)
|
||||
allow(faraday_connection).to receive(:put).and_raise(Faraday::TimeoutError)
|
||||
expect { action.call }.to raise_exception Runner::Error::FaradayError
|
||||
@ -312,8 +312,15 @@ describe Runner::Strategy::Poseidon do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Poseidon returns NotFound (404)' do
|
||||
let(:response_status) { 404 }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { action.call }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'Unauthorized (401) error handling'
|
||||
include_examples 'NotFound (404) error handling'
|
||||
include_examples 'InternalServerError (500) error handling'
|
||||
include_examples 'unknown response status error handling'
|
||||
include_examples 'Faraday error handling'
|
||||
@ -328,7 +335,7 @@ describe Runner::Strategy::Poseidon do
|
||||
WebMock
|
||||
.stub_request(:patch, "#{described_class.config[:url]}/runners/#{runner_id}/files")
|
||||
.with(
|
||||
body: {copy: [{path: file.filepath, content: encoded_file_content}], delete: ['/workspace']},
|
||||
body: {copy: [{path: file.filepath, content: encoded_file_content}], delete: ['./*']},
|
||||
headers: {'Content-Type' => 'application/json'}
|
||||
)
|
||||
.to_return(body: response_body, status: response_status)
|
||||
|
@ -14,7 +14,7 @@ describe UserMailer do
|
||||
end
|
||||
|
||||
it 'sets the correct sender' do
|
||||
expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from])
|
||||
expect(mail.from).to include('codeocean@hpi.de')
|
||||
end
|
||||
|
||||
it 'sets the correct subject' do
|
||||
@ -45,7 +45,7 @@ describe UserMailer do
|
||||
end
|
||||
|
||||
it 'sets the correct sender' do
|
||||
expect(mail.from).to include(CodeOcean::Application.config.action_mailer[:default_options][:from])
|
||||
expect(mail.from).to include('codeocean@hpi.de')
|
||||
end
|
||||
|
||||
it 'sets the correct subject' do
|
||||
@ -60,4 +60,107 @@ describe UserMailer do
|
||||
expect(mail.body).to include(reset_password_internal_user_url(user, token: user.reset_password_token))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#got_new_comment' do
|
||||
let(:user) { create(:learner) }
|
||||
let(:token) { AuthenticationToken.find_by(user: user) }
|
||||
let(:request_for_comment) { create(:rfc_with_comment, user: user) }
|
||||
let(:commenting_user) { InternalUser.create(attributes_for(:teacher)) }
|
||||
let(:mail) { described_class.got_new_comment(request_for_comment.comments.first, request_for_comment, commenting_user).deliver_now }
|
||||
|
||||
it 'sets the correct sender' do
|
||||
expect(mail.from).to include('codeocean@hpi.de')
|
||||
end
|
||||
|
||||
it 'sets the correct subject' do
|
||||
expect(mail.subject).to eq(I18n.t('mailers.user_mailer.got_new_comment.subject', commenting_user_displayname: commenting_user.displayname))
|
||||
end
|
||||
|
||||
it 'sets the correct receiver' do
|
||||
expect(mail.to).to include(request_for_comment.user.email)
|
||||
end
|
||||
|
||||
it 'includes the correct URL' do
|
||||
expect(mail.body).to include(request_for_comment_url(request_for_comment, token: token.shared_secret))
|
||||
end
|
||||
|
||||
it 'creates a new authentication token' do
|
||||
expect { mail }.to change(AuthenticationToken, :count).by(1)
|
||||
end
|
||||
|
||||
it 'sets a non-expired authentication token' do
|
||||
mail
|
||||
# A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token.
|
||||
expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#got_new_comment_for_subscription' do
|
||||
let(:user) { create(:learner) }
|
||||
let(:token) { AuthenticationToken.find_by(user: user) }
|
||||
let(:request_for_comment) { create(:rfc_with_comment, user: user) }
|
||||
let(:subscription) { Subscription.create(request_for_comment: request_for_comment, user: user) }
|
||||
let(:from_user) { InternalUser.create(attributes_for(:teacher)) }
|
||||
let(:mail) { described_class.got_new_comment_for_subscription(request_for_comment.comments.first, subscription, from_user).deliver_now }
|
||||
|
||||
it 'sets the correct sender' do
|
||||
expect(mail.from).to include('codeocean@hpi.de')
|
||||
end
|
||||
|
||||
it 'sets the correct subject' do
|
||||
expect(mail.subject).to eq(I18n.t('mailers.user_mailer.got_new_comment_for_subscription.subject', author_displayname: from_user.displayname))
|
||||
end
|
||||
|
||||
it 'sets the correct receiver' do
|
||||
expect(mail.to).to include(subscription.user.email)
|
||||
end
|
||||
|
||||
it 'includes the correct URL' do
|
||||
expect(mail.body).to include(request_for_comment_url(subscription.request_for_comment, token: token.shared_secret))
|
||||
end
|
||||
|
||||
it 'creates a new authentication token' do
|
||||
expect { mail }.to change(AuthenticationToken, :count).by(1)
|
||||
end
|
||||
|
||||
it 'sets a non-expired authentication token' do
|
||||
mail
|
||||
# A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token.
|
||||
expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_thank_you_note' do
|
||||
let(:user) { create(:learner) }
|
||||
let(:token) { AuthenticationToken.find_by(user: user) }
|
||||
let(:request_for_comments) { create(:rfc_with_comment, user: user) }
|
||||
let(:receiver) { InternalUser.create(attributes_for(:teacher)) }
|
||||
let(:mail) { described_class.send_thank_you_note(request_for_comments, receiver).deliver_now }
|
||||
|
||||
it 'sets the correct sender' do
|
||||
expect(mail.from).to include('codeocean@hpi.de')
|
||||
end
|
||||
|
||||
it 'sets the correct subject' do
|
||||
expect(mail.subject).to eq(I18n.t('mailers.user_mailer.send_thank_you_note.subject', author: request_for_comments.user.displayname))
|
||||
end
|
||||
|
||||
it 'sets the correct receiver' do
|
||||
expect(mail.to).to include(receiver.email)
|
||||
end
|
||||
|
||||
it 'includes the correct URL' do
|
||||
expect(mail.body).to include(request_for_comment_url(request_for_comments, token: token.shared_secret))
|
||||
end
|
||||
|
||||
it 'creates a new authentication token' do
|
||||
expect { mail }.to change(AuthenticationToken, :count).by(1)
|
||||
end
|
||||
|
||||
it 'sets a non-expired authentication token' do
|
||||
mail
|
||||
# A five minute tolerance is allowed to account for the time difference between `now` and the creation timestamp of the token.
|
||||
expect(token.expire_at - Time.zone.now).to be_within(5.minutes).of(7.days)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -56,4 +56,24 @@ describe CodeOcean::File do
|
||||
expect(file.errors[:weight]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a native file' do
|
||||
let(:file) { create(:file, :image) }
|
||||
|
||||
after { file.native_file.remove! }
|
||||
|
||||
context 'when the path has not been modified' do
|
||||
it 'reads the native file' do
|
||||
expect(file.read).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the path has been modified' do
|
||||
before { file.update(native_file: '../../../../secrets.yml') }
|
||||
|
||||
it 'does not read the native file' do
|
||||
expect(file.read).not_to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -153,7 +153,7 @@ describe ExecutionEnvironment do
|
||||
describe '#working_docker_image?' do
|
||||
let(:execution_environment) { create(:ruby) }
|
||||
let(:working_docker_image?) { execution_environment.send(:working_docker_image?) }
|
||||
let(:runner) { instance_double 'runner' }
|
||||
let(:runner) { instance_double Runner }
|
||||
|
||||
before do
|
||||
allow(execution_environment).to receive(:sync_runner_environment).and_return(true)
|
||||
|
@ -115,10 +115,12 @@ describe Runner do
|
||||
end
|
||||
|
||||
it 'attaches the execution time to the error' do
|
||||
starting_time = Time.zone.now
|
||||
test_starting_time = Time.zone.now
|
||||
expect { runner.attach_to_execution(command) }.to raise_error do |raised_error|
|
||||
test_time = Time.zone.now - starting_time
|
||||
test_time = Time.zone.now - test_starting_time
|
||||
expect(raised_error.execution_duration).to be_between(0.0, test_time).exclusive
|
||||
# The `starting_time` is shortly after the `test_starting_time``
|
||||
expect(raised_error.starting_time).to be > test_starting_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -123,22 +123,6 @@ describe Submission do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with enough exercise feedback' do
|
||||
let(:exercise) { create(:dummy_with_user_feedbacks, user_feedbacks_count: 42) }
|
||||
let(:user) { create(:external_user) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:redirect_to_feedback?).and_return(false)
|
||||
end
|
||||
|
||||
it 'sends nobody to feedback page' do
|
||||
30.times do |_i|
|
||||
submission = create(:submission, exercise: exercise, user: create(:external_user))
|
||||
expect(submission.send(:redirect_to_feedback?)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#calculate_score' do
|
||||
|
@ -45,7 +45,7 @@ RSpec.describe ExerciseService::PushExternal do
|
||||
end
|
||||
|
||||
context 'when response status is success' do
|
||||
it { is_expected.to be nil }
|
||||
it { is_expected.to be_nil }
|
||||
|
||||
context 'when response status is 500' do
|
||||
let(:status) { 500 }
|
||||
@ -58,7 +58,7 @@ RSpec.describe ExerciseService::PushExternal do
|
||||
context 'when an error occurs' do
|
||||
before { allow(Faraday).to receive(:new).and_raise(StandardError) }
|
||||
|
||||
it { is_expected.not_to be nil }
|
||||
it { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ describe ProformaService::ExportTask do
|
||||
subject(:export_task) { described_class.new }
|
||||
|
||||
it 'assigns exercise' do
|
||||
expect(export_task.instance_variable_get(:@exercise)).to be nil
|
||||
expect(export_task.instance_variable_get(:@exercise)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -26,7 +26,7 @@ describe ProformaService::ExportTask do
|
||||
|
||||
let(:task) { Proforma::Task.new }
|
||||
let(:exercise) { build(:dummy) }
|
||||
let(:exporter) { instance_double('Proforma::Exporter', perform: 'zip') }
|
||||
let(:exporter) { instance_double(Proforma::Exporter, perform: 'zip') }
|
||||
|
||||
before do
|
||||
allow(ProformaService::ConvertExerciseToTask).to receive(:call).with(exercise: exercise).and_return(task)
|
||||
|
@ -15,7 +15,7 @@
|
||||
# The `.rspec` file also contains a few flags that are not defaults but that
|
||||
# users commonly want.
|
||||
#
|
||||
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||
# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||
|
||||
unless RUBY_PLATFORM == 'java'
|
||||
require 'simplecov'
|
||||
@ -65,7 +65,7 @@ RSpec.configure do |config|
|
||||
config.expect_with :rspec do |expectations|
|
||||
# Enable only the newer, non-monkey-patching expect syntax.
|
||||
# For more details, see:
|
||||
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
||||
# - https://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
||||
expectations.syntax = :expect
|
||||
end
|
||||
|
||||
@ -73,8 +73,6 @@ RSpec.configure do |config|
|
||||
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
||||
config.mock_with :rspec do |mocks|
|
||||
# Enable only the newer, non-monkey-patching expect syntax.
|
||||
# For more details, see:
|
||||
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
||||
mocks.syntax = :expect
|
||||
|
||||
# Prevents you from mocking or stubbing a method that does not exist on
|
||||
|
@ -49,9 +49,9 @@ def expect_redirect(path = nil)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_status(status)
|
||||
def expect_http_status(status)
|
||||
it "responds with status #{status}" do
|
||||
expect(response.status).to eq(status)
|
||||
expect(response).to have_http_status(status)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
require 'capybara/rspec'
|
||||
require 'selenium/webdriver'
|
||||
|
||||
if ENV['HEADLESS_TEST'] == 'true' || ENV['USER'] == 'vagrant'
|
||||
if ENV.fetch('HEADLESS_TEST', nil) == 'true' || ENV.fetch('USER', nil) == 'vagrant'
|
||||
require 'headless'
|
||||
|
||||
headless = Headless.new
|
||||
@ -14,7 +14,7 @@ Capybara.register_driver :selenium do |app|
|
||||
profile = Selenium::WebDriver::Firefox::Profile.new
|
||||
profile['intl.accept_languages'] = 'en'
|
||||
options = Selenium::WebDriver::Firefox::Options.new
|
||||
options.headless! if ENV['CI'] == 'true'
|
||||
options.headless! if ENV.fetch('CI', nil) == 'true'
|
||||
options.profile = profile
|
||||
driver = Capybara::Selenium::Driver.new(app, browser: :firefox, capabilities: options)
|
||||
driver.browser.manage.window.resize_to(1280, 960)
|
||||
|
@ -11,7 +11,7 @@ describe FileUploader do
|
||||
after { uploader.remove! }
|
||||
|
||||
it 'uses the specified storage directory' do
|
||||
expect(uploader.file.path).to start_with(Rails.root.join('public', uploader.store_dir).to_s)
|
||||
expect(uploader.file.path).to start_with(Rails.public_path.join(uploader.store_dir).to_s)
|
||||
expect(uploader.file.path).to end_with(file_path.basename.to_s)
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user