merge master

This commit is contained in:
Karol
2022-08-20 22:20:52 +02:00
291 changed files with 5413 additions and 9429 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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