Apply manual rubocop fixes

This commit is contained in:
Sebastian Serth
2021-05-14 11:07:11 +02:00
parent 6cbecb5b39
commit da0a682ffb
109 changed files with 431 additions and 416 deletions

View File

@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base
MEMBER_ACTIONS = %i[destroy edit show update].freeze MEMBER_ACTIONS = %i[destroy edit show update].freeze
after_action :verify_authorized, except: %i[help welcome] after_action :verify_authorized, except: %i[welcome]
around_action :mnemosyne_trace around_action :mnemosyne_trace
before_action :set_sentry_context, :set_locale, :allow_iframe_requests, :load_embed_options before_action :set_sentry_context, :set_locale, :allow_iframe_requests, :load_embed_options
protect_from_forgery(with: :exception, prepend: true) protect_from_forgery(with: :exception, prepend: true)

View File

@ -31,7 +31,10 @@ module CodeOcean
path: path, status: :created) path: path, status: :created)
else else
filename = "#{@object.path || ''}/#{@object.name || ''}#{@object.file_type.try(:file_extension) || ''}" filename = "#{@object.path || ''}/#{@object.name || ''}#{@object.file_type.try(:file_extension) || ''}"
format.html { redirect_to(options[:path]); flash[:danger] = t('files.error.filename', name: filename) } format.html do
flash[:danger] = t('files.error.filename', name: filename)
redirect_to(options[:path])
end
format.json { render(json: @object.errors, status: :unprocessable_entity) } format.json { render(json: @object.errors, status: :unprocessable_entity) }
end end
end end

View File

@ -3,7 +3,7 @@
class CodeharborLinksController < ApplicationController class CodeharborLinksController < ApplicationController
include CommonBehavior include CommonBehavior
before_action :verify_codeharbor_activation before_action :verify_codeharbor_activation
before_action :set_codeharbor_link, only: %i[show edit update destroy] before_action :set_codeharbor_link, only: %i[edit update destroy]
def new def new
base_url = CodeOcean::Config.new(:code_ocean).read[:codeharbor][:url] || '' base_url = CodeOcean::Config.new(:code_ocean).read[:codeharbor][:url] || ''

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class CommentsController < ApplicationController class CommentsController < ApplicationController
before_action :set_comment, only: %i[show edit update destroy] before_action :set_comment, only: %i[show update destroy]
# to disable authorization check: comment the line below back in # to disable authorization check: comment the line below back in
# skip_after_action :verify_authorized # skip_after_action :verify_authorized

View File

@ -60,11 +60,9 @@ module Lti
if provider.roles.present? if provider.roles.present?
provider.roles.each do |role| provider.roles.each do |role|
case role.downcase case role.downcase
when 'administrator' when 'administrator', 'instructor'
# We don't want anyone to get admin privileges through LTI # We don't want anyone to get admin privileges through LTI
result = 'teacher' if result == 'learner' result = 'teacher' if result == 'learner'
when 'instructor'
result = 'teacher' if result == 'learner'
else # 'learner' else # 'learner'
next next
end end
@ -172,8 +170,7 @@ module Lti
normalized_lit_score *= 0.0 normalized_lit_score *= 0.0
end end
response = provider.post_replace_result!(normalized_lit_score) response = provider.post_replace_result!(normalized_lit_score)
{code: response.response_code, message: response.post_response.body, status: response.code_major, {code: response.response_code, message: response.post_response.body, status: response.code_major, score_sent: normalized_lit_score}
score_sent: normalized_lit_score}
else else
{status: 'unsupported'} {status: 'unsupported'}
end end

View File

@ -5,8 +5,7 @@ module RemoteEvaluationParameters
def remote_evaluation_params def remote_evaluation_params
if params[:remote_evaluation].present? if params[:remote_evaluation].present?
remote_evaluation_params = params[:remote_evaluation].permit(:validation_token, params[:remote_evaluation].permit(:validation_token, files_attributes: file_attributes)
files_attributes: file_attributes)
end end
end end
private :remote_evaluation_params private :remote_evaluation_params

View File

@ -81,7 +81,7 @@ module SubmissionScoring
end end
end end
submission.update(score: score) submission.update(score: score)
if submission.normalized_score == 1.0 if submission.normalized_score.to_d == 1.0.to_d
Thread.new do Thread.new do
RequestForComment.where(exercise_id: submission.exercise_id, user_id: submission.user_id, RequestForComment.where(exercise_id: submission.exercise_id, user_id: submission.user_id,
user_type: submission.user_type).each do |rfc| user_type: submission.user_type).each do |rfc|

View File

@ -58,8 +58,8 @@ class ExerciseCollectionsController < ApplicationController
end end
sanitized_params[:exercise_ids] = sanitized_params[:exercise_ids].reject {|v| v.nil? or v == '' } sanitized_params[:exercise_ids] = sanitized_params[:exercise_ids].reject {|v| v.nil? or v == '' }
sanitized_params.tap do |p| sanitized_params.tap do |p|
p[:exercise_collection_items] = p[:exercise_ids].map.with_index do |_id, index| p[:exercise_collection_items] = p[:exercise_ids].map.with_index do |id, index|
ExerciseCollectionItem.find_or_create_by(exercise_id: _id, exercise_collection_id: @exercise_collection.id, position: index) ExerciseCollectionItem.find_or_create_by(exercise_id: id, exercise_collection_id: @exercise_collection.id, position: index)
end end
p.delete(:exercise_ids) p.delete(:exercise_ids)
end end

View File

@ -61,7 +61,7 @@ raise: false
def collect_paths(files) def collect_paths(files)
unique_paths = files.map(&:path).reject(&:blank?).uniq unique_paths = files.map(&:path).reject(&:blank?).uniq
subpaths = unique_paths.map do |path| subpaths = unique_paths.map do |path|
(path.split('/').length + 1).times.map do |n| Array.new((path.split('/').length + 1)) do |n|
path.split('/').shift(n).join('/') path.split('/').shift(n).join('/')
end end
end end
@ -271,7 +271,7 @@ user_id: current_user.id, user_type: current_user.class.name
def implement def implement
redirect_to(@exercise, alert: t('exercises.implement.unpublished')) if @exercise.unpublished? && current_user.role != 'admin' && current_user.role != 'teacher' # TODO: TESTESTEST redirect_to(@exercise, alert: t('exercises.implement.unpublished')) if @exercise.unpublished? && current_user.role != 'admin' && current_user.role != 'teacher' # TODO: TESTESTEST
redirect_to(@exercise, alert: t('exercises.implement.no_files')) unless @exercise.files.visible.exists? redirect_to(@exercise, alert: t('exercises.implement.no_files')) unless @exercise.files.visible.exists?
user_solved_exercise = @exercise.has_user_solved(current_user) user_solved_exercise = @exercise.solved_by?(current_user)
count_interventions_today = UserExerciseIntervention.where(user: current_user).where('created_at >= ?', count_interventions_today = UserExerciseIntervention.where(user: current_user).where('created_at >= ?',
Time.zone.now.beginning_of_day).count Time.zone.now.beginning_of_day).count
user_got_intervention_in_exercise = UserExerciseIntervention.where(user: current_user, user_got_intervention_in_exercise = UserExerciseIntervention.where(user: current_user,
@ -280,11 +280,10 @@ exercise: @exercise).size >= max_intervention_count_per_exercise
if @embed_options[:disable_interventions] if @embed_options[:disable_interventions]
@show_rfc_interventions = false @show_rfc_interventions = false
@show_break_interventions = false
else else
@show_rfc_interventions = (!user_solved_exercise && !user_got_enough_interventions).to_s @show_rfc_interventions = (!user_solved_exercise && !user_got_enough_interventions).to_s
@show_break_interventions = false
end end
@show_break_interventions = false
@hide_rfc_button = @embed_options[:disable_rfc] @hide_rfc_button = @embed_options[:disable_rfc]
@ -308,9 +307,7 @@ exercise: @exercise).size >= max_intervention_count_per_exercise
lti_json = lti_parameters.lti_parameters['launch_presentation_return_url'] lti_json = lti_parameters.lti_parameters['launch_presentation_return_url']
@course_token = @course_token =
if lti_json.nil? if lti_json.present? && (match = lti_json.match(%r{^.*courses/([a-z0-9\-]+)/sections}))
''
elsif match = lti_json.match(%r{^.*courses/([a-z0-9\-]+)/sections})
match.captures.first match.captures.first
else else
'' ''
@ -470,7 +467,7 @@ working_time_accumulated: working_time_accumulated})
authorize(@external_user, :statistics?) authorize(@external_user, :statistics?)
if policy(@exercise).detailed_statistics? if policy(@exercise).detailed_statistics?
@submissions = Submission.where(user: @external_user, @submissions = Submission.where(user: @external_user,
exercise_id: @exercise.id).in_study_group_of(current_user).order('created_at') exercise_id: @exercise.id).in_study_group_of(current_user).order('created_at')
interventions = UserExerciseIntervention.where('user_id = ? AND exercise_id = ?', @external_user.id, interventions = UserExerciseIntervention.where('user_id = ? AND exercise_id = ?', @external_user.id,
@exercise.id) @exercise.id)
@all_events = (@submissions + interventions).sort_by(&:created_at) @all_events = (@submissions + interventions).sort_by(&:created_at)
@ -480,11 +477,11 @@ exercise_id: @exercise.id).in_study_group_of(current_user).order('created_at')
end end
@working_times_until = [] @working_times_until = []
@all_events.each_with_index do |_, index| @all_events.each_with_index do |_, index|
@working_times_until.push((format_time_difference(@deltas[0..index].inject(:+)) if index.positive?)) @working_times_until.push((format_time_difference(@deltas[0..index].sum) if index.positive?))
end end
else else
final_submissions = Submission.where(user: @external_user, final_submissions = Submission.where(user: @external_user,
exercise_id: @exercise.id).in_study_group_of(current_user).final exercise_id: @exercise.id).in_study_group_of(current_user).final
@submissions = [] @submissions = []
%i[before_deadline within_grace_period after_late_deadline].each do |filter| %i[before_deadline within_grace_period after_late_deadline].each do |filter|
relevant_submission = final_submissions.send(filter).latest relevant_submission = final_submissions.send(filter).latest
@ -570,7 +567,7 @@ normalized_score: @submission.normalized_score})
def redirect_after_submit def redirect_after_submit
Rails.logger.debug("Redirecting user with score:s #{@submission.normalized_score}") Rails.logger.debug("Redirecting user with score:s #{@submission.normalized_score}")
if @submission.normalized_score == 1.0 if @submission.normalized_score.to_d == 1.0.to_d
# if user is external and has an own rfc, redirect to it and message him to clean up and accept the answer. (we need to check that the user is external, # if user is external and has an own rfc, redirect to it and message him to clean up and accept the answer. (we need to check that the user is external,
# otherwise an internal user could be shown a false rfc here, since current_user.id is polymorphic, but only makes sense for external users when used with rfcs.) # otherwise an internal user could be shown a false rfc here, since current_user.id is polymorphic, but only makes sense for external users when used with rfcs.)
# redirect 10 percent pseudorandomly to the feedback page # redirect 10 percent pseudorandomly to the feedback page
@ -603,7 +600,7 @@ normalized_score: @submission.normalized_score})
flash.keep(:notice) flash.keep(:notice)
# increase counter 'times_featured' in rfc # increase counter 'times_featured' in rfc
rfc.increment!(:times_featured) rfc.increment(:times_featured)
clear_lti_session_data(@submission.exercise_id, @submission.user_id) clear_lti_session_data(@submission.exercise_id, @submission.user_id)
respond_to do |format| respond_to do |format|

View File

@ -23,7 +23,7 @@ class FlowrController < ApplicationController
# for each error get all attributes, filter out uninteresting ones, and build a query # for each error get all attributes, filter out uninteresting ones, and build a query
insights = errors.map do |error| insights = errors.map do |error|
attributes = error.structured_error_attributes.select do |attribute| attributes = error.structured_error_attributes.select do |attribute|
is_interesting(attribute) and attribute.match interesting?(attribute) and attribute.match
end end
# once the programming language model becomes available, the language name can be added to the query to # once the programming language model becomes available, the language name can be added to the query to
# produce more relevant results # produce more relevant results
@ -35,8 +35,8 @@ class FlowrController < ApplicationController
render json: insights, status: :ok render json: insights, status: :ok
end end
def is_interesting(attribute) def interesting?(attribute)
attribute.error_template_attribute.key.index(/error message|error type/i) != nil !attribute.error_template_attribute.key.index(/error message|error type/i).nil?
end end
private :is_interesting private :interesting?
end end

View File

@ -6,7 +6,7 @@ class RequestForCommentsController < ApplicationController
before_action :require_user! before_action :require_user!
before_action :set_request_for_comment, only: %i[show mark_as_solved set_thank_you_note] before_action :set_request_for_comment, only: %i[show mark_as_solved set_thank_you_note]
before_action :set_study_group_grouping, before_action :set_study_group_grouping,
only: %i[index get_my_comment_requests get_rfcs_with_my_comments get_rfcs_for_exercise] only: %i[index my_comment_requests rfcs_with_my_comments rfcs_for_exercise]
def authorize! def authorize!
authorize(@request_for_comments || @request_for_comment) authorize(@request_for_comments || @request_for_comment)
@ -31,7 +31,7 @@ class RequestForCommentsController < ApplicationController
end end
# GET /my_request_for_comments # GET /my_request_for_comments
def get_my_comment_requests def my_comment_requests
@search = RequestForComment @search = RequestForComment
.with_last_activity .with_last_activity
.where(user: current_user) .where(user: current_user)
@ -44,7 +44,7 @@ class RequestForCommentsController < ApplicationController
end end
# GET /my_rfc_activity # GET /my_rfc_activity
def get_rfcs_with_my_comments def rfcs_with_my_comments
@search = RequestForComment @search = RequestForComment
.with_last_activity .with_last_activity
.joins(:comments) # we don't need to outer join here, because we know the user has commented on these .joins(:comments) # we don't need to outer join here, because we know the user has commented on these
@ -58,7 +58,7 @@ class RequestForCommentsController < ApplicationController
end end
# GET /exercises/:id/request_for_comments # GET /exercises/:id/request_for_comments
def get_rfcs_for_exercise def rfcs_for_exercise
exercise = Exercise.find(params[:exercise_id]) exercise = Exercise.find(params[:exercise_id])
@search = RequestForComment @search = RequestForComment
.with_last_activity .with_last_activity

View File

@ -9,7 +9,7 @@ class SubmissionsController < ApplicationController
include Tubesock::Hijack include Tubesock::Hijack
before_action :set_submission, before_action :set_submission,
only: %i[download download_file render_file run score extract_errors show statistics stop test] only: %i[download download_file render_file run score extract_errors show statistics test]
before_action :set_docker_client, only: %i[run test] before_action :set_docker_client, only: %i[run test]
before_action :set_files, only: %i[download download_file render_file show run] before_action :set_files, only: %i[download download_file render_file show run]
before_action :set_file, only: %i[download_file render_file run] before_action :set_file, only: %i[download_file render_file run]
@ -199,7 +199,7 @@ user_type: current_user.class.name, row: annotation[1][:row], column: annotation
socket.send data socket.send data
Rails.logger.debug("Sent the received client data to docker:#{data}") Rails.logger.debug("Sent the received client data to docker:#{data}")
end end
rescue JSON::ParserError => e rescue JSON::ParserError
socket.send data socket.send data
Rails.logger.debug("Rescued parsing error, sent the received client data to docker:#{data}") Rails.logger.debug("Rescued parsing error, sent the received client data to docker:#{data}")
Sentry.set_extras(data: data) Sentry.set_extras(data: data)
@ -266,7 +266,7 @@ user_type: current_user.class.name, row: annotation[1][:row], column: annotation
end end
end end
def parse_message(message, output_stream, socket, container = nil, recursive = true) def parse_message(message, output_stream, socket, container = nil, recursive: true)
parsed = '' parsed = ''
begin begin
parsed = JSON.parse(message) parsed = JSON.parse(message)
@ -279,11 +279,11 @@ user_type: current_user.class.name, row: annotation[1][:row], column: annotation
socket.send_data JSON.dump(parsed) socket.send_data JSON.dump(parsed)
Rails.logger.info("parse_message sent: #{JSON.dump(parsed)}") Rails.logger.info("parse_message sent: #{JSON.dump(parsed)}")
end end
rescue JSON::ParserError => e rescue JSON::ParserError
# Check wether the message contains multiple lines, if true try to parse each line # Check wether the message contains multiple lines, if true try to parse each line
if recursive && message.include?("\n") if recursive && message.include?("\n")
message.split("\n").each do |part| message.split("\n").each do |part|
parse_message(part, output_stream, socket, container, false) parse_message(part, output_stream, socket, container, recursive: false)
end end
elsif message.include?('<img') || message.start_with?('{"cmd') || message.include?('"turtlebatch"') elsif message.include?('<img') || message.start_with?('{"cmd') || message.include?('"turtlebatch"')
# Rails.logger.info('img foung') # Rails.logger.info('img foung')

View File

@ -39,7 +39,7 @@ class UserExerciseFeedbacksController < ApplicationController
authorize! authorize!
if validate_inputs(uef_params) if validate_inputs(uef_params)
path = path =
if rfc && submission && submission.normalized_score == 1.0 if rfc && submission && submission.normalized_score.to_d == 1.0.to_d
request_for_comment_path(rfc) request_for_comment_path(rfc)
else else
implement_exercise_path(@exercise) implement_exercise_path(@exercise)
@ -82,7 +82,7 @@ class UserExerciseFeedbacksController < ApplicationController
authorize! authorize!
if @exercise && validate_inputs(uef_params) if @exercise && validate_inputs(uef_params)
path = path =
if rfc && submission && submission.normalized_score == 1.0 if rfc && submission && submission.normalized_score.to_d == 1.0.to_d
request_for_comment_path(rfc) request_for_comment_path(rfc)
else else
implement_exercise_path(@exercise) implement_exercise_path(@exercise)

View File

@ -42,7 +42,9 @@ module ApplicationHelper
end end
def render_markdown(markdown) def render_markdown(markdown)
# rubocop:disable Rails/OutputSafety
Kramdown::Document.new(markdown).to_html.html_safe Kramdown::Document.new(markdown).to_html.html_safe
# rubocop:enable Rails/OutputSafety
end end
def row(options = {}, &block) def row(options = {}, &block)

View File

@ -0,0 +1,4 @@
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
end

View File

@ -1,10 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
module DefaultValues module DefaultValues
# rubocop:disable Naming/AccessorMethodName
def set_default_values_if_present(options = {}) def set_default_values_if_present(options = {})
options.each do |attribute, value| options.each do |attribute, value|
send(:"#{attribute}=", send(:"#{attribute}") || value) if has_attribute?(attribute) send(:"#{attribute}=", send(:"#{attribute}") || value) if has_attribute?(attribute)
end end
end end
private :set_default_values_if_present private :set_default_values_if_present
# rubocop:enable Naming/AccessorMethodName
end end

View File

@ -49,7 +49,7 @@ class Exercise < ApplicationRecord
MAX_GROUP_EXERCISE_FEEDBACKS = 20 MAX_GROUP_EXERCISE_FEEDBACKS = 20
def average_percentage def average_percentage
if average_score && (maximum_score != 0.0) && submissions.exists?(cause: 'submit') if average_score && (maximum_score.to_d != 0.0.to_d) && submissions.exists?(cause: 'submit')
(average_score / maximum_score * 100).round(2) (average_score / maximum_score * 100).round(2)
else else
0 0
@ -477,7 +477,6 @@ class Exercise < ApplicationRecord
return 'reference_implementation' return 'reference_implementation'
elsif (file_class == 'template') && (comment == 'main') elsif (file_class == 'template') && (comment == 'main')
return 'main_file' return 'main_file'
elsif (file_class == 'internal') && (comment == 'main')
end end
'regular_file' 'regular_file'
@ -536,7 +535,7 @@ class Exercise < ApplicationRecord
submissions.final.where(user_id: user.id, user_type: user.class.name).order(created_at: :desc).first submissions.final.where(user_id: user.id, user_type: user.class.name).order(created_at: :desc).first
end end
def has_user_solved(user) def solved_by?(user)
maximum_score(user).to_i == maximum_score.to_i maximum_score(user).to_i == maximum_score.to_i
end end
@ -579,7 +578,7 @@ cause: %w[submit assess remoteSubmit remoteAssess]}).distinct
private :valid_submission_deadlines? private :valid_submission_deadlines?
def needs_more_feedback?(submission) def needs_more_feedback?(submission)
if submission.normalized_score == 1.00 if submission.normalized_score.to_d == 1.0.to_d
user_exercise_feedbacks.final.size <= MAX_GROUP_EXERCISE_FEEDBACKS user_exercise_feedbacks.final.size <= MAX_GROUP_EXERCISE_FEEDBACKS
else else
user_exercise_feedbacks.intermediate.size <= MAX_GROUP_EXERCISE_FEEDBACKS user_exercise_feedbacks.intermediate.size <= MAX_GROUP_EXERCISE_FEEDBACKS

View File

@ -23,8 +23,8 @@ working_time: time_to_f(item.exercise.average_working_time)}
0 0
else else
values = collection_statistics.values.reject {|o| o[:working_time].nil? } values = collection_statistics.values.reject {|o| o[:working_time].nil? }
sum = values.reduce(0) {|sum, item| sum + item[:working_time] } total_sum = values.reduce(0) {|sum, item| sum + item[:working_time] }
sum / values.size total_sum / values.size
end end
end end

View File

@ -8,7 +8,7 @@ class Intervention < ApplicationRecord
name name
end end
def self.createDefaultInterventions def self.create_default_interventions
%w[BreakIntervention QuestionIntervention].each do |name| %w[BreakIntervention QuestionIntervention].each do |name|
Intervention.find_or_create_by(name: name) Intervention.find_or_create_by(name: name)
end end

View File

@ -42,7 +42,7 @@ class ProxyExercise < ApplicationRecord
end end
def get_matching_exercise(user) def get_matching_exercise(user)
assigned_user_proxy_exercise = user_proxy_exercise_exercises.where(user: user).first assigned_user_proxy_exercise = user_proxy_exercise_exercises.find_by(user: user)
if assigned_user_proxy_exercise if assigned_user_proxy_exercise
Rails.logger.debug("retrieved assigned exercise for user #{user.id}: Exercise #{assigned_user_proxy_exercise.exercise}") Rails.logger.debug("retrieved assigned exercise for user #{user.id}: Exercise #{assigned_user_proxy_exercise.exercise}")
assigned_user_proxy_exercise.exercise assigned_user_proxy_exercise.exercise
@ -105,9 +105,9 @@ exercise: matching_exercise, proxy_exercise: self, reason: @reason.to_json)
relative_knowledge_improvement[potex] = 0.0 relative_knowledge_improvement[potex] = 0.0
Rails.logger.debug("review potential exercise #{potex.id}") Rails.logger.debug("review potential exercise #{potex.id}")
tags.each do |tag| tags.each do |tag|
tag_ratio = potex.exercise_tags.where(tag: tag).first.factor.to_f / potex.exercise_tags.inject(0) do |sum, et| tag_ratio = potex.exercise_tags.find_by(tag: tag).factor.to_f / potex.exercise_tags.inject(0) do |sum, et|
sum += et.factor sum + et.factor
end end
max_topic_knowledge_ratio = potex.expected_difficulty * tag_ratio max_topic_knowledge_ratio = potex.expected_difficulty * tag_ratio
old_relative_loss_tag = topic_knowledge_user[tag] / topic_knowledge_max[tag] old_relative_loss_tag = topic_knowledge_user[tag] / topic_knowledge_max[tag]
new_relative_loss_tag = topic_knowledge_user[tag] / (topic_knowledge_max[tag] + max_topic_knowledge_ratio) new_relative_loss_tag = topic_knowledge_user[tag] / (topic_knowledge_max[tag] + max_topic_knowledge_ratio)
@ -123,9 +123,9 @@ exercise: matching_exercise, proxy_exercise: self, reason: @reason.to_json)
@reason[:current_users_knowledge_lack] = current_users_knowledge_lack @reason[:current_users_knowledge_lack] = current_users_knowledge_lack
@reason[:relative_knowledge_improvement] = relative_knowledge_improvement @reason[:relative_knowledge_improvement] = relative_knowledge_improvement
Rails.logger.debug('current users knowledge loss: ' + current_users_knowledge_lack.map do |k, v| Rails.logger.debug("current users knowledge loss: #{current_users_knowledge_lack.map do |k, v|
"#{k} => #{v}" "#{k} => #{v}"
end.to_s) end}")
Rails.logger.debug("relative improvements #{relative_knowledge_improvement.map {|k, v| "#{k.id}:#{v}" }}") Rails.logger.debug("relative improvements #{relative_knowledge_improvement.map {|k, v| "#{k.id}:#{v}" }}")
best_matching_exercise best_matching_exercise
end end
@ -166,22 +166,22 @@ exercise: matching_exercise, proxy_exercise: self, reason: @reason.to_json)
end end
private :scoring_matrix_quantiles private :scoring_matrix_quantiles
def score(user, ex) def score(user, exercise)
max_score = ex.maximum_score.to_f max_score = exercise.maximum_score.to_f
if max_score <= 0 if max_score <= 0
Rails.logger.debug("scoring user #{user.id} for exercise #{ex.id}: score: 0") Rails.logger.debug("scoring user #{user.id} for exercise #{exercise.id}: score: 0")
return 0.0 return 0.0
end end
points_ratio = ex.maximum_score(user) / max_score points_ratio = exercise.maximum_score(user) / max_score
if points_ratio == 0.0 if points_ratio.to_d == 0.0.to_d
Rails.logger.debug("scoring user #{user.id} for exercise #{ex.id}: points_ratio=#{points_ratio} score: 0") Rails.logger.debug("scoring user #{user.id} for exercise #{exercise.id}: points_ratio=#{points_ratio} score: 0")
return 0.0 return 0.0
elsif points_ratio > 1.0 elsif points_ratio > 1.0
points_ratio = 1.0 # The score of the exercise was adjusted and is now lower than it was points_ratio = 1.0 # The score of the exercise was adjusted and is now lower than it was
end end
points_ratio_index = ((scoring_matrix.size - 1) * points_ratio).to_i points_ratio_index = ((scoring_matrix.size - 1) * points_ratio).to_i
working_time_user = ex.accumulated_working_time_for_only(user) working_time_user = exercise.accumulated_working_time_for_only(user)
quantiles_working_time = ex.get_quantiles(scoring_matrix_quantiles) quantiles_working_time = exercise.get_quantiles(scoring_matrix_quantiles)
quantile_index = quantiles_working_time.size quantile_index = quantiles_working_time.size
quantiles_working_time.each_with_index do |quantile_time, i| quantiles_working_time.each_with_index do |quantile_time, i|
if working_time_user <= quantile_time if working_time_user <= quantile_time
@ -190,7 +190,7 @@ exercise: matching_exercise, proxy_exercise: self, reason: @reason.to_json)
end end
end end
Rails.logger.debug( Rails.logger.debug(
"scoring user #{user.id} exercise #{ex.id}: worktime #{working_time_user}, points: #{points_ratio}" \ "scoring user #{user.id} exercise #{exercise.id}: worktime #{working_time_user}, points: #{points_ratio}" \
"(index #{points_ratio_index}) quantiles #{quantiles_working_time} placed into quantile index #{quantile_index} " \ "(index #{points_ratio_index}) quantiles #{quantiles_working_time} placed into quantile index #{quantile_index} " \
"score: #{scoring_matrix[points_ratio_index][quantile_index]}" "score: #{scoring_matrix[points_ratio_index][quantile_index]}"
) )
@ -217,12 +217,12 @@ exercise: matching_exercise, proxy_exercise: self, reason: @reason.to_json)
ex.tags.each do |t| ex.tags.each do |t|
tags_counter[t] += 1 tags_counter[t] += 1
tag_diminishing_return_factor = tag_diminishing_return_function(tags_counter[t], all_used_tags_with_count[t]) tag_diminishing_return_factor = tag_diminishing_return_function(tags_counter[t], all_used_tags_with_count[t])
tag_ratio = ex.exercise_tags.where(tag: t).first.factor.to_f / ex.exercise_tags.inject(0) do |sum, et| tag_ratio = ex.exercise_tags.find_by(tag: t).factor.to_f / ex.exercise_tags.inject(0) do |sum, et|
sum + et.factor sum + et.factor
end end
Rails.logger.debug("tag: #{t}, factor: #{ex.exercise_tags.where(tag: t).first.factor}, sumall: #{ex.exercise_tags.inject(0) do |sum, et| Rails.logger.debug("tag: #{t}, factor: #{ex.exercise_tags.find_by(tag: t).factor}, sumall: #{ex.exercise_tags.inject(0) do |sum, et|
sum + et.factor sum + et.factor
end }") end }")
Rails.logger.debug("tag #{t}, count #{tags_counter[t]}, max: #{all_used_tags_with_count[t]}, factor: #{tag_diminishing_return_factor}") Rails.logger.debug("tag #{t}, count #{tags_counter[t]}, max: #{all_used_tags_with_count[t]}, factor: #{tag_diminishing_return_factor}")
Rails.logger.debug("tag_ratio #{tag_ratio}") Rails.logger.debug("tag_ratio #{tag_ratio}")
topic_knowledge_ratio = ex.expected_difficulty * tag_ratio topic_knowledge_ratio = ex.expected_difficulty * tag_ratio

View File

@ -127,7 +127,7 @@ class Submission < ApplicationRecord
end end
def own_unsolved_rfc def own_unsolved_rfc
RequestForComment.unsolved.where(exercise_id: exercise, user_id: user_id).first RequestForComment.unsolved.find_by(exercise_id: exercise, user_id: user_id)
end end
def unsolved_rfc def unsolved_rfc

View File

@ -6,11 +6,7 @@ class Tag < ApplicationRecord
validates :name, uniqueness: true validates :name, uniqueness: true
def destroy before_destroy :can_be_destroyed?, prepend: true
if can_be_destroyed?
super
end
end
def can_be_destroyed? def can_be_destroyed?
exercises.none? exercises.none?

View File

@ -5,7 +5,7 @@ class ExercisePolicy < AdminOrAuthorPolicy
admin? admin?
end end
%i[show? feedback? statistics? get_rfcs_for_exercise?].each do |action| %i[show? feedback? statistics? rfcs_for_exercise?].each do |action|
define_method(action) { admin? || teacher_in_study_group? || teacher? && @record.public? || author? } define_method(action) { admin? || teacher_in_study_group? || teacher? && @record.public? || author? }
end end

View File

@ -37,11 +37,11 @@ class RequestForCommentPolicy < ApplicationPolicy
everyone everyone
end end
def get_my_comment_requests? def my_comment_requests?
everyone everyone
end end
def get_rfcs_with_my_comments? def rfcs_with_my_comments?
everyone everyone
end end
end end

View File

@ -3,6 +3,7 @@
module ExerciseService module ExerciseService
class CheckExternal < ServiceBase class CheckExternal < ServiceBase
def initialize(uuid:, codeharbor_link:) def initialize(uuid:, codeharbor_link:)
super()
@uuid = uuid @uuid = uuid
@codeharbor_link = codeharbor_link @codeharbor_link = codeharbor_link
end end

View File

@ -3,6 +3,7 @@
module ExerciseService module ExerciseService
class PushExternal < ServiceBase class PushExternal < ServiceBase
def initialize(zip:, codeharbor_link:) def initialize(zip:, codeharbor_link:)
super()
@zip = zip @zip = zip
@codeharbor_link = codeharbor_link @codeharbor_link = codeharbor_link
end end

View File

@ -7,6 +7,7 @@ module ProformaService
DEFAULT_LANGUAGE = 'de' DEFAULT_LANGUAGE = 'de'
def initialize(exercise: nil) def initialize(exercise: nil)
super()
@exercise = exercise @exercise = exercise
end end

View File

@ -3,6 +3,7 @@
module ProformaService module ProformaService
class ConvertTaskToExercise < ServiceBase class ConvertTaskToExercise < ServiceBase
def initialize(task:, user:, exercise: nil) def initialize(task:, user:, exercise: nil)
super()
@task = task @task = task
@user = user @user = user
@exercise = exercise || Exercise.new(unpublished: true) @exercise = exercise || Exercise.new(unpublished: true)

View File

@ -3,6 +3,7 @@
module ProformaService module ProformaService
class ExportTask < ServiceBase class ExportTask < ServiceBase
def initialize(exercise: nil) def initialize(exercise: nil)
super()
@exercise = exercise @exercise = exercise
end end

View File

@ -3,6 +3,7 @@
module ProformaService module ProformaService
class Import < ServiceBase class Import < ServiceBase
def initialize(zip:, user:) def initialize(zip:, user:)
super()
@zip = zip @zip = zip
@user = user @user = user
end end

View File

@ -6,8 +6,8 @@
span.caret span.caret
ul.dropdown-menu.p-0.mt-1 role='menu' ul.dropdown-menu.p-0.mt-1 role='menu'
li = link_to(t('request_for_comments.index.all'), request_for_comments_path, class: 'dropdown-item') if policy(:request_for_comment).index? li = link_to(t('request_for_comments.index.all'), request_for_comments_path, class: 'dropdown-item') if policy(:request_for_comment).index?
li = link_to(t('request_for_comments.index.get_my_rfc_activity'), my_rfc_activity_path, class: 'dropdown-item') li = link_to(t('request_for_comments.index.my_rfc_activity'), my_rfc_activity_path, class: 'dropdown-item')
li = link_to(t('request_for_comments.index.get_my_comment_requests'), my_request_for_comments_path, class: 'dropdown-item') li = link_to(t('request_for_comments.index.my_comment_requests'), my_request_for_comments_path, class: 'dropdown-item')
- if current_user.try(:admin?) or current_user.try(:teacher?) - if current_user.try(:admin?) or current_user.try(:teacher?)
li = link_to(t('consumers.show.link'), current_user.consumer, class: 'dropdown-item') if current_user.consumer and policy(current_user.consumer).show? li = link_to(t('consumers.show.link'), current_user.consumer, class: 'dropdown-item') if current_user.consumer and policy(current_user.consumer).show?
li = link_to(t('internal_users.show.link'), current_user, class: 'dropdown-item') if policy(current_user).show? li = link_to(t('internal_users.show.link'), current_user, class: 'dropdown-item') if policy(current_user).show?

View File

@ -45,7 +45,7 @@ h1 = Exercise.model_name.human(count: 2)
ul.dropdown-menu.float-right role="menu" ul.dropdown-menu.float-right role="menu"
li = link_to(t('shared.show'), exercise, 'data-turbolinks' => "false", class: 'dropdown-item') if policy(exercise).show? li = link_to(t('shared.show'), exercise, 'data-turbolinks' => "false", class: 'dropdown-item') if policy(exercise).show?
li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).feedback? li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).feedback?
li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).get_rfcs_for_exercise? li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).rfcs_for_exercise?
li = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item') if policy(exercise).destroy? li = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item') if policy(exercise).destroy?
li = link_to(t('.clone'), clone_exercise_path(exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item') if policy(exercise).clone? li = link_to(t('.clone'), clone_exercise_path(exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item') if policy(exercise).clone?
li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start', data: {'exercise-id' => exercise.id}) if policy(exercise).export_external_confirm? li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start', data: {'exercise-id' => exercise.id}) if policy(exercise).export_external_confirm?

View File

@ -14,7 +14,7 @@ h1
li = link_to(t('exercises.index.implement'), implement_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).implement? li = link_to(t('exercises.index.implement'), implement_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).implement?
li = link_to(t('shared.statistics'), statistics_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).statistics? li = link_to(t('shared.statistics'), statistics_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).statistics?
li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).feedback? li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).feedback?
li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).get_rfcs_for_exercise? li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).rfcs_for_exercise?
li = link_to(t('shared.destroy'), @exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@exercise).destroy? li = link_to(t('shared.destroy'), @exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@exercise).destroy?
li = link_to(t('exercises.index.clone'), clone_exercise_path(@exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item text-dark') if policy(@exercise).clone? li = link_to(t('exercises.index.clone'), clone_exercise_path(@exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item text-dark') if policy(@exercise).clone?
li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start text-dark', data: {'exercise-id' => @exercise.id}) if policy(@exercise).export_external_confirm? li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start text-dark', data: {'exercise-id' => @exercise.id}) if policy(@exercise).export_external_confirm?

View File

@ -3,5 +3,5 @@
link_to_comment: link_to(@rfc_link, @rfc_link), link_to_comment: link_to(@rfc_link, @rfc_link),
commenting_user_displayname: @commenting_user_displayname, commenting_user_displayname: @commenting_user_displayname,
comment_text: @comment_text, comment_text: @comment_text,
link_my_comments: link_to(t('request_for_comments.index.get_my_comment_requests'), my_request_for_comments_url), link_my_comments: link_to(t('request_for_comments.index.my_comment_requests'), my_request_for_comments_url),
link_all_comments: link_to(t('request_for_comments.index.all'), request_for_comments_url) ) link_all_comments: link_to(t('request_for_comments.index.all'), request_for_comments_url) )

View File

@ -3,5 +3,5 @@
unsubscribe_link: link_to(@unsubscribe_link, @unsubscribe_link), unsubscribe_link: link_to(@unsubscribe_link, @unsubscribe_link),
author_displayname: @author_displayname, author_displayname: @author_displayname,
comment_text: @comment_text, comment_text: @comment_text,
link_my_comments: link_to(t('request_for_comments.index.get_my_comment_requests'), my_request_for_comments_url), link_my_comments: link_to(t('request_for_comments.index.my_comment_requests'), my_request_for_comments_url),
link_all_comments: link_to(t('request_for_comments.index.all'), request_for_comments_url) ) link_all_comments: link_to(t('request_for_comments.index.all'), request_for_comments_url) )

View File

@ -20,7 +20,7 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled. # Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching. # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist? if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true config.action_controller.enable_fragment_cache_logging = true

View File

@ -3,15 +3,7 @@
unless Array.respond_to?(:average) unless Array.respond_to?(:average)
class Array class Array
def average def average
inject(:+) / length if present? sum / length if present?
end
end
end
unless Array.respond_to?(:to_h)
class Array
def to_h
to_h
end end
end end
end end

View File

@ -723,11 +723,11 @@ de:
Mit "Kommentar abschicken" wird der Kommentar dann gesichert und taucht als Sprechblase neben der Zeile auf. Mit "Kommentar abschicken" wird der Kommentar dann gesichert und taucht als Sprechblase neben der Zeile auf.
howto_title: 'Anleitung' howto_title: 'Anleitung'
index: index:
get_my_comment_requests: Meine Kommentaranfragen my_comment_requests: Meine Kommentaranfragen
all: "Alle Kommentaranfragen" all: "Alle Kommentaranfragen"
get_rfcs_with_my_comments: Kommentaranfragen die ich kommentiert habe rfcs_with_my_comments: Kommentaranfragen die ich kommentiert habe
get_my_rfc_activity: "Meine Kommentaraktivität" my_rfc_activity: "Meine Kommentaraktivität"
get_rfcs_for_exercise: "Übungsspezifische Kommentare" rfcs_for_exercise: "Übungsspezifische Kommentare"
study_groups: study_groups:
placeholder: "Lerngruppe" placeholder: "Lerngruppe"
current: "Aktuelle Lerngruppe" current: "Aktuelle Lerngruppe"

View File

@ -724,10 +724,10 @@ en:
howto_title: 'How to comment' howto_title: 'How to comment'
index: index:
all: All Requests for Comments all: All Requests for Comments
get_my_comment_requests: My Requests for Comments my_comment_requests: My Requests for Comments
get_rfcs_with_my_comments: Requests for Comments I have commented on rfcs_with_my_comments: Requests for Comments I have commented on
get_my_rfc_activity: "My Comment Activity" my_rfc_activity: "My Comment Activity"
get_rfcs_for_exercise: "Exercise Comments" rfcs_for_exercise: "Exercise Comments"
study_groups: study_groups:
placeholder: "Study group" placeholder: "Study group"
current: "Current Study Group" current: "Current Study Group"

View File

@ -23,9 +23,9 @@ Rails.application.routes.draw do
end end
end end
resources :comments, defaults: {format: :json} resources :comments, defaults: {format: :json}
get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#get_my_comment_requests' get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#my_comment_requests'
get '/my_rfc_activity', as: 'my_rfc_activity', to: 'request_for_comments#get_rfcs_with_my_comments' get '/my_rfc_activity', as: 'my_rfc_activity', to: 'request_for_comments#rfcs_with_my_comments'
get '/exercises/:exercise_id/request_for_comments', as: 'rfcs_for_exercise', to: 'request_for_comments#get_rfcs_for_exercise' get '/exercises/:exercise_id/request_for_comments', as: 'rfcs_for_exercise', to: 'request_for_comments#rfcs_for_exercise'
delete '/comment_by_id', to: 'comments#destroy_by_id' delete '/comment_by_id', to: 'comments#destroy_by_id'
put '/comments', to: 'comments#update', defaults: {format: :json} put '/comments', to: 'comments#update', defaults: {format: :json}

View File

@ -6,7 +6,7 @@ class AddPoolSizeToExecutionEnvironments < ActiveRecord::Migration[4.2]
reversible do |direction| reversible do |direction|
direction.up do direction.up do
ExecutionEnvironment.update_all(pool_size: 0) ExecutionEnvironment.update(pool_size: 0)
end end
end end
end end

View File

@ -6,7 +6,7 @@ class AddMemoryLimitToExecutionEnvironments < ActiveRecord::Migration[4.2]
reversible do |direction| reversible do |direction|
direction.up do direction.up do
ExecutionEnvironment.update_all(memory_limit: DockerClient::DEFAULT_MEMORY_LIMIT) ExecutionEnvironment.update(memory_limit: DockerClient::DEFAULT_MEMORY_LIMIT)
end end
end end
end end

View File

@ -6,7 +6,7 @@ class AddNetworkEnabledToExecutionEnvironments < ActiveRecord::Migration[4.2]
reversible do |direction| reversible do |direction|
direction.up do direction.up do
ExecutionEnvironment.update_all(network_enabled: true) ExecutionEnvironment.update(network_enabled: true)
end end
end end
end end

View File

@ -17,6 +17,6 @@ class CreateInterventions < ActiveRecord::Migration[4.2]
t.timestamps t.timestamps
end end
Intervention.createDefaultInterventions Intervention.create_default_interventions
end end
end end

View File

@ -3,6 +3,6 @@
class SetDefaultForRequestForCommentSolved < ActiveRecord::Migration[4.2] class SetDefaultForRequestForCommentSolved < ActiveRecord::Migration[4.2]
def change def change
change_column_default :request_for_comments, :solved, false change_column_default :request_for_comments, :solved, false
RequestForComment.where(solved: nil).update_all(solved: false) RequestForComment.where(solved: nil).update(solved: false)
end end
end end

View File

@ -6,7 +6,7 @@ class AddCauseToTestruns < ActiveRecord::Migration[4.2]
Testrun.reset_column_information Testrun.reset_column_information
Testrun.all.each do |testrun| Testrun.all.each do |testrun|
if testrun.submission.nil? if testrun.submission.nil?
say_with_time "#{testrun.id} has no submission" do end say_with_time "#{testrun.id} has no submission"
else else
testrun.cause = testrun.submission.cause testrun.cause = testrun.submission.cause
testrun.save testrun.save

View File

@ -4,7 +4,7 @@ class AddReachedFullScoreToRequestForComment < ActiveRecord::Migration[4.2]
def up def up
add_column :request_for_comments, :full_score_reached, :boolean, default: false add_column :request_for_comments, :full_score_reached, :boolean, default: false
RequestForComment.find_each do |rfc| RequestForComment.find_each do |rfc|
if rfc.submission.present? && rfc.submission.exercise.has_user_solved(rfc.user) if rfc.submission.present? && rfc.submission.exercise.solved_by?(rfc.user)
rfc.full_score_reached = true rfc.full_score_reached = true
rfc.save rfc.save
end end

View File

@ -6,6 +6,6 @@ class AddUserToProxyExercise < ActiveRecord::Migration[5.2]
add_column :proxy_exercises, :public, :boolean, null: false, default: false add_column :proxy_exercises, :public, :boolean, null: false, default: false
internal_user = InternalUser.find_by(id: 46) || InternalUser.first internal_user = InternalUser.find_by(id: 46) || InternalUser.first
ProxyExercise.update_all(user_id: internal_user&.id || 1, user_type: internal_user.class.name) ProxyExercise.update(user_id: internal_user&.id || 1, user_type: internal_user.class.name)
end end
end end

View File

@ -5,6 +5,6 @@ class AddUserTypeToRemoteEvaluationMappings < ActiveRecord::Migration[5.2]
add_column :remote_evaluation_mappings, :user_type, :string add_column :remote_evaluation_mappings, :user_type, :string
# Update all existing records and set user_type to `ExternalUser` (safe way to prevent any function loss). # Update all existing records and set user_type to `ExternalUser` (safe way to prevent any function loss).
# We are not using a default value here on intend to be in line with the other `user_type` columns # We are not using a default value here on intend to be in line with the other `user_type` columns
RemoteEvaluationMapping.update_all(user_type: 'ExternalUser') RemoteEvaluationMapping.update(user_type: 'ExternalUser')
end end
end end

View File

@ -21,7 +21,7 @@ class DropErrors < ActiveRecord::Migration[5.2]
end end
def change def change
puts 'Migrating CodeOcean::Errors to StructuredErrors using RegEx. This might take a (long) while but will return.' Rails.logger.info 'Migrating CodeOcean::Errors to StructuredErrors using RegEx. This might take a (long) while but will return.'
submissions_controller = SubmissionsController.new submissions_controller = SubmissionsController.new
# Iterate only over those Errors containing a message and submission_id # Iterate only over those Errors containing a message and submission_id

View File

@ -6,8 +6,8 @@ class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
if table_exists?(:active_storage_blobs) && !column_exists?(:active_storage_blobs, :service_name) if table_exists?(:active_storage_blobs) && !column_exists?(:active_storage_blobs, :service_name)
add_column :active_storage_blobs, :service_name, :string add_column :active_storage_blobs, :service_name, :string
if configured_service = ActiveStorage::Blob.service.name if (configured_service = ActiveStorage::Blob.service.name)
ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) ActiveStorage::Blob.unscoped.update(service_name: configured_service)
end end
change_column :active_storage_blobs, :service_name, :string, null: false change_column :active_storage_blobs, :service_name, :string, null: false

View File

@ -23,7 +23,7 @@ Rails.application.eager_load!
(ApplicationRecord.descendants - [ActiveRecord::SchemaMigration, User]).each(&:delete_all) (ApplicationRecord.descendants - [ActiveRecord::SchemaMigration, User]).each(&:delete_all)
# delete file uploads # delete file uploads
FileUtils.rm_rf(Rails.root.join('public', 'uploads')) FileUtils.rm_rf(Rails.root.join('public/uploads'))
# load environment-dependent seeds # load environment-dependent seeds
load(Rails.root.join('db', 'seeds', "#{Rails.env}.rb")) load(Rails.root.join("db/seeds/#{Rails.env}.rb"))

View File

@ -1,3 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
def fibonacci(n); end def fibonacci(number); end

View File

@ -4,11 +4,11 @@ require './exercise'
require './reference' require './reference'
describe '#fibonacci' do describe '#fibonacci' do
SAMPLE_COUNT = 32 let(:sample_count) { 32 }
let(:reference) { Class.new.extend(Reference) } let(:reference) { Class.new.extend(Reference) }
SAMPLE_COUNT.times do |i| sample_count.times do |i|
instance_eval do instance_eval do
it "obtains the correct result for input #{i}" do it "obtains the correct result for input #{i}" do
expect(fibonacci(i)).to eq(reference.fibonacci(i)) expect(fibonacci(i)).to eq(reference.fibonacci(i))

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Reference module Reference
def fibonacci(n) def fibonacci(number)
n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2) number < 2 ? number : fibonacci(number - 1) + fibonacci(number - 2)
end end
end end

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
# Write your code here

View File

@ -29,7 +29,7 @@ FileType.create_factories
# change all resources' author # change all resources' author
[ExecutionEnvironment, Exercise, FileType].each do |model| [ExecutionEnvironment, Exercise, FileType].each do |model|
model.update_all(user_id: InternalUser.first.id) model.update(user_id: InternalUser.first.id)
end end
# delete temporary users # delete temporary users

View File

@ -10,5 +10,7 @@ database = SQLite3::Database.new('/database.db')
missing_tuples = database.execute(REFERENCE_QUERY) - database.execute(STUDENT_QUERY) missing_tuples = database.execute(REFERENCE_QUERY) - database.execute(STUDENT_QUERY)
unexpected_tuples = database.execute(STUDENT_QUERY) - database.execute(REFERENCE_QUERY) unexpected_tuples = database.execute(STUDENT_QUERY) - database.execute(REFERENCE_QUERY)
# rubocop:disable Rails/Output
puts("Missing tuples: #{missing_tuples}") puts("Missing tuples: #{missing_tuples}")
puts("Unexpected tuples: #{unexpected_tuples}") puts("Unexpected tuples: #{unexpected_tuples}")
# rubocop:enable Rails/Output

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
# Write your code here

View File

@ -3,10 +3,12 @@
module ActiveModel module ActiveModel
module Validations module Validations
class BooleanPresenceValidator < EachValidator class BooleanPresenceValidator < EachValidator
BOOLEAN_VALUES = [false, true].freeze
def validate(record) def validate(record)
[attributes].flatten.each do |attribute| [attributes].flatten.each do |attribute|
value = record.send(:read_attribute_for_validation, attribute) value = record.send(:read_attribute_for_validation, attribute)
record.errors.add(attribute, nil, options) unless [false, true].include?(value) record.errors.add(attribute, nil, options) unless BOOLEAN_VALUES.include?(value)
end end
end end
end end

View File

@ -12,7 +12,7 @@ class Assessor
def calculate_score(test_outcome) def calculate_score(test_outcome)
score = 0.0 score = 0.0
if test_outcome[:passed].to_f != 0.0 && test_outcome[:count].to_f != 0.0 if test_outcome[:passed].to_d != 0.0.to_d && test_outcome[:count].to_d != 0.0.to_d
score = (test_outcome[:passed].to_f / test_outcome[:count]) score = (test_outcome[:passed].to_f / test_outcome[:count])
# prevent negative scores # prevent negative scores
score = [0.0, score].max score = [0.0, score].max

View File

@ -9,7 +9,7 @@ module CodeOcean
def read(options = {}) def read(options = {})
path = Rails.root.join('config', "#{@filename}.yml#{options[:erb] ? '.erb' : ''}") path = Rails.root.join('config', "#{@filename}.yml#{options[:erb] ? '.erb' : ''}")
if ::File.exist?(path) if ::File.exist?(path)
content = options[:erb] ? YAML.safe_load(ERB.new(::File.new(path, 'r').read).result) : YAML.load_file(path) content = options[:erb] ? YAML.safe_load(ERB.new(::File.new(path, 'r').read).result, aliases: true, permitted_classes: [Range]) : YAML.load_file(path)
content[Rails.env].with_indifferent_access content[Rails.env].with_indifferent_access
else else
raise Error.new("Configuration file not found: #{path}") raise Error.new("Configuration file not found: #{path}")

View File

@ -78,7 +78,7 @@ class DockerClient
} }
end end
def create_socket(container, stderr = false) def create_socket(container, stderr: false)
# TODO: factor out query params # TODO: factor out query params
# todo separate stderr # todo separate stderr
query_params = "logs=0&stream=1&#{stderr ? 'stderr=1' : 'stdout=1&stdin=1'}" query_params = "logs=0&stream=1&#{stderr ? 'stderr=1' : 'stdout=1&stdin=1'}"
@ -111,7 +111,7 @@ class DockerClient
end end
def self.create_container(execution_environment) def self.create_container(execution_environment)
tries ||= 0 # tries ||= 0
# container.start sometimes creates the passed local_workspace_path on disk (depending on the setup). # container.start sometimes creates the passed local_workspace_path on disk (depending on the setup).
# this is however not guaranteed and caused issues on the server already. Therefore create the necessary folders manually! # this is however not guaranteed and caused issues on the server already. Therefore create the necessary folders manually!
local_workspace_path = generate_local_workspace_path local_workspace_path = generate_local_workspace_path
@ -133,14 +133,14 @@ class DockerClient
Thread.new do Thread.new do
timeout = SELF_DESTROY_GRACE_PERIOD.to_i timeout = SELF_DESTROY_GRACE_PERIOD.to_i
sleep(timeout) sleep(timeout)
container.docker_client.kill_container(container, false) container.docker_client.kill_container(container)
Rails.logger.info("Force killing container in status #{container.status} after #{Time.zone.now - container.start_time} seconds.") Rails.logger.info("Force killing container in status #{container.status} after #{Time.zone.now - container.start_time} seconds.")
ensure ensure
# guarantee that the thread is releasing the DB connection after it is done # guarantee that the thread is releasing the DB connection after it is done
ActiveRecord::Base.connection_pool.release_connection ActiveRecord::Base.connection_pool.release_connection
end end
else else
container.docker_client.kill_container(container, false) container.docker_client.kill_container(container)
Rails.logger.info("Killing container in status #{container.status} after #{Time.zone.now - container.start_time} seconds.") Rails.logger.info("Killing container in status #{container.status} after #{Time.zone.now - container.start_time} seconds.")
end end
ensure ensure
@ -280,7 +280,7 @@ class DockerClient
{status: :container_depleted, waiting_for_container_time: waiting_for_container_time, {status: :container_depleted, waiting_for_container_time: waiting_for_container_time,
container_execution_time: nil} container_execution_time: nil}
end end
rescue Excon::Errors::SocketError => e rescue Excon::Errors::SocketError
# socket errors seems to be normal when using exec # socket errors seems to be normal when using exec
# so lets ignore them for now # so lets ignore them for now
# (tries += 1) <= RETRY_COUNT ? retry : raise(error) # (tries += 1) <= RETRY_COUNT ? retry : raise(error)
@ -310,10 +310,8 @@ container_execution_time: nil}
end end
def kill_after_timeout(container) def kill_after_timeout(container)
" # We need to start a second thread to kill the websocket connection,
We need to start a second thread to kill the websocket connection, # as it is impossible to determine whether further input is requested.
as it is impossible to determine whether further input is requested.
"
container.status = :executing container.status = :executing
@thread = Thread.new do @thread = Thread.new do
timeout = @execution_environment.permitted_execution_time.to_i # seconds timeout = @execution_environment.permitted_execution_time.to_i # seconds
@ -365,16 +363,14 @@ container_execution_time: nil}
end end
end end
def kill_container(container, _create_new = true) def kill_container(container)
exit_thread_if_alive exit_thread_if_alive
Rails.logger.info("killing container #{container}") Rails.logger.info("killing container #{container}")
self.class.destroy_container(container) self.class.destroy_container(container)
end end
def execute_run_command(submission, filename, &block) def execute_run_command(submission, filename, &block)
" # Run commands by attaching a websocket to Docker.
Run commands by attaching a websocket to Docker.
"
filepath = submission.collect_files.find {|f| f.name_with_extension == filename }.filepath filepath = submission.collect_files.find {|f| f.name_with_extension == filename }.filepath
command = submission.execution_environment.run_command % command_substitutions(filepath) command = submission.execution_environment.run_command % command_substitutions(filepath)
create_workspace_files = proc { create_workspace_files(container, submission) } create_workspace_files = proc { create_workspace_files(container, submission) }
@ -383,9 +379,7 @@ container_execution_time: nil}
end end
def execute_test_command(submission, filename, &block) def execute_test_command(submission, filename, &block)
" # Stick to existing Docker API with exec command.
Stick to existing Docker API with exec command.
"
file = submission.collect_files.find {|f| f.name_with_extension == filename } file = submission.collect_files.find {|f| f.name_with_extension == filename }
filepath = file.filepath filepath = file.filepath
command = submission.execution_environment.test_command % command_substitutions(filepath) command = submission.execution_environment.test_command % command_substitutions(filepath)
@ -495,26 +489,23 @@ container_execution_time: nil}
if output.nil? if output.nil?
kill_container(container) kill_container(container)
else else
result = {status: (output[2]).zero? ? :ok : :failed, stdout: output[0].join.force_encoding('utf-8'), result = {status: (output[2])&.zero? ? :ok : :failed, stdout: output[0].join.force_encoding('utf-8'), stderr: output[1].join.force_encoding('utf-8')}
stderr: output[1].join.force_encoding('utf-8')}
end end
# if we use pooling and recylce the containers, put it back. otherwise, destroy it. # if we use pooling and recylce the containers, put it back. otherwise, destroy it.
if DockerContainerPool.config[:active] && RECYCLE_CONTAINERS if DockerContainerPool.config[:active] && RECYCLE_CONTAINERS
self.class.return_container(container, self.class.return_container(container, @execution_environment)
@execution_environment)
else else
self.class.destroy_container(container) self.class.destroy_container(container)
end end
result result
rescue Timeout::Error rescue Timeout::Error
Rails.logger.info("got timeout error for container #{container}") Rails.logger.info("got timeout error for container #{container}")
stdout = container.exec(['cat', '/tmp/stdout.log'])[0].join.force_encoding('utf-8') stdout = container.exec(%w[cat /tmp/stdout.log])[0].join.force_encoding('utf-8')
stderr = container.exec(['cat', '/tmp/stderr.log'])[0].join.force_encoding('utf-8') stderr = container.exec(%w[cat /tmp/stderr.log])[0].join.force_encoding('utf-8')
kill_container(container) kill_container(container)
{status: :timeout, stdout: stdout, stderr: stderr} {status: :timeout, stdout: stdout, stderr: stderr}
end end
private :send_command private :send_command
class Error < RuntimeError; end class Error < RuntimeError; end

View File

@ -52,9 +52,7 @@ class FileTree < Tree::TreeNode
private :map_to_js_tree private :map_to_js_tree
def node_icon(node) def node_icon(node)
if node.is_root? if node.is_leaf? && !node.is_root?
folder_icon
elsif node.is_leaf?
file_icon(node.content) file_icon(node.content)
else else
folder_icon folder_icon

View File

@ -9,13 +9,9 @@ class Python20CourseWeek
2 2
when /Python20 Aufgabe 3/ when /Python20 Aufgabe 3/
3 3
when /Python20 Aufgabe 4/ when /Python20 Aufgabe 4/, /Python20 Snake/
4 4
when /Python20 Snake/ # else: Not part of the Python20 course
4
else
# Not part of the Python20 course
nil
end end
end end

View File

@ -81,7 +81,7 @@ namespace :detect_exercise_anomalies do
def find_anomalies(collection) def find_anomalies(collection)
working_times = collect_working_times(collection).compact working_times = collect_working_times(collection).compact
if working_times.values.size.positive? if working_times.values.size.positive?
average = working_times.values.reduce(:+) / working_times.values.size average = working_times.values.sum / working_times.values.size
return working_times.select do |_, working_time| return working_times.select do |_, working_time|
working_time > average * MAX_TIME_FACTOR or working_time < average * MIN_TIME_FACTOR working_time > average * MAX_TIME_FACTOR or working_time < average * MIN_TIME_FACTOR
end end
@ -120,7 +120,8 @@ namespace :detect_exercise_anomalies do
users_to_notify = [] users_to_notify = []
users = {} users = {}
%i[performers_by_time performers_by_score].each do |method| methods = %i[performers_by_time performers_by_score]
methods.each do |method|
# merge users found by multiple methods returning a hash {best: [], worst: []} # merge users found by multiple methods returning a hash {best: [], worst: []}
users = users.merge(send(method, exercise, NUMBER_OF_USERS_PER_CLASS)) {|_key, this, other| this + other } users = users.merge(send(method, exercise, NUMBER_OF_USERS_PER_CLASS)) {|_key, this, other| this + other }
end end

View File

@ -33,11 +33,11 @@ describe Lti do
let(:last_name) { 'Doe' } let(:last_name) { 'Doe' }
let(:full_name) { 'John Doe' } let(:full_name) { 'John Doe' }
let(:provider) { double } let(:provider) { double }
let(:provider_full) { double(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 context 'when a full name is provided' do
it 'returns the full name' do it 'returns the full name' do
expect(provider_full).to receive(:lis_person_name_full).twice.and_return(full_name) allow(provider_full).to receive(:lis_person_name_full).and_return(full_name)
expect(controller.send(:external_user_name, provider_full)).to eq(full_name) expect(controller.send(:external_user_name, provider_full)).to eq(full_name)
end end
end end
@ -45,7 +45,7 @@ describe Lti do
context 'when only partial information is provided' do context 'when only partial information is provided' do
it 'returns the first available name' do it 'returns the first available name' do
expect(provider).to receive(:lis_person_name_full) expect(provider).to receive(:lis_person_name_full)
expect(provider).to receive(:lis_person_name_given).and_return(first_name) allow(provider).to receive(:lis_person_name_given).and_return(first_name)
expect(provider).not_to receive(:lis_person_name_family) expect(provider).not_to receive(:lis_person_name_family)
expect(controller.send(:external_user_name, provider)).to eq(first_name) expect(controller.send(:external_user_name, provider)).to eq(first_name)
end end
@ -64,7 +64,7 @@ describe Lti do
context 'with a return URL' do context 'with a return URL' do
let(:consumer_return_url) { 'http://example.org' } let(:consumer_return_url) { 'http://example.org' }
before { expect(controller).to receive(:params).and_return(launch_presentation_return_url: consumer_return_url) } before { allow(controller).to receive(:params).and_return(launch_presentation_return_url: consumer_return_url) }
it 'redirects to the tool consumer' do it 'redirects to the tool consumer' do
expect(controller).to receive(:redirect_to).with(consumer_return_url) expect(controller).to receive(:redirect_to).with(consumer_return_url)
@ -80,8 +80,8 @@ describe Lti do
context 'without a return URL' do context 'without a return URL' do
before do before do
expect(controller).to receive(:params).and_return({}) allow(controller).to receive(:params).and_return({})
expect(controller).to receive(:redirect_to).with(:root) allow(controller).to receive(:redirect_to).with(:root)
end end
it 'redirects to the root URL' do it 'redirects to the root URL' do
@ -104,7 +104,10 @@ describe Lti do
let(:consumer) { FactoryBot.create(:consumer) } let(:consumer) { FactoryBot.create(:consumer) }
let(:score) { 0.5 } let(:score) { 0.5 }
let(:submission) { FactoryBot.create(:submission) } let(:submission) { FactoryBot.create(:submission) }
let!(:lti_parameter) { FactoryBot.create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id) }
before do
FactoryBot.create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id)
end
context 'with an invalid score' do context 'with an invalid score' do
it 'raises an exception' do it 'raises an exception' do
@ -117,7 +120,7 @@ describe Lti do
context 'with a tool consumer' do context 'with a tool consumer' do
context 'when grading is not supported' do context 'when grading is not supported' do
it 'returns a corresponding status' do it 'returns a corresponding status' do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false) allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false)
allow(submission).to receive(:normalized_score).and_return score allow(submission).to receive(:normalized_score).and_return score
expect(controller.send(:send_score, submission)[:status]).to eq('unsupported') expect(controller.send(:send_score, submission)[:status]).to eq('unsupported')
end end
@ -127,12 +130,12 @@ describe Lti do
let(:response) { double } let(:response) { double }
before do before do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(true) allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(true)
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:post_replace_result!).with(score).and_return(response) allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:post_replace_result!).with(score).and_return(response)
expect(response).to receive(:response_code).at_least(:once).and_return(200) allow(response).to receive(:response_code).at_least(:once).and_return(200)
expect(response).to receive(:post_response).and_return(response) allow(response).to receive(:post_response).and_return(response)
expect(response).to receive(:body).at_least(:once).and_return('') allow(response).to receive(:body).at_least(:once).and_return('')
expect(response).to receive(:code_major).at_least(:once).and_return('success') allow(response).to receive(:code_major).at_least(:once).and_return('success')
end end
it 'sends the score' do it 'sends the score' do

View File

@ -8,30 +8,29 @@ end
describe SubmissionScoring do describe SubmissionScoring do
let(:controller) { Controller.new } let(:controller) { Controller.new }
let(:submission) { FactoryBot.create(:submission, cause: 'submit') }
before(:all) { @submission = FactoryBot.create(:submission, cause: 'submit') }
before { controller.instance_variable_set(:@current_user, FactoryBot.create(:external_user)) } before { controller.instance_variable_set(:@current_user, FactoryBot.create(:external_user)) }
describe '#collect_test_results' do describe '#collect_test_results' do
after { controller.send(:collect_test_results, @submission) } after { controller.send(:collect_test_results, submission) }
it 'executes every teacher-defined test file' do it 'executes every teacher-defined test file' do
@submission.collect_files.select(&:teacher_defined_assessment?).each do |file| submission.collect_files.select(&:teacher_defined_assessment?).each do |file|
expect(controller).to receive(:execute_test_file).with(file, @submission).and_return({}) allow(controller).to receive(:execute_test_file).with(file, submission).and_return({})
end end
end end
end end
describe '#score_submission' do describe '#score_submission' do
after { controller.score_submission(@submission) } after { controller.score_submission(submission) }
it 'collects the test results' do it 'collects the test results' do
expect(controller).to receive(:collect_test_results).and_return([]) allow(controller).to receive(:collect_test_results).and_return([])
end end
it 'assigns a score to the submissions' do it 'assigns a score to the submissions' do
expect(@submission).to receive(:update).with(score: anything) expect(submission).to receive(:update).with(score: anything)
end end
end end
end end

View File

@ -27,7 +27,7 @@ describe ApplicationController do
describe '#render_not_authorized' do describe '#render_not_authorized' do
before do before do
expect(controller).to receive(:welcome) { controller.send(:render_not_authorized) } allow(controller).to receive(:welcome) { controller.send(:render_not_authorized) }
get :welcome get :welcome
end end
@ -41,14 +41,14 @@ describe ApplicationController do
context 'when specifying a locale' do context 'when specifying a locale' do
before { allow(session).to receive(:[]=).at_least(:once) } before { allow(session).to receive(:[]=).at_least(:once) }
context "using the 'custom_locale' parameter" do context "when using the 'custom_locale' parameter" do
it 'overwrites the session' do it 'overwrites the session' do
expect(session).to receive(:[]=).with(:locale, locale.to_s) expect(session).to receive(:[]=).with(:locale, locale.to_s)
get :welcome, params: {custom_locale: locale} get :welcome, params: {custom_locale: locale}
end end
end end
context "using the 'locale' parameter" do context "when using the 'locale' parameter" do
it 'overwrites the session' do it 'overwrites the session' do
expect(session).to receive(:[]=).with(:locale, locale.to_s) expect(session).to receive(:[]=).with(:locale, locale.to_s)
get :welcome, params: {locale: locale} get :welcome, params: {locale: locale}

View File

@ -14,7 +14,7 @@ describe CodeharborLinksController do
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
end end
context 'GET #new' do describe 'GET #new' do
before do before do
get :new get :new
end end

View File

@ -54,9 +54,10 @@ describe ConsumersController do
end end
describe 'GET #index' do describe 'GET #index' do
let!(:consumers) { FactoryBot.create_pair(:consumer) } before do
FactoryBot.create_pair(:consumer)
before { get :index } get :index
end
expect_assigns(consumers: Consumer.all) expect_assigns(consumers: Consumer.all)
expect_status(200) expect_status(200)

View File

@ -9,7 +9,7 @@ describe ExecutionEnvironmentsController do
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
describe 'POST #create' do describe 'POST #create' do
before { expect(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) } before { allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) }
context 'with a valid execution environment' do context 'with a valid execution environment' do
let(:perform_request) { proc { post :create, params: {execution_environment: FactoryBot.build(:ruby).attributes} } } let(:perform_request) { proc { post :create, params: {execution_environment: FactoryBot.build(:ruby).attributes} } }
@ -50,7 +50,7 @@ describe ExecutionEnvironmentsController do
describe 'GET #edit' do describe 'GET #edit' do
before do before do
expect(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
get :edit, params: {id: execution_environment.id} get :edit, params: {id: execution_environment.id}
end end
@ -64,8 +64,8 @@ describe ExecutionEnvironmentsController do
let(:command) { 'which ruby' } let(:command) { 'which ruby' }
before do before do
expect(DockerClient).to receive(:new).with(execution_environment: execution_environment).and_call_original allow(DockerClient).to receive(:new).with(execution_environment: execution_environment).and_call_original
expect_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).with(command) allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).with(command)
post :execute_command, params: {command: command, id: execution_environment.id} post :execute_command, params: {command: command, id: execution_environment.id}
end end
@ -76,9 +76,10 @@ describe ExecutionEnvironmentsController do
end end
describe 'GET #index' do describe 'GET #index' do
before(:all) { FactoryBot.create_pair(:ruby) } before do
FactoryBot.create_pair(:ruby)
before { get :index } get :index
end
expect_assigns(execution_environments: ExecutionEnvironment.all) expect_assigns(execution_environments: ExecutionEnvironment.all)
expect_status(200) expect_status(200)
@ -87,7 +88,7 @@ describe ExecutionEnvironmentsController do
describe 'GET #new' do describe 'GET #new' do
before do before do
expect(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
get :new get :new
end end
@ -102,8 +103,8 @@ describe ExecutionEnvironmentsController do
let(:docker_images) { [1, 2, 3] } let(:docker_images) { [1, 2, 3] }
before do before do
expect(DockerClient).to receive(:check_availability!).at_least(:once) allow(DockerClient).to receive(:check_availability!).at_least(:once)
expect(DockerClient).to receive(:image_tags).and_return(docker_images) allow(DockerClient).to receive(:image_tags).and_return(docker_images)
controller.send(:set_docker_images) controller.send(:set_docker_images)
end end
@ -114,7 +115,7 @@ describe ExecutionEnvironmentsController do
let(:error_message) { 'Docker is unavailable' } let(:error_message) { 'Docker is unavailable' }
before do before do
expect(DockerClient).to receive(:check_availability!).at_least(:once).and_raise(DockerClient::Error.new(error_message)) allow(DockerClient).to receive(:check_availability!).at_least(:once).and_raise(DockerClient::Error.new(error_message))
controller.send(:set_docker_images) controller.send(:set_docker_images)
end end
@ -154,7 +155,7 @@ describe ExecutionEnvironmentsController do
describe 'PUT #update' do describe 'PUT #update' do
context 'with a valid execution environment' do context 'with a valid execution environment' do
before do before do
expect(DockerClient).to receive(:image_tags).at_least(:once).and_return([]) allow(DockerClient).to receive(:image_tags).at_least(:once).and_return([])
put :update, params: {execution_environment: FactoryBot.attributes_for(:ruby), id: execution_environment.id} put :update, params: {execution_environment: FactoryBot.attributes_for(:ruby), id: execution_environment.id}
end end

View File

@ -45,7 +45,7 @@ describe ExercisesController do
context 'when saving fails' do context 'when saving fails' do
before do before do
expect_any_instance_of(Exercise).to receive(:save).and_return(false) allow_any_instance_of(Exercise).to receive(:save).and_return(false)
perform_request.call perform_request.call
end end
@ -87,7 +87,7 @@ describe ExercisesController do
let(:files_attributes) { {'0' => FactoryBot.build(:file, file_type: file_type).attributes.merge(content: uploaded_file)} } let(:files_attributes) { {'0' => FactoryBot.build(:file, file_type: file_type).attributes.merge(content: uploaded_file)} }
context 'when uploading a binary file' do context 'when uploading a binary file' do
let(:file_path) { Rails.root.join('db', 'seeds', 'audio_video', 'devstories.mp4') } let(:file_path) { Rails.root.join('db/seeds/audio_video/devstories.mp4') }
let(:file_type) { FactoryBot.create(:dot_mp4) } let(:file_type) { FactoryBot.create(:dot_mp4) }
let(:uploaded_file) { Rack::Test::UploadedFile.new(file_path, 'video/mp4', true) } let(:uploaded_file) { Rack::Test::UploadedFile.new(file_path, 'video/mp4', true) }
@ -102,7 +102,7 @@ describe ExercisesController do
end end
context 'when uploading a non-binary file' do context 'when uploading a non-binary file' do
let(:file_path) { Rails.root.join('db', 'seeds', 'fibonacci', 'exercise.rb') } let(:file_path) { Rails.root.join('db/seeds/fibonacci/exercise.rb') }
let(:file_type) { FactoryBot.create(:dot_rb) } let(:file_type) { FactoryBot.create(:dot_rb) }
let(:uploaded_file) { Rack::Test::UploadedFile.new(file_path, 'text/x-ruby', false) } let(:uploaded_file) { Rack::Test::UploadedFile.new(file_path, 'text/x-ruby', false) }
@ -189,9 +189,10 @@ describe ExercisesController do
describe 'GET #index' do describe 'GET #index' do
let(:scope) { Pundit.policy_scope!(user, Exercise) } let(:scope) { Pundit.policy_scope!(user, Exercise) }
before(:all) { FactoryBot.create_pair(:dummy) } before do
FactoryBot.create_pair(:dummy)
before { get :index } get :index
end
expect_assigns(exercises: :scope) expect_assigns(exercises: :scope)
expect_status(200) expect_status(200)
@ -208,7 +209,7 @@ describe ExercisesController do
end end
describe 'GET #show' do describe 'GET #show' do
context 'as admin' do context 'when being admin' do
before { get :show, params: {id: exercise.id} } before { get :show, params: {id: exercise.id} }
expect_assigns(exercise: :exercise) expect_assigns(exercise: :exercise)
@ -218,7 +219,7 @@ describe ExercisesController do
end end
describe 'GET #reload' do describe 'GET #reload' do
context 'as anyone' do context 'when being anyone' do
before { get :reload, format: :json, params: {id: exercise.id} } before { get :reload, format: :json, params: {id: exercise.id} }
expect_assigns(exercise: :exercise) expect_assigns(exercise: :exercise)
@ -239,22 +240,22 @@ describe ExercisesController do
let(:output) { {} } let(:output) { {} }
let(:perform_request) { post :submit, format: :json, params: {id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id}} } let(:perform_request) { post :submit, format: :json, params: {id: exercise.id, submission: {cause: 'submit', exercise_id: exercise.id}} }
let(:user) { FactoryBot.create(:external_user) } let(:user) { FactoryBot.create(:external_user) }
let!(:lti_parameter) { FactoryBot.create(:lti_parameter, external_user: user, exercise: exercise) }
before do before do
FactoryBot.create(:lti_parameter, external_user: user, exercise: exercise)
allow_any_instance_of(Submission).to receive(:normalized_score).and_return(1) allow_any_instance_of(Submission).to receive(:normalized_score).and_return(1)
expect(controller).to receive(:collect_test_results).and_return([{score: 1, weight: 1}]) allow(controller).to receive(:collect_test_results).and_return([{score: 1, weight: 1}])
expect(controller).to receive(:score_submission).and_call_original allow(controller).to receive(:score_submission).and_call_original
end end
context 'when LTI outcomes are supported' do context 'when LTI outcomes are supported' do
before do before do
expect(controller).to receive(:lti_outcome_service?).and_return(true) allow(controller).to receive(:lti_outcome_service?).and_return(true)
end end
context 'when the score transmission succeeds' do context 'when the score transmission succeeds' do
before do before do
expect(controller).to receive(:send_score).and_return(status: 'success') allow(controller).to receive(:send_score).and_return(status: 'success')
perform_request perform_request
end end
@ -270,7 +271,7 @@ describe ExercisesController do
context 'when the score transmission fails' do context 'when the score transmission fails' do
before do before do
expect(controller).to receive(:send_score).and_return(status: 'unsupported') allow(controller).to receive(:send_score).and_return(status: 'unsupported')
perform_request perform_request
end end
@ -287,8 +288,7 @@ describe ExercisesController do
context 'when LTI outcomes are not supported' do context 'when LTI outcomes are not supported' do
before do before do
expect(controller).to receive(:lti_outcome_service?).and_return(false) allow(controller).to receive(:lti_outcome_service?).and_return(false)
expect(controller).not_to receive(:send_score)
perform_request perform_request
end end
@ -298,6 +298,10 @@ describe ExercisesController do
expect(assigns(:submission)).to be_a(Submission) expect(assigns(:submission)).to be_a(Submission)
end end
it 'does not send scores' do
expect(controller).not_to receive(:send_score)
end
expect_json expect_json
expect_status(200) expect_status(200)
end end
@ -333,7 +337,7 @@ describe ExercisesController do
let(:external_check_hash) { {message: message, exercise_found: true, update_right: update_right, error: error} } let(:external_check_hash) { {message: message, exercise_found: true, update_right: update_right, error: error} }
let(:message) { 'message' } let(:message) { 'message' }
let(:update_right) { true } let(:update_right) { true }
let(:error) {} let(:error) { nil }
before { allow(ExerciseService::CheckExternal).to receive(:call).with(uuid: exercise.uuid, codeharbor_link: codeharbor_link).and_return(external_check_hash) } before { allow(ExerciseService::CheckExternal).to receive(:call).with(uuid: exercise.uuid, codeharbor_link: codeharbor_link).and_return(external_check_hash) }
@ -384,7 +388,7 @@ describe ExercisesController do
let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) }
let(:post_request) { post :export_external_confirm, params: {id: exercise.id, codeharbor_link: codeharbor_link.id} } let(:post_request) { post :export_external_confirm, params: {id: exercise.id, codeharbor_link: codeharbor_link.id} }
let(:error) {} let(:error) { nil }
let(:zip) { 'zip' } let(:zip) { 'zip' }
before do before do

View File

@ -57,9 +57,10 @@ describe FileTypesController do
end end
describe 'GET #index' do describe 'GET #index' do
before(:all) { FactoryBot.create_pair(:dot_rb) } before do
FactoryBot.create_pair(:dot_rb)
before { get :index } get :index
end
expect_assigns(file_types: FileType.all) expect_assigns(file_types: FileType.all)
expect_status(200) expect_status(200)

View File

@ -45,6 +45,9 @@ describe InternalUsersController do
before do before do
user.send(:setup_activation) user.send(:setup_activation)
user.save(validate: false) user.save(validate: false)
end
it 'adds an activation token' do
expect(user.activation_token).to be_present expect(user.activation_token).to be_present
end end
@ -171,7 +174,7 @@ describe InternalUsersController do
before do before do
allow(controller).to receive(:set_sentry_context).and_return(nil) allow(controller).to receive(:set_sentry_context).and_return(nil)
expect(controller).to receive(:current_user).and_return(nil) allow(controller).to receive(:current_user).and_return(nil)
get :forgot_password get :forgot_password
end end
@ -183,7 +186,7 @@ describe InternalUsersController do
before do before do
allow(controller).to receive(:set_sentry_context).and_return(nil) allow(controller).to receive(:set_sentry_context).and_return(nil)
expect(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
get :forgot_password get :forgot_password
end end
@ -199,7 +202,7 @@ describe InternalUsersController do
before { perform_request.call } before { perform_request.call }
it 'delivers instructions to reset the password' do it 'delivers instructions to reset the password' do
expect(InternalUser).to receive(:where).and_return([user]) allow(InternalUser).to receive(:where).and_return([user])
expect(user).to receive(:deliver_reset_password_instructions!) expect(user).to receive(:deliver_reset_password_instructions!)
perform_request.call perform_request.call
end end

View File

@ -32,24 +32,24 @@ describe RequestForCommentsController do
end end
end end
describe 'GET #get_my_comment_requests' do describe 'GET #my_comment_requests' do
before { get :get_my_comment_requests } before { get :my_comment_requests }
expect_status(200) expect_status(200)
expect_template(:index) expect_template(:index)
end end
describe 'GET #get_rfcs_with_my_comments' do describe 'GET #rfcs_with_my_comments' do
before { get :get_rfcs_with_my_comments } before { get :rfcs_with_my_comments }
expect_status(200) expect_status(200)
expect_template(:index) expect_template(:index)
end end
describe 'GET #get_rfcs_for_exercise' do describe 'GET #rfcs_for_exercise' do
before do before do
exercise = FactoryBot.create(:even_odd) exercise = FactoryBot.create(:even_odd)
get :get_rfcs_for_exercise, params: {exercise_id: exercise.id} get :rfcs_for_exercise, params: {exercise_id: exercise.id}
end end
expect_status(200) expect_status(200)

View File

@ -58,8 +58,8 @@ describe SessionsController do
context 'without a unique OAuth nonce' do context 'without a unique OAuth nonce' do
it 'refuses the LTI launch' do it 'refuses the LTI launch' do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true) allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true)
expect(NonceStore).to receive(:has?).with(nonce).and_return(true) allow(NonceStore).to receive(:has?).with(nonce).and_return(true)
expect(controller).to receive(:refuse_lti_launch).with(message: I18n.t('sessions.oauth.used_nonce')).and_call_original expect(controller).to receive(:refuse_lti_launch).with(message: I18n.t('sessions.oauth.used_nonce')).and_call_original
post :create_through_lti, params: {oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex} post :create_through_lti, params: {oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex}
end end
@ -67,7 +67,7 @@ describe SessionsController do
context 'without a valid exercise token' do context 'without a valid exercise token' do
it 'refuses the LTI launch' do it 'refuses the LTI launch' do
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true) allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true)
expect(controller).to receive(:refuse_lti_launch).with(message: I18n.t('sessions.oauth.invalid_exercise_token')).and_call_original expect(controller).to receive(:refuse_lti_launch).with(message: I18n.t('sessions.oauth.invalid_exercise_token')).and_call_original
post :create_through_lti, params: {custom_token: '', oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: '123'} post :create_through_lti, params: {custom_token: '', oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: '123'}
end end
@ -78,7 +78,7 @@ describe SessionsController do
let(:perform_request) { post :create_through_lti, params: {custom_locale: locale, custom_token: exercise.token, oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: user.external_id} } let(:perform_request) { post :create_through_lti, params: {custom_locale: locale, custom_token: exercise.token, oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: user.external_id} }
let(:user) { FactoryBot.create(:external_user, consumer_id: consumer.id) } let(:user) { FactoryBot.create(:external_user, consumer_id: consumer.id) }
before { expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true) } before { allow_any_instance_of(IMS::LTI::ToolProvider).to receive(:valid_request?).and_return(true) }
it 'assigns the current user' do it 'assigns the current user' do
perform_request perform_request
@ -112,7 +112,7 @@ describe SessionsController do
let(:message) { I18n.t('sessions.create_through_lti.session_with_outcome', consumer: consumer) } let(:message) { I18n.t('sessions.create_through_lti.session_with_outcome', consumer: consumer) }
before do before do
expect(controller).to receive(:lti_outcome_service?).and_return(true) allow(controller).to receive(:lti_outcome_service?).and_return(true)
perform_request perform_request
end end
@ -123,7 +123,7 @@ describe SessionsController do
let(:message) { I18n.t('sessions.create_through_lti.session_without_outcome', consumer: consumer) } let(:message) { I18n.t('sessions.create_through_lti.session_without_outcome', consumer: consumer) }
before do before do
expect(controller).to receive(:lti_outcome_service?).and_return(false) allow(controller).to receive(:lti_outcome_service?).and_return(false)
perform_request perform_request
end end
@ -159,7 +159,7 @@ describe SessionsController do
before do before do
allow(controller).to receive(:set_sentry_context).and_return(nil) allow(controller).to receive(:set_sentry_context).and_return(nil)
expect(controller).to receive(:current_user).at_least(:once).and_return(user) allow(controller).to receive(:current_user).at_least(:once).and_return(user)
end end
context 'with an internal user' do context 'with an internal user' do
@ -199,7 +199,14 @@ describe SessionsController do
describe 'GET #destroy_through_lti' do 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: {consumer_id: consumer.id, submission_id: submission.id} } }
let(:submission) { FactoryBot.create(:submission, exercise: FactoryBot.create(:dummy)) } before { perform_request.call } let(:submission) { FactoryBot.create(:submission, exercise: FactoryBot.create(:dummy)) }
before do
# Todo replace session with lti_parameter
# Todo create LtiParameter Object
# session[:lti_parameters] = {}
perform_request.call
end
it 'clears the session' do it 'clears the session' do
# Todo replace session with lti_parameter /should be done already # Todo replace session with lti_parameter /should be done already
@ -216,7 +223,7 @@ describe SessionsController do
before do before do
allow(controller).to receive(:set_sentry_context).and_return(nil) allow(controller).to receive(:set_sentry_context).and_return(nil)
expect(controller).to receive(:current_user).and_return(nil) allow(controller).to receive(:current_user).and_return(nil)
get :new get :new
end end
@ -228,7 +235,7 @@ describe SessionsController do
before do before do
allow(controller).to receive(:set_sentry_context).and_return(nil) allow(controller).to receive(:set_sentry_context).and_return(nil)
expect(controller).to receive(:current_user).and_return(FactoryBot.build(:teacher)) allow(controller).to receive(:current_user).and_return(FactoryBot.build(:teacher))
get :new get :new
end end

View File

@ -50,7 +50,7 @@ describe SubmissionsController do
before { get :download_file, params: {filename: file.name_with_extension, id: submission.id} } before { get :download_file, params: {filename: file.name_with_extension, id: submission.id} }
context 'for a binary file' do context 'with a binary file' do
let(:file) { submission.collect_files.detect {|file| file.name == 'exercise' && file.file_type.file_extension == '.sql' } } let(:file) { submission.collect_files.detect {|file| file.name == 'exercise' && file.file_type.file_extension == '.sql' } }
expect_assigns(file: :file) expect_assigns(file: :file)
@ -69,7 +69,7 @@ describe SubmissionsController do
before { get :download_file, params: {filename: file.name_with_extension, id: submission.id} } before { get :download_file, params: {filename: file.name_with_extension, id: submission.id} }
context 'for a binary file' do context 'with a binary file' do
let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.mp4' } } let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.mp4' } }
expect_assigns(file: :file) expect_assigns(file: :file)
@ -82,7 +82,7 @@ describe SubmissionsController do
end end
end end
context 'for a non-binary file' do context 'with a non-binary file' do
let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.js' } } let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.js' } }
expect_assigns(file: :file) expect_assigns(file: :file)
@ -98,9 +98,10 @@ describe SubmissionsController do
end end
describe 'GET #index' do describe 'GET #index' do
before(:all) { FactoryBot.create_pair(:submission) } before do
FactoryBot.create_pair(:submission)
before { get :index } get :index
end
expect_assigns(submissions: Submission.all) expect_assigns(submissions: Submission.all)
expect_status(200) expect_status(200)
@ -121,7 +122,7 @@ describe SubmissionsController do
before { get :render_file, params: {filename: file.name_with_extension, id: submission.id} } before { get :render_file, params: {filename: file.name_with_extension, id: submission.id} }
context 'for a binary file' do context 'with a binary file' do
let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.mp4' } } let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.mp4' } }
expect_assigns(file: :file) expect_assigns(file: :file)
@ -134,7 +135,7 @@ describe SubmissionsController do
end end
end end
context 'for a non-binary file' do context 'with a non-binary file' do
let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.js' } } let(:file) { submission.collect_files.detect {|file| file.file_type.file_extension == '.js' } }
expect_assigns(file: :file) expect_assigns(file: :file)
@ -154,12 +155,12 @@ describe SubmissionsController do
let(:perform_request) { get :run, params: {filename: filename, id: submission.id} } let(:perform_request) { get :run, params: {filename: filename, id: submission.id} }
before do before do
expect_any_instance_of(ActionController::Live::SSE).to receive(:write).at_least(3).times allow_any_instance_of(ActionController::Live::SSE).to receive(:write).at_least(3).times
end end
context 'when no errors occur during execution' do context 'when no errors occur during execution' do
before do before do
expect_any_instance_of(DockerClient).to receive(:execute_run_command).with(submission, filename).and_return({}) allow_any_instance_of(DockerClient).to receive(:execute_run_command).with(submission, filename).and_return({})
perform_request perform_request
end end
@ -222,7 +223,7 @@ describe SubmissionsController do
let(:output) { {} } let(:output) { {} }
before do before do
expect_any_instance_of(DockerClient).to receive(:execute_test_command).with(submission, filename) allow_any_instance_of(DockerClient).to receive(:execute_test_command).with(submission, filename)
get :test, params: {filename: filename, id: submission.id} get :test, params: {filename: filename, id: submission.id}
end end

View File

@ -9,7 +9,9 @@ describe 'seeds' do
CodeOcean::Application.load_tasks CodeOcean::Application.load_tasks
# We want to execute the seeds for the dev environment against the test database # We want to execute the seeds for the dev environment against the test database
# rubocop:disable Rails/Inquiry
allow(Rails).to receive(:env) { 'development'.inquiry } allow(Rails).to receive(:env) { 'development'.inquiry }
# rubocop:enable Rails/Inquiry
allow(ActiveRecord::Base).to receive(:establish_connection).and_call_original allow(ActiveRecord::Base).to receive(:establish_connection).and_call_original
allow(ActiveRecord::Base).to receive(:establish_connection).with(:development) { allow(ActiveRecord::Base).to receive(:establish_connection).with(:development) {
ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.establish_connection(:test)

View File

@ -16,7 +16,7 @@ module CodeOcean
trait(:image) do trait(:image) do
association :file_type, factory: :dot_png association :file_type, factory: :dot_png
name { 'poster' } name { 'poster' }
native_file { Rack::Test::UploadedFile.new(Rails.root.join('db', 'seeds', 'audio_video', 'poster.png'), 'image/png') } native_file { Rack::Test::UploadedFile.new(Rails.root.join('db/seeds/audio_video/poster.png'), 'image/png') }
end end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
LTI_PARAMETERS = { lti_params = {
lis_result_sourcedid: 'c2db0c7c-4411-4b27-a52b-ddfc3dc32065', 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', 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', 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',
@ -12,10 +12,10 @@ FactoryBot.define do
association :exercise, factory: :math association :exercise, factory: :math
association :external_user association :external_user
lti_parameters { LTI_PARAMETERS } lti_parameters { lti_params }
trait :without_outcome_service_url do trait :without_outcome_service_url do
lti_parameters { LTI_PARAMETERS.except(:lis_outcome_service_url) } lti_parameters { lti_params.except(:lis_outcome_service_url) }
end end
end end
end end

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe 'Authorization' do describe 'Authorization' do
context 'as an admin' do context 'when being an admin' do
let(:user) { FactoryBot.create(:admin) } let(:user) { FactoryBot.create(:admin) }
before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) }
@ -13,7 +13,7 @@ describe 'Authorization' do
end end
end end
context 'as an external user' do context 'with being an external user' do
let(:user) { FactoryBot.create(:external_user) } let(:user) { FactoryBot.create(:external_user) }
before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) }
@ -23,7 +23,7 @@ describe 'Authorization' do
end end
end end
context 'as a teacher' do context 'with being a teacher' do
let(:user) { FactoryBot.create(:teacher) } let(:user) { FactoryBot.create(:teacher) }
before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } before { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) }

View File

@ -29,7 +29,7 @@ describe 'Editor', js: true do
fill_in('email', with: user.email) fill_in('email', with: user.email)
fill_in('password', with: FactoryBot.attributes_for(:teacher)[:password]) fill_in('password', with: FactoryBot.attributes_for(:teacher)[:password])
click_button(I18n.t('sessions.new.link')) click_button(I18n.t('sessions.new.link'))
expect_any_instance_of(LtiHelper).to receive(:lti_outcome_service?).and_return(true) allow_any_instance_of(LtiHelper).to receive(:lti_outcome_service?).and_return(true)
visit(implement_exercise_path(exercise)) visit(implement_exercise_path(exercise))
end end

View File

@ -7,8 +7,9 @@ describe Prometheus::Controller do
let(:prometheus_config) { {prometheus_exporter: {enabled: true}} } let(:prometheus_config) { {prometheus_exporter: {enabled: true}} }
def stub_metrics def stub_metrics
metrics = %i[@instance_count @rfc_count @rfc_commented_count]
%i[increment decrement observe].each do |method| %i[increment decrement observe].each do |method|
%i[@instance_count @rfc_count @rfc_commented_count].each do |metric| metrics.each do |metric|
allow(described_class.instance_variable_get(metric)).to receive(method) allow(described_class.instance_variable_get(metric)).to receive(method)
end end
end end

View File

@ -11,7 +11,7 @@ describe Assessor do
context 'when an error occurs' do context 'when an error occurs' do
before do before do
expect_any_instance_of(TestingFrameworkAdapter).to receive(:test_outcome).and_raise allow_any_instance_of(TestingFrameworkAdapter).to receive(:test_outcome).and_raise
end end
it 'catches the error' do it 'catches the error' do

View File

@ -4,7 +4,7 @@ require 'rails_helper'
describe CodeOcean::Config do describe CodeOcean::Config do
describe '#read' do describe '#read' do
let(:content) { {foo: 'bar'} } let(:content) { {'foo' => 'bar'} }
let(:filename) { :foo } let(:filename) { :foo }
context 'with a .yml file' do context 'with a .yml file' do

View File

@ -3,8 +3,6 @@
require 'rails_helper' require 'rails_helper'
require 'seeds_helper' require 'seeds_helper'
# rubocop:disable RSpec/MultipleMemoizedHelpers
WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test') WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test')
describe DockerClient, docker: true do describe DockerClient, docker: true do
@ -26,14 +24,14 @@ describe DockerClient, docker: true do
describe '.check_availability!' do describe '.check_availability!' do
context 'when a socket error occurs' do context 'when a socket error occurs' do
it 'raises an error' do it 'raises an error' do
expect(Docker).to receive(:version).and_raise(Excon::Errors::SocketError.new(StandardError.new)) allow(Docker).to receive(:version).and_raise(Excon::Errors::SocketError.new(StandardError.new))
expect { described_class.check_availability! }.to raise_error(DockerClient::Error) expect { described_class.check_availability! }.to raise_error(DockerClient::Error)
end end
end end
context 'when a timeout occurs' do context 'when a timeout occurs' do
it 'raises an error' do it 'raises an error' do
expect(Docker).to receive(:version).and_raise(Timeout::Error) allow(Docker).to receive(:version).and_raise(Timeout::Error)
expect { described_class.check_availability! }.to raise_error(DockerClient::Error) expect { described_class.check_availability! }.to raise_error(DockerClient::Error)
end end
end end
@ -115,7 +113,7 @@ describe DockerClient, docker: true do
context 'when retries are left' do context 'when retries are left' do
before do before do
expect(described_class).to receive(:mapped_directories).and_raise(error).and_call_original allow(described_class).to receive(:mapped_directories).and_raise(error).and_call_original
end end
it 'retries to create a container' do it 'retries to create a container' do
@ -125,7 +123,7 @@ describe DockerClient, docker: true do
context 'when no retries are left' do context 'when no retries are left' do
before do before do
expect(described_class).to receive(:mapped_directories).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error) allow(described_class).to receive(:mapped_directories).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error)
end end
it 'raises the error' do it 'raises the error' do
@ -140,7 +138,7 @@ describe DockerClient, docker: true do
let(:container) { double } let(:container) { double }
before do before do
expect(container).to receive(:binds).at_least(:once).and_return(["#{workspace_path}:#{DockerClient::CONTAINER_WORKSPACE_PATH}"]) allow(container).to receive(:binds).at_least(:once).and_return(["#{workspace_path}:#{DockerClient::CONTAINER_WORKSPACE_PATH}"])
end end
after { docker_client.send(:create_workspace_files, container, submission) } after { docker_client.send(:create_workspace_files, container, submission) }
@ -183,11 +181,11 @@ describe DockerClient, docker: true do
after { described_class.destroy_container(container) } after { described_class.destroy_container(container) }
it 'kills running processes' do it 'kills running processes' do
expect(container).to receive(:kill).and_return(container) allow(container).to receive(:kill).and_return(container)
end end
it 'releases allocated ports' do it 'releases allocated ports' do
expect(container).to receive(:port_bindings).at_least(:once).and_return(foo: [{'HostPort' => '42'}]) allow(container).to receive(:port_bindings).at_least(:once).and_return(foo: [{'HostPort' => '42'}])
expect(PortPool).to receive(:release) expect(PortPool).to receive(:release)
end end
@ -211,7 +209,7 @@ describe DockerClient, docker: true do
end end
it 'sends the command' do it 'sends the command' do
expect(docker_client).to receive(:send_command).with(command, kind_of(Docker::Container)).and_return({}) allow(docker_client).to receive(:send_command).with(command, kind_of(Docker::Container)).and_return({})
execute_arbitrary_command execute_arbitrary_command
end end
@ -222,7 +220,7 @@ describe DockerClient, docker: true do
let(:result) { {status: 'ok', stdout: 42} } let(:result) { {status: 'ok', stdout: 42} }
before do before do
expect(docker_client).to receive(:send_command).and_raise(error).and_return(result) allow(docker_client).to receive(:send_command).and_raise(error).and_return(result)
end end
it 'retries to execute the command' do it 'retries to execute the command' do
@ -232,13 +230,13 @@ describe DockerClient, docker: true do
context 'when no retries are left' do context 'when no retries are left' do
before do before do
expect(docker_client).to receive(:send_command).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error) allow(docker_client).to receive(:send_command).exactly(DockerClient::RETRY_COUNT + 1).times.and_raise(error)
end end
it 'raises the error' do it 'raises the error' do
pending('retries are disabled') pending('retries are disabled')
# !TODO Retries is disabled # TODO: Retries is disabled
# expect { execute_arbitrary_command }.to raise_error(error) expect { execute_arbitrary_command }.to raise_error(error)
end end
end end
end end
@ -250,12 +248,10 @@ describe DockerClient, docker: true do
after { docker_client.send(:execute_run_command, submission, filename) } after { docker_client.send(:execute_run_command, submission, filename) }
it 'takes a container from the pool' do it 'takes a container from the pool' do
pending('todo in the future')
expect(DockerContainerPool).to receive(:get_container).with(submission.execution_environment).and_call_original expect(DockerContainerPool).to receive(:get_container).with(submission.execution_environment).and_call_original
end end
it 'creates the workspace files' do it 'creates the workspace files' do
pending('todo in the future')
expect(docker_client).to receive(:create_workspace_files) expect(docker_client).to receive(:create_workspace_files)
end end
@ -281,7 +277,7 @@ describe DockerClient, docker: true do
it 'executes the test command' do it 'executes the test command' do
expect(submission.execution_environment).to receive(:test_command).and_call_original expect(submission.execution_environment).to receive(:test_command).and_call_original
expect(docker_client).to receive(:send_command).with(kind_of(String), kind_of(Docker::Container)).and_return({}) allow(docker_client).to receive(:send_command).with(kind_of(String), kind_of(Docker::Container)).and_return({})
end end
end end
@ -305,7 +301,7 @@ describe DockerClient, docker: true do
end end
context 'with incomplete configuration' do context 'with incomplete configuration' do
before { expect(described_class).to receive(:config).at_least(:once).and_return({}) } before { allow(described_class).to receive(:config).at_least(:once).and_return({}) }
it 'raises an error' do it 'raises an error' do
expect { described_class.initialize_environment }.to raise_error(DockerClient::Error) expect { described_class.initialize_environment }.to raise_error(DockerClient::Error)
@ -357,7 +353,7 @@ describe DockerClient, docker: true do
end end
describe '#send_command' do describe '#send_command' do
let(:block) { proc {} } let(:block) { proc { nil } }
let(:container) { described_class.create_container(execution_environment) } let(:container) { described_class.create_container(execution_environment) }
let(:send_command) { docker_client.send(:send_command, command, container, &block) } let(:send_command) { docker_client.send(:send_command, command, container, &block) }
@ -379,8 +375,13 @@ describe DockerClient, docker: true do
context 'when a timeout occurs' do context 'when a timeout occurs' do
before do before do
expect(container).to receive(:exec).once.and_raise(Timeout::Error) exec_called = 0
expect(container).to receive(:exec).twice.and_return([[], []]) allow(container).to receive(:exec) do
exec_called += 1
raise Timeout::Error if exec_called == 1
[[], []]
end
end end
it 'destroys the container asynchronously' do it 'destroys the container asynchronously' do
@ -410,5 +411,3 @@ describe DockerClient, docker: true do
end end
end end
end end
# rubocop:enable RSpec/MultipleMemoizedHelpers

View File

@ -11,7 +11,7 @@ describe DockerContainerMixin do
end end
it 'returns the correct information' do it 'returns the correct information' do
expect(CONTAINER).to receive(:json).and_return('HostConfig' => {'Binds' => binds}) allow(CONTAINER).to receive(:json).and_return('HostConfig' => {'Binds' => binds})
expect(CONTAINER.binds).to eq(binds) expect(CONTAINER.binds).to eq(binds)
end end
end end
@ -25,7 +25,7 @@ describe DockerContainerMixin do
end end
it 'returns the correct information' do it 'returns the correct information' do
expect(CONTAINER).to receive(:json).and_return('HostConfig' => {'PortBindings' => port_bindings}) allow(CONTAINER).to receive(:json).and_return('HostConfig' => {'PortBindings' => port_bindings})
expect(CONTAINER.port_bindings).to eq(port => port) expect(CONTAINER.port_bindings).to eq(port => port)
end end
end end

View File

@ -8,8 +8,8 @@ describe FileTree do
describe '#file_icon' do describe '#file_icon' do
let(:file_icon) { file_tree.send(:file_icon, file) } let(:file_icon) { file_tree.send(:file_icon, file) }
context 'for a media file' do context 'with a media file' do
context 'for an audio file' do context 'with an audio file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp3)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp3)) }
it 'is an audio file icon' do it 'is an audio file icon' do
@ -17,7 +17,7 @@ describe FileTree do
end end
end end
context 'for an image file' do context 'with an image file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_jpg)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_jpg)) }
it 'is an image file icon' do it 'is an image file icon' do
@ -25,7 +25,7 @@ describe FileTree do
end end
end end
context 'for a video file' do context 'with a video file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp4)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp4)) }
it 'is a video file icon' do it 'is a video file icon' do
@ -34,8 +34,8 @@ describe FileTree do
end end
end end
context 'for other files' do context 'with other files' do
context 'for a read-only file' do context 'with a read-only file' do
let(:file) { FactoryBot.build(:file, read_only: true) } let(:file) { FactoryBot.build(:file, read_only: true) }
it 'is a lock icon' do it 'is a lock icon' do
@ -43,7 +43,7 @@ describe FileTree do
end end
end end
context 'for an executable file' do context 'with an executable file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_py)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_py)) }
it 'is a code file icon' do it 'is a code file icon' do
@ -51,7 +51,7 @@ describe FileTree do
end end
end end
context 'for a renderable file' do context 'with a renderable file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_svg)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_svg)) }
it 'is a text file icon' do it 'is a text file icon' do
@ -59,7 +59,7 @@ describe FileTree do
end end
end end
context 'for all other files' do context 'with all other files' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_md)) } let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_md)) }
it 'is a generic file icon' do it 'is a generic file icon' do
@ -100,7 +100,7 @@ describe FileTree do
let!(:leaf) { root.add(Tree::TreeNode.new('', file)) } let!(:leaf) { root.add(Tree::TreeNode.new('', file)) }
let(:root) { Tree::TreeNode.new('', file) } let(:root) { Tree::TreeNode.new('', file) }
context 'for a leaf node' do context 'with a leaf node' do
let(:node) { leaf } let(:node) { leaf }
it 'produces the required attributes' do it 'produces the required attributes' do
@ -116,7 +116,7 @@ describe FileTree do
end end
end end
context 'for a non-leaf node' do context 'with a non-leaf node' do
let(:node) { root } let(:node) { root }
it "traverses the node's children" do it "traverses the node's children" do
@ -144,7 +144,7 @@ describe FileTree do
let(:node_icon) { file_tree.send(:node_icon, node) } let(:node_icon) { file_tree.send(:node_icon, node) }
let(:root) { Tree::TreeNode.new('') } let(:root) { Tree::TreeNode.new('') }
context 'for the root node' do context 'with the root node' do
let(:node) { root } let(:node) { root }
it 'is a folder icon' do it 'is a folder icon' do
@ -152,7 +152,7 @@ describe FileTree do
end end
end end
context 'for leaf nodes' do context 'with leaf nodes' do
let(:node) { root.add(Tree::TreeNode.new('')) } let(:node) { root.add(Tree::TreeNode.new('')) }
it 'is a file icon' do it 'is a file icon' do
@ -161,7 +161,7 @@ describe FileTree do
end end
end end
context 'for intermediary nodes' do context 'with intermediary nodes' do
let(:node) do let(:node) do
root.add(Tree::TreeNode.new('').tap {|node| node.add(Tree::TreeNode.new('')) }) root.add(Tree::TreeNode.new('').tap {|node| node.add(Tree::TreeNode.new('')) })
end end

View File

@ -27,7 +27,7 @@ describe TestingFrameworkAdapterGenerator do
it 'builds a correct class skeleton' do it 'builds a correct class skeleton' do
file_content = File.new(path, 'r').read file_content = File.new(path, 'r').read
expect(file_content).to start_with("class #{name}Adapter < TestingFrameworkAdapter") expect(file_content&.strip).to start_with("class #{name}Adapter < TestingFrameworkAdapter")
end end
it 'generates a corresponding test' do it 'generates a corresponding test' do

View File

@ -5,6 +5,10 @@ require 'rails_helper'
describe NonceStore do describe NonceStore do
let(:nonce) { SecureRandom.hex } let(:nonce) { SecureRandom.hex }
before do
stub_const('Lti::MAXIMUM_SESSION_AGE', 1)
end
describe '.add' do describe '.add' do
it 'stores a nonce in the cache' do it 'stores a nonce in the cache' do
expect(Rails.cache).to receive(:write) expect(Rails.cache).to receive(:write)
@ -28,8 +32,6 @@ describe NonceStore do
end end
it 'returns false for expired nonces' do it 'returns false for expired nonces' do
Lti.send(:remove_const, 'MAXIMUM_SESSION_AGE')
Lti::MAXIMUM_SESSION_AGE = 1
described_class.add(nonce) described_class.add(nonce)
expect(described_class.has?(nonce)).to be true expect(described_class.has?(nonce)).to be true
sleep(Lti::MAXIMUM_SESSION_AGE) sleep(Lti::MAXIMUM_SESSION_AGE)

View File

@ -42,7 +42,7 @@ describe TestingFrameworkAdapter do
describe '#test_outcome' do describe '#test_outcome' do
it 'calls the framework-specific implementation' do it 'calls the framework-specific implementation' do
expect(adapter).to receive(:parse_output).and_return(count: count, failed: failed, passed: passed) allow(adapter).to receive(:parse_output).and_return(count: count, failed: failed, passed: passed)
adapter.test_outcome('') adapter.test_outcome('')
end end
end end

View File

@ -25,7 +25,7 @@ describe CodeOcean::File do
expect(file.errors[:read_only]).to be_blank expect(file.errors[:read_only]).to be_blank
end end
context 'as a teacher-defined test' do context 'with a teacher-defined test' do
before { file.update(role: 'teacher_defined_test') } before { file.update(role: 'teacher_defined_test') }
it 'validates the presence of a feedback message' do it 'validates the presence of a feedback message' do

View File

@ -6,7 +6,7 @@ describe ExecutionEnvironment do
let(:execution_environment) { described_class.create.tap {|execution_environment| execution_environment.update(network_enabled: nil) } } let(:execution_environment) { described_class.create.tap {|execution_environment| execution_environment.update(network_enabled: nil) } }
it 'validates that the Docker image works', docker: true do it 'validates that the Docker image works', docker: true do
expect(execution_environment).to receive(:validate_docker_image?).and_return(true) allow(execution_environment).to receive(:validate_docker_image?).and_return(true)
expect(execution_environment).to receive(:working_docker_image?) expect(execution_environment).to receive(:working_docker_image?)
execution_environment.update(docker_image: FactoryBot.attributes_for(:ruby)[:docker_image]) execution_environment.update(docker_image: FactoryBot.attributes_for(:ruby)[:docker_image])
end end
@ -124,22 +124,22 @@ describe ExecutionEnvironment do
describe '#working_docker_image?', docker: true do describe '#working_docker_image?', docker: true do
let(:working_docker_image?) { execution_environment.send(:working_docker_image?) } let(:working_docker_image?) { execution_environment.send(:working_docker_image?) }
before { expect(DockerClient).to receive(:find_image_by_tag).and_return(Object.new) } before { allow(DockerClient).to receive(:find_image_by_tag).and_return(Object.new) }
it 'instantiates a Docker client' do it 'instantiates a Docker client' do
expect(DockerClient).to receive(:new).with(execution_environment: execution_environment).and_call_original expect(DockerClient).to receive(:new).with(execution_environment: execution_environment).and_call_original
expect_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_return({}) allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_return({})
working_docker_image? working_docker_image?
end end
it 'executes the validation command' do it 'executes the validation command' do
expect_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).with(ExecutionEnvironment::VALIDATION_COMMAND).and_return({}) allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).with(ExecutionEnvironment::VALIDATION_COMMAND).and_return({})
working_docker_image? working_docker_image?
end end
context 'when the command produces an error' do context 'when the command produces an error' do
it 'adds an error' do it 'adds an error' do
expect_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_return(stderr: 'command not found') allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_return(stderr: 'command not found')
working_docker_image? working_docker_image?
expect(execution_environment.errors[:docker_image]).to be_present expect(execution_environment.errors[:docker_image]).to be_present
end end
@ -147,7 +147,7 @@ describe ExecutionEnvironment do
context 'when the Docker client produces an error' do context 'when the Docker client produces an error' do
it 'adds an error' do it 'adds an error' do
expect_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_raise(DockerClient::Error) allow_any_instance_of(DockerClient).to receive(:execute_arbitrary_command).and_raise(DockerClient::Error)
working_docker_image? working_docker_image?
expect(execution_environment.errors[:docker_image]).to be_present expect(execution_environment.errors[:docker_image]).to be_present
end end

View File

@ -118,9 +118,7 @@ describe Exercise do
end end
it 'duplicates all associated files' do it 'duplicates all associated files' do
exercise.files.each do |file| expect(exercise.files).to all(receive(:dup).and_call_original)
expect(file).to receive(:dup).and_call_original
end
end end
it 'returns the duplicated exercise' do it 'returns the duplicated exercise' do

View File

@ -34,7 +34,7 @@ describe Submission do
before { submission.score = submission.exercise.maximum_score / 2 } before { submission.score = submission.exercise.maximum_score / 2 }
it 'returns the score as a value between 0 and 1' do it 'returns the score as a value between 0 and 1' do
expect(0..1).to include(submission.normalized_score) expect(submission.normalized_score).to be_between(0, 1)
end end
end end
@ -54,7 +54,7 @@ describe Submission do
before { submission.score = submission.exercise.maximum_score / 2 } before { submission.score = submission.exercise.maximum_score / 2 }
it 'returns the score expressed as a percentage' do it 'returns the score expressed as a percentage' do
expect(0..100).to include(submission.percentage) expect(submission.percentage).to be_between(0, 100)
end end
end end

View File

@ -3,19 +3,19 @@
require 'rails_helper' require 'rails_helper'
describe Admin::DashboardPolicy do describe Admin::DashboardPolicy do
subject { described_class } subject(:policy) { described_class }
permissions :show? do permissions :show? do
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), :dashboard) expect(policy).to permit(FactoryBot.build(:admin), :dashboard)
end end
it 'does not grant access to teachers' do it 'does not grant access to teachers' do
expect(subject).not_to permit(FactoryBot.build(:teacher), :dashboard) expect(policy).not_to permit(FactoryBot.build(:teacher), :dashboard)
end end
it 'does not grant access to external users' do it 'does not grant access to external users' do
expect(subject).not_to permit(FactoryBot.build(:external_user), :dashboard) expect(policy).not_to permit(FactoryBot.build(:external_user), :dashboard)
end end
end end
end end

View File

@ -3,86 +3,86 @@
require 'rails_helper' require 'rails_helper'
describe CodeOcean::FilePolicy do describe CodeOcean::FilePolicy do
subject { described_class } subject(:policy) { described_class }
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { FactoryBot.create(:fibonacci) }
let(:submission) { FactoryBot.create(:submission) } let(:submission) { FactoryBot.create(:submission) }
permissions :create? do permissions :create? do
context 'as part of an exercise' do context 'when being part of an exercise' do
let(:file) { exercise.files.first } let(:file) { exercise.files.first }
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), file) expect(policy).to permit(FactoryBot.build(:admin), file)
end end
it 'grants access to authors' do it 'grants access to authors' do
expect(subject).to permit(exercise.author, file) expect(policy).to permit(exercise.author, file)
end end
it 'does not grant access to all other users' do it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name| %i[external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), file) expect(policy).not_to permit(FactoryBot.build(factory_name), file)
end end
end end
end end
context 'as part of a submission' do context 'when being part of a submission' do
let(:file) { submission.files.first } let(:file) { submission.files.first }
context 'where file creation is allowed' do context 'when file creation is allowed' do
before do before do
submission.exercise.update(allow_file_creation: true) submission.exercise.update(allow_file_creation: true)
end end
it 'grants access to authors' do it 'grants access to authors' do
expect(subject).to permit(submission.author, file) expect(policy).to permit(submission.author, file)
end end
end end
context 'where file creation is not allowed' do context 'when file creation is not allowed' do
before do before do
submission.exercise.update(allow_file_creation: false) submission.exercise.update(allow_file_creation: false)
end end
it 'grants access to authors' do it 'grants access to authors' do
expect(subject).not_to permit(submission.author, file) expect(policy).not_to permit(submission.author, file)
end end
end end
it 'does not grant access to all other users' do it 'does not grant access to all other users' do
%i[admin external_user teacher].each do |factory_name| %i[admin external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), file) expect(policy).not_to permit(FactoryBot.build(factory_name), file)
end end
end end
end end
end end
permissions :destroy? do permissions :destroy? do
context 'as part of an exercise' do context 'when being part of an exercise' do
let(:file) { exercise.files.first } let(:file) { exercise.files.first }
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), file) expect(policy).to permit(FactoryBot.build(:admin), file)
end end
it 'grants access to authors' do it 'grants access to authors' do
expect(subject).to permit(exercise.author, file) expect(policy).to permit(exercise.author, file)
end end
it 'does not grant access to all other users' do it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name| %i[external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), file) expect(policy).not_to permit(FactoryBot.build(factory_name), file)
end end
end end
end end
context 'as part of a submission' do context 'when being part of a submission' do
let(:file) { submission.files.first } let(:file) { submission.files.first }
it 'does not grant access to anyone' do it 'does not grant access to anyone' do
%i[admin external_user teacher].each do |factory_name| %i[admin external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), file) expect(policy).not_to permit(FactoryBot.build(factory_name), file)
end end
end end
end end

View File

@ -3,14 +3,14 @@
require 'rails_helper' require 'rails_helper'
describe ConsumerPolicy do describe ConsumerPolicy do
subject { described_class } subject(:policy) { described_class }
%i[create? destroy? edit? index? new? show? update?].each do |action| %i[create? destroy? edit? index? new? show? update?].each do |action|
permissions(action) do permissions(action) do
it 'grants access to admins only' do it 'grants access to admins only' do
expect(subject).to permit(FactoryBot.build(:admin), Consumer.new) expect(policy).to permit(FactoryBot.build(:admin), Consumer.new)
%i[external_user teacher].each do |factory_name| %i[external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), Consumer.new) expect(policy).not_to permit(FactoryBot.build(factory_name), Consumer.new)
end end
end end
end end

View File

@ -3,22 +3,22 @@
require 'rails_helper' require 'rails_helper'
describe ExecutionEnvironmentPolicy do describe ExecutionEnvironmentPolicy do
subject { described_class } subject(:policy) { described_class }
let(:execution_environment) { FactoryBot.build(:ruby) } let(:execution_environment) { FactoryBot.build(:ruby) }
[:index?].each do |action| [:index?].each do |action|
permissions(action) do permissions(action) do
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), execution_environment) expect(policy).to permit(FactoryBot.build(:admin), execution_environment)
end end
it 'grants access to teachers' do it 'grants access to teachers' do
expect(subject).to permit(FactoryBot.build(:teacher), execution_environment) expect(policy).to permit(FactoryBot.build(:teacher), execution_environment)
end end
it 'does not grant access to external users' do it 'does not grant access to external users' do
expect(subject).not_to permit(FactoryBot.build(:external_user), execution_environment) expect(policy).not_to permit(FactoryBot.build(:external_user), execution_environment)
end end
end end
end end
@ -26,16 +26,16 @@ describe ExecutionEnvironmentPolicy do
%i[execute_command? shell? statistics? show?].each do |action| %i[execute_command? shell? statistics? show?].each do |action|
permissions(action) do permissions(action) do
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), execution_environment) expect(policy).to permit(FactoryBot.build(:admin), execution_environment)
end end
it 'grants access to authors' do it 'grants access to authors' do
expect(subject).to permit(execution_environment.author, execution_environment) expect(policy).to permit(execution_environment.author, execution_environment)
end end
it 'does not grant access to all other users' do it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name| %i[external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), execution_environment) expect(policy).not_to permit(FactoryBot.build(factory_name), execution_environment)
end end
end end
end end
@ -44,16 +44,16 @@ describe ExecutionEnvironmentPolicy do
%i[destroy? edit? update? new? create?].each do |action| %i[destroy? edit? update? new? create?].each do |action|
permissions(action) do permissions(action) do
it 'grants access to admins' do it 'grants access to admins' do
expect(subject).to permit(FactoryBot.build(:admin), execution_environment) expect(policy).to permit(FactoryBot.build(:admin), execution_environment)
end end
it 'does not grant access to authors' do it 'does not grant access to authors' do
expect(subject).not_to permit(execution_environment.author, execution_environment) expect(policy).not_to permit(execution_environment.author, execution_environment)
end end
it 'does not grant access to all other users' do it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name| %i[external_user teacher].each do |factory_name|
expect(subject).not_to permit(FactoryBot.build(factory_name), execution_environment) expect(policy).not_to permit(FactoryBot.build(factory_name), execution_environment)
end end
end end
end end

Some files were not shown because too many files have changed in this diff Show More