Merge branch 'master' into refactor_proforma_import_export

# Conflicts:
#	spec/controllers/exercises_controller_spec.rb
This commit is contained in:
Karol
2022-01-11 22:20:18 +01:00
122 changed files with 1558 additions and 1424 deletions

View File

@ -77,7 +77,7 @@ jobs:
run: bundle exec rspec --color --format progress --require spec_helper --require rails_helper run: bundle exec rspec --color --format progress --require spec_helper --require rails_helper
- name: Send coverage to CodeClimate - name: Send coverage to CodeClimate
uses: paambaati/codeclimate-action@v2.7.4 uses: paambaati/codeclimate-action@v3.0.0
continue-on-error: true continue-on-error: true
if: ${{ success() || failure() }} if: ${{ success() || failure() }}
env: env:

View File

@ -1,9 +1,9 @@
GIT GIT
remote: https://github.com/evolve75/RubyTree.git remote: https://github.com/evolve75/RubyTree.git
revision: eb045068f73529c66d9d84f0553fdf85fc98bc4f revision: 6081d0959b706dcefb85e85faa329ebb2dabcf9e
specs: specs:
rubytree (1.0.2) rubytree (1.0.2)
json (~> 2.3.1) json (~> 2.6.1)
structured_warnings (~> 0.4.0) structured_warnings (~> 0.4.0)
GIT GIT
@ -30,64 +30,64 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
ZenTest (4.12.0) ZenTest (4.12.0)
actioncable (6.1.4.1) actioncable (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.1.4.1) actionmailbox (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
activejob (= 6.1.4.1) activejob (= 6.1.4.4)
activerecord (= 6.1.4.1) activerecord (= 6.1.4.4)
activestorage (= 6.1.4.1) activestorage (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.1.4.1) actionmailer (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
actionview (= 6.1.4.1) actionview (= 6.1.4.4)
activejob (= 6.1.4.1) activejob (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.1.4.1) actionpack (6.1.4.4)
actionview (= 6.1.4.1) actionview (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
rack (~> 2.0, >= 2.0.9) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.4.1) actiontext (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
activerecord (= 6.1.4.1) activerecord (= 6.1.4.4)
activestorage (= 6.1.4.1) activestorage (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.1.4.1) actionview (6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.1.4.1) activejob (6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.1.4.1) activemodel (6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
activemodel-serializers-xml (1.0.2) activemodel-serializers-xml (1.0.2)
activemodel (> 5.x) activemodel (> 5.x)
activesupport (> 5.x) activesupport (> 5.x)
builder (~> 3.1) builder (~> 3.1)
activerecord (6.1.4.1) activerecord (6.1.4.4)
activemodel (= 6.1.4.1) activemodel (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
activestorage (6.1.4.1) activestorage (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
activejob (= 6.1.4.1) activejob (= 6.1.4.4)
activerecord (= 6.1.4.1) activerecord (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
marcel (~> 1.0.0) marcel (~> 1.0.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (6.1.4.1) activesupport (6.1.4.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
@ -109,7 +109,7 @@ GEM
bindex (0.8.1) bindex (0.8.1)
binding_of_caller (1.0.0) binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (1.9.1) bootsnap (1.9.3)
msgpack (~> 1.0) msgpack (~> 1.0)
bootstrap-will_paginate (1.0.0) bootstrap-will_paginate (1.0.0)
will_paginate will_paginate
@ -151,7 +151,7 @@ GEM
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
debug_inspector (1.1.0) debug_inspector (1.1.0)
diff-lcs (1.4.4) diff-lcs (1.5.0)
docile (1.4.0) docile (1.4.0)
docker-api (2.2.0) docker-api (2.2.0)
excon (>= 0.47.0) excon (>= 0.47.0)
@ -162,37 +162,41 @@ GEM
regexp_parser (~> 2.0) regexp_parser (~> 2.0)
erubi (1.10.0) erubi (1.10.0)
eventmachine (1.2.7) eventmachine (1.2.7)
excon (0.88.0) excon (0.89.0)
factory_bot (6.2.0) factory_bot (6.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
faraday (1.8.0) faraday (1.9.3)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1) faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1) faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0) faraday-patron (~> 1.0)
faraday-rack (~> 1.0) faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3) faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0) faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0) faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0) faraday-excon (1.1.0)
faraday-httpclient (1.0.1) faraday-httpclient (1.0.1)
faraday-multipart (1.0.2)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0) faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0) faraday-rack (1.0.0)
faraday-retry (1.0.3)
faye-websocket (0.11.1) faye-websocket (0.11.1)
eventmachine (>= 0.12.0) eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1) websocket-driver (>= 0.5.1)
ffi (1.15.4) ffi (1.15.4)
forgery (0.8.1) forgery (0.8.1)
globalid (0.5.2) globalid (1.0.0)
activesupport (>= 5.0) activesupport (>= 5.0)
haml (5.2.2) haml (5.2.2)
temple (>= 0.8.0) temple (>= 0.8.0)
@ -215,7 +219,8 @@ GEM
builder (>= 1.0, < 4.0) builder (>= 1.0, < 4.0)
oauth (>= 0.4.5, < 0.6) oauth (>= 0.4.5, < 0.6)
influxdb (0.8.1) influxdb (0.8.1)
jbuilder (2.11.3) jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
jquery-rails (4.4.0) jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
@ -223,33 +228,33 @@ GEM
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1) jquery-ui-rails (6.0.1)
railties (>= 3.2.16) railties (>= 3.2.16)
js-routes (2.1.2) js-routes (2.2.0)
railties (>= 4) railties (>= 4)
json (2.3.1) json (2.6.1)
json_schemer (0.2.18) json_schemer (0.2.18)
ecma-re-validator (~> 0.3) ecma-re-validator (~> 0.3)
hana (~> 1.3) hana (~> 1.3)
regexp_parser (~> 2.0) regexp_parser (~> 2.0)
uri_template (~> 0.7) uri_template (~> 0.7)
jwt (2.3.0) jwt (2.3.0)
kaminari (1.2.1) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1) kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.1) kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.1) kaminari-core (= 1.2.2)
kaminari-actionview (1.2.1) kaminari-actionview (1.2.2)
actionview actionview
kaminari-core (= 1.2.1) kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.1) kaminari-activerecord (1.2.2)
activerecord activerecord
kaminari-core (= 1.2.1) kaminari-core (= 1.2.2)
kaminari-core (1.2.1) kaminari-core (1.2.2)
kramdown (2.3.1) kramdown (2.3.1)
rexml rexml
listen (3.7.0) listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.12.0) loofah (2.13.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
@ -257,16 +262,16 @@ GEM
marcel (1.0.2) marcel (1.0.2)
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.4.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2021.0901) mime-types-data (3.2022.0105)
mimemagic (0.4.3) mimemagic (0.4.3)
nokogiri (~> 1) nokogiri (~> 1)
rake rake
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.1.2) mini_mime (1.1.2)
mini_portile2 (2.5.3) mini_portile2 (2.5.3)
minitest (5.14.4) minitest (5.15.0)
minitest-autotest (1.1.1) minitest-autotest (1.1.1)
minitest-server (~> 1.0) minitest-server (~> 1.0)
path_expander (~> 1.0) path_expander (~> 1.0)
@ -283,14 +288,14 @@ GEM
net-http-persistent (4.0.1) net-http-persistent (4.0.1)
connection_pool (~> 2.2) connection_pool (~> 2.2)
netrc (0.11.0) netrc (0.11.0)
newrelic_rpm (8.1.0) newrelic_rpm (8.2.0)
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.11.7) nokogiri (1.11.7)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
nyan-cat-formatter (0.12.0) nyan-cat-formatter (0.12.0)
rspec (>= 2.99, >= 2.14.2, < 4) rspec (>= 2.99, >= 2.14.2, < 4)
oauth (0.5.7) oauth (0.5.8)
oauth2 (1.4.7) oauth2 (1.4.7)
faraday (>= 0.8, < 2.0) faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0) jwt (>= 1.0, < 3.0)
@ -300,11 +305,11 @@ GEM
pagedown-bootstrap-rails (2.1.4) pagedown-bootstrap-rails (2.1.4)
railties (> 3.1) railties (> 3.1)
parallel (1.21.0) parallel (1.21.0)
parser (3.0.2.0) parser (3.1.0.0)
ast (~> 2.4.1) ast (~> 2.4.1)
path_expander (1.1.0) path_expander (1.1.0)
pg (1.2.3) pg (1.2.3)
prometheus_exporter (0.8.1) prometheus_exporter (1.0.1)
webrick webrick
pry (0.13.1) pry (0.13.1)
coderay (~> 1.1) coderay (~> 1.1)
@ -326,24 +331,24 @@ GEM
rack-pjax (1.1.0) rack-pjax (1.1.0)
nokogiri (~> 1.5) nokogiri (~> 1.5)
rack (>= 1.1) rack (>= 1.1)
rack-proxy (0.7.0) rack-proxy (0.7.2)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.1.4.1) rails (6.1.4.4)
actioncable (= 6.1.4.1) actioncable (= 6.1.4.4)
actionmailbox (= 6.1.4.1) actionmailbox (= 6.1.4.4)
actionmailer (= 6.1.4.1) actionmailer (= 6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
actiontext (= 6.1.4.1) actiontext (= 6.1.4.4)
actionview (= 6.1.4.1) actionview (= 6.1.4.4)
activejob (= 6.1.4.1) activejob (= 6.1.4.4)
activemodel (= 6.1.4.1) activemodel (= 6.1.4.4)
activerecord (= 6.1.4.1) activerecord (= 6.1.4.4)
activestorage (= 6.1.4.1) activestorage (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 6.1.4.1) railties (= 6.1.4.4)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -354,9 +359,9 @@ GEM
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2) rails-html-sanitizer (1.4.2)
loofah (~> 2.3) loofah (~> 2.3)
rails-i18n (6.0.0) rails-i18n (7.0.1)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 8)
rails-timeago (2.19.1) rails-timeago (2.19.1)
actionpack (>= 3.1) actionpack (>= 3.1)
activesupport (>= 3.1) activesupport (>= 3.1)
@ -372,15 +377,15 @@ GEM
rails (>= 5.0, < 7) rails (>= 5.0, < 7)
remotipart (~> 1.3) remotipart (~> 1.3)
sassc-rails (>= 1.3, < 3) sassc-rails (>= 1.3, < 3)
railties (6.1.4.1) railties (6.1.4.4)
actionpack (= 6.1.4.1) actionpack (= 6.1.4.4)
activesupport (= 6.1.4.1) activesupport (= 6.1.4.4)
method_source method_source
rake (>= 0.13) rake (>= 0.13)
thor (~> 1.0) thor (~> 1.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.6) rake (13.0.6)
ransack (2.4.2) ransack (2.5.0)
activerecord (>= 5.2.4) activerecord (>= 5.2.4)
activesupport (>= 5.2.4) activesupport (>= 5.2.4)
i18n i18n
@ -388,7 +393,7 @@ GEM
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rbtree (0.4.4) rbtree (0.4.4)
regexp_parser (2.1.1) regexp_parser (2.2.0)
remotipart (1.4.4) remotipart (1.4.4)
rest-client (2.1.0) rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0) http-accept (>= 1.7.0, < 2.0)
@ -421,28 +426,28 @@ GEM
rspec-mocks (~> 3.10) rspec-mocks (~> 3.10)
rspec-support (~> 3.10) rspec-support (~> 3.10)
rspec-support (3.10.3) rspec-support (3.10.3)
rubocop (1.23.0) rubocop (1.24.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml rexml
rubocop-ast (>= 1.12.0, < 2.0) rubocop-ast (>= 1.15.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.13.0) rubocop-ast (1.15.1)
parser (>= 3.0.1.1) parser (>= 3.0.1.1)
rubocop-performance (1.12.0) rubocop-performance (1.13.1)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0) rubocop-ast (>= 0.4.0)
rubocop-rails (2.12.4) rubocop-rails (2.13.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
rubocop-rspec (2.6.0) rubocop-rspec (2.7.0)
rubocop (~> 1.19) rubocop (~> 1.19)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby-vips (2.1.3) ruby-vips (2.1.4)
ffi (~> 1.12) ffi (~> 1.12)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
@ -461,18 +466,18 @@ GEM
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
semantic_range (3.0.0) semantic_range (3.0.0)
sentry-rails (4.8.0) sentry-rails (4.8.3)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby-core (~> 4.8.0) sentry-ruby-core (~> 4.8.3)
sentry-ruby (4.8.0) sentry-ruby (4.8.3)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
faraday (>= 1.0) faraday (~> 1.0)
sentry-ruby-core (= 4.8.0) sentry-ruby-core (= 4.8.3)
sentry-ruby-core (4.8.0) sentry-ruby-core (4.8.3)
concurrent-ruby concurrent-ruby
faraday faraday
set (1.0.2) set (1.0.2)
shoulda-matchers (5.0.0) shoulda-matchers (5.1.0)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
simplecov (0.21.2) simplecov (0.21.2)
docile (~> 1.1) docile (~> 1.1)
@ -487,27 +492,27 @@ GEM
actionpack (>= 3.1) actionpack (>= 3.1)
railties (>= 3.1) railties (>= 3.1)
slim (>= 3.0, < 5.0) slim (>= 3.0, < 5.0)
sorcery (0.16.1) sorcery (0.16.2)
bcrypt (~> 3.1) bcrypt (~> 3.1)
oauth (~> 0.5, >= 0.5.5) oauth (~> 0.5, >= 0.5.5)
oauth2 (~> 1.0, >= 0.8.0) oauth2 (~> 1.0, >= 0.8.0)
sorted_set (1.0.3) sorted_set (1.0.3)
rbtree rbtree
set (~> 1.0) set (~> 1.0)
spring (3.0.0) spring (4.0.0)
sprockets (4.0.2) sprockets (4.0.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.2) sprockets-rails (3.4.2)
actionpack (>= 4.0) actionpack (>= 5.2)
activesupport (>= 4.0) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
ssrf_filter (1.0.7) ssrf_filter (1.0.7)
structured_warnings (0.4.0) structured_warnings (0.4.0)
telegraf (2.0.0) telegraf (2.0.0)
influxdb influxdb
temple (0.8.2) temple (0.8.2)
thor (1.1.0) thor (1.2.1)
tilt (2.0.10) tilt (2.0.10)
turbolinks (5.2.1) turbolinks (5.2.1)
turbolinks-source (~> 5.2) turbolinks-source (~> 5.2)
@ -543,7 +548,7 @@ GEM
will_paginate (3.3.1) will_paginate (3.3.1)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.5.1) zeitwerk (2.5.3)
PLATFORMS PLATFORMS
ruby ruby
@ -624,4 +629,4 @@ DEPENDENCIES
whenever whenever
BUNDLED WITH BUNDLED WITH
2.2.29 2.3.4

View File

@ -68,7 +68,7 @@ $(document).on('turbolinks:load', function() {
_.each(response.docker, function(data) { _.each(response.docker, function(data) {
groups.update({ groups.update({
id: data.id, id: data.id,
visible: data.pool_size > 0 visible: data.prewarmingPoolSize > 0
}); });
}); });
}; };
@ -78,26 +78,27 @@ $(document).on('turbolinks:load', function() {
dataset.add({ dataset.add({
group: data.id, group: data.id,
x: vis.moment(), x: vis.moment(),
y: data.quantity y: data.usedRunners
}); });
}); });
}; };
var updateProgressBar = function(progress_bar, data) { var updateProgressBar = function(progress_bar, data) {
var percentage = Math.min(Math.round(data.quantity / data.pool_size * 100), 100); var percentage = Math.min(Math.round(data.idleRunners / data.prewarmingPoolSize * 100), 100);
progress_bar.attr({ progress_bar.attr({
'aria-valuemax': data.pool_size, 'aria-valuemax': data.prewarmingPoolSize,
'aria-valuenow': data.quantity, 'aria-valuenow': data.idleRunners,
style: 'width: ' + percentage + '%' style: 'width: ' + percentage + '%'
}); });
progress_bar.html(data.quantity); progress_bar.html(data.idleRunners);
}; };
var updateTable = function(response) { var updateTable = function(response) {
_.each(response.docker, function(data) { _.each(response.docker, function(data) {
var row = $('tbody tr[data-id=' + data.id + ']'); var row = $('tbody tr[data-id=' + data.id + ']');
$('.pool-size', row).html(data.pool_size); $('.prewarming-pool-size', row).html(data.prewarmingPoolSize);
var progress_bar = $('.quantity .progress .progress-bar', row); $('.used-runners', row).html(`+ ${data.usedRunners}`);
var progress_bar = $('.idle-runners .progress .progress-bar', row);
updateProgressBar(progress_bar, data); updateProgressBar(progress_bar, data);
}); });
}; };

View File

@ -17,6 +17,7 @@ var CodeOceanEditor = {
//Request-For-Comments-Configuration //Request-For-Comments-Configuration
REQUEST_FOR_COMMENTS_DELAY: 0, REQUEST_FOR_COMMENTS_DELAY: 0,
REQUEST_TOOLTIP_TIME: 5000, REQUEST_TOOLTIP_TIME: 5000,
REQUEST_TOOLTIP_DELAY: 10 * 60 * 1000,
editors: [], editors: [],
editor_for_file: new Map(), editor_for_file: new Map(),
@ -170,7 +171,7 @@ var CodeOceanEditor = {
this.active_frame = frame; this.active_frame = frame;
frame.show(); frame.show();
this.resizeParentOfAceEditor(frame.find('.ace_editor.ace-tm')); this.resizeParentOfAceEditor(frame.find('.ace_editor'));
}, },
getProgressBarClass: function (percentage) { getProgressBarClass: function (percentage) {
@ -406,7 +407,7 @@ var CodeOceanEditor = {
initializeRegexes: function () { initializeRegexes: function () {
// These RegEx are run on the HTML escaped output! // These RegEx are run on the HTML escaped output!
this.regex_for_language.set("ace/mode/python", /File &quot;(.+?)&quot;, line (\d+)/g); this.regex_for_language.set("ace/mode/python", /File &quot;(.+?)&quot;, line (\d+)/g);
this.regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g); this.regex_for_language.set("ace/mode/java", /(?:\.\/)?(.*\.java):(\d+):/g);
}, },
initializeTooltips: function () { initializeTooltips: function () {
@ -444,10 +445,12 @@ var CodeOceanEditor = {
setTimeout(function () { setTimeout(function () {
button.prop('disabled', false); button.prop('disabled', false);
button.tooltip('show');
setTimeout(function () { setTimeout(function () {
button.tooltip('hide'); button.tooltip('show');
}, this.REQUEST_TOOLTIP_TIME); setTimeout(function () {
button.tooltip('hide');
}, this.REQUEST_TOOLTIP_TIME);
}, this.REQUEST_TOOLTIP_DELAY)
}.bind(this), this.REQUEST_FOR_COMMENTS_DELAY); }.bind(this), this.REQUEST_FOR_COMMENTS_DELAY);
}, },
@ -623,15 +626,18 @@ var CodeOceanEditor = {
}, },
jumpToSourceLine: function (event) { jumpToSourceLine: function (event) {
var file = $(event.target).data('file'); const file = $(event.target).data('file');
var line = $(event.target).data('line'); const line = $(event.target).data('line');
// set active file, only needed for codepilot, so skipped for now const frame = $('div.frame[data-filename="' + file + '"]');
var frame = $('div.frame[data-filename="' + file + '"]');
this.showFrame(frame); this.showFrame(frame);
this.toggleButtonStates();
var editor = this.editor_for_file.get(file); const file_id = frame.find('.editor').data('file-id');
this.setActiveFile(frame.data('filename'), file_id);
this.selectFileInJsTree($('#files'), file_id);
const editor = this.editor_for_file.get(file);
editor.gotoLine(line, 0); editor.gotoLine(line, 0);
event.preventDefault(); event.preventDefault();
}, },
@ -829,7 +835,11 @@ var CodeOceanEditor = {
const percentile75 = data['working_time_75_percentile']; const percentile75 = data['working_time_75_percentile'];
const accumulatedWorkTimeUser = data['working_time_accumulated']; const accumulatedWorkTimeUser = data['working_time_accumulated'];
const minTimeIntervention = 10 * 60 * 1000; let minTimeIntervention = 10 * 60 * 1000;
if ($('#editor').data('exercise-id') === 909) {
// 30 minutes for our large Map exercise
minTimeIntervention = 30 * 60 * 1000;
}
let timeUntilIntervention; let timeUntilIntervention;
if ((accumulatedWorkTimeUser - percentile75) > 0) { if ((accumulatedWorkTimeUser - percentile75) > 0) {

View File

@ -115,7 +115,7 @@ CodeOceanEditorSubmissions = {
this.showSpinner(this); this.showSpinner(this);
this.ajax({ this.ajax({
method: 'GET', method: 'GET',
url: $('#start-over').data('url') url: $('#start-over').data('url') || $('#start-over-active-file').data('url')
}).done(function(response) { }).done(function(response) {
this.hideSpinner(); this.hideSpinner();
_.each(this.editors, function(editor) { _.each(this.editors, function(editor) {
@ -196,9 +196,9 @@ CodeOceanEditorSubmissions = {
const button = $(event.target) || $('#submit'); const button = $(event.target) || $('#submit');
this.createSubmission(button, null, function (response) { this.createSubmission(button, null, function (response) {
if (response.redirect) { if (response.redirect) {
this.unloadAutoSave();
this.editors = []; this.editors = [];
Turbolinks.clearCache(); Turbolinks.clearCache();
clearTimeout(this.autosaveTimer);
Turbolinks.visit(response.redirect); Turbolinks.visit(response.redirect);
} else if (response.status === 'container_depleted') { } else if (response.status === 'container_depleted') {
this.showContainerDepletedMessage(); this.showContainerDepletedMessage();

View File

@ -29,16 +29,17 @@ class CommunitySolutionsController < ApplicationController
if last_contribution.blank? if last_contribution.blank?
last_contribution = @community_solution.exercise last_contribution = @community_solution.exercise
new_readme_file = {content: '', file_type: FileType.find_by(file_extension: '.txt'), hidden: false, read_only: false, name: 'ReadMe', role: 'regular_file', context: @community_solution} new_readme_file = {content: '', file_type: FileType.find_by(file_extension: '.txt'), hidden: false, read_only: false, name: 'ReadMe', role: 'regular_file', context: @community_solution}
@files << CodeOcean::File.create!(new_readme_file) # If the first user did not save, the ReadMe file already exists
@files << CodeOcean::File.find_or_create_by!(new_readme_file)
end end
all_visible_files = last_contribution.files.select(&:visible) all_visible_files = last_contribution.files.select(&:visible)
# Add the ReadMe file first # Add the ReadMe file first
@files += all_visible_files.select {|f| CodeOcean::File.find_by(id: f.file_id).context_type == 'CommunitySolution' } @files += all_visible_files.select {|f| CodeOcean::File.find_by(id: f.file_id)&.context_type == 'CommunitySolution' }
# Then, add all remaining files and sort them by name with extension # Then, add all remaining files and sort them by name with extension
@files += (all_visible_files - @files).sort_by(&:name_with_extension) @files += (all_visible_files - @files).sort_by(&:filepath)
# Own Submission as a reference # Own Submission as a reference
@own_files = @submission.collect_files.select(&:visible).sort_by(&:name_with_extension) @own_files = @submission.collect_files.select(&:visible).sort_by(&:filepath)
# Remove the file_id from the second graph. Otherwise, the comparison and file-tree selection does not work as expected # Remove the file_id from the second graph. Otherwise, the comparison and file-tree selection does not work as expected
@own_files.map do |file| @own_files.map do |file|
file.file_id = nil file.file_id = nil

View File

@ -5,7 +5,7 @@ module RedirectBehavior
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.to_d == 1.0.to_d if @submission.normalized_score.to_d == BigDecimal('1.0')
if redirect_to_community_solution? if redirect_to_community_solution?
redirect_to_community_solution redirect_to_community_solution
return return

View File

@ -4,7 +4,7 @@ class ExecutionEnvironmentsController < ApplicationController
include CommonBehavior include CommonBehavior
before_action :set_docker_images, only: %i[create edit new update] before_action :set_docker_images, only: %i[create edit new update]
before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics] before_action :set_execution_environment, only: MEMBER_ACTIONS + %i[execute_command shell statistics sync_to_runner_management]
before_action :set_testing_framework_adapters, only: %i[create edit new update] before_action :set_testing_framework_adapters, only: %i[create edit new update]
def authorize! def authorize!
@ -166,6 +166,20 @@ class ExecutionEnvironmentsController < ApplicationController
update_and_respond(object: @execution_environment, params: execution_environment_params) update_and_respond(object: @execution_environment, params: execution_environment_params)
end end
def sync_to_runner_management
return unless Runner.management_active?
begin
Runner.strategy_class.sync_environment(@execution_environment)
rescue Runner::Error => e
Rails.logger.debug { "Runner error while synchronizing execution environment with id #{@execution_environment.id}: #{e.message}" }
Sentry.capture_exception(e)
redirect_to @execution_environment, alert: t('execution_environments.index.synchronize.failure', error: e.message)
else
redirect_to @execution_environment, notice: t('execution_environments.index.synchronize.success')
end
end
def sync_all_to_runner_management def sync_all_to_runner_management
authorize ExecutionEnvironment authorize ExecutionEnvironment
@ -179,6 +193,7 @@ class ExecutionEnvironmentsController < ApplicationController
success << true success << true
rescue Runner::Error => e rescue Runner::Error => e
Rails.logger.debug { "Runner error while getting all execution environments: #{e.message}" } Rails.logger.debug { "Runner error while getting all execution environments: #{e.message}" }
Sentry.capture_exception(e)
environments_to_remove = [] environments_to_remove = []
success << false success << false
end end
@ -189,6 +204,7 @@ class ExecutionEnvironmentsController < ApplicationController
Runner.strategy_class.sync_environment(execution_environment) Runner.strategy_class.sync_environment(execution_environment)
rescue Runner::Error => e rescue Runner::Error => e
Rails.logger.debug { "Runner error while synchronizing execution environment with id #{execution_environment.id}: #{e.message}" } Rails.logger.debug { "Runner error while synchronizing execution environment with id #{execution_environment.id}: #{e.message}" }
Sentry.capture_exception(e)
false false
end end
@ -198,6 +214,7 @@ class ExecutionEnvironmentsController < ApplicationController
Runner.strategy_class.remove_environment(execution_environment) Runner.strategy_class.remove_environment(execution_environment)
rescue Runner::Error => e rescue Runner::Error => e
Rails.logger.debug { "Runner error while deleting execution environment with id #{execution_environment.id}: #{e.message}" } Rails.logger.debug { "Runner error while deleting execution environment with id #{execution_environment.id}: #{e.message}" }
Sentry.capture_exception(e)
false false
end end

View File

@ -59,7 +59,7 @@ raise: false
end end
def collect_paths(files) def collect_paths(files)
unique_paths = files.map(&:path).reject(&:blank?).uniq unique_paths = files.map(&:path).compact_blank.uniq
subpaths = unique_paths.map do |path| subpaths = unique_paths.map do |path|
Array.new((path.split('/').length + 1)) do |n| Array.new((path.split('/').length + 1)) do |n|
path.split('/').shift(n).join('/') path.split('/').shift(n).join('/')
@ -321,7 +321,7 @@ raise: false
@search = Search.new @search = Search.new
@search.exercise = @exercise @search.exercise = @exercise
@submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first @submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first
@files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:name_with_extension) @files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:filepath)
@paths = collect_paths(@files) @paths = collect_paths(@files)
@user_id = if current_user.respond_to? :external_id @user_id = if current_user.respond_to? :external_id

View File

@ -15,8 +15,9 @@ class PingController < ApplicationController
private private
def postgres_connected! def postgres_connected!
# any unhandled exception leads to a HTTP 500 response.
ApplicationRecord.establish_connection ApplicationRecord.establish_connection
ApplicationRecord.connection ApplicationRecord.connection
ApplicationRecord.connected? raise ActiveRecord::ConnectionNotEstablished unless ApplicationRecord.connected?
end end
end end

View File

@ -24,7 +24,7 @@ class ProxyExercisesController < ApplicationController
def create def create
myparams = proxy_exercise_params myparams = proxy_exercise_params
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject(&:empty?)) myparams[:exercises] = Exercise.find(myparams[:exercise_ids].compact_blank)
@proxy_exercise = ProxyExercise.new(myparams) @proxy_exercise = ProxyExercise.new(myparams)
authorize! authorize!
@ -78,7 +78,7 @@ class ProxyExercisesController < ApplicationController
def update def update
myparams = proxy_exercise_params myparams = proxy_exercise_params
myparams[:exercises] = Exercise.find(myparams[:exercise_ids].reject(&:blank?)) myparams[:exercises] = Exercise.find(myparams[:exercise_ids].compact_blank)
update_and_respond(object: @proxy_exercise, params: myparams) update_and_respond(object: @proxy_exercise, params: myparams)
end end
end end

View File

@ -23,7 +23,7 @@ class StudyGroupsController < ApplicationController
def update def update
myparams = study_group_params myparams = study_group_params
myparams[:external_users] = myparams[:external_users] =
StudyGroupMembership.find(myparams[:study_group_membership_ids].reject(&:empty?)).map(&:user) StudyGroupMembership.find(myparams[:study_group_membership_ids].compact_blank).map(&:user)
myparams.delete(:study_group_membership_ids) myparams.delete(:study_group_membership_ids)
update_and_respond(object: @study_group, params: myparams) update_and_respond(object: @study_group, params: myparams)
end end

View File

@ -27,12 +27,7 @@ class SubmissionsController < ApplicationController
stringio = Zip::OutputStream.write_buffer do |zio| stringio = Zip::OutputStream.write_buffer do |zio|
@files.each do |file| @files.each do |file|
zio.put_next_entry(if file.path.to_s == '' zio.put_next_entry(file.filepath)
file.name_with_extension
else
File.join(file.path,
file.name_with_extension)
end)
zio.write(file.content.presence || file.native_file.read) zio.write(file.content.presence || file.native_file.read)
end end
@ -247,12 +242,9 @@ class SubmissionsController < ApplicationController
# parse remote request url # parse remote request url
content += "#{request.base_url}/evaluate\n" content += "#{request.base_url}/evaluate\n"
@submission.files.each do |file| @submission.files.each do |file|
file_path = file.path.to_s == '' ? file.name_with_extension : File.join(file.path, file.name_with_extension) content += "#{file.filepath}=#{file.file_id}\n"
content += "#{file_path}=#{file.file_id}\n"
end
File.open(path, 'w+') do |f|
f.write(content)
end end
File.write(path, content)
path path
end end
@ -317,7 +309,7 @@ class SubmissionsController < ApplicationController
# @files contains all visible files for the user # @files contains all visible files for the user
# @file contains the specific file requested for run / test / render / ... # @file contains the specific file requested for run / test / render / ...
set_files set_files
@file = @files.detect {|file| file.name_with_extension == sanitize_filename } @file = @files.detect {|file| file.filepath == sanitize_filename }
head :not_found unless @file head :not_found unless @file
end end

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.to_d == 1.0.to_d if rfc && submission && submission.normalized_score.to_d == BigDecimal('1.0')
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.to_d == 1.0.to_d if rfc && submission && submission.normalized_score.to_d == BigDecimal('1.0')
request_for_comment_path(rfc) request_for_comment_path(rfc)
else else
implement_exercise_path(@exercise) implement_exercise_path(@exercise)

View File

@ -11,12 +11,29 @@ module Admin
Runner.strategy_class.pool_size Runner.strategy_class.pool_size
rescue Runner::Error => e rescue Runner::Error => e
Rails.logger.debug { "Runner error while fetching current pool size: #{e.message}" } Rails.logger.debug { "Runner error while fetching current pool size: #{e.message}" }
[] {}
end end
ExecutionEnvironment.order(:id).select(:id, :pool_size).map do |execution_environment| ExecutionEnvironment.order(:id).select(:id, :pool_size).map do |execution_environment|
execution_environment.attributes.merge(quantity: pool_size[execution_environment.id]) # Fetch the actual values (ID is stored as a symbol) or get an empty hash for merge
actual = pool_size[execution_environment.id.to_s.to_sym] || {}
template = {
id: execution_environment.id,
prewarmingPoolSize: execution_environment.pool_size,
idleRunners: 0,
usedRunners: 0,
}
# Existing values in the template get replaced with actual values
template.merge(actual)
end end
end end
def self.runner_management_release
Runner.strategy_class.release
rescue Runner::Error => e
e.inspect
end
end end
end end

View File

@ -41,7 +41,6 @@ module CodeOcean
validates :feedback_message, if: :teacher_defined_assessment?, presence: true validates :feedback_message, if: :teacher_defined_assessment?, presence: true
validates :feedback_message, absence: true, unless: :teacher_defined_assessment? validates :feedback_message, absence: true, unless: :teacher_defined_assessment?
validates :file_type_id, presence: true
validates :hashed_content, if: :content_present?, presence: true validates :hashed_content, if: :content_present?, presence: true
validates :hidden, boolean_presence: true validates :hidden, boolean_presence: true
validates :name, presence: true validates :name, presence: true
@ -99,7 +98,7 @@ module CodeOcean
private :incomplete_descendent? private :incomplete_descendent?
def name_with_extension def name_with_extension
name + (file_type.file_extension || '') name.to_s + (file_type&.file_extension || '')
end end
def set_ancestor_values def set_ancestor_values

View File

@ -7,8 +7,5 @@ module Creation
belongs_to :user, polymorphic: true belongs_to :user, polymorphic: true
alias_method :author, :user alias_method :author, :user
alias_method :creator, :user alias_method :creator, :user
validates :user_id, presence: true
validates :user_type, presence: true
end end
end end

View File

@ -36,6 +36,7 @@ class ExecutionEnvironment < ApplicationRecord
after_destroy :delete_runner_environment after_destroy :delete_runner_environment
after_save :working_docker_image?, if: :validate_docker_image? after_save :working_docker_image?, if: :validate_docker_image?
after_update_commit :sync_runner_environment, unless: proc {|_| Rails.env.test? }
after_rollback :delete_runner_environment, on: :create after_rollback :delete_runner_environment, on: :create
after_rollback :sync_runner_environment, on: %i[update destroy] after_rollback :sync_runner_environment, on: %i[update destroy]
@ -79,7 +80,7 @@ class ExecutionEnvironment < ApplicationRecord
def validate_docker_image? def validate_docker_image?
# We only validate the code execution with the provided image if there is at least one container to test with. # We only validate the code execution with the provided image if there is at least one container to test with.
pool_size.positive? && docker_image.present? && !Rails.env.test? pool_size.positive? && docker_image.present? && !Rails.env.test? && Runner.management_active?
end end
def working_docker_image? def working_docker_image?
@ -92,9 +93,9 @@ class ExecutionEnvironment < ApplicationRecord
rescue Runner::Error => e rescue Runner::Error => e
# In case of an Runner::Error, we retry multiple times before giving up. # In case of an Runner::Error, we retry multiple times before giving up.
# The time between each retry increases to allow the runner management to catch up. # The time between each retry increases to allow the runner management to catch up.
if retries < 5 && !Rails.env.test? if retries < 30 && !Rails.env.test?
retries += 1 retries += 1
sleep retries sleep 1.second.to_i
retry retry
elsif errors.exclude?(:docker_image) elsif errors.exclude?(:docker_image)
errors.add(:docker_image, "error: #{e}") errors.add(:docker_image, "error: #{e}")
@ -104,7 +105,7 @@ class ExecutionEnvironment < ApplicationRecord
end end
def delete_runner_environment def delete_runner_environment
Runner.strategy_class.remove_environment(self) Runner.strategy_class.remove_environment(self) if Runner.management_active?
rescue Runner::Error => e rescue Runner::Error => e
unless errors.include?(:docker_image) unless errors.include?(:docker_image)
errors.add(:docker_image, "error: #{e}") errors.add(:docker_image, "error: #{e}")
@ -114,7 +115,7 @@ class ExecutionEnvironment < ApplicationRecord
def sync_runner_environment def sync_runner_environment
previous_saved_environment = self.class.find(id) previous_saved_environment = self.class.find(id)
Runner.strategy_class.sync_environment(previous_saved_environment) Runner.strategy_class.sync_environment(previous_saved_environment) if Runner.management_active?
rescue Runner::Error => e rescue Runner::Error => e
unless errors.include?(:docker_image) unless errors.include?(:docker_image)
errors.add(:docker_image, "error: #{e}") errors.add(:docker_image, "error: #{e}")

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.to_d != 0.0.to_d) && submissions.exists?(cause: 'submit') if average_score && (maximum_score.to_d != BigDecimal('0.0')) && submissions.exists?(cause: 'submit')
(average_score / maximum_score * 100).round(2) (average_score / maximum_score * 100).round(2)
else else
0 0
@ -580,7 +580,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.to_d == 1.0.to_d if submission.normalized_score.to_d == BigDecimal('1.0')
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

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class ExternalUser < User class ExternalUser < User
validates :consumer_id, presence: true
validates :external_id, presence: true validates :external_id, presence: true
def displayname def displayname

View File

@ -175,7 +175,7 @@ class ProxyExercise < ApplicationRecord
return 0.0 return 0.0
end end
points_ratio = exercise.maximum_score(user) / max_score points_ratio = exercise.maximum_score(user) / max_score
if points_ratio.to_d == 0.0.to_d if points_ratio.to_d == BigDecimal('0.0')
Rails.logger.debug { "scoring user #{user.id} for exercise #{exercise.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

View File

@ -6,7 +6,7 @@ class Runner < ApplicationRecord
before_validation :request_id before_validation :request_id
validates :execution_environment, :user, :runner_id, presence: true validates :runner_id, presence: true
attr_accessor :strategy attr_accessor :strategy

View File

@ -45,7 +45,6 @@ class Submission < ApplicationRecord
scope :in_study_group_of, ->(user) { where(study_group_id: user.study_groups) unless user.admin? } scope :in_study_group_of, ->(user) { where(study_group_id: user.study_groups) unless user.admin? }
validates :cause, inclusion: {in: CAUSES} validates :cause, inclusion: {in: CAUSES}
validates :exercise_id, presence: true
# after_save :trigger_working_times_action_cable # after_save :trigger_working_times_action_cable
@ -160,7 +159,7 @@ class Submission < ApplicationRecord
# @raise [Runner::Error] if the code could not be run due to a failure with the runner. # @raise [Runner::Error] if the code could not be run due to a failure with the runner.
# See the specific type and message for more details. # See the specific type and message for more details.
def run(file, &block) def run(file, &block)
run_command = command_for execution_environment.run_command, file.name_with_extension run_command = command_for execution_environment.run_command, file.filepath
durations = {} durations = {}
prepared_runner do |runner, waiting_duration| prepared_runner do |runner, waiting_duration|
durations[:execution_duration] = runner.attach_to_execution(run_command, &block) durations[:execution_duration] = runner.attach_to_execution(run_command, &block)
@ -185,7 +184,7 @@ class Submission < ApplicationRecord
end end
def run_test_file(file, runner, waiting_duration) def run_test_file(file, runner, waiting_duration)
test_command = command_for execution_environment.test_command, file.name_with_extension test_command = command_for execution_environment.test_command, file.filepath
result = {file_role: file.role, waiting_for_container_time: waiting_duration} result = {file_role: file.role, waiting_for_container_time: waiting_duration}
output = runner.execute_command(test_command, raise_exception: false) output = runner.execute_command(test_command, raise_exception: false)
result.merge(output) result.merge(output)
@ -222,7 +221,7 @@ class Submission < ApplicationRecord
end end
def command_for(template, file) def command_for(template, file)
filepath = collect_files.find {|f| f.name_with_extension == file }.filepath filepath = collect_files.find {|f| f.filepath == file }.filepath
template % command_substitutions(filepath) template % command_substitutions(filepath)
end end
@ -255,7 +254,7 @@ class Submission < ApplicationRecord
waiting_for_container_time: output[:waiting_for_container_time] waiting_for_container_time: output[:waiting_for_container_time]
) )
filename = file.name_with_extension filename = file.filepath
if file.teacher_defined_linter? if file.teacher_defined_linter?
LinterCheckRun.create_from(testrun, assessment) LinterCheckRun.create_from(testrun, assessment)
@ -291,8 +290,9 @@ class Submission < ApplicationRecord
score += output[:score] * output[:weight] unless output.nil? score += output[:score] * output[:weight] unless output.nil?
end end
end end
update(score: score) # Prevent floating point precision issues by converting to BigDecimal, e.g., for `0.28 * 25`
if normalized_score.to_d == 1.0.to_d update(score: score.to_d)
if normalized_score.to_d == BigDecimal('1.0')
Thread.new do Thread.new do
RequestForComment.where(exercise_id: exercise_id, user_id: user_id, user_type: user_type).find_each do |rfc| RequestForComment.where(exercise_id: exercise_id, user_id: user_id, user_type: user_type).find_each do |rfc|
rfc.full_score_reached = true rfc.full_score_reached = true

View File

@ -4,8 +4,4 @@ class UserExerciseIntervention < ApplicationRecord
belongs_to :user, polymorphic: true belongs_to :user, polymorphic: true
belongs_to :intervention belongs_to :intervention
belongs_to :exercise belongs_to :exercise
validates :user, presence: true
validates :exercise, presence: true
validates :intervention, presence: true
end end

View File

@ -5,10 +5,5 @@ class UserProxyExerciseExercise < ApplicationRecord
belongs_to :exercise belongs_to :exercise
belongs_to :proxy_exercise belongs_to :proxy_exercise
validates :user_id, presence: true
validates :user_type, presence: true
validates :exercise_id, presence: true
validates :proxy_exercise_id, presence: true
validates :user_id, uniqueness: {scope: %i[proxy_exercise_id user_type]} validates :user_id, uniqueness: {scope: %i[proxy_exercise_id user_type]}
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class ExecutionEnvironmentPolicy < AdminOnlyPolicy class ExecutionEnvironmentPolicy < AdminOnlyPolicy
%i[execute_command? shell? statistics? show?].each do |action| %i[execute_command? shell? statistics? show? sync_to_runner_management?].each do |action|
define_method(action) { admin? || author? } define_method(action) { admin? || author? }
end end

View File

@ -40,9 +40,9 @@ module ProformaService
end end
def task_files def task_files
@task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.map do |task_file| @task_files ||= @task.all_files.reject {|file| file.id == 'ms-placeholder-file' }.to_h do |task_file|
[task_file.id, codeocean_file_from_task_file(task_file)] [task_file.id, codeocean_file_from_task_file(task_file)]
end.to_h end
end end
def codeocean_file_from_task_file(file) def codeocean_file_from_task_file(file)

View File

@ -10,33 +10,38 @@ h1 = t('breadcrumbs.dashboard.show')
h2 Version h2 Version
div.mb-4 div.mb-4
= "CodeOcean Release:" = application_name
=< t("admin.dashboard.show.release")
| :
pre = Sentry.configuration.release pre = Sentry.configuration.release
- if Runner.management_active? - if Runner.management_active?
div.mb-4 div.mb-4
= Runner.strategy_class.name.demodulize = Runner.strategy_class.name.demodulize
=< "Release:" =< t("admin.dashboard.show.release")
pre = Runner.strategy_class.release | :
pre = Admin::DashboardHelper.runner_management_release
h2 Docker h2 Docker
- if Runner.management_active? - if Runner.management_active?
h3 = t('.current') h3 = t('admin.dashboard.show.current')
.table-responsive .table-responsive
table.table table.table
thead thead
tr tr
th = t('activerecord.models.execution_environment.one') th = t('activerecord.models.execution_environment.one')
th = t('activerecord.attributes.execution_environment.pool_size') th = t('activerecord.attributes.execution_environment.pool_size')
th = t('.quantity') th = t('admin.dashboard.show.idleRunners')
th = t('admin.dashboard.show.usedRunners')
tbody tbody
- ExecutionEnvironment.order(:name).each do |execution_environment| - ExecutionEnvironment.order(:name).each do |execution_environment|
tr data-id=execution_environment.id tr data-id=execution_environment.id
td.name = link_to_if(policy(execution_environment).show?, execution_environment, execution_environment) td.name = link_to_if(policy(execution_environment).show?, execution_environment, execution_environment)
td.pool-size td.prewarming-pool-size
td.quantity = progress_bar(0) td.idle-runners = progress_bar(0)
h3 = t('.history') td.used-runners
h3 = t('admin.dashboard.show.history')
#graph #graph
- else - else
p = t('.inactive') p = t('admin.dashboard.show.inactive')

View File

@ -4,13 +4,17 @@
= f.text_field(:name, class: 'form-control', required: true) = f.text_field(:name, class: 'form-control', required: true)
.form-group .form-group
= f.label(:path, t('activerecord.attributes.file.path')) = f.label(:path, t('activerecord.attributes.file.path'))
= f.select(:path, @paths, {}, class: 'form-control') | &nbsp;
a.toggle-input data={text_initial: t('shared.new'), text_toggled: t('shared.back')} href='#' = t('shared.new')
.original-input = f.select(:path, @paths, {}, class: 'form-control')
= f.text_field(:path, class: 'alternative-input form-control', disabled: true)
.form-group .form-group
= f.label(:file_type_id, t('activerecord.attributes.file.file_type_id')) = f.label(:file_type_id, t('activerecord.attributes.file.file_type_id'))
= f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control') = f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control')
.form-group - if FileTemplate.any?
= f.label(:file_template_id, t('activerecord.attributes.file.file_template_id')) .form-group
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control') = f.label(:file_template_id, t('activerecord.attributes.file.file_template_id'))
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control')
= f.hidden_field(:context_id) = f.hidden_field(:context_id)
.d-none#noTemplateLabel data-text=t('file_template.no_template_label') .d-none#noTemplateLabel data-text=t('file_template.no_template_label')
.actions = render('shared/submit_button', f: f, object: CodeOcean::File.new) .actions = render('shared/submit_button', f: f, object: CodeOcean::File.new)

View File

@ -1,3 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
json.extract! @file, :id, :name_with_extension json.extract! @file, :id, :filepath

View File

@ -1,6 +1,12 @@
h1 h1.d-inline-block = @execution_environment
= @execution_environment .btn-group.float-right
= render('shared/edit_button', object: @execution_environment) = render('shared/edit_button', object: @execution_environment)
button.btn.btn-secondary.float-right.dropdown-toggle data-toggle='dropdown' type='button'
ul.dropdown-menu.dropdown-menu-right role='menu'
li = link_to(t('execution_environments.index.synchronize.button'), sync_to_runner_management_execution_environment_path(@execution_environment), method: :post, class: 'dropdown-item text-dark') if policy(@execution_environment).sync_to_runner_management?
li = link_to(t('execution_environments.index.shell'), shell_execution_environment_path(@execution_environment), class: 'dropdown-item text-dark') if policy(@execution_environment).shell?
li = link_to(t('shared.statistics'), statistics_execution_environment_path(@execution_environment), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@execution_environment).statistics?
li = link_to(t('shared.destroy'), @execution_environment, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@execution_environment).destroy?
= row(label: 'execution_environment.name', value: @execution_environment.name) = row(label: 'execution_environment.name', value: @execution_environment.name)
= row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author)) = row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author))

View File

@ -1,4 +1,4 @@
div class=(defined?(own_solution) ? "own-frame" : "frame") data-executable=file.file_type.executable? data-filename=file.name_with_extension data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only div class=(defined?(own_solution) ? "own-frame" : "frame") data-executable=file.file_type.executable? data-filename=file.filepath data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only
- if file.file_type.binary? - if file.file_type.binary?
.binary-file data-file-id=file.ancestor_id .binary-file data-file-id=file.ancestor_id
- if file.file_type.renderable? - if file.file_type.renderable?

View File

@ -7,10 +7,7 @@ li.card.mt-2
a class=['file-heading', collapsed_class] data-toggle="collapse" href="#collapse#{f.index}" aria-expanded="#{aria_expanded}" a class=['file-heading', collapsed_class] data-toggle="collapse" href="#collapse#{f.index}" aria-expanded="#{aria_expanded}"
div.clearfix role="button" div.clearfix role="button"
i class="fa" aria-hidden="true" i class="fa" aria-hidden="true"
- if f.object.name.present? && f.object.file_type.present? span = f.object.filepath
span = f.object.name_with_extension
- else
span = f.object.name
.card-collapse.collapse class=('in' if f.object.name.nil?) id="collapse#{f.index}" role="tabpanel" .card-collapse.collapse class=('in' if f.object.name.nil?) id="collapse#{f.index}" role="tabpanel"
.card-body .card-body
- if policy(f.object).destroy? && id.present? - if policy(f.object).destroy? && id.present?

View File

@ -65,9 +65,9 @@ h1
td.align-middle td.align-middle
-this.testruns.includes(:file).order("files.name").each do |run| -this.testruns.includes(:file).order("files.name").each do |run|
- if run.passed - if run.passed
.unit-test-result.positive-result title=[run.file&.name_with_extension, run.output].join("\n").strip .unit-test-result.positive-result title=[run.file&.filepath, run.output].join("\n").strip
- else - else
.unit-test-result.unknown-result title=[run.file&.name_with_extension, run.output].join("\n").strip .unit-test-result.unknown-result title=[run.file&.filepath, run.output].join("\n").strip
td = @working_times_until[index] if index > 0 if policy(@exercise).detailed_statistics? td = @working_times_until[index] if index > 0 if policy(@exercise).detailed_statistics?
- elsif this.is_a? UserExerciseIntervention - elsif this.is_a? UserExerciseIntervention
td = this.created_at.strftime("%F %T") td = this.created_at.strftime("%F %T")

View File

@ -49,7 +49,7 @@ ul.list-unstyled#files
a.file-heading.collapsed data-toggle="collapse" data-parent="#files" href=".collapse#{file.id}" a.file-heading.collapsed data-toggle="collapse" data-parent="#files" href=".collapse#{file.id}"
div.clearfix role="button" div.clearfix role="button"
i class="fa" aria-hidden="true" i class="fa" aria-hidden="true"
span = file.name_with_extension span = file.filepath
.card-collapse.collapse class="collapse#{file.id}" role="tabpanel" .card-collapse.collapse class="collapse#{file.id}" role="tabpanel"
.card-body .card-body
- if policy(file).destroy? - if policy(file).destroy?

View File

@ -254,7 +254,7 @@ javascript:
function deleteComment(commentId, editor, file_id, callback) { function deleteComment(commentId, editor, file_id, callback) {
var jqxhr = $.ajax({ var jqxhr = $.ajax({
type: 'DELETE', type: 'DELETE',
url: Routes.comments_path(commentId) url: Routes.comment_path(commentId)
}); });
jqxhr.done(function () { jqxhr.done(function () {
setAnnotations(editor, file_id); setAnnotations(editor, file_id);
@ -266,7 +266,7 @@ javascript:
function updateComment(commentId, text, editor, file_id, callback) { function updateComment(commentId, text, editor, file_id, callback) {
var jqxhr = $.ajax({ var jqxhr = $.ajax({
type: 'PATCH', type: 'PATCH',
url: Routes.comments_path(commentId), url: Routes.comment_path(commentId),
data: { data: {
comment: { comment: {
text: text text: text

View File

@ -7,4 +7,4 @@
- if file.teacher_defined_assessment? - if file.teacher_defined_assessment?
= row(label: 'file.feedback_message', value: render_markdown(file.feedback_message), class: 'm-0') = row(label: 'file.feedback_message', value: render_markdown(file.feedback_message), class: 'm-0')
= row(label: 'file.weight', value: file.weight) = row(label: 'file.weight', value: file.weight)
= row(label: 'file.content', value: file.native_file? ? link_to_if(policy(file).show?, file.native_file.file.filename, file.native_file.url) : code_tag(file.content)) = row(label: 'file.content', value: file.native_file? ? link_to_if(policy(file).show?, file.native_file.file.filename, file.native_file.url) : code_tag(file.content, file.file_type.programming_language))

View File

@ -7,5 +7,33 @@ Sentry.init do |config|
# Set tracesSampleRate to 1.0 to capture 100% # Set tracesSampleRate to 1.0 to capture 100%
# of transactions for performance monitoring. # of transactions for performance monitoring.
# We recommend adjusting this value in production # We recommend adjusting this value in production
config.traces_sample_rate = 0.01 config.traces_sampler = lambda do |sampling_context|
# if this is the continuation of a trace, just use that decision (rate controlled by the caller)
unless sampling_context[:parent_sampled].nil?
next sampling_context[:parent_sampled]
end
# transaction_context is the transaction object in hash form
# keep in mind that sampling happens right after the transaction is initialized
# for example, at the beginning of the request
transaction_context = sampling_context[:transaction_context]
# transaction_context helps you sample transactions with more sophistication
# for example, you can provide different sample rates based on the operation or name
op = transaction_context[:op]
transaction_name = transaction_context[:name]
case op
when /request/
# for Rails applications, transaction_name would be the request's path (env["PATH_INFO"]) instead of "Controller#action"
case transaction_name
when '/', '/ping'
0.00 # ignore health check
else
0.05
end
else
0.0 # ignore all other transactions
end
end
end end

View File

@ -193,7 +193,7 @@ de:
external_user: external_user:
one: Externer Nutzer one: Externer Nutzer
other: Externe Nutzer other: Externe Nutzer
file: code_ocean/file:
one: Datei one: Datei
other: Dateien other: Dateien
file_template: file_template:
@ -241,10 +241,12 @@ de:
admin: admin:
dashboard: dashboard:
show: show:
release: Release
current: Aktuelle Verfügbarkeit current: Aktuelle Verfügbarkeit
history: Verfügbarkeitsverlauf history: Nutzungsverlauf
inactive: Es ist kein Runner Management aktiv. inactive: Es ist kein Runner Management aktiv.
quantity: Verfügbare Container idleRunners: Freie Runner
usedRunners: Reservierte Runner
application: application:
not_authorized: Sie Sind nicht berechtigt, diese Aktion auszuführen. not_authorized: Sie Sind nicht berechtigt, diese Aktion auszuführen.
welcome: welcome:
@ -301,6 +303,10 @@ de:
not_synced_to_runner_management: Die Ausführungsumgebung wurde erstellt, aber aufgrund eines Fehlers nicht zum Runnermanagement synchronisiert. not_synced_to_runner_management: Die Ausführungsumgebung wurde erstellt, aber aufgrund eines Fehlers nicht zum Runnermanagement synchronisiert.
index: index:
shell: Shell shell: Shell
synchronize:
button: Synchronisieren
success: Die Ausführungsumgebung wurde erfolgreich synchronisiert.
failure: "Beim Synchronisieren der Ausführungsumgebung ist folgender Fehler aufgetreten: %{error}"
synchronize_all: synchronize_all:
button: Alle synchronisieren button: Alle synchronisieren
success: Alle Ausführungsumgebungen wurden erfolgreich synchronisiert. success: Alle Ausführungsumgebungen wurden erfolgreich synchronisiert.

View File

@ -193,7 +193,7 @@ en:
external_user: external_user:
one: External User one: External User
other: External Users other: External Users
file: code_ocean/file:
one: File one: File
other: Files other: Files
file_template: file_template:
@ -241,10 +241,12 @@ en:
admin: admin:
dashboard: dashboard:
show: show:
release: Release
current: Current Availability current: Current Availability
history: Availability History history: Usage History
inactive: No runner management is currently enabled. inactive: No runner management is currently enabled.
quantity: Available Containers idleRunners: Idle Runners
usedRunners: Reserved Runners
application: application:
not_authorized: You are not authorized to perform this action. not_authorized: You are not authorized to perform this action.
welcome: welcome:
@ -301,9 +303,13 @@ en:
not_synced_to_runner_management: The execution environment was created but not synced to the runner management due to an error. not_synced_to_runner_management: The execution environment was created but not synced to the runner management due to an error.
index: index:
shell: Shell shell: Shell
synchronize:
button: Synchronize
success: The execution environment was synchronized successfully.
failure: "The execution environment could not be synchronised due to the following error: %{error}"
synchronize_all: synchronize_all:
button: Synchronize all button: Synchronize all
success: All execution environemnts were synchronized successfully. success: All execution environments were synchronized successfully.
failure: At least one execution environment could not be synchronised due to an error. failure: At least one execution environment could not be synchronised due to an error.
shell: shell:
command: Command command: Command

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
FILENAME_REGEXP = /[\w.]+/.freeze unless Kernel.const_defined?(:FILENAME_REGEXP) FILENAME_REGEXP = %r{[\w./]+}.freeze unless Kernel.const_defined?(:FILENAME_REGEXP)
Rails.application.routes.draw do Rails.application.routes.draw do
resources :community_solutions, only: %i[index edit update] resources :community_solutions, only: %i[index edit update]
@ -66,6 +66,7 @@ Rails.application.routes.draw do
get :shell get :shell
post 'shell', as: :execute_command, action: :execute_command post 'shell', as: :execute_command, action: :execute_command
get :statistics get :statistics
post :sync_to_runner_management
end end
post :sync_all_to_runner_management, on: :collection post :sync_all_to_runner_management, on: :collection

View File

@ -9,7 +9,6 @@ class DropErrors < ActiveRecord::Migration[5.2]
scope :for_execution_environment, ->(execution_environment) { where(execution_environment_id: execution_environment.id) } scope :for_execution_environment, ->(execution_environment) { where(execution_environment_id: execution_environment.id) }
scope :grouped_by_message, -> { select('MAX(created_at) AS created_at, MAX(id) AS id, message, COUNT(id) AS count').group(:message).order('count DESC') } scope :grouped_by_message, -> { select('MAX(created_at) AS created_at, MAX(id) AS id, message, COUNT(id) AS count').group(:message).order('count DESC') }
validates :execution_environment_id, presence: true
validates :message, presence: true validates :message, presence: true
def self.nested_resource? def self.nested_resource?

View File

@ -8,7 +8,7 @@ module ActiveModel
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 BOOLEAN_VALUES.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_d != 0.0.to_d && test_outcome[:count].to_d != 0.0.to_d if test_outcome[:passed].to_d != BigDecimal('0.0') && test_outcome[:count].to_d != BigDecimal('0.0')
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,9 @@ 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, aliases: true, permitted_classes: [Range]) : YAML.load_file(path) yaml_content = ::File.new(path, 'r').read || ''
yaml_content = ERB.new(yaml_content).result if options[:erb]
content = YAML.safe_load(yaml_content, aliases: true, permitted_classes: [Range, Symbol])
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

@ -17,7 +17,7 @@ class CppCatch2Adapter < TestingFrameworkAdapter
count = output[:stdout].scan(COUNT_REGEXP).try(:last).try(:first).try(:to_i) || 0 count = output[:stdout].scan(COUNT_REGEXP).try(:last).try(:first).try(:to_i) || 0
failed = output[:stdout].scan(FAILURES_REGEXP).try(:last).try(:first).try(:to_i) || 0 failed = output[:stdout].scan(FAILURES_REGEXP).try(:last).try(:first).try(:to_i) || 0
error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || [] error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || []
{count: count, failed: failed, error_messages: error_matches.flatten.reject(&:blank?)} {count: count, failed: failed, error_messages: error_matches.flatten.compact_blank}
end end
end end
end end

View File

@ -315,7 +315,7 @@ container_execution_time: nil}
@tubesock&.send_data JSON.dump({'cmd' => 'timeout'}) @tubesock&.send_data JSON.dump({'cmd' => 'timeout'})
if @socket if @socket
begin begin
@socket.send('#timeout') @socket.send('#timeout') # rubocop:disable Performance/StringIdentifierArgument
# sleep one more second to ensure that the message reaches the submissions_controller. # sleep one more second to ensure that the message reaches the submissions_controller.
sleep(1) sleep(1)
@socket.close @socket.close
@ -434,9 +434,9 @@ container_execution_time: nil}
end end
def self.mapped_ports(execution_environment) def self.mapped_ports(execution_environment)
execution_environment.exposed_ports.map do |port| execution_environment.exposed_ports.to_h do |port|
["#{port}/tcp", [{'HostPort' => PortPool.available_port.to_s}]] ["#{port}/tcp", [{'HostPort' => PortPool.available_port.to_s}]]
end.to_h end
end end
def self.pull(docker_image) def self.pull(docker_image)

View File

@ -29,7 +29,7 @@ class FileTree
# Our tree needs a root node, but we won't display it. # Our tree needs a root node, but we won't display it.
@root = Tree::TreeNode.new('ROOT') @root = Tree::TreeNode.new('ROOT')
files.uniq(&:name_with_extension).each do |file| files.uniq(&:filepath).each do |file|
parent = @root parent = @root
(file.path || '').split('/').each do |segment| (file.path || '').split('/').each do |segment|
node = parent.children.detect {|child| child.name == segment } || parent.add(Tree::TreeNode.new(segment)) node = parent.children.detect {|child| child.name == segment } || parent.add(Tree::TreeNode.new(segment))

View File

@ -16,7 +16,7 @@ class Junit5Adapter < TestingFrameworkAdapter
{count: count, passed: count} {count: count, passed: count}
else else
error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || [] error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || []
{count: count, failed: failed, error_messages: error_matches.flatten.reject(&:blank?)} {count: count, failed: failed, error_messages: error_matches.flatten.compact_blank}
end end
end end
end end

View File

@ -17,7 +17,7 @@ class JunitAdapter < TestingFrameworkAdapter
count = output[:stdout].scan(COUNT_REGEXP).try(:last).try(:first).try(:to_i) || 0 count = output[:stdout].scan(COUNT_REGEXP).try(:last).try(:first).try(:to_i) || 0
failed = output[:stdout].scan(FAILURES_REGEXP).try(:last).try(:first).try(:to_i) || 0 failed = output[:stdout].scan(FAILURES_REGEXP).try(:last).try(:first).try(:to_i) || 0
error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || [] error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || []
{count: count, failed: failed, error_messages: error_matches.flatten.reject(&:blank?)} {count: count, failed: failed, error_messages: error_matches.flatten.compact_blank}
end end
end end
end end

View File

@ -43,8 +43,8 @@ class PyLintAdapter < TestingFrameworkAdapter
{ {
count: count, count: count,
failed: failed, failed: failed,
error_messages: concatenated_errors.flatten.reject(&:blank?), error_messages: concatenated_errors.flatten.compact_blank,
detailed_linter_results: assertion_error_matches.flatten.reject(&:blank?), detailed_linter_results: assertion_error_matches.flatten.compact_blank,
} }
end end

View File

@ -32,6 +32,6 @@ class PyUnitAdapter < TestingFrameworkAdapter
Sentry.capture_message({stderr: output[:stderr], regex: ASSERTION_ERROR_REGEXP}.to_json) Sentry.capture_message({stderr: output[:stderr], regex: ASSERTION_ERROR_REGEXP}.to_json)
assertion_error_matches = [] assertion_error_matches = []
end end
{count: count, failed: failed + errors, error_messages: assertion_error_matches.flatten.reject(&:blank?)} {count: count, failed: failed + errors, error_messages: assertion_error_matches.flatten.compact_blank}
end end
end end

View File

@ -14,6 +14,6 @@ class RScriptAdapter < TestingFrameworkAdapter
passed = captures.second passed = captures.second
failed = count - passed failed = count - passed
assertion_error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || [] assertion_error_matches = output[:stdout].scan(ASSERTION_ERROR_REGEXP) || []
{count: count, failed: failed, error_messages: assertion_error_matches.flatten.reject(&:blank?)} {count: count, failed: failed, error_messages: assertion_error_matches.flatten.compact_blank}
end end
end end

View File

@ -97,7 +97,7 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
FileUtils.cp(file.native_file.path, local_file_path) FileUtils.cp(file.native_file.path, local_file_path)
else else
begin begin
File.open(local_file_path, 'w') {|f| f.write(file.content) } File.write(local_file_path, file.content)
rescue IOError => e rescue IOError => e
raise Runner::Error::WorkspaceError.new("Could not create file #{file.filepath}: #{e.inspect}") raise Runner::Error::WorkspaceError.new("Could not create file #{file.filepath}: #{e.inspect}")
end end
@ -166,7 +166,7 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
url = "#{config[:url]}/docker_container_pool/quantities" url = "#{config[:url]}/docker_container_pool/quantities"
response = Faraday.get(url) response = Faraday.get(url)
pool_size = JSON.parse(response.body) pool_size = JSON.parse(response.body)
pool_size.transform_keys(&:to_i) pool_size.deep_symbolize_keys
rescue Faraday::Error => e rescue Faraday::Error => e
raise Runner::Error::FaradayError.new("Request to DockerContainerPool failed: #{e.inspect}") raise Runner::Error::FaradayError.new("Request to DockerContainerPool failed: #{e.inspect}")
rescue JSON::ParserError => e rescue JSON::ParserError => e
@ -259,7 +259,7 @@ class Runner::Strategy::DockerContainerPool < Runner::Strategy
# TODO: Super dirty hack to redirect test output to stderr # TODO: Super dirty hack to redirect test output to stderr
# This is only required for Python and the unittest module but must not be used with PyLint # This is only required for Python and the unittest module but must not be used with PyLint
@stream = 'stderr' @stream = 'stderr'
when /\*\*\*\*\*\*\*\*\*\*\*\*\* Module/ when /\*\*\*\*\*\*\*\*\*\*\*\*\* Module/, / Your code has been rated at/
# Identification of PyLint output, change stream back to stdout and return event # Identification of PyLint output, change stream back to stdout and return event
@stream = 'stdout' @stream = 'stdout'
{'type' => @stream, 'data' => event_data} {'type' => @stream, 'data' => event_data}

View File

@ -152,11 +152,44 @@ class Runner::Strategy::Poseidon < Runner::Strategy
end end
def self.release def self.release
nil url = "#{config[:url]}/version"
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Getting release from #{url}" }
response = http_connection.get url
case response.status
when 200
JSON.parse(response.body)
when 404
'N/A'
else
handle_error response
end
rescue Faraday::Error => e
raise Runner::Error::FaradayError.new("Request to Poseidon failed: #{e.inspect}")
rescue JSON::ParserError => e
# Poseidon should not send invalid json
raise Runner::Error::UnexpectedResponse.new("Error parsing response from Poseidon: #{e.message}")
ensure
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished getting release information" }
end end
def self.pool_size def self.pool_size
{} url = "#{config[:url]}/statistics/execution-environments"
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Getting statistics from #{url}" }
response = http_connection.get url
case response.status
when 200
response_body = parse response
response_body
else
handle_error response
end
rescue Faraday::Error => e
raise Runner::Error::FaradayError.new("Request to Poseidon failed: #{e.inspect}")
rescue JSON::ParserError => e
# Poseidon should not send invalid json
raise Runner::Error::UnexpectedResponse.new("Error parsing response from Poseidon: #{e.message}")
ensure
Rails.logger.debug { "#{Time.zone.now.getutc.inspect}: Finished getting statistics" }
end end
def self.websocket_header def self.websocket_header

View File

@ -6,9 +6,9 @@
"bootstrap": "^4.6.1", "bootstrap": "^4.6.1",
"bootswatch": "^4.6.0", "bootswatch": "^4.6.0",
"chosen-js": "^1.8.7", "chosen-js": "^1.8.7",
"d3": "^7.1.1", "d3": "^7.3.0",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"highlight.js": "^11.3.1", "highlight.js": "^11.4.0",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"jquery-ui": "^1.13.0", "jquery-ui": "^1.13.0",
"jquery-ujs": "^1.2.3", "jquery-ujs": "^1.2.3",
@ -17,7 +17,7 @@
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"rails-erb-loader": "^5.5.2", "rails-erb-loader": "^5.5.2",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"underscore": "^1.13.1", "underscore": "^1.13.2",
"vis": "^4.21.0", "vis": "^4.21.0",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },

View File

@ -2,9 +2,9 @@
######## VERSION INFORMATION ######## ######## VERSION INFORMATION ########
postgres_version=13 postgres_version=14
node_version=14 node_version=14
ruby_version=2.7.2 ruby_version=2.7.5
########## INSTALL SCRIPT ########### ########## INSTALL SCRIPT ###########
@ -24,7 +24,7 @@ sudo apt -qq -y upgrade
# PostgreSQL # PostgreSQL
curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt -qq update && sudo apt -qq install -y postgresql-client postgresql sudo apt -qq update && sudo apt -qq install -y postgresql-client-$postgres_version postgresql-$postgres_version
sudo sed -i "/# TYPE/q" /etc/postgresql/$postgres_version/main/pg_hba.conf sudo sed -i "/# TYPE/q" /etc/postgresql/$postgres_version/main/pg_hba.conf
sudo tee -a /etc/postgresql/$postgres_version/main/pg_hba.conf <<EOF sudo tee -a /etc/postgresql/$postgres_version/main/pg_hba.conf <<EOF

View File

@ -8,11 +8,11 @@ end
describe FileParameters do describe FileParameters do
let(:controller) { Controller.new } let(:controller) { Controller.new }
let(:hello_world) { FactoryBot.create(:hello_world) } let(:hello_world) { create(:hello_world) }
describe '#reject_illegal_file_attributes!' do describe '#reject_illegal_file_attributes!' do
def file_accepted?(file) def file_accepted?(file)
files = [[0, FactoryBot.attributes_for(:file, context: hello_world, file_id: file.id)]] files = [[0, attributes_for(:file, context: hello_world, file_id: file.id)]]
filtered_files = controller.send(:reject_illegal_file_attributes, hello_world, files) filtered_files = controller.send(:reject_illegal_file_attributes, hello_world, files)
files.eql?(filtered_files) files.eql?(filtered_files)
end end
@ -24,31 +24,31 @@ describe FileParameters do
end end
it 'new file' do it 'new file' do
submission = FactoryBot.create(:submission, exercise: hello_world, id: 1337) submission = create(:submission, exercise: hello_world, id: 1337)
new_file = FactoryBot.create(:file, context: submission) new_file = create(:file, context: submission)
expect(file_accepted?(new_file)).to be true expect(file_accepted?(new_file)).to be true
end end
end end
describe 'rejects' do describe 'rejects' do
it 'file of different exercise' do it 'file of different exercise' do
fibonacci = FactoryBot.create(:fibonacci, allow_file_creation: true) fibonacci = create(:fibonacci, allow_file_creation: true)
other_exercises_file = FactoryBot.create(:file, context: fibonacci) other_exercises_file = create(:file, context: fibonacci)
expect(file_accepted?(other_exercises_file)).to be false expect(file_accepted?(other_exercises_file)).to be false
end end
it 'hidden file' do it 'hidden file' do
hidden_file = FactoryBot.create(:file, context: hello_world, hidden: true) hidden_file = create(:file, context: hello_world, hidden: true)
expect(file_accepted?(hidden_file)).to be false expect(file_accepted?(hidden_file)).to be false
end end
it 'read only file' do it 'read only file' do
read_only_file = FactoryBot.create(:file, context: hello_world, read_only: true) read_only_file = create(:file, context: hello_world, read_only: true)
expect(file_accepted?(read_only_file)).to be false expect(file_accepted?(read_only_file)).to be false
end end
it 'non existent file' do it 'non existent file' do
non_existent_file = FactoryBot.build(:file, context: hello_world, id: 42) non_existent_file = build(:file, context: hello_world, id: 42)
expect(file_accepted?(non_existent_file)).to be false expect(file_accepted?(non_existent_file)).to be false
end end
end end

View File

@ -13,7 +13,7 @@ describe Lti do
describe '#build_tool_provider' do describe '#build_tool_provider' do
it 'instantiates a tool provider' do it 'instantiates a tool provider' do
expect(IMS::LTI::ToolProvider).to receive(:new) expect(IMS::LTI::ToolProvider).to receive(:new)
controller.send(:build_tool_provider, consumer: FactoryBot.build(:consumer), parameters: {}) controller.send(:build_tool_provider, consumer: build(:consumer), parameters: {})
end end
end end
@ -101,12 +101,12 @@ describe Lti do
end end
describe '#send_score' do describe '#send_score' do
let(:consumer) { FactoryBot.create(:consumer) } let(:consumer) { create(:consumer) }
let(:score) { 0.5 } let(:score) { 0.5 }
let(:submission) { FactoryBot.create(:submission) } let(:submission) { create(:submission) }
before do before do
FactoryBot.create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id) create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id)
end end
context 'with an invalid score' do context 'with an invalid score' do
@ -168,18 +168,18 @@ describe Lti do
let(:parameters) { ActionController::Parameters.new({}) } let(:parameters) { ActionController::Parameters.new({}) }
it 'stores data in the session' do it 'stores data in the session' do
controller.instance_variable_set(:@current_user, FactoryBot.create(:external_user)) controller.instance_variable_set(:@current_user, create(:external_user))
controller.instance_variable_set(:@exercise, FactoryBot.create(:fibonacci)) controller.instance_variable_set(:@exercise, create(:fibonacci))
expect(controller.session).to receive(:[]=).with(:external_user_id, anything) expect(controller.session).to receive(:[]=).with(:external_user_id, anything)
expect(controller.session).to receive(:[]=).with(:lti_parameters_id, anything) expect(controller.session).to receive(:[]=).with(:lti_parameters_id, anything)
controller.send(:store_lti_session_data, consumer: FactoryBot.build(:consumer), parameters: parameters) controller.send(:store_lti_session_data, consumer: build(:consumer), parameters: parameters)
end end
it 'creates an LtiParameter Object' do it 'creates an LtiParameter Object' do
before_count = LtiParameter.count before_count = LtiParameter.count
controller.instance_variable_set(:@current_user, FactoryBot.create(:external_user)) controller.instance_variable_set(:@current_user, create(:external_user))
controller.instance_variable_set(:@exercise, FactoryBot.create(:fibonacci)) controller.instance_variable_set(:@exercise, create(:fibonacci))
controller.send(:store_lti_session_data, consumer: FactoryBot.build(:consumer), parameters: parameters) controller.send(:store_lti_session_data, consumer: build(:consumer), parameters: parameters)
expect(LtiParameter.count).to eq(before_count + 1) expect(LtiParameter.count).to eq(before_count + 1)
end end
end end

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe Admin::DashboardController do describe Admin::DashboardController do
before { allow(controller).to receive(:current_user).and_return(FactoryBot.build(:admin)) } before { allow(controller).to receive(:current_user).and_return(build(:admin)) }
describe 'GET #show' do describe 'GET #show' do
describe 'with format HTML' do describe 'with format HTML' do

View File

@ -5,7 +5,7 @@ require 'rails_helper'
describe ApplicationController do describe ApplicationController do
describe '#current_user' do describe '#current_user' do
context 'with an external user' do context 'with an external user' do
let(:external_user) { FactoryBot.create(:external_user) } let(:external_user) { create(:external_user) }
before { session[:external_user_id] = external_user.id } before { session[:external_user_id] = external_user.id }
@ -15,7 +15,7 @@ describe ApplicationController do
end end
context 'without an external user' do context 'without an external user' do
let(:internal_user) { FactoryBot.create(:teacher) } let(:internal_user) { create(:teacher) }
before { login_user(internal_user) } before { login_user(internal_user) }

View File

@ -3,15 +3,15 @@
require 'rails_helper' require 'rails_helper'
describe CodeOcean::FilesController do describe CodeOcean::FilesController do
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
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
let(:submission) { FactoryBot.create(:submission, user: user) } let(:submission) { create(:submission, user: user) }
context 'with a valid file' do context 'with a valid file' do
let(:perform_request) { proc { post :create, params: {code_ocean_file: FactoryBot.build(:file, context: submission).attributes, format: :json} } } let(:perform_request) { proc { post :create, params: {code_ocean_file: build(:file, context: submission).attributes, format: :json} } }
before do before do
submission.exercise.update(allow_file_creation: true) submission.exercise.update(allow_file_creation: true)
@ -41,7 +41,7 @@ describe CodeOcean::FilesController do
end end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
let(:perform_request) { proc { delete :destroy, params: {id: exercise.files.first.id} } } let(:perform_request) { proc { delete :destroy, params: {id: exercise.files.first.id} } }
before { perform_request.call } before { perform_request.call }
@ -49,7 +49,7 @@ describe CodeOcean::FilesController do
expect_assigns(file: CodeOcean::File) expect_assigns(file: CodeOcean::File)
it 'destroys the file' do it 'destroys the file' do
FactoryBot.create(:fibonacci) create(:fibonacci)
expect { perform_request.call }.to change(CodeOcean::File, :count).by(-1) expect { perform_request.call }.to change(CodeOcean::File, :count).by(-1)
end end

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe CodeharborLinksController do describe CodeharborLinksController do
let(:user) { FactoryBot.create(:teacher) } let(:user) { create(:teacher) }
let(:codeocean_config) { instance_double(CodeOcean::Config) } let(:codeocean_config) { instance_double(CodeOcean::Config) }
let(:codeharbor_config) { {codeharbor: {enabled: true, url: 'http://test.url'}} } let(:codeharbor_config) { {codeharbor: {enabled: true, url: 'http://test.url'}} }
@ -23,7 +23,7 @@ describe CodeharborLinksController do
end end
describe 'GET #edit' do describe 'GET #edit' do
let(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let(:codeharbor_link) { create(:codeharbor_link, user: user) }
before { get :edit, params: {id: codeharbor_link.id} } before { get :edit, params: {id: codeharbor_link.id} }
@ -57,7 +57,7 @@ describe CodeharborLinksController do
end end
describe 'PUT #update' do describe 'PUT #update' do
let(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let(:codeharbor_link) { create(:codeharbor_link, user: user) }
let(:put_request) { patch :update, params: {id: codeharbor_link.id, codeharbor_link: params} } let(:put_request) { patch :update, params: {id: codeharbor_link.id, codeharbor_link: params} }
let(:params) { {push_url: 'http://foo.bar/push', check_uuid_url: 'http://foo.bar/check', api_key: 'api_key'} } let(:params) { {push_url: 'http://foo.bar/push', check_uuid_url: 'http://foo.bar/check', api_key: 'api_key'} }
@ -92,7 +92,7 @@ describe CodeharborLinksController do
end end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let!(:codeharbor_link) { create(:codeharbor_link, user: user) }
let(:destroy_request) { delete :destroy, params: {id: codeharbor_link.id} } let(:destroy_request) { delete :destroy, params: {id: codeharbor_link.id} }
it 'deletes codeharbor_link' do it 'deletes codeharbor_link' do

View File

@ -3,14 +3,14 @@
require 'rails_helper' require 'rails_helper'
describe ConsumersController do describe ConsumersController do
let(:consumer) { FactoryBot.create(:consumer) } let(:consumer) { create(:consumer) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
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
context 'with a valid consumer' do context 'with a valid consumer' do
let(:perform_request) { proc { post :create, params: {consumer: FactoryBot.attributes_for(:consumer)} } } let(:perform_request) { proc { post :create, params: {consumer: attributes_for(:consumer)} } }
before { perform_request.call } before { perform_request.call }
@ -38,7 +38,7 @@ describe ConsumersController do
expect_assigns(consumer: Consumer) expect_assigns(consumer: Consumer)
it 'destroys the consumer' do it 'destroys the consumer' do
consumer = FactoryBot.create(:consumer) consumer = create(:consumer)
expect { delete :destroy, params: {id: consumer.id} }.to change(Consumer, :count).by(-1) expect { delete :destroy, params: {id: consumer.id} }.to change(Consumer, :count).by(-1)
end end
@ -55,7 +55,7 @@ describe ConsumersController do
describe 'GET #index' do describe 'GET #index' do
before do before do
FactoryBot.create_pair(:consumer) create_pair(:consumer)
get :index get :index
end end
@ -82,7 +82,7 @@ describe ConsumersController do
describe 'PUT #update' do describe 'PUT #update' do
context 'with a valid consumer' do context 'with a valid consumer' do
before { put :update, params: {consumer: FactoryBot.attributes_for(:consumer), id: consumer.id} } before { put :update, params: {consumer: attributes_for(:consumer), id: consumer.id} }
expect_assigns(consumer: Consumer) expect_assigns(consumer: Consumer)
expect_redirect(:consumer) expect_redirect(:consumer)

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe ErrorTemplateAttributesController do describe ErrorTemplateAttributesController do
let!(:error_template_attribute) { FactoryBot.create(:error_template_attribute) } let!(:error_template_attribute) { create(:error_template_attribute) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
@ -35,7 +35,7 @@ describe ErrorTemplateAttributesController do
end end
it 'updates error_template_attribute' do it 'updates error_template_attribute' do
patch :update, params: {id: error_template_attribute, error_template_attribute: FactoryBot.attributes_for(:error_template_attribute)} patch :update, params: {id: error_template_attribute, error_template_attribute: attributes_for(:error_template_attribute)}
expect(response).to redirect_to(error_template_attribute_path(assigns(:error_template_attribute))) expect(response).to redirect_to(error_template_attribute_path(assigns(:error_template_attribute)))
end end

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe ErrorTemplatesController do describe ErrorTemplatesController do
let!(:error_template) { FactoryBot.create(:error_template) } let!(:error_template) { create(:error_template) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
@ -35,7 +35,7 @@ describe ErrorTemplatesController do
end end
it 'updates error_template' do it 'updates error_template' do
patch :update, params: {id: error_template, error_template: FactoryBot.attributes_for(:error_template)} patch :update, params: {id: error_template, error_template: attributes_for(:error_template)}
expect(response).to redirect_to(error_template_path(assigns(:error_template))) expect(response).to redirect_to(error_template_path(assigns(:error_template)))
end end

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe EventsController do describe EventsController do
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe ExecutionEnvironmentsController do describe ExecutionEnvironmentsController do
let(:execution_environment) { FactoryBot.create(:ruby) } let(:execution_environment) { create(:ruby) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before do before do
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
@ -13,7 +13,7 @@ describe ExecutionEnvironmentsController do
describe 'POST #create' do describe 'POST #create' do
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, pool_size: 1).attributes} } } let(:perform_request) { proc { post :create, params: {execution_environment: build(:ruby, pool_size: 1).attributes} } }
before do before do
allow(Rails.env).to receive(:test?).and_return(false, true) allow(Rails.env).to receive(:test?).and_return(false, true)
@ -64,7 +64,7 @@ describe ExecutionEnvironmentsController do
expect_assigns(execution_environment: :execution_environment) expect_assigns(execution_environment: :execution_environment)
it 'destroys the execution environment' do it 'destroys the execution environment' do
execution_environment = FactoryBot.create(:ruby) execution_environment = create(:ruby)
expect { delete :destroy, params: {id: execution_environment.id} }.to change(ExecutionEnvironment, :count).by(-1) expect { delete :destroy, params: {id: execution_environment.id} }.to change(ExecutionEnvironment, :count).by(-1)
end end
@ -103,7 +103,7 @@ describe ExecutionEnvironmentsController do
describe 'GET #index' do describe 'GET #index' do
before do before do
FactoryBot.create_pair(:ruby) create_pair(:ruby)
get :index get :index
end end
@ -186,7 +186,7 @@ describe ExecutionEnvironmentsController do
runner = instance_double 'runner' runner = instance_double 'runner'
allow(Runner).to receive(:for).and_return(runner) allow(Runner).to receive(:for).and_return(runner)
allow(runner).to receive(:execute_command).and_return({}) allow(runner).to receive(:execute_command).and_return({})
put :update, params: {execution_environment: FactoryBot.attributes_for(:ruby, pool_size: 1), id: execution_environment.id} put :update, params: {execution_environment: attributes_for(:ruby, pool_size: 1), id: execution_environment.id}
end end
expect_assigns(docker_images: Array) expect_assigns(docker_images: Array)
@ -216,8 +216,8 @@ describe ExecutionEnvironmentsController do
end end
describe '#sync_all_to_runner_management' do describe '#sync_all_to_runner_management' do
let(:execution_environments) { %i[ruby java python].map {|environment| FactoryBot.create(environment) } } let(:execution_environments) { %i[ruby java python].map {|environment| create(environment) } }
let(:outdated_execution_environments) { %i[node_js html].map {|environment| FactoryBot.build_stubbed(environment) } } let(:outdated_execution_environments) { %i[node_js html].map {|environment| build_stubbed(environment) } }
let(:codeocean_config) { instance_double(CodeOcean::Config) } let(:codeocean_config) { instance_double(CodeOcean::Config) }
let(:runner_management_config) { {runner_management: {enabled: true, strategy: :poseidon}} } let(:runner_management_config) { {runner_management: {enabled: true, strategy: :poseidon}} }

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe ExercisesController do describe ExercisesController do
let(:exercise) { FactoryBot.create(:dummy) } let(:exercise) { create(:dummy) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
@ -56,7 +56,7 @@ describe ExercisesController do
end end
describe 'POST #create' do describe 'POST #create' do
let(:exercise_attributes) { FactoryBot.build(:dummy).attributes } let(:exercise_attributes) { build(:dummy).attributes }
context 'with a valid exercise' do context 'with a valid exercise' do
let(:perform_request) { proc { post :create, params: {exercise: exercise_attributes} } } let(:perform_request) { proc { post :create, params: {exercise: exercise_attributes} } }
@ -76,7 +76,7 @@ describe ExercisesController do
let(:perform_request) { proc { post :create, params: {exercise: exercise_attributes.merge(files_attributes: files_attributes)} } } let(:perform_request) { proc { post :create, params: {exercise: exercise_attributes.merge(files_attributes: files_attributes)} } }
context 'when specifying the file content within the form' do context 'when specifying the file content within the form' do
let(:files_attributes) { {'0' => FactoryBot.build(:file).attributes} } let(:files_attributes) { {'0' => build(:file).attributes} }
it 'creates the file' do it 'creates the file' do
expect { perform_request.call }.to change(CodeOcean::File, :count) expect { perform_request.call }.to change(CodeOcean::File, :count)
@ -84,11 +84,11 @@ describe ExercisesController do
end end
context 'when uploading a file' do context 'when uploading a file' do
let(:files_attributes) { {'0' => FactoryBot.build(:file, file_type: file_type).attributes.merge(content: uploaded_file)} } let(:files_attributes) { {'0' => 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) { 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) }
it 'creates the file' do it 'creates the file' do
@ -103,7 +103,7 @@ describe ExercisesController do
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) { 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) }
it 'creates the file' do it 'creates the file' do
@ -133,7 +133,7 @@ describe ExercisesController do
expect_assigns(exercise: :exercise) expect_assigns(exercise: :exercise)
it 'destroys the exercise' do it 'destroys the exercise' do
exercise = FactoryBot.create(:dummy) exercise = create(:dummy)
expect { delete :destroy, params: {id: exercise.id} }.to change(Exercise, :count).by(-1) expect { delete :destroy, params: {id: exercise.id} }.to change(Exercise, :count).by(-1)
end end
@ -152,14 +152,14 @@ describe ExercisesController do
let(:perform_request) { proc { get :implement, params: {id: exercise.id} } } let(:perform_request) { proc { get :implement, params: {id: exercise.id} } }
context 'with an exercise with visible files' do context 'with an exercise with visible files' do
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
before { perform_request.call } before { perform_request.call }
expect_assigns(exercise: :exercise) expect_assigns(exercise: :exercise)
context 'with an existing submission' do context 'with an existing submission' do
let!(:submission) { FactoryBot.create(:submission, exercise_id: exercise.id, user_id: user.id, user_type: user.class.name) } let!(:submission) { create(:submission, exercise_id: exercise.id, user_id: user.id, user_type: user.class.name) }
it "populates the editors with the submission's files' content" do it "populates the editors with the submission's files' content" do
perform_request.call perform_request.call
@ -190,7 +190,7 @@ describe ExercisesController do
let(:scope) { Pundit.policy_scope!(user, Exercise) } let(:scope) { Pundit.policy_scope!(user, Exercise) }
before do before do
FactoryBot.create_pair(:dummy) create_pair(:dummy)
get :index get :index
end end
@ -239,7 +239,7 @@ describe ExercisesController do
describe 'POST #submit' do describe 'POST #submit' 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) { create(:external_user) }
let(:scoring_response) do let(:scoring_response) do
[{ [{
status: :ok, status: :ok,
@ -260,8 +260,8 @@ describe ExercisesController do
end end
before do before do
FactoryBot.create(:lti_parameter, external_user: user, exercise: exercise) create(:lti_parameter, external_user: user, exercise: exercise)
submission = FactoryBot.build(:submission, exercise: exercise, user: user) submission = build(:submission, exercise: exercise, user: user)
allow(submission).to receive(:normalized_score).and_return(1) allow(submission).to receive(:normalized_score).and_return(1)
allow(submission).to receive(:calculate_score).and_return(scoring_response) allow(submission).to receive(:calculate_score).and_return(scoring_response)
allow(Submission).to receive(:create).and_return(submission) allow(Submission).to receive(:create).and_return(submission)
@ -328,7 +328,7 @@ describe ExercisesController do
describe 'PUT #update' do describe 'PUT #update' do
context 'with a valid exercise' do context 'with a valid exercise' do
let(:exercise_attributes) { FactoryBot.build(:dummy).attributes } let(:exercise_attributes) { build(:dummy).attributes }
before { put :update, params: {exercise: exercise_attributes, id: exercise.id} } before { put :update, params: {exercise: exercise_attributes, id: exercise.id} }
@ -352,7 +352,7 @@ describe ExercisesController do
render_views render_views
let(:post_request) { post :export_external_check, params: {id: exercise.id} } let(:post_request) { post :export_external_check, params: {id: exercise.id} }
let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let!(:codeharbor_link) { create(:codeharbor_link, user: user) }
let(:external_check_hash) { {message: message, task_found: true, update_right: update_right, error: error} } let(:external_check_hash) { {message: message, task_found: true, update_right: update_right, error: error} }
let(:message) { 'message' } let(:message) { 'message' }
let(:update_right) { true } let(:update_right) { true }
@ -405,7 +405,7 @@ describe ExercisesController do
describe '#export_external_confirm' do describe '#export_external_confirm' do
render_views render_views
let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let!(:codeharbor_link) { 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) { nil } let(:error) { nil }
let(:zip) { 'zip' } let(:zip) { 'zip' }
@ -440,8 +440,8 @@ describe ExercisesController do
end end
describe '#import_uuid_check' do describe '#import_uuid_check' do
let(:exercise) { FactoryBot.create(:dummy, uuid: SecureRandom.uuid) } let(:exercise) { create(:dummy, uuid: SecureRandom.uuid) }
let!(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let!(:codeharbor_link) { create(:codeharbor_link, user: user) }
let(:uuid) { exercise.reload.uuid } let(:uuid) { exercise.reload.uuid }
let(:post_request) { post :import_uuid_check, params: {uuid: uuid} } let(:post_request) { post :import_uuid_check, params: {uuid: uuid} }
let(:headers) { {'Authorization' => "Bearer #{codeharbor_link.api_key}"} } let(:headers) { {'Authorization' => "Bearer #{codeharbor_link.api_key}"} }
@ -466,7 +466,7 @@ describe ExercisesController do
end end
context 'when the user cannot update the exercise' do context 'when the user cannot update the exercise' do
let(:codeharbor_link) { FactoryBot.create(:codeharbor_link, api_key: 'anotherkey') } let(:codeharbor_link) { create(:codeharbor_link, api_key: 'anotherkey') }
it 'renders correct response' do it 'renders correct response' do
post_request post_request
@ -490,8 +490,8 @@ describe ExercisesController do
end end
describe 'POST #import_exercise' do describe 'POST #import_exercise' do
let(:codeharbor_link) { FactoryBot.create(:codeharbor_link, user: user) } let(:codeharbor_link) { create(:codeharbor_link, user: user) }
let!(:imported_exercise) { FactoryBot.create(:fibonacci) } let!(:imported_exercise) { create(:fibonacci) }
let(:post_request) { post :import_exercise, body: zip_file_content } let(:post_request) { post :import_exercise, body: zip_file_content }
let(:zip_file_content) { 'zipped task xml' } let(:zip_file_content) { 'zipped task xml' }
let(:headers) { {'Authorization' => "Bearer #{codeharbor_link.api_key}"} } let(:headers) { {'Authorization' => "Bearer #{codeharbor_link.api_key}"} }

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe ExternalUsersController do describe ExternalUsersController do
let(:user) { FactoryBot.build(:admin) } let(:user) { build(:admin) }
let!(:users) { FactoryBot.create_pair(:external_user) } let!(:users) { create_pair(:external_user) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }

View File

@ -3,14 +3,14 @@
require 'rails_helper' require 'rails_helper'
describe FileTypesController do describe FileTypesController do
let(:file_type) { FactoryBot.create(:dot_rb) } let(:file_type) { create(:dot_rb) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
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
context 'with a valid file type' do context 'with a valid file type' do
let(:perform_request) { proc { post :create, params: {file_type: FactoryBot.attributes_for(:dot_rb)} } } let(:perform_request) { proc { post :create, params: {file_type: attributes_for(:dot_rb)} } }
before { perform_request.call } before { perform_request.call }
@ -40,7 +40,7 @@ describe FileTypesController do
expect_assigns(file_type: FileType) expect_assigns(file_type: FileType)
it 'destroys the file type' do it 'destroys the file type' do
file_type = FactoryBot.create(:dot_rb) file_type = create(:dot_rb)
expect { delete :destroy, params: {id: file_type.id} }.to change(FileType, :count).by(-1) expect { delete :destroy, params: {id: file_type.id} }.to change(FileType, :count).by(-1)
end end
@ -58,7 +58,7 @@ describe FileTypesController do
describe 'GET #index' do describe 'GET #index' do
before do before do
FactoryBot.create_pair(:dot_rb) create_pair(:dot_rb)
get :index get :index
end end
@ -86,7 +86,7 @@ describe FileTypesController do
describe 'PUT #update' do describe 'PUT #update' do
context 'with a valid file type' do context 'with a valid file type' do
before { put :update, params: {file_type: FactoryBot.attributes_for(:dot_rb), id: file_type.id} } before { put :update, params: {file_type: attributes_for(:dot_rb), id: file_type.id} }
expect_assigns(editor_modes: Array) expect_assigns(editor_modes: Array)
expect_assigns(file_type: FileType) expect_assigns(file_type: FileType)

View File

@ -3,11 +3,11 @@
require 'rails_helper' require 'rails_helper'
describe InternalUsersController do describe InternalUsersController do
let(:user) { FactoryBot.build(:admin) } let(:user) { build(:admin) }
let!(:users) { FactoryBot.create_pair(:teacher) } let!(:users) { create_pair(:teacher) }
describe 'GET #activate' do describe 'GET #activate' do
let(:user) { InternalUser.create(FactoryBot.attributes_for(:teacher)) } let(:user) { InternalUser.create(attributes_for(:teacher)) }
before do before do
user.send(:setup_activation) user.send(:setup_activation)
@ -39,7 +39,7 @@ describe InternalUsersController do
end end
describe 'PUT #activate' do describe 'PUT #activate' do
let(:user) { InternalUser.create(FactoryBot.build(:teacher).attributes) } let(:user) { InternalUser.create(build(:teacher).attributes) }
let(:password) { SecureRandom.hex } let(:password) { SecureRandom.hex }
before do before do
@ -108,7 +108,7 @@ describe InternalUsersController do
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
context 'with a valid internal user' do context 'with a valid internal user' do
let(:perform_request) { proc { post :create, params: {internal_user: FactoryBot.build(:teacher).attributes} } } let(:perform_request) { proc { post :create, params: {internal_user: build(:teacher).attributes} } }
before { perform_request.call } before { perform_request.call }
@ -316,7 +316,7 @@ describe InternalUsersController do
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
context 'with a valid internal user' do context 'with a valid internal user' do
before { put :update, params: {internal_user: FactoryBot.attributes_for(:teacher), id: users.first.id} } before { put :update, params: {internal_user: attributes_for(:teacher), id: users.first.id} }
expect_assigns(user: InternalUser) expect_assigns(user: InternalUser)
expect_redirect { user } expect_redirect { user }

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe RequestForCommentsController do describe RequestForCommentsController do
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
@ -16,13 +16,13 @@ describe RequestForCommentsController do
end end
it 'shows only rfc`s belonging to selected study group' do it 'shows only rfc`s belonging to selected study group' do
my_study_group = FactoryBot.create(:study_group) my_study_group = create(:study_group)
rfc_within_my_study_group = FactoryBot.create(:rfc, user: user) rfc_within_my_study_group = create(:rfc, user: user)
user.update(study_groups: [my_study_group]) user.update(study_groups: [my_study_group])
rfc_within_my_study_group.submission.update(study_group: my_study_group) rfc_within_my_study_group.submission.update(study_group: my_study_group)
another_study_group = FactoryBot.create(:study_group) another_study_group = create(:study_group)
rfc_other_study_group = FactoryBot.create(:rfc) rfc_other_study_group = create(:rfc)
rfc_other_study_group.user.update(study_groups: [another_study_group]) rfc_other_study_group.user.update(study_groups: [another_study_group])
rfc_other_study_group.submission.update(study_group: another_study_group) rfc_other_study_group.submission.update(study_group: another_study_group)
@ -48,7 +48,7 @@ describe RequestForCommentsController do
describe 'GET #rfcs_for_exercise' do describe 'GET #rfcs_for_exercise' do
before do before do
exercise = FactoryBot.create(:even_odd) exercise = create(:even_odd)
get :rfcs_for_exercise, params: {exercise_id: exercise.id} get :rfcs_for_exercise, params: {exercise_id: exercise.id}
end end

View File

@ -3,12 +3,12 @@
require 'rails_helper' require 'rails_helper'
describe SessionsController do describe SessionsController do
let(:consumer) { FactoryBot.create(:consumer) } let(:consumer) { create(:consumer) }
describe 'POST #create' do describe 'POST #create' do
let(:password) { FactoryBot.attributes_for(:teacher)[:password] } let(:password) { attributes_for(:teacher)[:password] }
let(:user) { InternalUser.create(user_attributes.merge(password: password)) } let(:user) { InternalUser.create(user_attributes.merge(password: password)) }
let(:user_attributes) { FactoryBot.build(:teacher).attributes } let(:user_attributes) { build(:teacher).attributes }
context 'with valid credentials' do context 'with valid credentials' do
before do before do
@ -29,8 +29,8 @@ describe SessionsController do
end end
describe 'POST #create_through_lti' do describe 'POST #create_through_lti' do
let(:exercise) { FactoryBot.create(:dummy) } let(:exercise) { create(:dummy) }
let(:exercise2) { FactoryBot.create(:dummy) } let(:exercise2) { create(:dummy) }
let(:nonce) { SecureRandom.hex } let(:nonce) { SecureRandom.hex }
context 'without OAuth parameters' do context 'without OAuth parameters' do
@ -74,7 +74,7 @@ describe SessionsController do
context 'with valid launch parameters' do context 'with valid launch parameters' do
let(:locale) { :de } let(:locale) { :de }
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) { create(:external_user, consumer_id: consumer.id) }
before { allow_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) }
@ -139,14 +139,14 @@ describe SessionsController do
end end
it 'redirects to recommended exercise if requested token of proxy exercise' do it 'redirects to recommended exercise if requested token of proxy exercise' do
FactoryBot.create(:proxy_exercise, exercises: [exercise]) create(:proxy_exercise, exercises: [exercise])
post :create_through_lti, params: {custom_locale: locale, custom_token: ProxyExercise.first.token, oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: user.external_id} post :create_through_lti, params: {custom_locale: locale, custom_token: ProxyExercise.first.token, oauth_consumer_key: consumer.oauth_key, oauth_nonce: nonce, oauth_signature: SecureRandom.hex, user_id: user.external_id}
expect(controller).to redirect_to(implement_exercise_path(exercise.id)) expect(controller).to redirect_to(implement_exercise_path(exercise.id))
end end
it 'recommends only exercises who are 1 degree more complicated than what user has seen' do it 'recommends only exercises who are 1 degree more complicated than what user has seen' do
# dummy user has no exercises finished, therefore his highest difficulty is 0 # dummy user has no exercises finished, therefore his highest difficulty is 0
FactoryBot.create(:proxy_exercise, exercises: [exercise, exercise2]) create(:proxy_exercise, exercises: [exercise, exercise2])
exercise.expected_difficulty = 3 exercise.expected_difficulty = 3
exercise.save exercise.save
exercise2.expected_difficulty = 1 exercise2.expected_difficulty = 1
@ -202,7 +202,7 @@ 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)) } let(:submission) { create(:submission, exercise: create(:dummy)) }
before do before do
# Todo replace session with lti_parameter # Todo replace session with lti_parameter
@ -238,7 +238,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)
allow(controller).to receive(:current_user).and_return(FactoryBot.build(:teacher)) allow(controller).to receive(:current_user).and_return(build(:teacher))
get :new get :new
end end

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe StatisticsController do describe StatisticsController do
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe SubmissionsController do describe SubmissionsController do
let(:submission) { FactoryBot.create(:submission) } let(:submission) { create(:submission) }
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
before { allow(controller).to receive(:current_user).and_return(user) } before { allow(controller).to receive(:current_user).and_return(user) }
@ -14,8 +14,8 @@ describe SubmissionsController do
end end
context 'with a valid submission' do context 'with a valid submission' do
let(:exercise) { FactoryBot.create(:hello_world) } let(:exercise) { create(:hello_world) }
let(:perform_request) { proc { post :create, format: :json, params: {submission: FactoryBot.attributes_for(:submission, exercise_id: exercise.id)} } } let(:perform_request) { proc { post :create, format: :json, params: {submission: attributes_for(:submission, exercise_id: exercise.id)} } }
before { perform_request.call } before { perform_request.call }
@ -46,7 +46,7 @@ describe SubmissionsController do
end end
context 'with a valid binary filename' do context 'with a valid binary filename' do
let(:submission) { FactoryBot.create(:submission, exercise: FactoryBot.create(:sql_select)) } let(:submission) { create(:submission, exercise: create(:sql_select)) }
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} }
@ -65,7 +65,7 @@ describe SubmissionsController do
end end
context 'with a valid filename' do context 'with a valid filename' do
let(:submission) { FactoryBot.create(:submission, exercise: FactoryBot.create(:audio_video)) } let(:submission) { create(:submission, exercise: create(:audio_video)) }
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} }
@ -99,7 +99,7 @@ describe SubmissionsController do
describe 'GET #index' do describe 'GET #index' do
before do before do
FactoryBot.create_pair(:submission) create_pair(:submission)
get :index get :index
end end
@ -118,7 +118,7 @@ describe SubmissionsController do
end end
context 'with a valid filename' do context 'with a valid filename' do
let(:submission) { FactoryBot.create(:submission, exercise: FactoryBot.create(:audio_video)) } let(:submission) { create(:submission, exercise: create(:audio_video)) }
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} }

View File

@ -17,6 +17,7 @@ describe 'seeds' do
ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.establish_connection(:test)
} }
allow_any_instance_of(ExecutionEnvironment).to receive(:working_docker_image?).and_return true allow_any_instance_of(ExecutionEnvironment).to receive(:working_docker_image?).and_return true
allow_any_instance_of(ExecutionEnvironment).to receive(:sync_runner_environment).and_return true
end end
describe 'execute db:seed', cleaning_strategy: :truncation do describe 'execute db:seed', cleaning_strategy: :truncation do

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe 'Authentication' do describe 'Authentication' do
let(:user) { FactoryBot.create(:admin) } let(:user) { create(:admin) }
let(:password) { FactoryBot.attributes_for(:admin)[:password] } let(:password) { attributes_for(:admin)[:password] }
context 'when signed out' do context 'when signed out' do
before { visit(root_path) } before { visit(root_path) }

View File

@ -6,7 +6,7 @@ describe 'Authorization' do
before { allow(Runner.strategy_class).to receive(:available_images).and_return([]) } before { allow(Runner.strategy_class).to receive(:available_images).and_return([]) }
context 'when being an admin' do context 'when being an admin' do
let(:user) { FactoryBot.create(:admin) } let(:user) { 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) }
@ -16,7 +16,7 @@ describe 'Authorization' do
end end
context 'with being an external user' do context 'with being an external user' do
let(:user) { FactoryBot.create(:external_user) } let(:user) { 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) }
@ -26,7 +26,7 @@ describe 'Authorization' do
end end
context 'with being a teacher' do context 'with being a teacher' do
let(:user) { FactoryBot.create(:teacher) } let(:user) { 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

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe 'Editor', js: true do describe 'Editor', js: true do
let(:exercise) { FactoryBot.create(:audio_video, description: Forgery(:lorem_ipsum).sentence) } let(:exercise) { create(:audio_video, description: Forgery(:lorem_ipsum).sentence) }
let(:scoring_response) do let(:scoring_response) do
[{ [{
status: :ok, status: :ok,
@ -22,12 +22,12 @@ describe 'Editor', js: true do
weight: 2.0, weight: 2.0,
}] }]
end end
let(:user) { FactoryBot.create(:teacher) } let(:user) { create(:teacher) }
before do before do
visit(sign_in_path) visit(sign_in_path)
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: attributes_for(:teacher)[:password])
click_button(I18n.t('sessions.new.link')) click_button(I18n.t('sessions.new.link'))
allow_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))
@ -94,7 +94,7 @@ describe 'Editor', js: true do
end end
it 'contains a button for submitting the exercise' do it 'contains a button for submitting the exercise' do
submission = FactoryBot.build(:submission, user: user, exercise: exercise) submission = build(:submission, user: user, exercise: exercise)
allow(submission).to receive(:calculate_score).and_return(scoring_response) allow(submission).to receive(:calculate_score).and_return(scoring_response)
allow(Submission).to receive(:find).and_return(submission) allow(Submission).to receive(:find).and_return(submission)
click_button(I18n.t('exercises.editor.score')) click_button(I18n.t('exercises.editor.score'))

View File

@ -26,7 +26,7 @@ describe Prometheus::Controller do
describe 'instance count' do describe 'instance count' do
it 'initializes the metrics with the current database entries' do it 'initializes the metrics with the current database entries' do
FactoryBot.create_list(:proxy_exercise, 3) create_list(:proxy_exercise, 3)
described_class.register_metrics described_class.register_metrics
stub_metrics stub_metrics
described_class.initialize_instance_count described_class.initialize_instance_count
@ -35,25 +35,25 @@ describe Prometheus::Controller do
it 'gets notified when an object is created' do it 'gets notified when an object is created' do
allow(described_class).to receive(:create_notification) allow(described_class).to receive(:create_notification)
proxy_exercise = FactoryBot.create(:proxy_exercise) proxy_exercise = create(:proxy_exercise)
expect(described_class).to have_received(:create_notification).with(proxy_exercise).once expect(described_class).to have_received(:create_notification).with(proxy_exercise).once
end end
it 'gets notified when an object is destroyed' do it 'gets notified when an object is destroyed' do
allow(described_class).to receive(:destroy_notification) allow(described_class).to receive(:destroy_notification)
proxy_exercise = FactoryBot.create(:proxy_exercise).destroy proxy_exercise = create(:proxy_exercise).destroy
expect(described_class).to have_received(:destroy_notification).with(proxy_exercise).once expect(described_class).to have_received(:destroy_notification).with(proxy_exercise).once
end end
it 'increments gauge when creating a new instance' do it 'increments gauge when creating a new instance' do
FactoryBot.create(:proxy_exercise) create(:proxy_exercise)
expect(described_class.instance_variable_get(:@instance_count)).to( expect(described_class.instance_variable_get(:@instance_count)).to(
have_received(:increment).with(class: ProxyExercise.name).once have_received(:increment).with(class: ProxyExercise.name).once
) )
end end
it 'decrements gauge when deleting an object' do it 'decrements gauge when deleting an object' do
FactoryBot.create(:proxy_exercise).destroy create(:proxy_exercise).destroy
expect(described_class.instance_variable_get(:@instance_count)).to( expect(described_class.instance_variable_get(:@instance_count)).to(
have_received(:decrement).with(class: ProxyExercise.name).once have_received(:decrement).with(class: ProxyExercise.name).once
) )
@ -63,7 +63,7 @@ describe Prometheus::Controller do
describe 'rfc count' do describe 'rfc count' do
context 'when initializing an rfc' do context 'when initializing an rfc' do
it 'updates rfc count when creating an ongoing rfc' do it 'updates rfc count when creating an ongoing rfc' do
FactoryBot.create(:rfc) create(:rfc)
expect(described_class.instance_variable_get(:@rfc_count)).to( expect(described_class.instance_variable_get(:@rfc_count)).to(
have_received(:increment).with(state: RequestForComment::ONGOING).once have_received(:increment).with(state: RequestForComment::ONGOING).once
) )
@ -71,7 +71,7 @@ describe Prometheus::Controller do
end end
context 'when changing the state of an rfc' do context 'when changing the state of an rfc' do
let(:rfc) { FactoryBot.create(:rfc) } let(:rfc) { create(:rfc) }
it 'updates rfc count when soft-solving an rfc' do it 'updates rfc count when soft-solving an rfc' do
rfc.full_score_reached = true rfc.full_score_reached = true
@ -90,12 +90,12 @@ describe Prometheus::Controller do
context 'when commenting an rfc' do context 'when commenting an rfc' do
it 'updates comment metric when commenting an rfc' do it 'updates comment metric when commenting an rfc' do
FactoryBot.create(:rfc_with_comment) create(:rfc_with_comment)
expect(described_class.instance_variable_get(:@rfc_commented_count)).to have_received(:increment).once expect(described_class.instance_variable_get(:@rfc_commented_count)).to have_received(:increment).once
end end
it 'does not update comment metric when commenting an rfc that already has a comment' do it 'does not update comment metric when commenting an rfc that already has a comment' do
rfc = FactoryBot.create(:rfc_with_comment) rfc = create(:rfc_with_comment)
expect(described_class.instance_variable_get(:@rfc_commented_count)).to have_received(:increment).once expect(described_class.instance_variable_get(:@rfc_commented_count)).to have_received(:increment).once
Comment.create(file: rfc.file, user: rfc.user, text: "comment a for rfc #{rfc.question}") Comment.create(file: rfc.file, user: rfc.user, text: "comment a for rfc #{rfc.question}")

View File

@ -3,21 +3,21 @@
require 'rails_helper' require 'rails_helper'
describe 'Request_for_Comments' do describe 'Request_for_Comments' do
let(:exercise) { FactoryBot.create(:audio_video, description: Forgery(:lorem_ipsum).sentence) } let(:exercise) { create(:audio_video, description: Forgery(:lorem_ipsum).sentence) }
let(:user) { FactoryBot.create(:teacher) } let(:user) { create(:teacher) }
before do before do
visit(sign_in_path) visit(sign_in_path)
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: attributes_for(:teacher)[:password])
click_button(I18n.t('sessions.new.link')) click_button(I18n.t('sessions.new.link'))
end end
it 'does not contain rfcs for unpublished exercises' do it 'does not contain rfcs for unpublished exercises' do
unpublished_rfc = FactoryBot.create(:rfc) unpublished_rfc = create(:rfc)
unpublished_rfc.exercise.update(title: 'Unpublished Exercise') unpublished_rfc.exercise.update(title: 'Unpublished Exercise')
unpublished_rfc.exercise.update(unpublished: true) unpublished_rfc.exercise.update(unpublished: true)
rfc = FactoryBot.create(:rfc) rfc = create(:rfc)
rfc.exercise.update(title: 'Normal Exercise') rfc.exercise.update(title: 'Normal Exercise')
rfc.exercise.update(unpublished: false) rfc.exercise.update(unpublished: false)

View File

@ -11,10 +11,10 @@ describe Admin::DashboardHelper do
describe '#docker_data' do describe '#docker_data' do
before do before do
FactoryBot.create(:ruby) create(:ruby)
dcp = instance_double 'docker_container_pool' dcp = instance_double 'docker_container_pool'
allow(Runner).to receive(:strategy_class).and_return dcp allow(Runner).to receive(:strategy_class).and_return dcp
allow(dcp).to receive(:pool_size).and_return([]) allow(dcp).to receive(:pool_size).and_return({})
end end
it 'contains an entry for every execution environment' do it 'contains an entry for every execution environment' do
@ -22,11 +22,15 @@ describe Admin::DashboardHelper do
end end
it 'contains the pool size for every execution environment' do it 'contains the pool size for every execution environment' do
expect(docker_data.first.symbolize_keys).to include(:pool_size) expect(docker_data.first.symbolize_keys).to include(:prewarmingPoolSize)
end end
it 'contains the number of available containers for every execution environment' do it 'contains the number of idle runners for every execution environment' do
expect(docker_data.first).to include(:quantity) expect(docker_data.first).to include(:idleRunners)
end
it 'contains the number of used runners for every execution environment' do
expect(docker_data.first).to include(:usedRunners)
end end
end end
end end

View File

@ -4,7 +4,7 @@ require 'rails_helper'
describe ExerciseHelper do describe ExerciseHelper do
describe '#embedding_parameters' do describe '#embedding_parameters' do
let(:exercise) { FactoryBot.build(:dummy) } let(:exercise) { build(:dummy) }
it 'contains the locale' do it 'contains the locale' do
expect(embedding_parameters(exercise)).to start_with("locale=#{I18n.locale}") expect(embedding_parameters(exercise)).to start_with("locale=#{I18n.locale}")

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe Assessor do describe Assessor do
let(:assessor) { described_class.new(execution_environment: FactoryBot.build(:ruby)) } let(:assessor) { described_class.new(execution_environment: build(:ruby)) }
describe '#assess' do describe '#assess' do
let(:assess) { assessor.assess(stdout: stdout) } let(:assess) { assessor.assess(stdout: stdout) }
@ -55,7 +55,7 @@ describe Assessor do
context 'with an execution environment without a testing framework adapter' do context 'with an execution environment without a testing framework adapter' do
it 'raises an error' do it 'raises an error' do
expect { described_class.new(execution_environment: FactoryBot.build(:ruby, testing_framework: nil)) }.to raise_error(Assessor::Error) expect { described_class.new(execution_environment: build(:ruby, testing_framework: nil)) }.to raise_error(Assessor::Error)
end end
end end
end end

View File

@ -7,14 +7,14 @@ WORKSPACE_PATH = Rails.root.join('tmp', 'files', Rails.env, 'code_ocean_test')
describe DockerClient do describe DockerClient do
let(:command) { 'whoami' } let(:command) { 'whoami' }
let(:docker_client) { described_class.new(execution_environment: FactoryBot.build(:java), user: FactoryBot.build(:admin)) } let(:docker_client) { described_class.new(execution_environment: build(:java), user: build(:admin)) }
let(:execution_environment) { FactoryBot.build(:java) } let(:execution_environment) { build(:java) }
let(:image) { double } let(:image) { double }
let(:submission) { FactoryBot.create(:submission) } let(:submission) { create(:submission) }
let(:workspace_path) { WORKSPACE_PATH } let(:workspace_path) { WORKSPACE_PATH }
before do before do
docker_image = Docker::Image.new(Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex, 'RepoTags' => [FactoryBot.attributes_for(:java)[:docker_image]]) docker_image = Docker::Image.new(Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex, 'RepoTags' => [attributes_for(:java)[:docker_image]])
allow(described_class).to receive(:find_image_by_tag).and_return(docker_image) allow(described_class).to receive(:find_image_by_tag).and_return(docker_image)
described_class.initialize_environment described_class.initialize_environment
allow(described_class).to receive(:container_creation_options).and_wrap_original do |original_method, *args, &block| allow(described_class).to receive(:container_creation_options).and_wrap_original do |original_method, *args, &block|
@ -177,7 +177,7 @@ describe DockerClient do
describe '#create_workspace_file' do describe '#create_workspace_file' do
let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) } let(:container) { Docker::Container.send(:new, Docker::Connection.new('http://example.org', {}), 'id' => SecureRandom.hex) }
let(:file) { FactoryBot.build(:file, content: 'puts 42') } let(:file) { build(:file, content: 'puts 42') }
let(:file_path) { File.join(workspace_path, file.name_with_extension) } let(:file_path) { File.join(workspace_path, file.name_with_extension) }
after { File.delete(file_path) } after { File.delete(file_path) }

View File

@ -10,7 +10,7 @@ describe FileTree do
context 'with a media file' do context 'with a media file' do
context 'with an audio file' do context 'with an audio file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp3)) } let(:file) { build(:file, file_type: build(:dot_mp3)) }
it 'is an audio file icon' do it 'is an audio file icon' do
expect(file_icon).to include('fa-file-audio-o') expect(file_icon).to include('fa-file-audio-o')
@ -18,7 +18,7 @@ describe FileTree do
end end
context 'with an image file' do context 'with an image file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_jpg)) } let(:file) { build(:file, file_type: build(:dot_jpg)) }
it 'is an image file icon' do it 'is an image file icon' do
expect(file_icon).to include('fa-file-image-o') expect(file_icon).to include('fa-file-image-o')
@ -26,7 +26,7 @@ describe FileTree do
end end
context 'with a video file' do context 'with a video file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_mp4)) } let(:file) { build(:file, file_type: build(:dot_mp4)) }
it 'is a video file icon' do it 'is a video file icon' do
expect(file_icon).to include('fa-file-video-o') expect(file_icon).to include('fa-file-video-o')
@ -36,7 +36,7 @@ describe FileTree do
context 'with other files' do context 'with other files' do
context 'with a read-only file' do context 'with a read-only file' do
let(:file) { FactoryBot.build(:file, read_only: true) } let(:file) { build(:file, read_only: true) }
it 'is a lock icon' do it 'is a lock icon' do
expect(file_icon).to include('fa-lock') expect(file_icon).to include('fa-lock')
@ -44,7 +44,7 @@ describe FileTree do
end end
context 'with an executable file' do context 'with an executable file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_py)) } let(:file) { build(:file, file_type: build(:dot_py)) }
it 'is a code file icon' do it 'is a code file icon' do
expect(file_icon).to include('fa-file-code-o') expect(file_icon).to include('fa-file-code-o')
@ -52,7 +52,7 @@ describe FileTree do
end end
context 'with a renderable file' do context 'with a renderable file' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_svg)) } let(:file) { build(:file, file_type: build(:dot_svg)) }
it 'is a text file icon' do it 'is a text file icon' do
expect(file_icon).to include('fa-file-text-o') expect(file_icon).to include('fa-file-text-o')
@ -60,7 +60,7 @@ describe FileTree do
end end
context 'with all other files' do context 'with all other files' do
let(:file) { FactoryBot.build(:file, file_type: FactoryBot.build(:dot_md)) } let(:file) { build(:file, file_type: build(:dot_md)) }
it 'is a generic file icon' do it 'is a generic file icon' do
expect(file_icon).to include('fa-file-o') expect(file_icon).to include('fa-file-o')
@ -77,7 +77,7 @@ describe FileTree do
describe '#initialize' do describe '#initialize' do
let(:file_tree) { described_class.new(files) } let(:file_tree) { described_class.new(files) }
let(:files) { FactoryBot.build_list(:file, 10, context: nil, path: 'foo/bar/baz') } let(:files) { build_list(:file, 10, context: nil, path: 'foo/bar/baz') }
it 'creates a root node' do it 'creates a root node' do
# Instead of checking #initialize on the parent, we validate #set_as_root! # Instead of checking #initialize on the parent, we validate #set_as_root!
@ -95,7 +95,7 @@ describe FileTree do
end end
describe '#map_to_js_tree' do describe '#map_to_js_tree' do
let(:file) { FactoryBot.build(:file) } let(:file) { build(:file) }
let(:js_tree) { file_tree.send(:map_to_js_tree, node) } let(:js_tree) { file_tree.send(:map_to_js_tree, node) }
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) }
@ -186,7 +186,7 @@ describe FileTree do
end end
context 'with files' do context 'with files' do
let(:files) { FactoryBot.build_list(:file, 10, context: nil, path: 'foo/bar/baz') } let(:files) { build_list(:file, 10, context: nil, path: 'foo/bar/baz') }
let(:file_tree) { described_class.new(files) } let(:file_tree) { described_class.new(files) }
let(:js_tree) { file_tree.to_js_tree } let(:js_tree) { file_tree.to_js_tree }

View File

@ -4,8 +4,8 @@ require 'rails_helper'
require 'pathname' require 'pathname'
describe Runner::Strategy::DockerContainerPool do describe Runner::Strategy::DockerContainerPool do
let(:runner_id) { FactoryBot.attributes_for(:runner)[:runner_id] } let(:runner_id) { attributes_for(:runner)[:runner_id] }
let(:execution_environment) { FactoryBot.create :ruby } let(:execution_environment) { create :ruby }
let(:container_pool) { described_class.new(runner_id, execution_environment) } let(:container_pool) { described_class.new(runner_id, execution_environment) }
let(:docker_container_pool_url) { 'http://localhost:1234' } let(:docker_container_pool_url) { 'http://localhost:1234' }
let(:config) { {url: docker_container_pool_url, unused_runner_expiration_time: 180} } let(:config) { {url: docker_container_pool_url, unused_runner_expiration_time: 180} }
@ -112,22 +112,20 @@ describe Runner::Strategy::DockerContainerPool do
context 'when receiving a normal file' do context 'when receiving a normal file' do
let(:file_content) { 'print("Hello World!")' } let(:file_content) { 'print("Hello World!")' }
let(:files) { [FactoryBot.build(:file, content: file_content)] } let(:files) { [build(:file, content: file_content)] }
it 'writes the file to disk' do it 'writes the file to disk' do
file = instance_double(File) expect(File).to receive(:write).with(local_path.join(files.first.filepath), file_content)
allow(File).to receive(:open).and_yield(file)
expect(file).to receive(:write).with(file_content)
container_pool.copy_files(files) container_pool.copy_files(files)
end end
it 'creates the file inside the workspace' do it 'creates the file inside the workspace' do
expect(File).to receive(:open).with(local_path.join(files.first.filepath), 'w') expect(File).to receive(:write).with(local_path.join(files.first.filepath), files.first.content)
container_pool.copy_files(files) container_pool.copy_files(files)
end end
it 'raises an error in case of an IOError' do it 'raises an error in case of an IOError' do
allow(File).to receive(:open).and_raise(IOError) allow(File).to receive(:write).and_raise(IOError)
expect { container_pool.copy_files(files) }.to raise_error(Runner::Error::WorkspaceError, /#{files.first.filepath}/) expect { container_pool.copy_files(files) }.to raise_error(Runner::Error::WorkspaceError, /#{files.first.filepath}/)
end end
@ -137,10 +135,10 @@ describe Runner::Strategy::DockerContainerPool do
context 'when the file is inside a directory' do context 'when the file is inside a directory' do
let(:directory) { 'temp/dir' } let(:directory) { 'temp/dir' }
let(:files) { [FactoryBot.build(:file, path: directory)] } let(:files) { [build(:file, path: directory)] }
before do before do
allow(File).to receive(:open) allow(File).to receive(:write)
allow(FileUtils).to receive(:mkdir_p).with(local_path) allow(FileUtils).to receive(:mkdir_p).with(local_path)
allow(FileUtils).to receive(:mkdir_p).with(local_path.join(directory)) allow(FileUtils).to receive(:mkdir_p).with(local_path.join(directory))
end end
@ -159,7 +157,7 @@ describe Runner::Strategy::DockerContainerPool do
end end
context 'when receiving a binary file' do context 'when receiving a binary file' do
let(:files) { [FactoryBot.build(:file, :image)] } let(:files) { [build(:file, :image)] }
it 'copies the file inside the workspace' do it 'copies the file inside the workspace' do
expect(FileUtils).to receive(:cp).with(files.first.native_file.path, local_path.join(files.first.filepath)) expect(FileUtils).to receive(:cp).with(files.first.native_file.path, local_path.join(files.first.filepath))
@ -168,11 +166,11 @@ describe Runner::Strategy::DockerContainerPool do
end end
context 'when receiving multiple files' do context 'when receiving multiple files' do
let(:files) { FactoryBot.build_list(:file, 3) } let(:files) { build_list(:file, 3) }
it 'creates all files' do it 'creates all files' do
files.each do |file| files.each do |file|
expect(File).to receive(:open).with(local_path.join(file.filepath), 'w') expect(File).to receive(:write).with(local_path.join(file.filepath), file.content)
end end
container_pool.copy_files(files) container_pool.copy_files(files)
end end

View File

@ -3,8 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe Runner::Strategy::Poseidon do describe Runner::Strategy::Poseidon do
let(:runner_id) { FactoryBot.attributes_for(:runner)[:runner_id] } let(:runner_id) { attributes_for(:runner)[:runner_id] }
let(:execution_environment) { FactoryBot.create :ruby } let(:execution_environment) { create :ruby }
let(:poseidon) { described_class.new(runner_id, execution_environment) } let(:poseidon) { described_class.new(runner_id, execution_environment) }
let(:error_message) { 'test error message' } let(:error_message) { 'test error message' }
let(:response_body) { nil } let(:response_body) { nil }
@ -128,7 +128,7 @@ describe Runner::Strategy::Poseidon do
describe '::sync_environment' do describe '::sync_environment' do
let(:action) { -> { described_class.sync_environment(execution_environment) } } let(:action) { -> { described_class.sync_environment(execution_environment) } }
let(:execution_environment) { FactoryBot.create(:ruby) } let(:execution_environment) { create(:ruby) }
it 'makes the correct request to Poseidon' do it 'makes the correct request to Poseidon' do
faraday_connection = instance_double 'Faraday::Connection' faraday_connection = instance_double 'Faraday::Connection'
@ -321,7 +321,7 @@ describe Runner::Strategy::Poseidon do
describe '#copy_files' do describe '#copy_files' do
let(:file_content) { 'print("Hello World!")' } let(:file_content) { 'print("Hello World!")' }
let(:file) { FactoryBot.build(:file, content: file_content) } let(:file) { build(:file, content: file_content) }
let(:action) { -> { poseidon.copy_files([file]) } } let(:action) { -> { poseidon.copy_files([file]) } }
let(:encoded_file_content) { Base64.strict_encode64(file.content) } let(:encoded_file_content) { Base64.strict_encode64(file.content) }
let!(:copy_files_stub) do let!(:copy_files_stub) do

View File

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe UserMailer do describe UserMailer do
let(:user) { InternalUser.create(FactoryBot.attributes_for(:teacher)) } let(:user) { InternalUser.create(attributes_for(:teacher)) }
describe '#activation_needed_email' do describe '#activation_needed_email' do
let(:mail) { described_class.activation_needed_email(user) } let(:mail) { described_class.activation_needed_email(user) }

View File

@ -6,7 +6,7 @@ describe CodeOcean::File do
let(:file) { described_class.create.tap {|file| file.update(content: nil, hidden: nil, read_only: nil) } } let(:file) { described_class.create.tap {|file| file.update(content: nil, hidden: nil, read_only: nil) } }
it 'validates the presence of a file type' do it 'validates the presence of a file type' do
expect(file.errors[:file_type_id]).to be_present expect(file.errors[:file_type]).to be_present
end end
it 'validates the presence of the hidden flag' do it 'validates the presence of the hidden flag' do

View File

@ -11,7 +11,7 @@ describe CodeharborLink do
describe '#to_s' do describe '#to_s' do
subject { codeharbor_link.to_s } subject { codeharbor_link.to_s }
let(:codeharbor_link) { FactoryBot.create(:codeharbor_link) } let(:codeharbor_link) { create(:codeharbor_link) }
it { is_expected.to eql codeharbor_link.id.to_s } it { is_expected.to eql codeharbor_link.id.to_s }
end end

View File

@ -14,7 +14,7 @@ describe Consumer do
end end
it 'validates the uniqueness of the OAuth key' do it 'validates the uniqueness of the OAuth key' do
consumer.update(oauth_key: FactoryBot.create(:consumer).oauth_key) consumer.update(oauth_key: create(:consumer).oauth_key)
expect(consumer.errors[:oauth_key]).to be_present expect(consumer.errors[:oauth_key]).to be_present
end end

View File

@ -8,7 +8,7 @@ describe ExecutionEnvironment do
it 'validates that the Docker image works' do it 'validates that the Docker image works' do
allow(execution_environment).to receive(:validate_docker_image?).and_return(true) allow(execution_environment).to receive(:validate_docker_image?).and_return(true)
allow(execution_environment).to receive(:working_docker_image?).and_return(true) allow(execution_environment).to receive(:working_docker_image?).and_return(true)
execution_environment.update(FactoryBot.build(:ruby).attributes) execution_environment.update(build(:ruby).attributes)
expect(execution_environment).to have_received(:working_docker_image?) expect(execution_environment).to have_received(:working_docker_image?)
end end
@ -81,8 +81,7 @@ describe ExecutionEnvironment do
end end
it 'validates the presence of a user' do it 'validates the presence of a user' do
expect(execution_environment.errors[:user_id]).to be_present expect(execution_environment.errors[:user]).to be_present
expect(execution_environment.errors[:user_type]).to be_present
end end
it 'validates the format of the exposed ports' do it 'validates the format of the exposed ports' do
@ -95,7 +94,7 @@ describe ExecutionEnvironment do
describe '#valid_test_setup?' do describe '#valid_test_setup?' do
context 'with a test command and a testing framework' do context 'with a test command and a testing framework' do
before { execution_environment.update(test_command: FactoryBot.attributes_for(:ruby)[:test_command], testing_framework: FactoryBot.attributes_for(:ruby)[:testing_framework]) } before { execution_environment.update(test_command: attributes_for(:ruby)[:test_command], testing_framework: attributes_for(:ruby)[:testing_framework]) }
it 'is valid' do it 'is valid' do
expect(execution_environment.errors[:test_command]).to be_blank expect(execution_environment.errors[:test_command]).to be_blank
@ -103,7 +102,7 @@ describe ExecutionEnvironment do
end end
context 'with a test command but no testing framework' do context 'with a test command but no testing framework' do
before { execution_environment.update(test_command: FactoryBot.attributes_for(:ruby)[:test_command], testing_framework: nil) } before { execution_environment.update(test_command: attributes_for(:ruby)[:test_command], testing_framework: nil) }
it 'is invalid' do it 'is invalid' do
expect(execution_environment.errors[:test_command]).to be_present expect(execution_environment.errors[:test_command]).to be_present
@ -111,7 +110,7 @@ describe ExecutionEnvironment do
end end
context 'with no test command but a testing framework' do context 'with no test command but a testing framework' do
before { execution_environment.update(test_command: nil, testing_framework: FactoryBot.attributes_for(:ruby)[:testing_framework]) } before { execution_environment.update(test_command: nil, testing_framework: attributes_for(:ruby)[:testing_framework]) }
it 'is invalid' do it 'is invalid' do
expect(execution_environment.errors[:test_command]).to be_present expect(execution_environment.errors[:test_command]).to be_present
@ -144,7 +143,7 @@ describe ExecutionEnvironment do
end end
it 'is true otherwise' do it 'is true otherwise' do
execution_environment.docker_image = FactoryBot.attributes_for(:ruby)[:docker_image] execution_environment.docker_image = attributes_for(:ruby)[:docker_image]
execution_environment.pool_size = 1 execution_environment.pool_size = 1
allow(Rails.env).to receive(:test?).and_return(false) allow(Rails.env).to receive(:test?).and_return(false)
expect(execution_environment.send(:validate_docker_image?)).to be true expect(execution_environment.send(:validate_docker_image?)).to be true
@ -152,7 +151,7 @@ describe ExecutionEnvironment do
end end
describe '#working_docker_image?' do describe '#working_docker_image?' do
let(:execution_environment) { FactoryBot.create(:ruby) } let(:execution_environment) { create(:ruby) }
let(:working_docker_image?) { execution_environment.send(:working_docker_image?) } let(:working_docker_image?) { execution_environment.send(:working_docker_image?) }
let(:runner) { instance_double 'runner' } let(:runner) { instance_double 'runner' }

View File

@ -4,15 +4,15 @@ require 'rails_helper'
describe Exercise do describe Exercise do
let(:exercise) { described_class.create.tap {|exercise| exercise.update(public: nil, token: nil) } } let(:exercise) { described_class.create.tap {|exercise| exercise.update(public: nil, token: nil) } }
let(:users) { FactoryBot.create_list(:external_user, 10) } let(:users) { create_list(:external_user, 10) }
def create_submissions def create_submissions
FactoryBot.create_list(:submission, 10, cause: 'submit', exercise: exercise, score: Forgery(:basic).number, user: users.sample) create_list(:submission, 10, cause: 'submit', exercise: exercise, score: Forgery(:basic).number, user: users.sample)
end end
it 'validates the number of main files' do it 'validates the number of main files' do
exercise = FactoryBot.create(:dummy) exercise = create(:dummy)
exercise.files += FactoryBot.create_pair(:file) exercise.files += create_pair(:file)
expect(exercise).to receive(:valid_main_file?).and_call_original expect(exercise).to receive(:valid_main_file?).and_call_original
exercise.save exercise.save
expect(exercise.errors[:files]).to be_present expect(exercise.errors[:files]).to be_present
@ -37,36 +37,35 @@ describe Exercise do
end end
it 'validates the presence of a user' do it 'validates the presence of a user' do
expect(exercise.errors[:user_id]).to be_present expect(exercise.errors[:user]).to be_present
expect(exercise.errors[:user_type]).to be_present
end end
context 'when exercise is unpublished' do context 'when exercise is unpublished' do
subject { FactoryBot.build(:dummy, unpublished: true) } subject { build(:dummy, unpublished: true) }
it { is_expected.not_to validate_presence_of(:execution_environment) } it { is_expected.not_to validate_presence_of(:execution_environment) }
end end
context 'when exercise is not unpublished' do context 'when exercise is not unpublished' do
subject { FactoryBot.build(:dummy, unpublished: false) } subject { build(:dummy, unpublished: false) }
it { is_expected.to validate_presence_of(:execution_environment) } it { is_expected.to validate_presence_of(:execution_environment) }
end end
context 'with uuid' do context 'with uuid' do
subject { FactoryBot.build(:dummy, uuid: SecureRandom.uuid) } subject { build(:dummy, uuid: SecureRandom.uuid) }
it { is_expected.to validate_uniqueness_of(:uuid).case_insensitive } it { is_expected.to validate_uniqueness_of(:uuid).case_insensitive }
end end
context 'without uuid' do context 'without uuid' do
subject { FactoryBot.build(:dummy, uuid: nil) } subject { build(:dummy, uuid: nil) }
it { is_expected.not_to validate_uniqueness_of(:uuid) } it { is_expected.not_to validate_uniqueness_of(:uuid) }
end end
describe '#average_percentage' do describe '#average_percentage' do
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
context 'without submissions' do context 'without submissions' do
it 'returns nil' do it 'returns nil' do
@ -85,7 +84,7 @@ describe Exercise do
end end
describe '#average_score' do describe '#average_score' do
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
context 'without submissions' do context 'without submissions' do
it 'returns nil' do it 'returns nil' do
@ -104,7 +103,7 @@ describe Exercise do
end end
describe '#duplicate' do describe '#duplicate' do
let(:exercise) { FactoryBot.create(:fibonacci) } let(:exercise) { create(:fibonacci) }
after { exercise.duplicate } after { exercise.duplicate }

View File

@ -6,7 +6,7 @@ describe ExternalUser do
let(:user) { described_class.create } let(:user) { described_class.create }
it 'validates the presence of a consumer' do it 'validates the presence of a consumer' do
expect(user.errors[:consumer_id]).to be_present expect(user.errors[:consumer]).to be_present
end end
it 'validates the presence of an external ID' do it 'validates the presence of an external ID' do
@ -15,7 +15,7 @@ describe ExternalUser do
describe '#admin?' do describe '#admin?' do
it 'is false' do it 'is false' do
expect(FactoryBot.build(:external_user).admin?).to be false expect(build(:external_user).admin?).to be false
end end
end end
@ -33,7 +33,7 @@ describe ExternalUser do
describe '#teacher?' do describe '#teacher?' do
it 'is false' do it 'is false' do
expect(FactoryBot.build(:external_user).teacher?).to be false expect(build(:external_user).teacher?).to be false
end end
end end
end end

View File

@ -52,7 +52,6 @@ describe FileType do
end end
it 'validates the presence of a user' do it 'validates the presence of a user' do
expect(file_type.errors[:user_id]).to be_present expect(file_type.errors[:user]).to be_present
expect(file_type.errors[:user_type]).to be_present
end end
end end

View File

@ -11,12 +11,12 @@ describe InternalUser do
end end
it 'validates the uniqueness of the email address' do it 'validates the uniqueness of the email address' do
user.update(email: FactoryBot.create(:admin).email) user.update(email: create(:admin).email)
expect(user.errors[:email]).to be_present expect(user.errors[:email]).to be_present
end end
context 'when not activated' do context 'when not activated' do
let(:user) { FactoryBot.create(:teacher) } let(:user) { create(:teacher) }
before do before do
user.send(:setup_activation) user.send(:setup_activation)
@ -35,7 +35,7 @@ describe InternalUser do
end end
context 'with a pending password reset' do context 'with a pending password reset' do
let(:user) { FactoryBot.create(:teacher) } let(:user) { create(:teacher) }
before { user.deliver_reset_password_instructions! } before { user.deliver_reset_password_instructions! }
@ -51,7 +51,7 @@ describe InternalUser do
end end
context 'when complete' do context 'when complete' do
let(:user) { FactoryBot.create(:teacher, activation_state: 'active') } let(:user) { create(:teacher, activation_state: 'active') }
it 'does not validate the confirmation of the password' do it 'does not validate the confirmation of the password' do
user.update(password: password, password_confirmation: '') user.update(password: password, password_confirmation: '')
@ -74,8 +74,8 @@ describe InternalUser do
describe '#admin?' do describe '#admin?' do
it 'is only true for admins' do it 'is only true for admins' do
expect(FactoryBot.build(:admin).admin?).to be true expect(build(:admin).admin?).to be true
expect(FactoryBot.build(:teacher).admin?).to be false expect(build(:teacher).admin?).to be false
end end
end end
@ -93,8 +93,8 @@ describe InternalUser do
describe '#teacher?' do describe '#teacher?' do
it 'is only true for teachers' do it 'is only true for teachers' do
expect(FactoryBot.build(:admin).teacher?).to be false expect(build(:admin).teacher?).to be false
expect(FactoryBot.build(:teacher).teacher?).to be true expect(build(:teacher).teacher?).to be true
end end
end end
end end

View File

@ -3,10 +3,10 @@
require 'rails_helper' require 'rails_helper'
describe RequestForComment do describe RequestForComment do
let!(:rfc) { FactoryBot.create(:rfc) } let!(:rfc) { create(:rfc) }
describe 'scope with_comments' do describe 'scope with_comments' do
let!(:rfc2) { FactoryBot.create(:rfc_with_comment) } let!(:rfc2) { create(:rfc_with_comment) }
it 'includes all RfCs with comments' do it 'includes all RfCs with comments' do
expect(described_class.with_comments).to include(rfc2) expect(described_class.with_comments).to include(rfc2)

View File

@ -3,12 +3,12 @@
require 'rails_helper' require 'rails_helper'
describe Runner do describe Runner do
let(:runner_id) { FactoryBot.attributes_for(:runner)[:runner_id] } let(:runner_id) { attributes_for(:runner)[:runner_id] }
let(:strategy_class) { described_class.strategy_class } let(:strategy_class) { described_class.strategy_class }
let(:strategy) { instance_double(strategy_class) } let(:strategy) { instance_double(strategy_class) }
describe 'attribute validation' do describe 'attribute validation' do
let(:runner) { FactoryBot.create :runner } let(:runner) { create :runner }
it 'validates the presence of the runner id' do it 'validates the presence of the runner id' do
described_class.skip_callback(:validation, :before, :request_id) described_class.skip_callback(:validation, :before, :request_id)
@ -162,8 +162,8 @@ describe Runner do
end end
describe 'creation' do describe 'creation' do
let(:user) { FactoryBot.create :external_user } let(:user) { create :external_user }
let(:execution_environment) { FactoryBot.create :ruby } let(:execution_environment) { create :ruby }
let(:create_action) { -> { described_class.create(user: user, execution_environment: execution_environment) } } let(:create_action) { -> { described_class.create(user: user, execution_environment: execution_environment) } }
it 'requests a runner id from the runner management' do it 'requests a runner id from the runner management' do
@ -187,12 +187,12 @@ describe Runner do
it 'does not call the runner management again while a runner id is set' do it 'does not call the runner management again while a runner id is set' do
expect(strategy_class).to receive(:request_from_management).and_return(runner_id).once expect(strategy_class).to receive(:request_from_management).and_return(runner_id).once
runner = create_action.call runner = create_action.call
runner.update(user: FactoryBot.create(:external_user)) runner.update(user: create(:external_user))
end end
end end
describe '#request_new_id' do describe '#request_new_id' do
let(:runner) { FactoryBot.create :runner } let(:runner) { create :runner }
context 'when the environment is available in the runner management' do context 'when the environment is available in the runner management' do
it 'requests the runner management' do it 'requests the runner management' do
@ -240,8 +240,8 @@ describe Runner do
end end
describe '::for' do describe '::for' do
let(:user) { FactoryBot.create :external_user } let(:user) { create :external_user }
let(:exercise) { FactoryBot.create :fibonacci } let(:exercise) { create :fibonacci }
context 'when the runner could not be saved' do context 'when the runner could not be saved' do
before { allow(strategy_class).to receive(:request_from_management).and_return(nil) } before { allow(strategy_class).to receive(:request_from_management).and_return(nil) }
@ -252,7 +252,7 @@ describe Runner do
end end
context 'when a runner already exists' do context 'when a runner already exists' do
let!(:existing_runner) { FactoryBot.create(:runner, user: user, execution_environment: exercise.execution_environment) } let!(:existing_runner) { create(:runner, user: user, execution_environment: exercise.execution_environment) }
it 'returns the existing runner' do it 'returns the existing runner' do
new_runner = described_class.for(user, exercise.execution_environment) new_runner = described_class.for(user, exercise.execution_environment)

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