diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 431b12e..86f2090 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,8 +132,9 @@ jobs: - name: Validate database migrations env: RAILS_ENV: production + SECRET_KEY_BASE: dummy DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 - run: rails --trace db:drop db:create db:migrate + run: bundle exec rails --trace db:drop db:create db:migrate - name: Upload artifacts if: ${{ always() }} diff --git a/.rubocop.yml b/.rubocop.yml index 47ffe9a..df1abec 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,8 @@ -require: +plugins: - rubocop-rails AllCops: + TargetRailsVersion: 8.0 UseCache: false Exclude: # Exclude generated files @@ -185,7 +186,7 @@ Lint/TripleQuotes: # (new in 1.9) Enabled: true Style/IfWithBooleanLiteralBranches: # (new in 1.9) Enabled: true -Gemspec/DateAssignment: # (new in 1.10) +Gemspec/DeprecatedAttributeAssignment: Enabled: true Style/HashConversion: # (new in 1.10) Enabled: true diff --git a/Dockerfile b/Dockerfile index 96521e0..7562713 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,13 +29,13 @@ RUN mkdir -p /opt/app \ # ------------------------------------------------------------ # Install packages common to dev and prod. -# Get list of available packages -RUN apt-get update -qq - # Install standard packages from the Debian repository -RUN apt-get install -y --no-install-recommends \ +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ libpq-dev \ - libvips42 + libvips42 \ + libyaml-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* # ------------------------------------------------------------ # Run configuration @@ -75,10 +75,11 @@ FROM base AS development USER root # Install system packages needed to build gems with C extensions. -RUN apt-get install -y --no-install-recommends \ +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ gcc \ g++ \ - make + make \ + && rm -rf /var/lib/apt/lists/* # ------------------------------------------------------------ # Install Ruby gems @@ -87,7 +88,7 @@ RUN apt-get install -y --no-install-recommends \ USER $APP_USER # Base image ships with an older version of bundler -RUN gem install bundler --version 2.5.22 +RUN gem install bundler --version 2.7.2 # Install gems. We don't enforce the validity of the Gemfile.lock until the # final (production) stage. diff --git a/Gemfile b/Gemfile index 89bcf8a..1086044 100644 --- a/Gemfile +++ b/Gemfile @@ -5,21 +5,22 @@ ruby '~> 3.3' gem 'berkeley_library-alma', '~> 0.1.1' gem 'berkeley_library-logging', '~> 0.3' -gem 'berkeley_library-util', '~> 0.2.0' +gem 'berkeley_library-util', '~> 0.3' gem 'drb' gem 'image_processing', '~> 1.12' gem 'jsonapi.rb', '~> 2.0' gem 'jsonapi-serializer', '~> 2.2' gem 'jwt', '~> 2.4' gem 'mutex_m' -gem 'omniauth', '~> 1.9', '>= 1.9.2' -gem 'omniauth-cas', '~> 2.0' +gem 'omniauth', '~> 2.1' +gem 'omniauth-cas', '~> 3.0' +gem 'omniauth-rails_csrf_protection', '~> 1.0' gem 'pg', '~> 1.4' gem 'pg_search', '~> 2.3' -gem 'puma', '~> 5.0' +gem 'puma', '~> 7.2' gem 'rack-cors' -gem 'rails', '~> 7.0.4' -gem 'ransack', '~> 2.6' +gem 'rails', '~> 8.0.4' +gem 'ransack', '~> 4.2' gem 'zaru', '~> 0.3.0' group :development, :test do @@ -30,19 +31,20 @@ end group :development do gem 'hashdiff', '~> 1.0.1' - gem 'rubocop', '~> 1.26.0' - gem 'rubocop-rails', '~> 2.13.2', require: false - gem 'rubocop-rspec', '~> 2.4.0', require: false + gem 'rubocop', '~> 1.86' + gem 'rubocop-rails', '~> 2.34', require: false + gem 'rubocop-rspec', '~> 3.6', require: false + gem 'rubocop-rspec_rails', '~> 2.30', require: false end group :test do gem 'database_cleaner-active_record', '~> 2.0' gem 'factory_bot_rails' gem 'rails-controller-testing' - gem 'rspec', '~> 3.10' - gem 'rspec_junit_formatter', '~> 0.5' - gem 'rspec-rails', '~> 5.0' - gem 'simplecov', '~> 0.21', require: false - gem 'simplecov-rcov', '~> 0.2', require: false + gem 'rspec', '~> 3.13' + gem 'rspec_junit_formatter', '~> 0.6' + gem 'rspec-rails', '~> 8.0' + gem 'simplecov', '~> 0.22', require: false + gem 'simplecov-rcov', '~> 0.3', require: false gem 'webmock', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 43f2cc8..391e8ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,76 +1,83 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.7) - actionpack (= 7.0.8.7) - activesupport (= 7.0.8.7) + actioncable (8.0.5) + actionpack (= 8.0.5) + activesupport (= 8.0.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.7) - actionpack (= 7.0.8.7) - activejob (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.0.8.7) - actionpack (= 7.0.8.7) - actionview (= 7.0.8.7) - activejob (= 7.0.8.7) - activesupport (= 7.0.8.7) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8.7) - actionview (= 7.0.8.7) - activesupport (= 7.0.8.7) - rack (~> 2.0, >= 2.2.4) + zeitwerk (~> 2.6) + actionmailbox (8.0.5) + actionpack (= 8.0.5) + activejob (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) + mail (>= 2.8.0) + actionmailer (8.0.5) + actionpack (= 8.0.5) + actionview (= 8.0.5) + activejob (= 8.0.5) + activesupport (= 8.0.5) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.5) + actionview (= 8.0.5) + activesupport (= 8.0.5) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.7) - actionpack (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.5) + actionpack (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.7) - activesupport (= 7.0.8.7) + actionview (8.0.5) + activesupport (= 8.0.5) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8.7) - activesupport (= 7.0.8.7) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.5) + activesupport (= 8.0.5) globalid (>= 0.3.6) - activemodel (7.0.8.7) - activesupport (= 7.0.8.7) - activerecord (7.0.8.7) - activemodel (= 7.0.8.7) - activesupport (= 7.0.8.7) - activestorage (7.0.8.7) - actionpack (= 7.0.8.7) - activejob (= 7.0.8.7) - activerecord (= 7.0.8.7) - activesupport (= 7.0.8.7) + activemodel (8.0.5) + activesupport (= 8.0.5) + activerecord (8.0.5) + activemodel (= 8.0.5) + activesupport (= 8.0.5) + timeout (>= 0.4.0) + activestorage (8.0.5) + actionpack (= 8.0.5) + activejob (= 8.0.5) + activerecord (= 8.0.5) + activesupport (= 8.0.5) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8.7) - concurrent-ruby (~> 1.0, >= 1.0.2) + activesupport (8.0.5) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) amazing_print (1.8.1) ast (2.4.3) base64 (0.3.0) + benchmark (0.5.0) berkeley_library-alma (0.1.1) berkeley_library-logging (~> 0.2) berkeley_library-marc (~> 0.3.1) @@ -86,21 +93,22 @@ GEM marc (~> 1.0) parslet (~> 2.0) ruby-marc-spec (~> 0.1) - berkeley_library-util (0.2.0) + berkeley_library-util (0.3.0) berkeley_library-logging (~> 0.3) rest-client (~> 2.1) typesafe_enum (~> 0.3) - bigdecimal (3.2.2) + bigdecimal (4.0.1) brakeman (7.1.0) racc builder (3.3.0) bundle-audit (0.1.0) bundler-audit - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) colorize (1.1.0) - concurrent-ruby (1.3.5) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) crack (1.0.0) bigdecimal rexml @@ -109,11 +117,12 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0) database_cleaner-core (2.0.1) - date (3.4.1) + date (3.5.1) diff-lcs (1.6.2) docile (1.4.1) domain_name (0.6.20240107) drb (2.2.3) + erb (6.0.2) erubi (1.13.1) factory_bot (6.5.5) activesupport (>= 6.1.0) @@ -122,19 +131,28 @@ GEM railties (>= 6.1.0) ffi (1.17.2) ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-linux-gnu) - globalid (1.2.1) + globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.0.1) - hashie (5.0.0) + hashie (5.1.0) + logger http-accept (1.7.0) - http-cookie (1.0.8) + http-cookie (1.1.0) domain_name (~> 0.5) - i18n (1.14.7) + i18n (1.14.8) concurrent-ruby (~> 1.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) + io-console (0.8.2) + irb (1.17.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.19.2) jsonapi-serializer (2.2.0) activesupport (>= 4.2) jsonapi.rb (2.1.1) @@ -142,16 +160,19 @@ GEM rack jwt (2.10.2) base64 + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) logger (1.7.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.24.1) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop @@ -159,19 +180,20 @@ GEM marc (1.3.0) nokogiri (~> 1.0) rexml - marcel (1.0.4) - method_source (1.1.0) + marcel (1.1.0) mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0819) + mime-types-data (3.2026.0317) mini_magick (5.3.1) logger mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.25.5) + minitest (6.0.2) + drb (~> 2.0) + prism (~> 1.5) mutex_m (0.3.0) - net-imap (0.5.9) + net-imap (0.6.3) date net-protocol net-pop (0.1.2) @@ -181,62 +203,85 @@ GEM net-smtp (0.5.1) net-protocol netrc (0.11.0) - nio4r (2.7.4) - nokogiri (1.18.9) + nio4r (2.7.5) + nokogiri (1.19.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.18.9-aarch64-linux-gnu) + nokogiri (1.19.2-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.9-x86_64-linux-gnu) + nokogiri (1.19.2-x86_64-linux-gnu) racc (~> 1.4) - oj (3.16.11) + oj (3.16.16) bigdecimal (>= 3.0) ostruct (>= 0.2) - omniauth (1.9.2) + omniauth (2.1.4) hashie (>= 3.4.6) - rack (>= 1.6.2, < 3) - omniauth-cas (2.0.0) - addressable (~> 2.3) - nokogiri (~> 1.5) - omniauth (~> 1.2) + logger + rack (>= 2.2.3) + rack-protection + omniauth-cas (3.0.2) + addressable (~> 2.8) + nokogiri (~> 1.12) + omniauth (~> 2.1) + omniauth-rails_csrf_protection (1.0.2) + actionpack (>= 4.2) + omniauth (~> 2.0) ostruct (0.6.3) ougai (2.0.0) oj (~> 3.10) parallel (1.27.0) - parser (3.3.9.0) + parser (3.3.10.2) ast (~> 2.4.1) racc parslet (2.0.0) pg (1.6.1) pg (1.6.1-aarch64-linux) + pg (1.6.1-arm64-darwin) pg (1.6.1-x86_64-linux) pg_search (2.3.7) activerecord (>= 6.1) activesupport (>= 6.1) - prism (1.4.0) - public_suffix (6.0.2) - puma (5.6.9) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + psych (5.3.1) + date + stringio + public_suffix (7.0.5) + puma (7.2.0) nio4r (~> 2.0) racc (1.8.1) - rack (2.2.17) + rack (3.2.5) rack-cors (2.0.2) rack (>= 2.0.0) + rack-protection (4.2.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rails (7.0.8.7) - actioncable (= 7.0.8.7) - actionmailbox (= 7.0.8.7) - actionmailer (= 7.0.8.7) - actionpack (= 7.0.8.7) - actiontext (= 7.0.8.7) - actionview (= 7.0.8.7) - activejob (= 7.0.8.7) - activemodel (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) + rackup (2.3.1) + rack (>= 3) + rails (8.0.5) + actioncable (= 8.0.5) + actionmailbox (= 8.0.5) + actionmailer (= 8.0.5) + actionpack (= 8.0.5) + actiontext (= 8.0.5) + actionview (= 8.0.5) + activejob (= 8.0.5) + activemodel (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) bundler (>= 1.15.0) - railties (= 7.0.8.7) + railties (= 8.0.5) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -245,23 +290,31 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.0.8.7) - actionpack (= 7.0.8.7) - activesupport (= 7.0.8.7) - method_source + railties (8.0.5) + actionpack (= 8.0.5) + activesupport (= 8.0.5) + irb (~> 1.13) + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.0) - ransack (2.6.0) - activerecord (>= 6.0.4) - activesupport (>= 6.0.4) + rake (13.3.1) + ransack (4.4.0) + activerecord (>= 7.1) + activesupport (>= 7.1) i18n - regexp_parser (2.11.2) + rdoc (7.2.0) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) rest-client (2.1.0) @@ -269,49 +322,57 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.4.1) + rexml (3.4.4) rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.5) + rspec-core (3.13.6) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.5) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (5.1.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - railties (>= 5.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-support (3.13.5) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.26.1) + rubocop (1.86.0) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.16.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.46.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) - prism (~> 1.4) - rubocop-rails (2.13.2) + prism (~> 1.7) + rubocop-rails (2.34.3) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-rspec (2.4.0) - rubocop (~> 1.0) - rubocop-ast (>= 1.1.0) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.9.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) ruby-marc-spec (0.1.3) marc (~> 1.1) parslet (~> 2.0) @@ -320,6 +381,7 @@ GEM ruby-vips (2.2.4) ffi (~> 1.12) logger + securerandom (0.4.1) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -328,12 +390,18 @@ GEM simplecov-rcov (0.3.7) simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) - thor (1.4.0) - timeout (0.4.3) + stringio (3.2.0) + thor (1.5.0) + timeout (0.6.1) + tsort (0.2.0) typesafe_enum (0.3.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.6.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + useragent (0.16.11) webmock (3.25.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -343,17 +411,18 @@ GEM websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) zaru (0.3.0) - zeitwerk (2.7.3) + zeitwerk (2.7.5) PLATFORMS aarch64-linux-gnu + arm64-darwin-23 x86_64-linux x86_64-unknown DEPENDENCIES berkeley_library-alma (~> 0.1.1) berkeley_library-logging (~> 0.3) - berkeley_library-util (~> 0.2.0) + berkeley_library-util (~> 0.3) brakeman bundle-audit colorize @@ -366,28 +435,30 @@ DEPENDENCIES jsonapi.rb (~> 2.0) jwt (~> 2.4) mutex_m - omniauth (~> 1.9, >= 1.9.2) - omniauth-cas (~> 2.0) + omniauth (~> 2.1) + omniauth-cas (~> 3.0) + omniauth-rails_csrf_protection (~> 1.0) pg (~> 1.4) pg_search (~> 2.3) - puma (~> 5.0) + puma (~> 7.2) rack-cors - rails (~> 7.0.4) + rails (~> 8.0.4) rails-controller-testing - ransack (~> 2.6) - rspec (~> 3.10) - rspec-rails (~> 5.0) - rspec_junit_formatter (~> 0.5) - rubocop (~> 1.26.0) - rubocop-rails (~> 2.13.2) - rubocop-rspec (~> 2.4.0) - simplecov (~> 0.21) - simplecov-rcov (~> 0.2) + ransack (~> 4.2) + rspec (~> 3.13) + rspec-rails (~> 8.0) + rspec_junit_formatter (~> 0.6) + rubocop (~> 1.86) + rubocop-rails (~> 2.34) + rubocop-rspec (~> 3.6) + rubocop-rspec_rails (~> 2.30) + simplecov (~> 0.22) + simplecov-rcov (~> 0.3) webmock zaru (~> 0.3.0) RUBY VERSION - ruby 3.3.9p170 + ruby 3.3.9p170 BUNDLED WITH - 2.5.22 + 2.7.2 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3313683..378b0ec 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -33,7 +33,7 @@ def authenticate! # # @return [User] the user def current_user - @current_user ||= (user_from_token || default_user) + @current_user ||= user_from_token || default_user end # Require an authenticated user with admin privileges diff --git a/app/controllers/concerns/exception_handlers.rb b/app/controllers/concerns/exception_handlers.rb index 5d7ce04..79c7682 100644 --- a/app/controllers/concerns/exception_handlers.rb +++ b/app/controllers/concerns/exception_handlers.rb @@ -10,7 +10,7 @@ module ExceptionHandlers # # - StandardError -> render_jsonapi_internal_server_error # - ActiveRecord::RecordNotFound -> render_jsonapi_not_found - # - ActionController::ParameterMissing -> render_jsonapi_unprocessable_entity + # - ActionController::ParameterMissing -> render_jsonapi_unprocessable_content # rubocop:disable Metrics/BlockLength included do @@ -21,8 +21,8 @@ module ExceptionHandlers rescue_from Error::ForbiddenError, with: :render_jsonapi_forbidden_error rescue_from ActionController::ParameterMissing, with: :render_jsonapi_parameter_missing rescue_from ActiveRecord::DeleteRestrictionError, with: :render_jsonapi_conflict - rescue_from ActiveRecord::RecordInvalid, with: :render_validation_errors_as_unprocessable_entity - rescue_from ActiveModel::ValidationError, with: :render_validation_errors_as_unprocessable_entity + rescue_from ActiveRecord::RecordInvalid, with: :render_validation_errors_as_unprocessable_content + rescue_from ActiveModel::ValidationError, with: :render_validation_errors_as_unprocessable_content # ------------------------------------------------------------ # Error handlers @@ -40,13 +40,13 @@ def render_jsonapi_unauthorized_error(exception) def render_jsonapi_parameter_missing(exception) logger.error(exception) - render_jsonapi_error(:unprocessable_entity, detail: exception.message) + render_jsonapi_error(:unprocessable_content, detail: exception.message) end - def render_validation_errors_as_unprocessable_entity(exception) + def render_validation_errors_as_unprocessable_content(exception) logger.error(exception) errors = validation_errors_from(exception) || [exception] - render jsonapi_errors: errors, status: :unprocessable_entity + render jsonapi_errors: errors, status: :unprocessable_content end # ------------------------------------------------------------ @@ -87,7 +87,8 @@ def render_jsonapi_conflict(exception) def validation_errors_from(exception) return unless (record = record_from(exception)) return unless record.respond_to?(:errors) && (errors = record.errors) - return errors if errors.is_a?(Enumerable) + + errors if errors.is_a?(Enumerable) end def render_jsonapi_error(status, detail: nil, meta: nil) diff --git a/app/lib/cors_helper.rb b/app/lib/cors_helper.rb index 6921f59..8135e4d 100644 --- a/app/lib/cors_helper.rb +++ b/app/lib/cors_helper.rb @@ -12,7 +12,7 @@ def allow?(source, env = Rails.env) def allowed_hosts config = Rails.application.config - cors_hosts = (config.cors_hosts || []) + cors_hosts = config.cors_hosts || [] cors_hosts + config.hosts # TODO: should we only do this in dev? end end diff --git a/app/models/closure.rb b/app/models/closure.rb index 6d0eedc..4f6ea93 100644 --- a/app/models/closure.rb +++ b/app/models/closure.rb @@ -67,7 +67,7 @@ def start_date_before_end_date FILTER_QUERIES.each do |scope_name, query| scope scope_name, -> { where("id IN (#{query})").order(**DEFAULT_ORDER) } - scope "not_#{scope_name}".to_sym, -> { where("id NOT IN (#{query})").order(**DEFAULT_ORDER) } + scope :"not_#{scope_name}", -> { where("id NOT IN (#{query})").order(**DEFAULT_ORDER) } end scope :filter_by, ->(filters) { diff --git a/app/models/image.rb b/app/models/image.rb index 7dbedc6..3cd8c73 100644 --- a/app/models/image.rb +++ b/app/models/image.rb @@ -127,7 +127,7 @@ def mime_type_of(uploaded_file) def jpeg_tempfile_from(uploaded_file) mime_type = mime_type_of(uploaded_file) - return uploaded_file if (mime_type) == JPEG_MIME_TYPE + return uploaded_file if mime_type == JPEG_MIME_TYPE logger.info("#{uploaded_file.path} (#{uploaded_file.original_filename}) is #{mime_type}; converting to #{JPEG_MIME_TYPE}") ImageProcessing::Vips.source(uploaded_file).convert('jpeg').call diff --git a/app/services/availability_service.rb b/app/services/availability_service.rb index e321f9f..ede501c 100644 --- a/app/services/availability_service.rb +++ b/app/services/availability_service.rb @@ -90,7 +90,7 @@ def fetch_and_cache_availability_for(ids) # @param ids Array a list of MMS IDs # @return Hash a hash from MMS IDs to availability. def fetch_availability_for(ids) - records = get_marc_records(*ids, max_records: (max_records || ids.size)) + records = get_marc_records(*ids, max_records: max_records || ids.size) # TODO: do we need to cache this? records.each_with_object({}) do |marc_record, availability| diff --git a/config/application.rb b/config/application.rb index db74adf..59e2ddd 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,10 @@ module GalcApi class Application < Rails::Application config.load_defaults 7.0 + + # Opt in to Rails 8.1 timezone behavior early + config.active_support.to_time_preserves_timezone = :zone + config.api_only = true # ------------------------------------------------------------ diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index b7fe33c..57976b0 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -4,8 +4,9 @@ namespace :db do tries = 0 max_tries = 4 begin - pool ||= ActiveRecord::Base.establish_connection - pool.connection + ActiveRecord::Base.establish_connection + + ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue PG::ConnectionBad raise unless (tries += 1) <= max_tries diff --git a/spec/.rubocop.yml b/spec/.rubocop.yml index c0f43fa..bff603f 100644 --- a/spec/.rubocop.yml +++ b/spec/.rubocop.yml @@ -1,7 +1,8 @@ inherit_from: ../.rubocop.yml -require: +plugins: - rubocop-rspec + - rubocop-rspec_rails Style/ClassAndModuleChildren: Enabled: false @@ -58,7 +59,7 @@ RSpec/DescribeSymbol: # setup cost / time >>> failure granularity RSpec/ExampleLength: - Max: 15 + Max: 25 CountAsOne: - array - hash @@ -69,7 +70,10 @@ RSpec/ExpectInHook: Enabled: false # your naming scheme is not in possession of all the facts -RSpec/FilePath: +RSpec/SpecFilePathFormat: + Enabled: false + +RSpec/SpecFilePathSuffix: Enabled: false # explicit >>> implicit @@ -116,5 +120,5 @@ RSpec/VerifiedDoubles: RSpec/IdenticalEqualityAssertion: # new in 2.4 Enabled: true -RSpec/Rails/AvoidSetupHook: # new in 2.4 +RSpecRails/AvoidSetupHook: Enabled: true diff --git a/spec/concerns/exception_handlers_spec.rb b/spec/concerns/exception_handlers_spec.rb index 76bee52..345c345 100644 --- a/spec/concerns/exception_handlers_spec.rb +++ b/spec/concerns/exception_handlers_spec.rb @@ -8,7 +8,7 @@ response_attrs = { headers: { 'Vary' => 'Accept' } } %i[status content_type media_type headers body].each do |attr| allow(response).to receive(attr) { response_attrs[attr] } - allow(response).to receive("#{attr}=".to_sym) { |v| response_attrs[attr] = v } + allow(response).to receive(:"#{attr}=") { |v| response_attrs[attr] = v } end allow(response).to receive(:reset_body!) end diff --git a/spec/factories/facets.rb b/spec/factories/facets.rb index 21dad45..6b8ab45 100644 --- a/spec/factories/facets.rb +++ b/spec/factories/facets.rb @@ -16,7 +16,7 @@ %w[Size Decade Genre Medium Appearance].each_with_index do |facet_name, index| multiple = multiple_allowed.include?(facet_name) - factory_name = "facet_#{facet_name.downcase}".to_sym + factory_name = :"facet_#{facet_name.downcase}" factory(factory_name) do name { facet_name } ord { index } diff --git a/spec/gemfile_lock_spec.rb b/spec/gemfile_lock_spec.rb index a694b4e..9063002 100644 --- a/spec/gemfile_lock_spec.rb +++ b/spec/gemfile_lock_spec.rb @@ -5,7 +5,7 @@ before do lockfile_path = File.expand_path('../Gemfile.lock', __dir__) - expect(File.exist?(lockfile_path)).to eq(true), "Gemfile.lock not found in #{lockfile_path}" + expect(File.exist?(lockfile_path)).to be(true), "Gemfile.lock not found in #{lockfile_path}" lockfile = File.read(lockfile_path) @parser = Bundler::LockfileParser.new(lockfile) diff --git a/spec/lib/cors_helper_spec.rb b/spec/lib/cors_helper_spec.rb index cf3f6d8..904bd8a 100644 --- a/spec/lib/cors_helper_spec.rb +++ b/spec/lib/cors_helper_spec.rb @@ -4,17 +4,17 @@ describe :allow? do it 'rejects an invalid URI' do invalid_uri = 'not a URI' - expect(CorsHelper.allow?(invalid_uri)).to eq(false) + expect(CorsHelper.allow?(invalid_uri)).to be(false) end it 'returns true for a lib.berkeley.edu origin' do source = 'https://www.lib.berkeley.edu/' - expect(CorsHelper.allow?(source)).to eq(true) + expect(CorsHelper.allow?(source)).to be(true) end it 'returns true for a pantheon.berkeley.edu origin' do source = 'https://dev.pantheon.berkeley.edu/' - expect(CorsHelper.allow?(source)).to eq(true) + expect(CorsHelper.allow?(source)).to be(true) end end end diff --git a/spec/models/image_spec.rb b/spec/models/image_spec.rb index f42e2de..8e2fd58 100644 --- a/spec/models/image_spec.rb +++ b/spec/models/image_spec.rb @@ -40,10 +40,10 @@ def uploaded_file expect(image).to be_persisted file_path = image.file_path - expect(File.exist?(file_path)).to eq(true) + expect(File.exist?(file_path)).to be(true) thumbnail_path = image.thumbnail_path - expect(File.exist?(thumbnail_path)).to eq(true) + expect(File.exist?(thumbnail_path)).to be(true) end it 'copies the original image' do @@ -99,8 +99,8 @@ def uploaded_file it 'handles name collisions with files on disk' do image1 = Image.from_uploaded_file(uploaded_file).tap(&:delete) expect { Image.find(image1.id) }.to raise_error(ActiveRecord::RecordNotFound) - expect(File.exist?(image1.file_path)).to eq(true) - expect(File.exist?(image1.thumbnail_path)).to eq(true) + expect(File.exist?(image1.file_path)).to be(true) + expect(File.exist?(image1.thumbnail_path)).to be(true) image2 = Image.from_uploaded_file(uploaded_file) @@ -125,7 +125,7 @@ def uploaded_file %i[file_path thumbnail_path].each do |path_attr| path = image1.send(path_attr) FileUtils.rm(path) - expect(File.exist?(path)).to eq(false) + expect(File.exist?(path)).to be(false) end image2 = Image.from_uploaded_file(uploaded_file) @@ -161,10 +161,10 @@ def uploaded_file expect(image).to be_persisted file_path = image.file_path - expect(File.exist?(file_path)).to eq(true) + expect(File.exist?(file_path)).to be(true) thumbnail_path = image.thumbnail_path - expect(File.exist?(thumbnail_path)).to eq(true) + expect(File.exist?(thumbnail_path)).to be(true) original = Vips::Image.new_from_file(source_file_path) uploaded = Vips::Image.new_from_file(file_path) @@ -188,10 +188,10 @@ def uploaded_file expect(image).to be_persisted file_path = image.file_path - expect(File.exist?(file_path)).to eq(true) + expect(File.exist?(file_path)).to be(true) thumbnail_path = image.thumbnail_path - expect(File.exist?(thumbnail_path)).to eq(true) + expect(File.exist?(thumbnail_path)).to be(true) original = Vips::Image.new_from_file(source_file_path) uploaded = Vips::Image.new_from_file(file_path) @@ -262,12 +262,12 @@ def uploaded_file it 'deletes the image files' do file_path = image.file_path thumbnail_path = image.thumbnail_path - expect(File.exist?(file_path)).to eq(true) # just to be sure - expect(File.exist?(thumbnail_path)).to eq(true) # just to be sure + expect(File.exist?(file_path)).to be(true) # just to be sure + expect(File.exist?(thumbnail_path)).to be(true) # just to be sure image.destroy - expect(File.exist?(file_path)).to eq(false) - expect(File.exist?(thumbnail_path)).to eq(false) + expect(File.exist?(file_path)).to be(false) + expect(File.exist?(thumbnail_path)).to be(false) end it 'does not delete an image in use by an item' do @@ -276,8 +276,8 @@ def uploaded_file expect { image.destroy }.to raise_error(ActiveRecord::DeleteRestrictionError) expect(Image.find(image.id)).to eq(image) - expect(File.exist?(image.file_path)).to eq(true) - expect(File.exist?(image.thumbnail_path)).to eq(true) + expect(File.exist?(image.file_path)).to be(true) + expect(File.exist?(image.thumbnail_path)).to be(true) expect(Item.find(item.id)).to eq(item) end diff --git a/spec/models/item_spec.rb b/spec/models/item_spec.rb index 8a71266..684c33b 100644 --- a/spec/models/item_spec.rb +++ b/spec/models/item_spec.rb @@ -12,7 +12,7 @@ expect(expected_items).not_to be_empty # just to be sure results = Item.with_facet_values({ 'Medium' => 'Woodcut' }) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end it 'finds items based on ORing values for a single facet' do @@ -22,7 +22,7 @@ expect(expected_items).not_to be_empty # just to be sure results = Item.with_facet_values({ 'Medium' => %w[Etching Lithograph] }) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end it 'finds items based on ANDing values for multiple facets' do @@ -32,7 +32,7 @@ expect(expected_items).not_to be_empty # just to be sure results = Item.with_facet_values({ 'Appearance' => 'Color', 'Genre' => 'Landscape' }) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end it 'finds items based on ANDing multiple OR values for multiple facets' do @@ -49,7 +49,7 @@ facet_values = { 'Medium' => %w[Etching Lithograph], 'Genre' => %w[Landscape Figurative] } results = Item.with_facet_values(facet_values) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end it 'handles completely bananas queries' do @@ -76,7 +76,7 @@ end results = Item.with_facet_values(facet_values) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end it 'finds items based on implicit parent material values' do @@ -88,7 +88,7 @@ facet_values = { 'Medium' => ['Relief'] } results = Item.with_facet_values(facet_values) - expect(results).to contain_exactly(*expected_items) + expect(results).to match_array(expected_items) end end @@ -163,7 +163,7 @@ item.save! item.reload - expect(item.suppressed).to eq(true) + expect(item.suppressed).to be(true) expect(item.image).to be_nil end @@ -194,7 +194,7 @@ item.save! item.reload - expect(item.suppressed).to eq(true) + expect(item.suppressed).to be(true) expect(item.mms_id).to be_nil end @@ -237,7 +237,7 @@ item.reload expect(item.mms_id).to be_nil expect(item.title).to eq(title) - expect(item.suppressed).to eq(true) + expect(item.suppressed).to be(true) end end end @@ -287,7 +287,7 @@ it 'returns nil for a missing image' do item = Item.find_by(artist: 'Minami, Keiko') item.image = nil - expect(item.image_uri).to eq(nil) + expect(item.image_uri).to be_nil end end @@ -301,7 +301,7 @@ it 'returns nil for a missing thumbnail' do item = Item.find_by(artist: 'Minami, Keiko') item.image = nil - expect(item.thumbnail_uri).to eq(nil) + expect(item.thumbnail_uri).to be_nil end end end diff --git a/spec/models/term_spec.rb b/spec/models/term_spec.rb index bc0bf22..fa4fc4e 100644 --- a/spec/models/term_spec.rb +++ b/spec/models/term_spec.rb @@ -64,7 +64,7 @@ 'Photoprint', 'Relief Etching' ] - expect(children.pluck(:value)).to contain_exactly(*expected_values) + expect(children.pluck(:value)).to match_array(expected_values) children.each do |child| expect(child.parent).to eq(term_intaglio) expect(child.facet).to eq(facet_medium) diff --git a/spec/requests/auth_spec.rb b/spec/requests/auth_spec.rb index 55f5930..02162ab 100644 --- a/spec/requests/auth_spec.rb +++ b/spec/requests/auth_spec.rb @@ -42,10 +42,8 @@ describe 'GET /auth/calnet' do # See https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284 it 'is disallowed' do - expect { get login_path, params: { origin: origin_url } }.to raise_error(ActionController::RoutingError) - - # TODO: use this instead of expect { … }.to raise_error ? - # expect(response).not_to be_successful + get login_path, params: { origin: origin_url } + expect(response).to have_http_status(:not_found) end end @@ -97,7 +95,10 @@ def callback_url_from_cas_redirect(loc) post login_path, params: { origin: bad_origin_url } callback_url = callback_url_from_cas_redirect(response.headers['Location']) - expect { get callback_url }.to raise_error(ActionController::Redirecting::UnsafeRedirectError) + + get callback_url + expect(response).to have_http_status(:internal_server_error) + expect(response.body).to include('ActionController::Redirecting::UnsafeRedirectError') end end diff --git a/spec/requests/closures_spec.rb b/spec/requests/closures_spec.rb index 1b9b33a..1d4896a 100644 --- a/spec/requests/closures_spec.rb +++ b/spec/requests/closures_spec.rb @@ -164,13 +164,13 @@ def expected_errors_for(invalid_attributes, closure_to_update: nil) expect { post closures_url, params: payload, as: :jsonapi }.not_to change(Closure, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 422 Unprocessable Entity for an invalid date range' do @@ -185,13 +185,13 @@ def expected_errors_for(invalid_attributes, closure_to_update: nil) expect { post closures_url, params: payload, as: :jsonapi }.not_to change(Closure, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end end end @@ -250,14 +250,14 @@ def expected_errors_for(invalid_attributes, closure_to_update: nil) patch closure_url(closure), params: payload, as: :jsonapi expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) invalid_attributes = old_attributes.merge(start_date: nil) expected_errors = expected_errors_for(invalid_attributes, closure_to_update: closure) actual_errors = JSON.parse(response.body)['errors'] expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) closure.reload old_attributes.each { |attr, val| expect(item.send(attr)).to eq(val), "Wrong value for #{attr}" } @@ -274,14 +274,14 @@ def expected_errors_for(invalid_attributes, closure_to_update: nil) patch closure_url(closure), params: payload, as: :jsonapi expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) invalid_attributes = old_attributes.merge(start_date: bad_start_date) expected_errors = expected_errors_for(invalid_attributes, closure_to_update: closure) actual_errors = JSON.parse(response.body)['errors'] expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) closure.reload old_attributes.each { |attr, val| expect(item.send(attr)).to eq(val), "Wrong value for #{attr}" } diff --git a/spec/requests/images_spec.rb b/spec/requests/images_spec.rb index b80acc9..8313a18 100644 --- a/spec/requests/images_spec.rb +++ b/spec/requests/images_spec.rb @@ -138,12 +138,12 @@ it 'deletes the image files' do file_path = image.file_path thumbnail_path = image.thumbnail_path - expect(File.exist?(file_path)).to eq(true) # just to be sure - expect(File.exist?(thumbnail_path)).to eq(true) # just to be sure + expect(File.exist?(file_path)).to be(true) # just to be sure + expect(File.exist?(thumbnail_path)).to be(true) # just to be sure delete image_url(image) - expect(File.exist?(file_path)).to eq(false) - expect(File.exist?(thumbnail_path)).to eq(false) + expect(File.exist?(file_path)).to be(false) + expect(File.exist?(thumbnail_path)).to be(false) end it 'does not delete an image in use by an item' do @@ -156,8 +156,8 @@ expect(Image.count).to eq(count_before) expect(Image.find(image.id)).to eq(image) - expect(File.exist?(image.file_path)).to eq(true) - expect(File.exist?(image.thumbnail_path)).to eq(true) + expect(File.exist?(image.file_path)).to be(true) + expect(File.exist?(image.thumbnail_path)).to be(true) expect(Item.find(item.id)).to eq(item) @@ -205,8 +205,8 @@ expect(Image.count).to eq(count_before) expect(Image.find(image.id)).to eq(image) - expect(File.exist?(image.file_path)).to eq(true) - expect(File.exist?(image.thumbnail_path)).to eq(true) + expect(File.exist?(image.file_path)).to be(true) + expect(File.exist?(image.thumbnail_path)).to be(true) end end end @@ -241,8 +241,8 @@ expect(Image.count).to eq(count_before) expect(Image.find(image.id)).to eq(image) - expect(File.exist?(image.file_path)).to eq(true) - expect(File.exist?(image.thumbnail_path)).to eq(true) + expect(File.exist?(image.file_path)).to be(true) + expect(File.exist?(image.thumbnail_path)).to be(true) end end end diff --git a/spec/requests/items_spec.rb b/spec/requests/items_spec.rb index 22f5c3d..e946258 100644 --- a/spec/requests/items_spec.rb +++ b/spec/requests/items_spec.rb @@ -294,7 +294,7 @@ expect(terms_data).to be_a(Array) expected_terms_data = item.terms.map { |t| { 'type' => 'term', 'id' => t.id.to_s } } - expect(terms_data).to contain_exactly(*expected_terms_data) + expect(terms_data).to match_array(expected_terms_data) end it 'can include both image and terms' do @@ -414,7 +414,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect(item).not_to be_nil valid_attributes.each { |attr, val| expect(item.send(attr)).to eq(val) } - expect(item.terms).to contain_exactly(*valid_terms) + expect(item.terms).to match_array(valid_terms) links = parsed_response.delete('links') expect(links['self']).to eq(items_url) @@ -491,7 +491,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect(item).not_to be_nil expected_attributes.each { |attr, val| expect(item.send(attr)).to eq(val) } - expect(item.terms).to contain_exactly(*valid_terms) + expect(item.terms).to match_array(valid_terms) expect(item.image).to be_nil expect(item).to be_suppressed @@ -512,13 +512,13 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'treats a blank title as missing' do @@ -529,14 +529,14 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] invalid_attributes = post_attributes.merge(title: nil) expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 422 Unprocessable Entity for a missing image' do @@ -552,7 +552,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActionController::ParameterMissing)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] @@ -568,13 +568,13 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 422 Unprocessable Entity for a missing MMS ID when not suppressed' do @@ -585,13 +585,13 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'treats a blank MMS ID as missing' do @@ -602,14 +602,14 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] invalid_attributes = post_attributes.merge(mms_id: nil) expected_errors = expected_errors_for(invalid_attributes) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 404 Not Found for an invalid term' do @@ -701,13 +701,13 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(valid_attributes, terms: invalid_terms, image: valid_image) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end end @@ -737,7 +737,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item item.reload expected_attributes.each { |attr, val| expect(item.send(attr)).to eq(val), "Wrong value for #{attr}" } - expect(item.terms).to contain_exactly(*expected_terms) + expect(item.terms).to match_array(expected_terms) parsed_response = JSON.parse(response.body) @@ -770,7 +770,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect(actual).to eq(val), "Wrong value for #{attr}; expected #{val.inspect}, was #{actual.inspect}" end - expect(item.terms).to contain_exactly(*expected_terms) + expect(item.terms).to match_array(expected_terms) expect(item.image).to be_nil expect(item).to be_suppressed @@ -802,7 +802,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect(actual).to eq(val), "Wrong value for #{attr}; expected #{val.inspect}, was #{actual.inspect}" end - expect(item.terms).to contain_exactly(*expected_terms) + expect(item.terms).to match_array(expected_terms) parsed_response = JSON.parse(response.body) @@ -834,7 +834,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect(actual).to eq(val), "Wrong value for #{attr}; expected #{val.inspect}, was #{actual.inspect}" end - expect(item.terms).to contain_exactly(*expected_terms) + expect(item.terms).to match_array(expected_terms) parsed_response = JSON.parse(response.body) @@ -856,13 +856,13 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item patch item_url(item), params: payload, as: :jsonapi expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(invalid_attributes, item_to_update: item) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 422 Unprocessable Entity for a duplicate MMS ID' do @@ -877,7 +877,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item patch item_url(item), params: payload, as: :jsonapi expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] @@ -885,12 +885,12 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expected_errors = expected_errors_for(invalid_attributes, item_to_update: item) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end it 'fails with 422 Unprocessable Entity for a missing MMS ID when not suppressed' do item = Item.take - expect(item.suppressed).to eq(false) # just to be sure + expect(item.suppressed).to be(false) # just to be sure invalid_attributes = { mms_id: nil } payload = { data: { type: 'item', id: item.id.to_s, attributes: invalid_attributes, relationships: valid_relationships } } @@ -899,7 +899,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item patch item_url(item), params: payload, as: :jsonapi expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid)) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] @@ -907,7 +907,7 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expected_errors = expected_errors_for(invalid_attributes, item_to_update: item) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) end xit 'returns 409 conflict for an invalid type' @@ -944,17 +944,17 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item expect { patch item_url(item), params: payload, as: :jsonapi }.not_to change(ItemsTerm, :count) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE) actual_errors = JSON.parse(response.body)['errors'] expected_errors = expected_errors_for(valid_attributes, terms: invalid_terms, image: valid_image, item_to_update: item) expected_json = expected_errors.map { |err| jsonapi_for(err) } - expect(actual_errors).to contain_exactly(*expected_json) + expect(actual_errors).to match_array(expected_json) item.reload expect(item.updated_at).to eq(original_updated_at) - expect(item.terms.pluck(:value)).to contain_exactly(*original_terms.map(&:value)) + expect(item.terms.pluck(:value)).to match_array(original_terms.map(&:value)) end end diff --git a/spec/requests/reservations_spec.rb b/spec/requests/reservations_spec.rb index 1b4e059..7b1c995 100644 --- a/spec/requests/reservations_spec.rb +++ b/spec/requests/reservations_spec.rb @@ -44,7 +44,6 @@ expect(parsed_response).to contain_jsonapi_for(expected_rsvn, include: [:item]) end - # rubocop:disable RSpec/ExampleLength it 'sends a request email' do expected_to = Rails.application.config.reserve_email_to expect(expected_to).not_to be_blank # just to be sure @@ -85,7 +84,6 @@ end end - # rubocop:enable RSpec/ExampleLength it 'rejects a nonexistent item' do Item.destroy_by(id: item.id) @@ -98,7 +96,7 @@ allow(AvailabilityService).to receive(:available?).with(item).and_return(false) post(reservations_url, params: payload, as: :jsonapi) - expect(response).to have_http_status(:unprocessable_entity) + expect(response).to have_http_status(:unprocessable_content) invalid_rsvn = Reservation.new(user: current_user, item: item).tap(&:validate) expected_errors = invalid_rsvn.errors diff --git a/spec/requests/users_spec.rb b/spec/requests/users_spec.rb index d12ff99..ce946c9 100644 --- a/spec/requests/users_spec.rb +++ b/spec/requests/users_spec.rb @@ -35,7 +35,7 @@ parsed_response = JSON.parse(response.body) expect(parsed_response).to contain_jsonapi_for(current_user) - expect(parsed_response['data']['attributes']['galc_admin']).to eq(true) + expect(parsed_response['data']['attributes']['galc_admin']).to be(true) end end end diff --git a/spec/services/availability_spec.rb b/spec/services/availability_spec.rb index 124cde2..69b5de5 100644 --- a/spec/services/availability_spec.rb +++ b/spec/services/availability_spec.rb @@ -3,6 +3,12 @@ describe AvailabilityService do let(:mms_ids) { %w[991005668359706532 991005668209706532] } + let(:logger) { spy('logger') } + + before do + allow(AvailabilityService).to receive(:logger).and_return(logger) + end + def query_value_for(ids) ids .sort @@ -22,30 +28,30 @@ def query_value_for(ids) sru_query_uri = BerkeleyLibrary::Alma::SRU.sru_query_uri(sru_query_value, max_records: mms_ids.size) @stub = stub_request(:get, sru_query_uri).to_return(body: File.read('spec/data/alma/availability-sru.xml')) - expect(Rails.logger).not_to receive(:warn) + expect(logger).not_to receive(:warn) end it 'gets the availability' do availability = AvailabilityService.availability_for(mms_ids) expect(availability.size).to eq(mms_ids.size) - expect(availability[mms_ids.first]).to eq(false) - expect(availability[mms_ids.last]).to eq(true) + expect(availability[mms_ids.first]).to be(false) + expect(availability[mms_ids.last]).to be(true) end it 'ignores nil IDs' do mms_ids_with_nils = [nil, mms_ids.first, nil, mms_ids.last, nil] availability = AvailabilityService.availability_for(mms_ids_with_nils) expect(availability.size).to eq(mms_ids.size) - expect(availability[mms_ids.first]).to eq(false) - expect(availability[mms_ids.last]).to eq(true) + expect(availability[mms_ids.first]).to be(false) + expect(availability[mms_ids.last]).to be(true) end it 'ignores garbage IDs' do mms_ids_with_garbage = ['garbage', mms_ids.first, 'trash', mms_ids.last, 'refuse'] availability = AvailabilityService.availability_for(mms_ids_with_garbage) expect(availability.size).to eq(mms_ids.size) - expect(availability[mms_ids.first]).to eq(false) - expect(availability[mms_ids.last]).to eq(true) + expect(availability[mms_ids.first]).to be(false) + expect(availability[mms_ids.last]).to be(true) end describe 'caching' do @@ -80,8 +86,8 @@ def query_value_for(ids) end end - expect(AvailabilityService.available?(items.first)).to eq(false) - expect(AvailabilityService.available?(items.last)).to eq(true) + expect(AvailabilityService.available?(items.first)).to be(false) + expect(AvailabilityService.available?(items.last)).to be(true) end end end @@ -109,20 +115,20 @@ def query_value_for(ids) let(:sru_query_value) { query_value_for(mms_ids) } - let(:query_uri_page_1) { BerkeleyLibrary::Alma::SRU.sru_query_uri(sru_query_value) } + let(:first_page_query_uri) { BerkeleyLibrary::Alma::SRU.sru_query_uri(sru_query_value) } - let(:query_uri_page_2) { BerkeleyLibrary::Util::URIs.append(query_uri_page_1, '&startRecord=11') } + let(:second_page_query_uri) { BerkeleyLibrary::Util::URIs.append(first_page_query_uri, '&startRecord=11') } before do - stub_request(:get, query_uri_page_1).to_return(body: File.read('spec/data/alma/availability-sru-page-1.xml')) - stub_request(:get, query_uri_page_2).to_return(body: File.read('spec/data/alma/availability-sru-page-2.xml')) + stub_request(:get, first_page_query_uri).to_return(body: File.read('spec/data/alma/availability-sru-page-1.xml')) + stub_request(:get, second_page_query_uri).to_return(body: File.read('spec/data/alma/availability-sru-page-2.xml')) end it 'retrieves availability for all IDs' do AvailabilityService.max_records = 10 availability = AvailabilityService.availability_for(mms_ids) expect(availability.size).to eq(mms_ids.size) - expect(availability.keys).to contain_exactly(*mms_ids) + expect(availability.keys).to match_array(mms_ids) end end @@ -137,7 +143,7 @@ def query_value_for(ids) it 'can handle a single ID' do availability = AvailabilityService.availability_for(mms_id) expect(availability.size).to eq(1) - expect(availability[mms_id]).to eq(false) + expect(availability[mms_id]).to be(false) end end @@ -179,16 +185,16 @@ def query_value_for(ids) expected_msg = "MARC record with 001 #{mms_id.inspect} does not have an AVA$e" expected_record = MARC::XMLReader.read(StringIO.new(response_body)).to_a.last - expect(Rails.logger).to receive(:warn).with(expected_msg, record: expected_record.to_hash) + expect(logger).to receive(:warn).with(expected_msg, record: expected_record.to_hash) availability = AvailabilityService.availability_for(mms_ids) expect(availability).to eq(mms_ids.to_h { |id| [id, false] }) end - it 'handles HTTP errors' do - stub_request(:get, sru_query_uri).to_return(status: 404) + it 'handles request exceptions' do + stub_request(:get, sru_query_uri).to_raise(StandardError.new('404 Not Found')) - expect(Rails.logger).to receive(:warn) + expect(logger).to receive(:warn) availability = AvailabilityService.availability_for(mms_ids) expect(availability).to eq({}) @@ -197,10 +203,18 @@ def query_value_for(ids) it 'handles garbage responses' do stub_request(:get, sru_query_uri).to_return(body: 'I am not an XML document') - expect(Rails.logger).to receive(:warn) + expect(logger).to receive(:warn) availability = AvailabilityService.availability_for(mms_ids) expect(availability).to eq({}) end + + it 'handles a 404 response gracefully' do + stub_request(:get, sru_query_uri).to_return(status: 404) + + availability = AvailabilityService.availability_for(mms_ids) + + expect(availability).to eq({}) + end end end diff --git a/spec/support/matchers/jsonapi_matchers.rb b/spec/support/matchers/jsonapi_matchers.rb index 04a0c59..8e875c2 100644 --- a/spec/support/matchers/jsonapi_matchers.rb +++ b/spec/support/matchers/jsonapi_matchers.rb @@ -70,7 +70,7 @@ def field_type(expected_error) return 'attribute' if attrs&.key?(key) rels = model_serializer.relationships_to_serialize - return 'relationship' if rels&.key?(key) + 'relationship' if rels&.key?(key) end end