diff --git a/.circleci/config.yml b/.circleci/config.yml index a5747d8f..6038199e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,11 @@ version: 2 # use CircleCI 2.0 +# NOTE: images have been migrated to 'next-gen' +# see: https://discuss.circleci.com/t/legacy-convenience-image-deprecation/41034 jobs: # a collection of steps build: # runs not using Workflows must have a `build` job as entry point parallelism: 3 # run three instances of this job in parallel docker: # run the steps with Docker - - image: circleci/ruby:2.6.9-node-browsers # ...with this image as the primary container; this is where all `steps` will run + - image: cimg/ruby:2.7.8-browsers # ...with this image as the primary container; this is where all `steps` will run environment: # environment variables for primary container BUNDLE_JOBS: 3 BUNDLE_RETRY: 3 @@ -11,7 +13,7 @@ jobs: # a collection of steps PGHOST: 127.0.0.1 PGUSER: postgres RAILS_ENV: test - - image: circleci/postgres:9.5-alpine # database image + - image: cimg/postgres:14.12 # database image steps: # a collection of executable commands - checkout # special step to check out source code to working directory diff --git a/.rubocop.yml b/.rubocop.yml index 450d55c3..de2fef8a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,12 @@ -require: - - rubocop-rspec +require: - rubocop-rails + - rubocop-rspec + - rubocop-factory_bot + - rubocop-capybara + - rubocop-performance + +inherit_gem: + # pundit: config/rubocop-rspec.yml AllCops: DisplayStyleGuide: true @@ -13,7 +19,8 @@ AllCops: Layout/LineLength: Max: 120 - +Layout/SpaceInsideHashLiteralBraces: + EnforcedStyle: no_space Lint/RedundantCopDisableDirective: Enabled: false @@ -23,15 +30,20 @@ Metrics/BlockLength: - 'test/**/*.rb' - 'Guardfile' - 'vendor/bundle' - - 'app/admin/productors.rb' + # - "app/admin/**/*" - 'config/environments/*.rb' + # - "app/views/admin/**/*" -Layout/SpaceAroundMethodCallOperator: - Enabled: true -Lint/RaiseException: - Enabled: true -Lint/StructNewOverride: - Enabled: true +Metrics/MethodLength: + Exclude: + - 'db/migrate/**/*' + - 'spec/**/*' + +Style/Documentation: + Exclude: + - 'app/controllers/**/*' + Include: + - 'app/controllers/concerns/**/*' Style/ExponentialNotation: Enabled: true Style/HashEachMethods: @@ -40,6 +52,28 @@ Style/HashTransformKeys: Enabled: true Style/HashTransformValues: Enabled: true +Style/SingleLineDoEndBlock: + Enabled: false Style/StringLiterals: Exclude: - db/schema.rb + +# ----------- Extensions overrides ----------- +Rails/UnknownEnv: + Environments: + - development + - production + - staging + - test + +RSpec/NestedGroups: + AllowedGroups: [describe] + Max: 3 + +RSpec/ExampleLength: + Exclude: + - 'spec/system/**/*' + +# Personal taste +Naming/MemoizedInstanceVariableName: + EnforcedStyleForLeadingUnderscores: required diff --git a/.ruby-version b/.ruby-version index 859f044e..f0c2138b 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.6.9 +ruby-2.7.8 diff --git a/.solargraph.yml b/.solargraph.yml index e907e396..976cdf32 100644 --- a/.solargraph.yml +++ b/.solargraph.yml @@ -1,17 +1,26 @@ --- include: -- "**/*.rb" + - '**/*.rb' exclude: -- spec/**/* -- test/**/* -- vendor/**/* -- ".bundle/**/*" + - spec/**/* + - test/**/* + - vendor/**/* + - '.bundle/**/*' require: [] domains: [] reporters: -- rubocop -- require_not_found -- typecheck + - rubocop + - require_not_found + - typecheck:typed + # - reek require_paths: [] -plugins: [] +formatter: + rubocop: + cops: safe + except: [] + only: [] + extra_args: [] +plugins: + - solargraph-rails + - solargraph-reek max_files: 5000 diff --git a/Gemfile b/Gemfile index 8cb02de1..fc083640 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.6.9' +ruby '2.7.8' # Rails base gems gem 'bootstrap', '~> 4.3.1' @@ -12,27 +12,27 @@ gem 'jbuilder', '~> 2.5' gem 'jquery-rails' gem 'pg', '>= 0.18', '< 2.0' gem 'puma', '~> 4.3' -gem 'rails', '~> 5.2.5' +gem 'rails', '~> 6.0.6' gem 'sass-rails', '~> 5.0' gem 'turbolinks', '~> 5' gem 'uglifier', '>= 1.3.0' # Reduces boot times through caching; required in config/boot.rb -gem 'bootsnap', '>= 1.1.0', require: false +gem 'bootsnap', '~> 1.18.0', require: false # See https://github.com/rails/execjs#readme for more supported runtimes -# gem 'mini_racer', platforms: :ruby +gem 'mini_racer', platforms: :ruby # Use Redis adapter to run Action Cable in production -gem 'redis', '~> 4.0' +gem 'redis', '~> 5.0' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development -gem 'mini_magick', '~> 4.9.5' # Use ActiveStorage variant +gem 'mini_magick', '~> 4.13' # Use ActiveStorage variant -gem 'activeadmin' # Admin interface -gem 'active_storage_validations' +gem 'activeadmin', '~> 2.9.0' # NOTE: update-me to 2.11+ when upgrading to Rails 7 +gem 'active_storage_validations', '~> 0.9' # NOTE: Update me to 1.1 when upgrading to Rails 7 gem 'addressable', '~> 2.8.0' # URI manipulations gem 'aws-sdk-s3', '= 1.48', require: false # S3 file upload storage gem 'bootstrap4-datetime-picker-rails' @@ -45,47 +45,44 @@ gem 'enumerize' gem 'httparty' # Http requests gem 'ice_cube' # Calendar events recurrence (for Missions) gem 'js_cookie_rails' # Cookie manager for js -gem 'leaflet-rails' # GeoMap generator gem 'mailjet' # Production mailer API gem 'pundit' # Authorization management -gem 'recurring_select' # Events recurrence rules set helper -gem 'thredded', '~> 0.16.16' # Forum engine +gem 'recurring_select', '~> 3.0' # Events recurrence rules set helper +gem 'thredded', '~> 1.0.0' # TODO: update me to 1.1 once upgraded to Rails 6 group :development, :test do - gem 'bullet' - gem 'factory_bot_rails', '~> 4.0' - gem 'faker' # Generate fake data for the seed.rb and spec factories - gem 'guard-rspec', require: false - gem 'pry-byebug', '~> 3.6' - gem 'rspec-rails', '~> 3.7', '>= 3.7.2' + gem 'bullet', '~> 7.1' + gem 'factory_bot_rails', '~> 6.4' + gem 'faker', '~> 3.3' # Generate fake data for the seed.rb and spec factories + gem 'pry-byebug', '~> 3.10' + gem 'rspec-rails', '~> 5.1' end group :development do - gem 'annotate', '~> 2.7', '>= 2.7.4' - gem 'letter_opener' - gem 'solargraph' # LSP provinding app documention through IDE + gem 'annotate', '~> 3.1' + gem 'letter_opener', '~> 1.8.0' # NOTE: Update me to 1.9 with Ruby3 + gem 'solargraph', '~> 0.50' # LSP provinding app documention through IDE # Access an interactive console on exception pages or by calling 'console' anywhere in the code. - gem 'listen', '>= 3.0.5', '< 3.2' + gem 'listen', '~> 3.9' # NOTE: Update me to 3.3 with Ruby3 gem 'rubocop', require: false gem 'rubocop-rails', require: false gem 'rubocop-rspec', require: false - gem 'web-console', '>= 3.3.0' + gem 'web-console', '~> 3.3' # NOTE: Update me to V4 with Rails6 # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' + gem 'spring', '~> 3.1' # NOTE: Update me to v4 with Ruby3 gem 'spring-commands-rspec' - gem 'spring-watcher-listen', '~> 2.0.0' + # gem 'spring-watcher-listen', '~> 2.0.0' # NOTE: Update me to 2.1 with spring 4 on Ruby3 end group :test do gem 'email_spec' # Adds support for Capybara system testing and selenium driver - gem 'capybara', '~> 3.21.0' - gem 'selenium-webdriver' + gem 'capybara', '~> 3.39.0' # NOTE: Update me when upgrading to Ruby3 + # gem 'selenium-webdriver', '~> 4.11' + gem 'webdrivers' # NOTE: Delete this gem, & use selenium-webdriver 4.11+ when upgrading to Ruby3 # Easy installation and use of chromedriver to run system tests with Chrome - gem 'chromedriver-helper' - gem 'database_cleaner' # Easy database + association testing gem 'rails-controller-testing' # If you are using Rails 5.x - gem 'shoulda-matchers', '4.0.0.rc1' + gem 'shoulda-matchers', '~> 5.3' # NOTE: Update me when ugrading to Rails6 and Ruby3 gem 'simplecov', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 744b2b54..b76542bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,80 +1,92 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.2.8) - actionpack (= 5.2.8) + actioncable (6.0.6) + actionpack (= 6.0.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.8) - actionpack (= 5.2.8) - actionview (= 5.2.8) - activejob (= 5.2.8) + actionmailbox (6.0.6) + actionpack (= 6.0.6) + activejob (= 6.0.6) + activerecord (= 6.0.6) + activestorage (= 6.0.6) + activesupport (= 6.0.6) + mail (>= 2.7.1) + actionmailer (6.0.6) + actionpack (= 6.0.6) + actionview (= 6.0.6) + activejob (= 6.0.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.8) - actionview (= 5.2.8) - activesupport (= 5.2.8) + actionpack (6.0.6) + actionview (= 6.0.6) + activesupport (= 6.0.6) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.8) - activesupport (= 5.2.8) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.6) + actionpack (= 6.0.6) + activerecord (= 6.0.6) + activestorage (= 6.0.6) + activesupport (= 6.0.6) + nokogiri (>= 1.8.5) + actionview (6.0.6) + activesupport (= 6.0.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) + rails-html-sanitizer (~> 1.1, >= 1.2.0) active_record_union (1.3.0) activerecord (>= 4.0) - active_storage_validations (0.8.7) - rails (>= 5.2.0) - activeadmin (1.4.3) - arbre (>= 1.1.1) - coffee-rails - formtastic (~> 3.1) - formtastic_i18n - inherited_resources (>= 1.9.0) - jquery-rails (>= 4.2.0) - kaminari (>= 0.15) - railties (>= 4.2, < 5.3) - ransack (>= 1.8.7) - sass (~> 3.1) - sprockets (< 4.1) - activejob (5.2.8) - activesupport (= 5.2.8) + active_storage_validations (0.9.8) + activejob (>= 5.2.0) + activemodel (>= 5.2.0) + activestorage (>= 5.2.0) + activesupport (>= 5.2.0) + activeadmin (2.9.0) + arbre (~> 1.2, >= 1.2.1) + formtastic (>= 3.1, < 5.0) + formtastic_i18n (~> 0.4) + inherited_resources (~> 1.7) + jquery-rails (~> 4.2) + kaminari (~> 1.0, >= 1.2.1) + railties (>= 5.2, < 6.2) + ransack (~> 2.1, >= 2.1.1) + activejob (6.0.6) + activesupport (= 6.0.6) globalid (>= 0.3.6) - activemodel (5.2.8) - activesupport (= 5.2.8) + activemodel (6.0.6) + activesupport (= 6.0.6) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (5.2.8) - activemodel (= 5.2.8) - activesupport (= 5.2.8) - arel (>= 9.0) - activestorage (5.2.8) - actionpack (= 5.2.8) - activerecord (= 5.2.8) - marcel (~> 1.0.0) - activesupport (5.2.8) + activerecord (6.0.6) + activemodel (= 6.0.6) + activesupport (= 6.0.6) + activestorage (6.0.6) + actionpack (= 6.0.6) + activejob (= 6.0.6) + activerecord (= 6.0.6) + marcel (~> 1.0) + activesupport (6.0.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - annotate (2.7.4) - activerecord (>= 3.2, < 6.0) - rake (>= 10.4, < 13.0) - arbre (1.1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) + rake (>= 10.4, < 14.0) + arbre (1.7.0) activesupport (>= 3.0.0) - archive-zip (0.11.0) - io-like (~> 0.3.0) - arel (9.0.0) + ruby2_keywords (>= 0.0.2) ast (2.4.2) - autoprefixer-rails (9.4.9) - execjs + autoprefixer-rails (10.4.19.0) + execjs (~> 2) aws-eventstream (1.2.0) aws-partitions (1.518.0) aws-sdk-core (3.121.3) @@ -96,11 +108,12 @@ GEM babel-source (>= 4.0, < 6) execjs (~> 2.0) backport (1.2.0) + base64 (0.2.0) bcrypt (3.1.13) - benchmark (0.1.1) - bindex (0.5.0) - bootsnap (1.3.2) - msgpack (~> 1.0) + benchmark (0.3.0) + bindex (0.8.1) + bootsnap (1.18.3) + msgpack (~> 1.2) bootstrap (4.3.1) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) @@ -109,38 +122,35 @@ GEM jquery-rails (~> 4.2, >= 4.2.0) moment-timezone-rails (~> 1.0) momentjs-rails (>= 2.10.5, <= 3.0.0) - builder (3.2.4) - bullet (6.1.0) + builder (3.3.0) + bullet (7.1.6) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - byebug (10.0.2) - capybara (3.21.0) + byebug (11.1.3) + capybara (3.39.2) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - childprocess (0.9.0) - ffi (~> 1.0, >= 1.0.11) - chromedriver-helper (2.1.0) - archive-zip (~> 0.10) - nokogiri (~> 1.8) cocoon (1.2.12) coderay (1.1.2) - coffee-rails (4.2.2) + coffee-rails (5.0.0) coffee-script (>= 2.2.0) - railties (>= 4.0.0) + railties (>= 5.2.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.3.4) + connection_pool (2.4.1) crass (1.0.6) - database_cleaner (1.7.0) - db_text_search (0.3.1) - activerecord (>= 4.1.15, < 7.0) + date (3.3.4) + db_text_search (1.0.0) + activerecord (>= 4.1.15) devise (4.7.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -150,8 +160,8 @@ GEM devise_invitable (2.0.1) actionmailer (>= 5.0) devise (>= 4.6) - diff-lcs (1.3) - docile (1.3.1) + diff-lcs (1.5.1) + docile (1.4.0) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) draper (4.0.1) @@ -186,48 +196,33 @@ GEM dry-matcher (>= 0.7.0) dry-monads (>= 0.4.0) e2mmap (0.1.0) - email_spec (2.2.0) + email_spec (2.2.2) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) enumerize (2.4.0) activesupport (>= 3.2) - erubi (1.10.0) - execjs (2.7.0) - factory_bot (4.11.1) - activesupport (>= 3.0.0) - factory_bot_rails (4.11.1) - factory_bot (~> 4.11.1) - railties (>= 3.0.0) - faker (2.5.0) - i18n (~> 1.6.0) - ffi (1.15.5) - formatador (0.2.5) - formtastic (3.1.5) - actionpack (>= 3.2.13) - formtastic_i18n (0.6.0) - friendly_id (5.4.0) + erubi (1.13.0) + execjs (2.9.1) + factory_bot (6.4.5) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) + faker (3.3.1) + i18n (>= 1.8.11, < 2) + ffi (1.17.0) + formtastic (4.0.0) + actionpack (>= 5.2.0) + formtastic_i18n (0.7.0) + friendly_id (5.5.1) activerecord (>= 4.0.0) - globalid (0.5.2) + globalid (1.1.0) activesupport (>= 5.0) - guard (2.15.0) - formatador (>= 0.2.4) - listen (>= 2.7, < 4.0) - lumberjack (>= 1.0.12, < 2.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.9.12) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-compat (1.2.1) - guard-rspec (4.7.3) - guard (~> 2.1) - guard-compat (~> 1.1) - rspec (>= 2.99.0, < 4.0) - has_scope (0.7.2) - actionpack (>= 4.1) - activesupport (>= 4.1) - html-pipeline (2.14.0) + has_scope (0.8.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) htmlentities (4.3.4) @@ -236,238 +231,260 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.6.0) + i18n (1.14.5) concurrent-ruby (~> 1.0) ice_cube (0.16.3) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - inherited_resources (1.9.0) - actionpack (>= 4.2, < 5.3) + inherited_resources (1.13.1) + actionpack (>= 5.2, < 7.1) has_scope (~> 0.6) - railties (>= 4.2, < 5.3) - responders - inline_svg (1.7.1) + railties (>= 5.2, < 7.1) + responders (>= 2, < 4) + inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-like (0.3.0) - jaro_winkler (1.5.4) + jaro_winkler (1.5.6) jbuilder (2.9.1) activesupport (>= 4.2.0) jmespath (1.6.1) - jquery-rails (4.3.3) + jquery-rails (4.6.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) js_cookie_rails (2.2.0) railties (>= 3.1) - json (2.3.1) - kaminari (1.2.1) + json (2.7.2) + kaminari (1.2.2) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.1) - kaminari-activerecord (= 1.2.1) - kaminari-core (= 1.2.1) - kaminari-actionview (1.2.1) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) actionview - kaminari-core (= 1.2.1) - kaminari-activerecord (1.2.1) + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) activerecord - kaminari-core (= 1.2.1) - kaminari-core (1.2.1) - kramdown (2.3.1) + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - launchy (2.4.3) - addressable (~> 2.3) - leaflet-rails (1.5.1) - rails (>= 4.2.0) - letter_opener (1.7.0) - launchy (~> 2.2) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.12.0) + language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) + letter_opener (1.8.1) + launchy (>= 2.2, < 3) + libv8-node (16.19.0.1) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.22.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - lumberjack (1.0.13) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp mailjet (1.5.4) activesupport (>= 3.1.0) rack (>= 1.4.0) rest-client - marcel (1.0.2) - method_source (0.9.2) + marcel (1.0.4) + matrix (0.4.2) + method_source (1.1.0) mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) - mini_magick (4.9.5) - mini_mime (1.1.2) - mini_portile2 (2.8.0) - minitest (5.14.4) + mini_magick (4.13.2) + mini_mime (1.1.5) + mini_portile2 (2.8.7) + mini_racer (0.6.4) + libv8-node (~> 16.19.0.0) + minitest (5.25.1) moment-timezone-rails (1.0.0) momentjs-rails (>= 2.10.5, <= 3.0.0) momentjs-rails (2.20.1) railties (>= 3.1) - msgpack (1.2.4) + msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) mustache (1.1.1) - nenv (0.3.0) + net-imap (0.4.16) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.0) + net-protocol netrc (0.11.0) - nio4r (2.5.8) - nokogiri (1.13.9) - mini_portile2 (~> 2.8.0) + nio4r (2.7.3) + nokogiri (1.15.6) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogumbo (2.0.2) - nokogiri (~> 1.8, >= 1.8.4) - notiffany (0.1.1) - nenv (~> 0.1) - shellany (~> 0.0) - onebox (1.9.24) + onebox (2.2.19) + addressable (~> 2.8.0) htmlentities (~> 4.3) multi_json (~> 1.11) mustache nokogiri (~> 1.7) sanitize orm_adapter (0.5.0) - parallel (1.20.1) - parser (3.0.2.0) + parallel (1.24.0) + parser (3.3.1.0) ast (~> 2.4.1) + racc pg (1.1.3) popper_js (1.14.5) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-byebug (3.6.0) - byebug (~> 10.0) - pry (~> 0.10) - public_suffix (4.0.7) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.10.1) + byebug (~> 11.0) + pry (>= 0.13, < 0.15) + public_suffix (5.0.5) puma (4.3.12) nio4r (~> 2.0) pundit (2.0.1) activesupport (>= 3.0.0) - racc (1.6.0) - rack (2.2.3.1) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.2.8) - actioncable (= 5.2.8) - actionmailer (= 5.2.8) - actionpack (= 5.2.8) - actionview (= 5.2.8) - activejob (= 5.2.8) - activemodel (= 5.2.8) - activerecord (= 5.2.8) - activestorage (= 5.2.8) - activesupport (= 5.2.8) + racc (1.8.1) + rack (2.2.9) + rack-test (2.1.0) + rack (>= 1.3) + rails (6.0.6) + actioncable (= 6.0.6) + actionmailbox (= 6.0.6) + actionmailer (= 6.0.6) + actionpack (= 6.0.6) + actiontext (= 6.0.6) + actionview (= 6.0.6) + activejob (= 6.0.6) + activemodel (= 6.0.6) + activerecord (= 6.0.6) + activestorage (= 6.0.6) + activesupport (= 6.0.6) bundler (>= 1.3.0) - railties (= 5.2.8) + railties (= 6.0.6) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) actionview (>= 5.0.1.x) activesupport (>= 5.0.1.x) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (5.2.8) - actionpack (= 5.2.8) - activesupport (= 5.2.8) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + rails_gravatar (1.0.4) + actionview + railties (6.0.6) + actionpack (= 6.0.6) + activesupport (= 6.0.6) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.3) - ransack (2.1.1) - actionpack (>= 5.0) - activerecord (>= 5.0) - activesupport (>= 5.0) + thor (>= 0.20.3, < 2.0) + rainbow (3.1.1) + rake (13.2.1) + ransack (2.5.0) + activerecord (>= 5.2.4) + activesupport (>= 5.2.4) i18n - rb-fsevent (0.10.3) - rb-gravatar (1.0.5) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - recurring_select (2.1.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rbs (2.8.4) + recurring_select (3.0.1) coffee-rails (>= 3.1) ice_cube (>= 0.11) jquery-rails (>= 3.0) - rails (>= 3.2) + rails (>= 5.2) sass-rails (>= 4.0) - redis (4.2.5) - regexp_parser (1.8.2) + redis (5.3.0) + redis-client (>= 0.22.0) + redis-client (0.22.2) + connection_pool + regexp_parser (2.9.0) request_store (1.5.0) rack (>= 1.4) - responders (3.0.0) - actionpack (>= 5.0) - railties (>= 5.0) + responders (3.1.1) + actionpack (>= 5.2) + railties (>= 5.2) rest-client (2.0.2) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - reverse_markdown (2.0.0) + reverse_markdown (2.1.1) nokogiri - rexml (3.2.5) + rexml (3.2.6) rinku (2.0.6) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - rubocop (1.19.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.1) + rubocop (1.63.5) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.9.1, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.9.1) - parser (>= 3.0.1.1) - rubocop-rails (2.11.3) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-rails (2.24.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-rspec (2.4.0) - rubocop (~> 1.0) - rubocop-ast (>= 1.1.0) - ruby-progressbar (1.11.0) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rspec (2.29.2) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.28.3) + rubocop (~> 1.40) + ruby-progressbar (1.13.0) ruby-vips (2.1.4) ffi (~> 1.12) - ruby_dep (1.5.0) - rubyzip (1.3.0) - sanitize (5.2.1) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + sanitize (6.1.3) crass (~> 1.0.2) - nokogiri (>= 1.8.0) - nokogumbo (~> 2.0) - sass (3.7.2) + nokogiri (>= 1.12.0) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) + sass-rails (5.1.0) + railties (>= 5.2.0) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -481,55 +498,55 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (3.141.0) - childprocess (~> 0.5) - rubyzip (~> 1.2, >= 1.2.2) - shellany (0.0.1) - shoulda-matchers (4.0.0.rc1) - activesupport (>= 4.2.0) - simplecov (0.16.1) + selenium-webdriver (4.9.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) + simplecov (0.22.0) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) - solargraph (0.41.2) - backport (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) + solargraph (0.50.0) + backport (~> 1.2) benchmark - bundler (>= 1.17.2) + bundler (~> 2.0) + diff-lcs (~> 1.4) e2mmap jaro_winkler (~> 1.5) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) parser (~> 3.0) - reverse_markdown (>= 1.0.5, < 3) - rubocop (>= 0.52) + rbs (~> 2.0) + reverse_markdown (~> 2.0) + rubocop (~> 1.38) thor (~> 1.0) tilt (~> 2.0) yard (~> 0.9, >= 0.9.24) - spring (2.0.2) - activesupport (>= 4.2) + spring (3.1.1) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (3.7.2) + sprockets (3.7.4) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-es6 (0.9.2) babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) - thor (1.1.0) + thor (1.3.2) thread_safe (0.3.6) - thredded (0.16.16) + thredded (1.0.1) active_record_union (>= 1.3.0) autoprefixer-rails - db_text_search (~> 0.3.0) + db_text_search friendly_id html-pipeline htmlentities @@ -538,29 +555,30 @@ GEM kramdown (>= 2.0.0) kramdown-parser-gfm nokogiri - onebox (~> 1.8, >= 1.8.99) + onebox (>= 1.8.99) pundit (>= 1.1.0) - rails (>= 4.2.10, != 6.0.0.rc2) - rb-gravatar + rails (>= 5.2.0, != 6.0.0.rc2) + rails_gravatar rinku sanitize sassc-rails (>= 2.0.0) sprockets-es6 timeago_js (>= 3.0.2.2) - tilt (2.0.10) + tilt (2.3.0) timeago_js (3.0.2.2) + timeout (0.4.1) turbolinks (5.2.0) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.10) + tzinfo (1.2.11) thread_safe (~> 0.1) uglifier (4.1.20) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (2.0.0) - uniform_notifier (1.13.0) + unicode-display_width (2.5.0) + uniform_notifier (1.16.0) warden (1.2.8) rack (>= 2.0.6) web-console (3.7.0) @@ -568,78 +586,80 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - websocket-driver (0.7.5) + webdrivers (5.3.1) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0, < 4.11) + websocket (1.2.10) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.26) + yard (0.9.36) + zeitwerk (2.6.18) PLATFORMS ruby DEPENDENCIES - active_storage_validations - activeadmin + active_storage_validations (~> 0.9) + activeadmin (~> 2.9.0) addressable (~> 2.8.0) - annotate (~> 2.7, >= 2.7.4) + annotate (~> 3.1) aws-sdk-s3 (= 1.48) - bootsnap (>= 1.1.0) + bootsnap (~> 1.18.0) bootstrap (~> 4.3.1) bootstrap4-datetime-picker-rails - bullet - capybara (~> 3.21.0) - chromedriver-helper + bullet (~> 7.1) + capybara (~> 3.39.0) cocoon (~> 1.2, >= 1.2.12) - database_cleaner devise (~> 4.7) devise_invitable (~> 2.0.0) draper dry-transaction email_spec enumerize - factory_bot_rails (~> 4.0) - faker - guard-rspec + factory_bot_rails (~> 6.4) + faker (~> 3.3) httparty ice_cube image_processing (~> 1.12) jbuilder (~> 2.5) jquery-rails js_cookie_rails - leaflet-rails - letter_opener - listen (>= 3.0.5, < 3.2) + letter_opener (~> 1.8.0) + listen (~> 3.9) mailjet - mini_magick (~> 4.9.5) + mini_magick (~> 4.13) + mini_racer pg (>= 0.18, < 2.0) - pry-byebug (~> 3.6) + pry-byebug (~> 3.10) puma (~> 4.3) pundit - rails (~> 5.2.5) + rails (~> 6.0.6) rails-controller-testing - recurring_select - redis (~> 4.0) - rspec-rails (~> 3.7, >= 3.7.2) + recurring_select (~> 3.0) + redis (~> 5.0) + rspec-rails (~> 5.1) rubocop rubocop-rails rubocop-rspec sass-rails (~> 5.0) - selenium-webdriver - shoulda-matchers (= 4.0.0.rc1) + shoulda-matchers (~> 5.3) simplecov - solargraph - spring + solargraph (~> 0.50) + spring (~> 3.1) spring-commands-rspec - spring-watcher-listen (~> 2.0.0) - thredded (~> 0.16.16) + thredded (~> 1.0.0) turbolinks (~> 5) tzinfo-data uglifier (>= 1.3.0) - web-console (>= 3.3.0) + web-console (~> 3.3) + webdrivers RUBY VERSION - ruby 2.6.9p207 + ruby 2.7.8p225 BUNDLED WITH - 1.17.3 + 2.4.19 diff --git a/README.md b/README.md index 7771be0d..7dbb93db 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ # Créons la Coop (CLAC) :ear_of_rice: - ## What's this app for: An association ('Créons la Coop') located north of France has created a food co-op, inspired by [Park Slope Food Coop](https://fr.wikipedia.org/wiki/Park_Slope_Food_Coop). Members of this co-op need to use more efficient means of communication than just e-mail and phone. Members buy products from productors, and sell these products to themselves. Each member must give at least 3 hours/month of their time to the association. - ## Details ### Why? @@ -36,7 +34,6 @@ Members buy products from productors, and sell these products to themselves. Eac ### Produtors - CRUD on `Produtor` model -- Geolocation on `index` page, coordinates auto-lookup on `Address` model instanciation, with [Leaflet.js](https://github.com/axyjo/leaflet-rails/) and [OpenStreetMap](https://wiki.openstreetmap.org/wiki/API_v0.6) ### Activities @@ -53,24 +50,23 @@ Members buy products from productors, and sell these products to themselves. Eac - Forum set with [Thredded](https://github.com/thredded/thredded) engine ### Theme + - Currently using [Boomerang](https://themes.getbootstrap.com/product/boomerang-bootstrap-4-business-corporate-theme/) bootstrap theme - If you want to use this app as it is, you must buy a Boomerang license. Otherwise, set up your own theme by deleting Boomerang resources from `/vendor` and asset pipeline. Libs and asset pipeline will be cleaned soon to facilitate this - ## Versions: - Ruby => 2.5.1 - Rails => 5.2.3 - ## Contributions / set up Any contributions would be very welcome. See projects tab if something interests you, or post an issue with a feature suggestion that you think would be useful considering the scope of this kind of app. To get started: + - Choose one: - * [Docker Compose](https://docs.docker.com/compose/install/) setup : `$ docker-compose up` to setup containers, DB, and launch server on `localhost:3000`. `$ docker-compose down` to unmount. You might need administrative privileges. - * Manual setup: You need to install [PostgreSQL](https://www.postgresql.org/). Then `$ rails db:setup` to create + migrate + seed DB, then `$ rails server` launch server on `localhost:3000`. + - [Docker Compose](https://docs.docker.com/compose/install/) setup : `$ docker-compose up` to setup containers, DB, and launch server on `localhost:3000`. `$ docker-compose down` to unmount. You might need administrative privileges. + - Manual setup: You need to install [PostgreSQL](https://www.postgresql.org/). Then `$ rails db:setup` to create + migrate + seed DB, then `$ rails server` launch server on `localhost:3000`. - The main branch is `development`, start your branch from there. -- Launch `$ bundle exec guard` if you want to auto-run tests on file save. - Launch `$ npm install` if you want to setup git hooks via `package.json` diff --git a/app/admin/members.rb b/app/admin/members.rb index 602fad4d..8036ffd8 100644 --- a/app/admin/members.rb +++ b/app/admin/members.rb @@ -18,7 +18,7 @@ :register_id, group_ids: [], group_members_attributes: [[%i[id assignment]]], - member_static_slots_attributes: [:id, :static_slot_id, :member_id, :_destroy] + member_static_slots_attributes: %i[id static_slot_id member_id _destroy] menu if: proc { authorized? :index, %i[active_admin Member] } # display menu according to ActiveAdmin::Policy @@ -67,8 +67,9 @@ end table_for member.group_members do column t('.groups') do |group_member| - link_to Arbre::Context.new { (status_tag class: 'important', label: group_member.group.name) }, - [:admin, group_member.group] + link_to [:admin, group_member.group] do + status_tag(group_member.group.name, class: 'important') + end end column t('.assignment'), :assignment end @@ -129,7 +130,7 @@ action_item :enroll_static_members, only: :index do link_to t('.enroll_static_members'), enroll_static_members_admin_members_path, - data: { confirm: t('.confirm_enroll_static_members') }, method: :post + data: {confirm: t('.confirm_enroll_static_members')}, method: :post end action_item :remove_static_slots_of_a_member, only: %i[show edit] do diff --git a/app/assets/javascripts/active_admin.js b/app/assets/javascripts/active_admin.js new file mode 100644 index 00000000..2c7ee5ab --- /dev/null +++ b/app/assets/javascripts/active_admin.js @@ -0,0 +1,2 @@ +//= require active_admin/base +//= require cable diff --git a/app/assets/javascripts/active_admin.js.coffee b/app/assets/javascripts/active_admin.js.coffee deleted file mode 100644 index 58f514bb..00000000 --- a/app/assets/javascripts/active_admin.js.coffee +++ /dev/null @@ -1,2 +0,0 @@ -#= require active_admin/base -#= require cable diff --git a/app/assets/javascripts/admin.coffee b/app/assets/javascripts/admin.coffee deleted file mode 100644 index 24f83d18..00000000 --- a/app/assets/javascripts/admin.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 25744130..dd33cf92 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -53,5 +53,4 @@ //= require imagesloaded.pkgd.min //= require thredded //= require thredded-custom -//= require flash_messages_on_xhr //= require js.cookie diff --git a/app/assets/javascripts/flash_messages_on_xhr.js b/app/assets/javascripts/flash_messages_on_xhr.js deleted file mode 100644 index ed0e8707..00000000 --- a/app/assets/javascripts/flash_messages_on_xhr.js +++ /dev/null @@ -1,34 +0,0 @@ -document.addEventListener('ajax:complete', (e) => { - const xhr = e.detail[0]; - const flashType = xhr.getResponseHeader('Flash-message-type'); - const flashMessage = xhr.getResponseHeader('Flash-message'); - - if (flashMessage) { - showAjaxFlashMessage(flashType, flashMessage); - } -}); - -function showAjaxFlashMessage(type, msg) { - const flashDiv = document.getElementById('flash_messages'); - flashDiv.innerHTML = ''; - - const message = document.createElement('div'); - const cssClass = chooseFlashMessageCssClass(type); - message.classList.add('alert', 'text-center', cssClass); - message.textContent = decodeURIComponent(escape(msg)); - - flashDiv.appendChild(message); -} - -function chooseFlashMessageCssClass(flashType) { - switch (flashType) { - case 'notice': - return 'alert-success'; - case 'alert': - return 'alert-warning'; - case 'error': - return 'alert-danger'; - default: - return undefined; - } -} diff --git a/app/assets/javascripts/openStreetMaps.js b/app/assets/javascripts/openStreetMaps.js deleted file mode 100644 index 6ed1c2e6..00000000 --- a/app/assets/javascripts/openStreetMaps.js +++ /dev/null @@ -1 +0,0 @@ -//= require leaflet diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c6f45d2f..e853e736 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -13,7 +13,6 @@ @import "static_pages"; @import "trix"; @import "trix-custom"; -@import "leaflet"; @import "recurring_select"; @import "thredded"; @import "thredded-custom"; diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 7c38e470..56ef0ede 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module ApplicationCable + # TODO: Add unit tests when upgrading to Rails6 class Connection < ActionCable::Connection::Base # rubocop:disable Style/Documentation identified_by :current_member diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7e96f715..96038fb3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base include Pundit before_action :configure_permitted_parameters, if: :devise_controller? before_action :set_locale - after_action :ajax_flash_to_headers rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized # The following lines are useful when developping Pundit policies @@ -15,10 +14,12 @@ class ApplicationController < ActionController::Base protected def configure_permitted_parameters - added_attrs = %i[first_name biography last_name phone_number email password password_confirmation remember_me avatar] + added_attrs = %i[ + first_name biography last_name phone_number email password password_confirmation remember_me avatar + ] devise_parameter_sanitizer.permit :sign_up, keys: added_attrs devise_parameter_sanitizer.permit :account_update, keys: added_attrs - devise_parameter_sanitizer.permit :accept_invitation, keys: [:first_name, :last_name] + devise_parameter_sanitizer.permit :accept_invitation, keys: %i[first_name last_name] end def active_admin_controller? @@ -33,7 +34,7 @@ def pundit_user def user_not_authorized(exception) policy_name = exception.policy.class.to_s.underscore - flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default + flash[:error] = t "#{policy_name}.#{exception.query}", scope: 'pundit', default: :default redirect_to root_path end @@ -51,19 +52,9 @@ def set_locale I18n.locale = :fr end - def ajax_flash_to_headers - return unless request.xhr? - - flash_type, flash_message = extract_flash - response.headers['Flash-message'] = flash_message - response.headers['Flash-message-type'] = flash_type - - flash.discard - end - def extract_flash flash.each do |type, msg| - return [type, msg] unless flash[type].blank? + return [type, msg] if flash[type].present? end end end diff --git a/app/controllers/concerns/active_admin/site_restriction.rb b/app/controllers/concerns/active_admin/site_restriction.rb index 46d74712..0fa3afea 100644 --- a/app/controllers/concerns/active_admin/site_restriction.rb +++ b/app/controllers/concerns/active_admin/site_restriction.rb @@ -2,6 +2,8 @@ module ActiveAdmin # manage access to active admin ressource + # TODO: there should be an option now to setup this directly in ActiveAdmin. + # Use that option instead of this override when upgrading ActiveAdmin. module SiteRestriction # Overriding active admin method in order to get an active admin namespace for pundit # This override drive active admin to retrieve policy in active_admin namespace diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb index 417ed897..0933f773 100644 --- a/app/controllers/documents_controller.rb +++ b/app/controllers/documents_controller.rb @@ -17,7 +17,6 @@ def create flash.merge! user_feedback_on_create(@document) respond_to do |format| - format.js format.html { redirect_to documents_path(anchor: 'documents') } end end @@ -29,7 +28,6 @@ def destroy model: @document.model_name.singular) respond_to do |format| - format.js format.html { redirect_to documents_path(anchor: 'documents') } end end @@ -42,17 +40,16 @@ def permitted_params def user_feedback_on_create(record) if record.persisted? - message = t("activerecord.notices.messages.record_created", + message = t('activerecord.notices.messages.record_created', model: @document.model_name.singular) - { notice: message } + {notice: message} elsif record.invalid? message = record.errors.full_messages.join(', ') - record.file.purge - { alert: message } + {alert: message} else message = t('activerecord.errors.messages.creation_fail', model: @document.model_name.singular) - { error: message } + {error: message} end end end diff --git a/app/decorators/member_decorator.rb b/app/decorators/member_decorator.rb index 67413425..adcb8bbe 100644 --- a/app/decorators/member_decorator.rb +++ b/app/decorators/member_decorator.rb @@ -9,6 +9,7 @@ def hours_worked_in_the_last_three_months h.safe_join(hours_per_month.map { |month_total| h.content_tag(:p, month_total) }.reverse) end + # TODO: delete me if this is actually unused def time_slot_already_taken?(time_slot, mission) enrollment = member.enrollments.find_by(mission_id: mission.id) return false if enrollment.nil? @@ -20,8 +21,8 @@ def time_slot_already_taken?(time_slot, mission) def hours_per_month 3.times.with_object([]) do |n, array| - array[n] = "#{I18n.localize(Date.current - n.month, format: :only_month)} : - #{model.monthly_worked_hours(Date.current - n.month)}" + array[n] = "#{I18n.l(Date.current - n.month, format: :only_month)} : + #{model.monthly_worked_hours(Date.current - n.month)}" end end end diff --git a/app/jobs/enroll_static_members_job.rb b/app/jobs/enroll_static_members_job.rb index 28326da8..044bbd97 100644 --- a/app/jobs/enroll_static_members_job.rb +++ b/app/jobs/enroll_static_members_job.rb @@ -3,12 +3,13 @@ class EnrollStaticMembersJob < ApplicationJob # rubocop:disable Style/Documentation queue_as :default - def perform - @recruiter = StaticMembersRecruiter.new - @recruiter.call + def perform(enrollment_service = StaticMembersRecruiter.new) + @enrollment_service = enrollment_service + + @enrollment_service.call end after_perform do - ActionCable.server.broadcast 'notifications', reports: @recruiter.reports + ActionCable.server.broadcast 'notifications', reports: @enrollment_service.reports end end diff --git a/app/jobs/generate_schedule_job.rb b/app/jobs/generate_schedule_job.rb index e040fdf4..72337984 100644 --- a/app/jobs/generate_schedule_job.rb +++ b/app/jobs/generate_schedule_job.rb @@ -3,11 +3,11 @@ class GenerateScheduleJob < ApplicationJob # rubocop:disable Style/Documentation queue_as :default - def perform(params) - schedule_generator = ScheduleGenerator.new(params[:current_member], params[:current_month].to_datetime) + def perform(options) + schedule_generator = ScheduleGenerator.new(options[:current_member], options[:current_month].to_datetime) schedule_generator.generate_schedule return unless schedule_generator.errors.empty? - HistoryOfGeneratedSchedule.create(month_number: args[0][:current_month].to_datetime) + HistoryOfGeneratedSchedule.create(month_number: options[:current_month].to_datetime) end end diff --git a/app/models/address.rb b/app/models/address.rb index d6eb882a..c904403f 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -4,15 +4,15 @@ # # Table name: addresses # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # postal_code :string # city :string not null # street_name_1 :string # street_name_2 :string # created_at :datetime not null # updated_at :datetime not null -# productor_id :bigint(8) -# member_id :bigint(8) +# productor_id :bigint +# member_id :bigint # coordinates :float is an Array # @@ -42,7 +42,9 @@ def assign_coordinates private def fetch_coordinates - template = Addressable::Template.new("https://api-adresse.data.gouv.fr/search/{?query*}") + return if Rails.env.test? + + template = Addressable::Template.new('https://api-adresse.data.gouv.fr/search/{?query*}') uri = template.expand( query: { q: "#{street_name_1} #{street_name_2}", @@ -66,6 +68,6 @@ def nullify_coordinates end def invalid_coordinates? - coordinates == [nil, nil] || !coordinates.nil? && !coordinates[0] || !coordinates.nil? && !coordinates[1] + coordinates == [nil, nil] || (!coordinates.nil? && !coordinates[0]) || (!coordinates.nil? && !coordinates[1]) end end diff --git a/app/models/document.rb b/app/models/document.rb index 4faeae69..6c1d4524 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -4,11 +4,11 @@ # # Table name: documents # -# id :bigint(8) not null, primary key -# category :string default: weekly_orders -# published :boolean default: false +# id :bigint not null, primary key # created_at :datetime not null # updated_at :datetime not null +# published :boolean default(FALSE) +# category :string default("weekly_orders") # class Document < ApplicationRecord @@ -28,7 +28,7 @@ class Document < ApplicationRecord recipes], default: :weekly_orders - validates :file, attached: true, size: { less_than: 20.megabytes }, content_type: [ + validates :file, attached: true, size: {less_than: 20.megabytes}, content_type: [ 'application/pdf', 'application/msword', # .doc 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', # .docx diff --git a/app/models/enrollment.rb b/app/models/enrollment.rb index ce529cb2..faa11f09 100644 --- a/app/models/enrollment.rb +++ b/app/models/enrollment.rb @@ -4,11 +4,13 @@ # # Table name: enrollments # -# member_id :bigint(8) not null -# mission_id :bigint(8) not null -# id :bigint(8) not null, primary key -# start_time :time -# end_time :time +# id :bigint not null, primary key +# member_id :bigint not null +# mission_id :bigint not null +# old_start_time :time +# old_end_time :time +# start_time :datetime +# end_time :datetime # # Represents a member enrolling to a given Mission diff --git a/app/models/group.rb b/app/models/group.rb index dd8817f0..16f446a7 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -2,12 +2,14 @@ # == Schema Information # -# Table name: missions +# Table name: groups +# +# id :bigint not null, primary key +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# roles :string # -# id :bigint(8) not null, primary key -# name :string not null -# roles :array[string] -# group_manager_mail :string # The members contribute to one or many groups. Each groups have a specific function in the association. class Group < ApplicationRecord @@ -21,5 +23,5 @@ class Group < ApplicationRecord serialize :roles, Array enumerize :roles, in: %i[redactor], multiple: true - validates :name, presence: true, uniqueness: true + validates :name, presence: true, uniqueness: {case_sensitive: false} end diff --git a/app/models/group_manager.rb b/app/models/group_manager.rb index c04e5f91..64422738 100644 --- a/app/models/group_manager.rb +++ b/app/models/group_manager.rb @@ -1,6 +1,16 @@ # frozen_string_literal: true -# join model of group management +# == Schema Information +# +# Table name: group_managers +# +# id :bigint not null, primary key +# managed_group_id :bigint +# manager_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# +# Join model between a Group and its managing Member class GroupManager < ApplicationRecord belongs_to :managed_group, class_name: :Group belongs_to :manager, class_name: :Member diff --git a/app/models/group_member.rb b/app/models/group_member.rb index 9c7919b2..f32fdc62 100644 --- a/app/models/group_member.rb +++ b/app/models/group_member.rb @@ -1,6 +1,17 @@ # frozen_string_literal: true -# join model of group management +# == Schema Information +# +# Table name: group_members +# +# id :bigint not null, primary key +# group_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# assignment :text +# +# Join model between a Group and a regular Member class GroupMember < ApplicationRecord belongs_to :group belongs_to :member diff --git a/app/models/history_of_generated_schedule.rb b/app/models/history_of_generated_schedule.rb index a14e3d79..34b8e862 100644 --- a/app/models/history_of_generated_schedule.rb +++ b/app/models/history_of_generated_schedule.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: history_of_generated_schedules +# +# id :bigint not null, primary key +# month_number :datetime +# created_at :datetime not null +# updated_at :datetime not null +# # This model retains the months for which a schedule had been generated # This allows us to allow or prevent the launch of a GenerateScheduleJob : if no schedule has been created for a # given month, we allow the GenerateScheduleJob to launch. diff --git a/app/models/history_of_static_slot_selection.rb b/app/models/history_of_static_slot_selection.rb index 6f5a4e59..7f76e4bb 100644 --- a/app/models/history_of_static_slot_selection.rb +++ b/app/models/history_of_static_slot_selection.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: history_of_static_slot_selections +# +# id :bigint not null, primary key +# member_id :bigint +# static_slot_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# # This model keeps the selections of static slots by the members. # It is used to prevent the abuses by the members. class HistoryOfStaticSlotSelection < ApplicationRecord diff --git a/app/models/info.rb b/app/models/info.rb index 34f2e7d8..bd4fa0ea 100644 --- a/app/models/info.rb +++ b/app/models/info.rb @@ -4,14 +4,14 @@ # # Table name: infos # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # content :text -# category :string -# published :boolean default, false # title :string # created_at :datetime not null # updated_at :datetime not null -# author_id :bigint(8) +# author_id :bigint +# category :string +# published :boolean default(FALSE) # class Info < ApplicationRecord diff --git a/app/models/member.rb b/app/models/member.rb index 68c64a27..6d9b5542 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -4,7 +4,7 @@ # # Table name: members # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # email :string default(""), not null # encrypted_password :string default(""), not null # reset_password_token :string @@ -27,11 +27,12 @@ # invitation_accepted_at :datetime # invitation_limit :integer # invited_by_type :string -# invited_by_id :bigint(8) +# invited_by_id :bigint # invitations_count :integer default(0) # display_name :string # moderator :boolean default(FALSE) # cash_register_proficiency :integer default("untrained") +# register_id :integer # # The websites users. Their 'role' attributes determines if fhey're an unvalidated user, a member, admin or super-admin diff --git a/app/models/member_static_slot.rb b/app/models/member_static_slot.rb index 91a83301..7e775b3a 100644 --- a/app/models/member_static_slot.rb +++ b/app/models/member_static_slot.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: member_static_slots +# +# id :bigint not null, primary key +# static_slot_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# # join model class MemberStaticSlot < ApplicationRecord belongs_to :member diff --git a/app/models/mission.rb b/app/models/mission.rb index 7d8c2f94..2e5744ec 100644 --- a/app/models/mission.rb +++ b/app/models/mission.rb @@ -4,20 +4,20 @@ # # Table name: missions # -# id :bigint(8) not null, primary key -# name :string not null -# description :text not null -# due_date :datetime -# created_at :datetime not null -# updated_at :datetime not null -# author_id :bigint(8) -# start_date :datetime -# recurrent :boolean -# max_member_count :integer -# min_member_count :integer -# delivery_expected :boolean default(FALSE) -# genre :integer default: 0 -# cash_register_proficiency_requirement :integer default(0) +# id :bigint not null, primary key +# name :string not null +# description :text not null +# due_date :datetime +# created_at :datetime not null +# updated_at :datetime not null +# author_id :bigint +# start_date :datetime +# recurrent :boolean +# max_member_count :integer +# min_member_count :integer +# delivery_expected :boolean default(FALSE) +# genre :integer default("standard") +# cash_register_proficiency_requirement :integer default("untrained") # # A Mission is an activity that has to be done for the Supermaket Team to function properly. @@ -39,23 +39,21 @@ class Mission < ApplicationRecord validates :description, presence: true validates :start_date, presence: true validates :due_date, presence: true - validates :min_member_count, numericality: { only_integer: true }, presence: true - validates :max_member_count, numericality: { only_integer: true }, allow_nil: true + validates :min_member_count, numericality: {only_integer: true}, presence: true + validates :max_member_count, numericality: {only_integer: true}, allow_nil: true validates :genre, presence: true validates_with MissionValidators::DurationValidator - validates_associated :enrollments, message: I18n.t('activerecord.errors.models.mission.related_enrollment_invalidation') + validates_associated :enrollments, + message: I18n.t('activerecord.errors.models.mission.related_enrollment_invalidation') accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true accepts_nested_attributes_for :enrollments, reject_if: :all_blank, allow_destroy: true - enum genre: { standard: 0, regulated: 1, event: 2 } + enum genre: {standard: 0, regulated: 1, event: 2} - enum cash_register_proficiency_requirement: { untrained: 0, beginner: 1, proficient: 2 } + enum cash_register_proficiency_requirement: {untrained: 0, beginner: 1, proficient: 2} - # Virtual attributes - attr_accessor :recurrence_rule - attr_accessor :recurrence_end_date - attr_accessor :recurrent_change + attr_accessor :recurrence_rule, :recurrence_end_date, :recurrent_change def duration (due_date - start_date).round @@ -65,6 +63,7 @@ def regulated? (genre == 'regulated') end + # @return [Array, nil] def selectable_time_slots(member = nil) return nil unless genre == 'regulated' @@ -77,6 +76,9 @@ def selectable_time_slots(member = nil) time_slots end + # @param current_time_slot [DateTime] + # @param member [Member] + # @return [Boolean] def time_slot_already_taken_by_member?(current_time_slot, member) member_enrollment = enrollments.find_by(mission: self, member: member) return false if member_enrollment.nil? @@ -90,6 +92,7 @@ def available_slots_count_for_a_time_slot(time_slot) max_member_count - occupied_slots_count end + # TODO: delete me if this is actually unused def inside_period?(enrollment) enrollment.start_time >= start_date && enrollment.start_time <= due_date && @@ -97,6 +100,7 @@ def inside_period?(enrollment) enrollment.end_time <= due_date end + # TODO: delete me if this is actually unused def match_a_time_slot?(enrollment) current_time_slot = start_date while current_time_slot < due_date @@ -107,6 +111,7 @@ def match_a_time_slot?(enrollment) false end + # TODO: delete me if this is actually unused def slot_available_for_given_cash_register_proficiency?(enrollment, cash_register_proficiency_level) current_time_slot = enrollment.start_time proficiency_level_of_mission = Mission.cash_register_proficiency_requirements[cash_register_proficiency_requirement] diff --git a/app/models/productor.rb b/app/models/productor.rb index da850062..3b4f9b8b 100644 --- a/app/models/productor.rb +++ b/app/models/productor.rb @@ -4,7 +4,7 @@ # # Table name: productors # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # name :string # description :text # phone_number :string @@ -12,6 +12,7 @@ # updated_at :datetime not null # website_url :string # local :boolean default(FALSE) +# category :string # # Ressource for the members to get products from (vegetables...), and are managed by the 'management/supply' team diff --git a/app/models/static_slot.rb b/app/models/static_slot.rb index b6734300..9127b9d1 100644 --- a/app/models/static_slot.rb +++ b/app/models/static_slot.rb @@ -2,14 +2,15 @@ # == Schema Information # -# Table name: calendar_locations +# Table name: static_slots +# +# id :bigint not null, primary key +# week_day :integer not null +# start_time :datetime not null +# week_type :integer not null +# created_at :datetime not null +# updated_at :datetime not null # -# id :bigint(8) not null, primary key -# week_day :integer not null -# start_time :datetime not null -# week_type :integer not null -# created_at :datetime not null -# updated_at :datetime not null # A StaticSlot refers to a place in calendar. It is combination of location in the week and a week_type. # A StaticSlot are coordinates in a 4 weeks cycle. diff --git a/app/policies/active_admin/page_policy.rb b/app/policies/active_admin/page_policy.rb index 7482300f..e4f45140 100644 --- a/app/policies/active_admin/page_policy.rb +++ b/app/policies/active_admin/page_policy.rb @@ -2,19 +2,22 @@ module ActiveAdmin class PagePolicy < ApplicationPolicy - def show? - case record.name - when "Dashboard" - true - else - false - end - end - - class Scope < Scope - def resolve - scope.all - end - end + # NOTE: This is not used right now, but serves as a documenting example. + # We keep this constant definition to follow Zeitwerk convention. + # + # def show? + # case record.name + # when "Dashboard" + # true + # else + # false + # end + # end + # + # class Scope < Scope + # def resolve + # scope.all + # end + # end end end diff --git a/app/policies/static_pages_policy.rb b/app/policies/static_pages_policy.rb deleted file mode 100644 index 2af7ff12..00000000 --- a/app/policies/static_pages_policy.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -class StaticPagesPolicy < ApplicationPolicy - def home - true - end - - def about_us - true - end - - def dashboard? - member_signed_in? - end -end diff --git a/app/validators/content_type_validator.rb b/app/validators/content_type_validator.rb deleted file mode 100644 index 4e8497f8..00000000 --- a/app/validators/content_type_validator.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class ContentTypeValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - return unless value.attached? && value.content_type.in?(content_types) - - value.purge if record.new_record? # Only purge the offending blob if the record is new - record.errors.add(attribute, :content_type, options) - end - - private - - def content_types - options.fetch(:in) - end -end diff --git a/app/views/documents/create.js.erb b/app/views/documents/create.js.erb deleted file mode 100644 index 2d3240ee..00000000 --- a/app/views/documents/create.js.erb +++ /dev/null @@ -1 +0,0 @@ -$('div#<%= @document.category %> > div > table > tbody').append("<%= j render @document unless @document.invalid? %>"); diff --git a/app/views/documents/destroy.js.erb b/app/views/documents/destroy.js.erb deleted file mode 100644 index 549a5b9f..00000000 --- a/app/views/documents/destroy.js.erb +++ /dev/null @@ -1 +0,0 @@ -document.getElementById("<%= @document.id %>").parentNode.parentNode.remove(); diff --git a/app/views/missions/_regulated_quick_enrollment_form.html.erb b/app/views/missions/_regulated_quick_enrollment_form.html.erb index 786dc11d..e6ab53ca 100644 --- a/app/views/missions/_regulated_quick_enrollment_form.html.erb +++ b/app/views/missions/_regulated_quick_enrollment_form.html.erb @@ -1,7 +1,7 @@ <% if @mission.selectable_time_slots.empty? %>

<%= t('.no_time_slot_available') %>

<% else %> - <%= form_with scope: :enrollment, url: mission_enrollments_path(@mission.id) do |f| %> + <%= form_with scope: :enrollment, url: mission_enrollments_path(@mission.id), local: true do |f| %>

<%= translate ".quick_enroll" %>

diff --git a/app/views/missions/_standard_quick_enrollment_form.html.erb b/app/views/missions/_standard_quick_enrollment_form.html.erb index 86219af1..3e2d72eb 100644 --- a/app/views/missions/_standard_quick_enrollment_form.html.erb +++ b/app/views/missions/_standard_quick_enrollment_form.html.erb @@ -1,4 +1,4 @@ -<%= form_with scope: 'enrollment', url: mission_enrollments_path(@mission.id) do |f| %> +<%= form_with scope: 'enrollment', url: mission_enrollments_path(@mission.id), local: true do |f| %>

<%= translate ".quick_enroll" %>

diff --git a/bin/setup b/bin/setup index e34796ed..25eed55f 100755 --- a/bin/setup +++ b/bin/setup @@ -2,7 +2,6 @@ # frozen_string_literal: true require 'fileutils' -include FileUtils # path to your application root. APP_ROOT = File.expand_path('..', __dir__) @@ -11,15 +10,16 @@ def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end -chdir APP_ROOT do - # This script is a starting point to setup your application. +FileUtils.chdir APP_ROOT do + # This script is a way to setup or update your development environment automatically. + # This script is idempotent, so that you can run it at anytime and get an expectable outcome. # Add necessary setup steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') - # Install JavaScript dependencies if using Yarn + # Install JavaScript dependencies system('bin/yarn') # puts "\n== Copying sample files ==" diff --git a/bin/yarn b/bin/yarn index 268a9398..333bb7a9 100755 --- a/bin/yarn +++ b/bin/yarn @@ -3,9 +3,11 @@ APP_ROOT = File.expand_path('..', __dir__) Dir.chdir(APP_ROOT) do - exec "yarnpkg", *ARGV -rescue Errno::ENOENT - warn "Yarn executable was not detected in the system." - warn "Download Yarn at https://yarnpkg.com/en/docs/install" - exit 1 + begin + exec "yarnpkg", *ARGV + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end end diff --git a/config/application.rb b/config/application.rb index dbb8164b..9249a4e9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -19,6 +19,9 @@ class Application < Rails::Application # the framework and any gems in your application. config.i18n.available_locales = %i[en fr] + config.i18n.default_locale = :fr config.i18n.load_path += Dir[Rails.root.join('config/locales/**/*.{rb,yml}')] + + config.autoloader = :zeitwerk end end diff --git a/config/cable.yml b/config/cable.yml index fa337d23..475ddc45 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -2,7 +2,7 @@ development: adapter: async test: - adapter: async + adapter: test production: adapter: redis diff --git a/config/environments/development.rb b/config/environments/development.rb index 89d4137d..6639f535 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -18,6 +18,7 @@ # Run rails dev:cache to toggle caching. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { @@ -29,7 +30,7 @@ config.cache_store = :null_store end - # Store uploaded files on the local file system (see config/storage.yml for options) + # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Don't care if the mailer can't send. @@ -37,7 +38,7 @@ config.action_mailer.perform_caching = false config.action_mailer.delivery_method = :letter_opener - config.action_mailer.default_url_options = { host: 'localhost:3000' } + config.action_mailer.default_url_options = {host: 'localhost:3000'} # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log @@ -70,5 +71,7 @@ Bullet.console = true Bullet.rails_logger = true Bullet.add_footer = true + + Redis.raise_deprecations = true end end diff --git a/config/environments/production.rb b/config/environments/production.rb index e61f7882..d697adb6 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -40,7 +40,7 @@ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX - # Store uploaded files on the local file system (see config/storage.yml for options) + # Store uploaded files on the :local file system or elsewhere (see config/storage.yml for options) config.active_storage.service = :amazon # Mount Action Cable outside main process or domain @@ -63,7 +63,7 @@ # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "creons_la_coop_#{Rails.env}" + # config.active_job.queue_name_prefix = "creons_la_coop_production" config.action_mailer.perform_caching = false config.action_mailer.delivery_method = :mailjet @@ -95,4 +95,25 @@ # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false + + # Inserts middleware to perform automatic connection switching. + # The `database_selector` hash is used to pass options to the DatabaseSelector + # middleware. The `delay` is used to determine how long to wait after a write + # to send a subsequent read to the primary. + # + # The `database_resolver` class is used by the middleware to determine which + # database is appropriate to use based on the time delay. + # + # The `database_resolver_context` class is used by the middleware to set + # timestamps for the last write to the primary. The resolver uses the context + # class timestamps to determine how long to wait before reading from the + # replica. + # + # By default Rails will store a last write timestamp in the session. The + # DatabaseSelector middleware is designed as such you can define your own + # strategy for connection switching and pass that into the middleware through + # these configuration options. + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session end diff --git a/config/environments/test.rb b/config/environments/test.rb index ff83db22..58593fe6 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true + config.cache_classes = false # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that @@ -30,7 +31,7 @@ # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false - # Store uploaded files on the local file system in a temporary directory + # Store uploaded files on the local file system in a temporary directory. config.active_storage.service = :test config.action_mailer.perform_caching = false @@ -39,7 +40,7 @@ # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - config.action_mailer.default_url_options = { host: 'localhost:3000' } + config.action_mailer.default_url_options = {host: 'localhost:3000'} # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr @@ -51,5 +52,9 @@ Bullet.enable = true Bullet.bullet_logger = false Bullet.raise = false # raise an error if n+1 query occurs + + Redis.raise_deprecations = true end + + config.active_job.queue_adapter = :test end diff --git a/config/initializers/active_admin.rb b/config/initializers/active_admin.rb index cd1f690c..dc6be4b5 100644 --- a/config/initializers/active_admin.rb +++ b/config/initializers/active_admin.rb @@ -293,6 +293,9 @@ # # config.order_clause = MyOrderClause end -ActiveAdmin::BaseController.class_eval do - include ActiveAdmin::SiteRestriction + +Rails.application.config.to_prepare do + ActiveAdmin::BaseController.class_eval do + include ActiveAdmin::SiteRestriction + end end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index efece005..09e7faf3 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -17,7 +17,6 @@ # folder are already added. Rails.application.config.assets.precompile += %w( fullCalendar.js fullCalendar.css - openStreetMaps.js missionForm.js missions.css infos.js infos.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index e3c96496..35d0f26f 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # Be sure to restart your server when you modify this file. # Define an application-wide content security policy @@ -13,6 +11,8 @@ # policy.object_src :none # policy.script_src :self, :https # policy.style_src :self, :https +# # If you are using webpack-dev-server then specify webpack-dev-server host +# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? # # Specify URI for violation reports # # policy.report_uri "/csp-violation-report-endpoint" @@ -21,6 +21,9 @@ # If you are using UJS then enable automatic nonce generation # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } +# Set the nonce only to specific directives +# Rails.application.config.content_security_policy_nonce_directives = %w(script-src) + # Report CSP violations to a specified URI # For further information see the following documentation: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only diff --git a/config/initializers/leaflet.rb b/config/initializers/leaflet.rb deleted file mode 100644 index ca74aa50..00000000 --- a/config/initializers/leaflet.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -Leaflet.tile_layer = "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png" -# You can also use any other tile layer here if you want - see http://leafletjs.com/reference.html#tilelayer for more - -Leaflet.attribution = 'données © OpenStreetMap/ODbL - rendu OSM France' -Leaflet.max_zoom = 18 diff --git a/config/initializers/new_framework_defaults_6_0.rb b/config/initializers/new_framework_defaults_6_0.rb new file mode 100644 index 00000000..f796f908 --- /dev/null +++ b/config/initializers/new_framework_defaults_6_0.rb @@ -0,0 +1,45 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 6.0 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Don't force requests from old versions of IE to be UTF-8 encoded. +Rails.application.config.action_view.default_enforce_utf8 = false + +# Embed purpose and expiry metadata inside signed and encrypted +# cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 6.0. +Rails.application.config.action_dispatch.use_cookies_with_metadata = true + +# Change the return value of `ActionDispatch::Response#content_type` to Content-Type header without modification. +Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false + +# Return false instead of self when enqueuing is aborted from a callback. +Rails.application.config.active_job.return_false_on_aborted_enqueue = true + +# Send Active Storage analysis and purge jobs to dedicated queues. +Rails.application.config.active_storage.queues.analysis = :active_storage_analysis +Rails.application.config.active_storage.queues.purge = :active_storage_purge + +# When assigning to a collection of attachments declared via `has_many_attached`, replace existing +# attachments instead of appending. Use #attach to add new attachments without replacing existing ones. +Rails.application.config.active_storage.replace_on_assign_to_many = true + +# Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail. +# +# The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob), +# will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions. +# If you send mail in the background, job workers need to have a copy of +# MailDeliveryJob to ensure all delivery jobs are processed properly. +# Make sure your entire app is migrated and stable on 6.0 before using this setting. +# Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" + +# Enable the same cache key to be reused when the object being cached of type +# `ActiveRecord::Relation` changes by moving the volatile information (max updated at and count) +# of the relation's cache key into the cache version to support recycling cache key. +Rails.application.config.active_record.collection_cache_versioning = true diff --git a/config/initializers/thredded.rb b/config/initializers/thredded.rb index 0909b443..4e89580a 100644 --- a/config/initializers/thredded.rb +++ b/config/initializers/thredded.rb @@ -29,8 +29,8 @@ # This method is used by Thredded controllers and views to fetch the currently signed-in user Thredded.current_user_method = :"current_#{Thredded.user_class_name.demodulize.underscore}" -# User avatar URL. rb-gravatar gem is used by default: -Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 156, 'mm') } +# User avatar URL. rails_gravatar gem is used by default: +Thredded.avatar_url = ->(user) { RailsGravatar.src(user.email, 156, 'mm') } # ==> Permissions Configuration # By default, thredded uses a simple permission model, where all the users can post to all message boards, @@ -65,6 +65,9 @@ # Whether admin users see button to delete entire messageboards on the messageboard edit page. Thredded.show_messageboard_delete_button = true +# Whether MessageboardGroup show page is enabled. +Thredded.show_messageboard_group_page = true + # Whether users that are following a topic are listed on the topic page. Thredded.show_topic_followers = false diff --git a/config/locales/models/group.fr.yml b/config/locales/models/group.fr.yml index be548053..02d3b741 100644 --- a/config/locales/models/group.fr.yml +++ b/config/locales/models/group.fr.yml @@ -3,6 +3,7 @@ fr: resource: show: groups: Groupes + activerecord: models: group: @@ -10,12 +11,12 @@ fr: other: Groupes attributes: group: - name: Nom - members_count: Nombre de membres managers: Référents + members_count: Nombre de membres + name: Nom role: Rôle + enumerize: group: roles: redactor: Rédacteur - diff --git a/config/locales/models/group_manager.en.yml b/config/locales/models/group_manager.en.yml new file mode 100644 index 00000000..4c06e8ea --- /dev/null +++ b/config/locales/models/group_manager.en.yml @@ -0,0 +1,10 @@ +en: + activerecord: + models: + group_manager: + one: Group <-> Manager Relation + other: Group <-> Manager Relations + attributes: + group_manager: + manager: Manager + managed_group: Managed group diff --git a/config/locales/models/group_manager.fr.yml b/config/locales/models/group_manager.fr.yml new file mode 100644 index 00000000..c8af23ec --- /dev/null +++ b/config/locales/models/group_manager.fr.yml @@ -0,0 +1,10 @@ +fr: + activerecord: + models: + group_manager: + one: Relation Groupe <-> Référent + other: Relations Groupe <-> Référent + attributes: + group_manager: + manager: Référent + managed_group: Groupe géré diff --git a/config/locales/models/group_member.fr.yml b/config/locales/models/group_member.fr.yml index 04fb47c2..f84d3882 100644 --- a/config/locales/models/group_member.fr.yml +++ b/config/locales/models/group_member.fr.yml @@ -7,3 +7,5 @@ fr: attributes: group_member: assignment: Tâche + group: Groupe + member: Membre diff --git a/config/puma.rb b/config/puma.rb index dd9e863b..5ed44377 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,13 +1,12 @@ -# frozen_string_literal: true - # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } -threads threads_count, threads_count +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # @@ -17,8 +16,11 @@ # environment ENV.fetch("RAILS_ENV") { "development" } +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + # Specifies the number of `workers` to boot in clustered mode. -# Workers are forked webserver processes. If using threads and workers together +# Workers are forked web server processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). diff --git a/config/spring.rb b/config/spring.rb index c5933e49..db5bf130 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,8 +1,6 @@ -# frozen_string_literal: true - -%w[ - .ruby-version - .rbenv-vars - tmp/restart.txt - tmp/caching-dev.txt -].each { |path| Spring.watch(path) } +Spring.watch( + ".ruby-version", + ".rbenv-vars", + "tmp/restart.txt", + "tmp/caching-dev.txt" +) diff --git a/db/migrate/20240913110627_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb b/db/migrate/20240913110627_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb new file mode 100644 index 00000000..ff5d72c7 --- /dev/null +++ b/db/migrate/20240913110627_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb @@ -0,0 +1,10 @@ +# This migration comes from active_storage (originally 20180723000244) +class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0] + def up + return if foreign_key_exists?(:active_storage_attachments, column: :blob_id) + + if table_exists?(:active_storage_blobs) + add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2f91e2cd..915c0697 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,15 +2,15 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_04_08_074322) do +ActiveRecord::Schema.define(version: 2024_09_13_110627) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -475,6 +475,7 @@ t.index ["user_id", "postable_id"], name: "thredded_user_topic_read_states_user_postable", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "addresses", "members" add_foreign_key "addresses", "productors" add_foreign_key "group_managers", "groups", column: "managed_group_id" diff --git a/spec/controllers/admin/infos_controller_spec.rb b/spec/controllers/admin/infos_controller_spec.rb index fc427140..c6a4718f 100644 --- a/spec/controllers/admin/infos_controller_spec.rb +++ b/spec/controllers/admin/infos_controller_spec.rb @@ -7,27 +7,27 @@ render_views let(:page) { Capybara::Node::Simple.new(response.body) } - let(:super_admin) { create(:member, :super_admin) } - before { sign_in super_admin } - let!(:info) { create(:info) } - - let(:valid_attributes) { build(:info).attributes } - + let(:valid_attributes) { attributes_for(:info, author_id: super_admin.id) } let(:invalid_attributes) do { title: '' } end + let(:super_admin) { create(:member, :super_admin) } + + before { sign_in super_admin } - describe "GET index" do + describe 'GET index' do it 'returns http success' do get :index expect(response).to have_http_status(:success) end + it 'assigns the info' do get :index expect(assigns(:infos)).to include(info) end - it "should render the expected columns" do + + it 'renders the expected columns' do get :index expect(page).to have_content(info.content) expect(page).to have_content(info.title) @@ -50,16 +50,18 @@ # end end - describe "GET new" do + describe 'GET new' do it 'returns http success' do get :new expect(response).to have_http_status(:success) end + it 'assigns the info' do get :new expect(assigns(:info)).to be_a_new(Info) end - it "should render the form elements" do + + it 'renders the form elements' do get :new expect(page).to have_field('info_title') expect(page).to have_field('info_content') @@ -68,66 +70,69 @@ end end - describe "POST create" do - context "with valid params" do - it "creates a new Info" do - expect { + describe 'POST create' do + context 'with valid params' do + it 'creates a new Info' do + expect do post :create, params: { info: valid_attributes } - }.to change(Info, :count).by(1) + end.to change(Info, :count).by(1) end - it "assigns a newly created info as @info" do + it 'assigns a newly created info as @info' do post :create, params: { info: valid_attributes } expect(assigns(:info)).to be_a(Info) expect(assigns(:info)).to be_persisted end - it "redirects to the created info" do + it 'redirects to the created info' do post :create, params: { info: valid_attributes } expect(response).to have_http_status(:redirect) expect(response).to redirect_to(admin_info_path(Info.last)) end - it 'should create the info' do + it 'creates the info' do post :create, params: { info: valid_attributes } info = Info.last - expect(info.title).to eq(valid_attributes["title"]) - expect(info.content).to eq(valid_attributes["content"]) - expect(info.author_id).to eq(valid_attributes["author_id"]) + expect(info.title).to eq(valid_attributes[:title]) + expect(info.content).to eq(valid_attributes[:content]) + expect(info.author_id).to eq(valid_attributes[:author_id]) end end - context "with invalid params" do + context 'with invalid params' do it 'invalid_attributes return http success' do post :create, params: { info: invalid_attributes } expect(response).to have_http_status(:success) end - it "assigns a newly created but unsaved info as @info" do + it 'assigns a newly created but unsaved info as @info' do post :create, params: { info: invalid_attributes } expect(assigns(:info)).to be_a_new(Info) end it 'invalid_attributes do not create a Info' do - expect { + expect do post :create, params: { info: invalid_attributes } - }.not_to change(Info, :count) + end.not_to change(Info, :count) end end end - describe "GET edit" do + describe 'GET edit' do before do get :edit, params: { id: info.id } end + it 'returns http success' do expect(response).to have_http_status(:success) end + it 'assigns the info' do expect(assigns(:info)).to eq(info) end - it "should render the form elements" do + + it 'renders the form elements' do expect(page).to have_field('info_title', with: info.title) expect(page).to have_field('info_content', with: info.content) expect(page).to have_select('info_author_id', with_options: [info.author.email]) @@ -135,63 +140,69 @@ end end - describe "PUT update" do + describe 'PUT update' do context 'with valid params' do before do put :update, params: { id: info.id, info: valid_attributes } end + it 'assigns the info' do expect(assigns(:info)).to eq(info) end + it 'returns http redirect' do expect(response).to have_http_status(:redirect) expect(response).to redirect_to(admin_info_path(info)) end - it "should update the info" do + + it 'updates the info' do info.reload - expect(info.title).to eq(valid_attributes["title"]) - expect(info.content).to eq(valid_attributes["content"]) + expect(info.title).to eq(valid_attributes[:title]) + expect(info.content).to eq(valid_attributes[:content]) end end + context 'with invalid params' do it 'returns http success' do put :update, params: { id: info.id, info: invalid_attributes } expect(response).to have_http_status(:success) end + it 'does not change info' do - expect { + expect do put :update, params: { id: info.id, info: invalid_attributes } - }.not_to change(info.reload.title, :methods) + end.not_to change(info.reload.title, :methods) end end end - describe "GET show" do - before do - get :show, params: { id: info.id } - end + describe 'GET show' do + before { get :show, params: { id: info.id } } + it 'returns http success' do expect(response).to have_http_status(:success) end + it 'assigns the info' do expect(assigns(:info)).to eq(info) end - it "should render the form elements" do + + it 'renders the form elements' do expect(page).to have_content(info.title) expect(page).to have_content(info.content) expect(page).to have_content(info.author.display_name) end end - describe "DELETE #destroy" do - it "destroys the requested select_option" do - expect { + describe 'DELETE #destroy' do + it 'destroys the requested select_option' do + expect do delete :destroy, params: { id: info.id } - }.to change(Info, :count).by(-1) + end.to change(Info, :count).by(-1) end - it "redirects to the index" do + it 'redirects to the index' do delete :destroy, params: { id: info.id } expect(response).to redirect_to(admin_infos_path) end diff --git a/spec/controllers/admin/members_controller_spec.rb b/spec/controllers/admin/members_controller_spec.rb deleted file mode 100644 index 5a9277fc..00000000 --- a/spec/controllers/admin/members_controller_spec.rb +++ /dev/null @@ -1,166 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::MembersController, type: :controller do - # this lets us inspect the rendered results - render_views - - before { sign_in create(:member, :super_admin) } - - let(:page) { Capybara::Node::Simple.new(response.body) } - let!(:member) { create(:member) } - let(:valid_attributes) { attributes_for(:member, cash_register_proficiency: "beginner", email: "update@update.com") } - let(:invalid_attributes) { { first_name: '' } } - - describe "GET index" do - before { get :index } - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - it 'assigns the member' do - expect(assigns(:members)).to include(member) - end - - %i(first_name last_name email role cash_register_proficiency).each do |attribute| - it "renders the expected columns" do - expect(page).to have_content(member.send(attribute)) - end - end - end - - describe "GET new" do - before { get :new } - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - it 'assigns the member' do - expect(assigns(:member)).to be_a_new(Member) - end - - %i( - first_name last_name email phone_number cash_register_proficiency biography - ).each do |attribute| - it "should render the #{attribute} field" do - expect(page).to have_field(Member.human_attribute_name(attribute)) - end - end - end - - describe "POST create" do - context "with valid params" do - before { post :create, params: { member: valid_attributes } } - - it "assigns @member" do - expect(assigns(:member)).to be_a(Member) - end - - it 'creates the member' do - expect(assigns(:member)).to be_persisted - end - - it "redirects to the created member" do - expect(response).to redirect_to(admin_member_path(Member.last)) - end - end - - context "with invalid params" do - it 'invalid_attributes return http success' do - post :create, params: { member: invalid_attributes } - expect(response).to have_http_status(:success) - end - - it "assigns a newly created but unsaved member as @member" do - post :create, params: { member: invalid_attributes } - expect(assigns(:member)).to be_a_new(Member) - end - - it 'invalid_attributes do not create a Member' do - expect do - post :create, params: { member: invalid_attributes } - end.not_to change(Member, :count) - end - end - end - - describe "GET edit" do - before { get :edit, params: { id: member.id } } - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - it 'assigns the member' do - expect(assigns(:member)).to eq(member) - end - end - - describe "PUT update" do - context 'with valid params' do - before { put :update, params: { id: member.id, member: valid_attributes } } - - it 'assigns the member' do - expect(assigns(:member)).to eq(member) - end - it 'returns http redirect' do - expect(response).to redirect_to(admin_member_path(member)) - end - - %i[ - first_name last_name phone_number cash_register_proficiency biography email - ].each do |attribute| - it "updates the #{attribute} attribute" do - member.reload - - expect(member.reload.send(attribute)).to eq(valid_attributes[attribute]) - end - end - end - - context 'with invalid params' do - it 'returns http success' do - put :update, params: { id: member.id, member: invalid_attributes } - expect(response).to have_http_status(:success) - end - it 'does not change member' do - expect { - put :update, params: { id: member.id, member: invalid_attributes } - }.not_to change(member.reload.first_name, :methods) - end - end - end - - describe "GET show" do - before { get :show, params: { id: member.id } } - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - it 'assigns the member' do - expect(assigns(:member)).to eq(member) - end - end - - describe "DELETE #destroy" do - it "destroys the requested select_option" do - expect { - delete :destroy, params: { id: member.id } - }.to change(Member, :count).by(-1) - end - - it "redirects to the members index" do - delete :destroy, params: { id: member.id } - expect(response).to redirect_to(admin_members_path) - end - - context "when a member has created a ressource" do - it "gets deleted even if the member has previously created an info" do - create(:info, author_id: member.id) - expect { - delete :destroy, params: { id: member.id } - }.to change(Member, :count).by(-1) - end - end - end -end diff --git a/spec/controllers/documents_controller_spec.rb b/spec/controllers/documents_controller_spec.rb index 55c29807..0814f322 100644 --- a/spec/controllers/documents_controller_spec.rb +++ b/spec/controllers/documents_controller_spec.rb @@ -5,38 +5,47 @@ RSpec.describe DocumentsController, type: :controller do before { sign_in create(:member, :admin) } - context "when a document is uploaded" do - context "successfully" do - it "gives a confirmation feedback to the user" do - post :create, params: { document: attributes_for(:document, :with_file) } + describe '#create' do + subject(:create_document) { post :create, params: params } - expect(flash[:notice]).to eq I18n.t('activerecord.notices.messages.record_created', - model: Document.model_name.singular) - end + let(:params) { {document: attributes_for(:document)} } + + it 'gives a confirmation feedback to the user' do + create_document + + expect(flash[:notice]).to eq I18n.t('activerecord.notices.messages.record_created', + model: Document.model_name.singular) end - context "when it is an invalid content type" do - before { post :create, params: { document: attributes_for(:document, :with_invalid_file_type) } } + context 'when it is an invalid content type' do + let(:params) do + { + document: attributes_for(:document, + file: fixture_file_upload(Rails.root.join('spec/support/fixtures/fixture.json'), + 'application/json')) + } + end it "gives a 'invalid file type' feedback to the user" do + create_document + expect(flash[:alert]).to eq(I18n.t('errors.format', attribute: Document.human_attribute_name(:file), message: I18n.t('errors.messages.content_type_invalid'))) end - # ActiveStorage in Rails 5.2 *immediatly* uploads files on assignment, even without using .save (thus without validation) - # So additionnal deletion of attachements, blobs, and stored file is necessary on invalid files - it "purges the attached file" do - new_document_instance = @controller.instance_variable_get(:@document) - - expect(new_document_instance.file).not_to be_attached - expect(ActiveStorage::Blob.count).to eq 0 + # ActiveStorage in Rails 5.2 *immediatly* uploaded files on assignment, before saving. + # So additionnal deletion of attachements, blobs, and stored file was necessary on invalid files. + # This was fixed on Rails 6, but we keep this test just because I'm paranoid + # see https://github.com/rails/rails/pull/33303 + it 'does not upload the attached file', :aggregate_failures do + expect { create_document }.not_to change ActiveStorage::Blob, :count end end - context "when no file is attached" do + context 'when no file is attached' do it "gives an 'no file attached' feedback to the user" do - post :create, params: { document: { random: 'whatever' } } + post :create, params: {document: {random: 'whatever'}} expect(flash[:alert]).to eq(I18n.t('errors.format', attribute: Document.human_attribute_name(:file), @@ -45,11 +54,11 @@ end end - context "when a document is deleted" do - it "gives a confirmation feedback to the user" do - document = create :document, :with_file + context 'when a document is deleted' do + it 'gives a confirmation feedback to the user' do + document = create(:document) - delete :destroy, params: { id: document.id } + delete :destroy, params: {id: document.id} expect(flash[:notice]).to eq I18n.t('activerecord.notices.messages.record_destroyed', model: Document.model_name.singular) diff --git a/spec/factories/addresses.rb b/spec/factories/addresses.rb index 18fec80e..e0c8d70c 100644 --- a/spec/factories/addresses.rb +++ b/spec/factories/addresses.rb @@ -4,15 +4,15 @@ # # Table name: addresses # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # postal_code :string # city :string not null # street_name_1 :string # street_name_2 :string # created_at :datetime not null # updated_at :datetime not null -# productor_id :bigint(8) -# member_id :bigint(8) +# productor_id :bigint +# member_id :bigint # coordinates :float is an Array # diff --git a/spec/factories/documents.rb b/spec/factories/documents.rb index 0f4b060b..e552d8d7 100644 --- a/spec/factories/documents.rb +++ b/spec/factories/documents.rb @@ -4,11 +4,11 @@ # # Table name: documents # -# id :bigint(8) not null, primary key -# category :string default: weekly_orders -# published :boolean default: false +# id :bigint not null, primary key # created_at :datetime not null # updated_at :datetime not null +# published :boolean default(FALSE) +# category :string default("weekly_orders") # # rubocop:disable Style/MixinUsage @@ -17,12 +17,6 @@ FactoryBot.define do factory :document do - trait :with_file do - file { fixture_file_upload(Rails.root.join('spec', 'support', 'fixtures', 'erd.pdf'), 'application/pdf') } - end - - trait :with_invalid_file_type do - file { fixture_file_upload(Rails.root.join('spec', 'support', 'fixtures', 'fixture.json'), 'application/json') } - end + file { fixture_file_upload(Rails.root.join('spec/support/fixtures/erd.pdf'), 'application/pdf') } end end diff --git a/spec/factories/enrollments.rb b/spec/factories/enrollments.rb index a0339d8a..f35c2300 100644 --- a/spec/factories/enrollments.rb +++ b/spec/factories/enrollments.rb @@ -4,11 +4,13 @@ # # Table name: enrollments # -# member_id :bigint(8) not null -# mission_id :bigint(8) not null -# id :bigint(8) not null, primary key -# start_time :time -# end_time :time +# id :bigint not null, primary key +# member_id :bigint not null +# mission_id :bigint not null +# old_start_time :time +# old_end_time :time +# start_time :datetime +# end_time :datetime # FactoryBot.define do diff --git a/spec/factories/group_managers.rb b/spec/factories/group_managers.rb index 57c85fe3..2977a998 100644 --- a/spec/factories/group_managers.rb +++ b/spec/factories/group_managers.rb @@ -1,4 +1,18 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: group_managers +# +# id :bigint not null, primary key +# managed_group_id :bigint +# manager_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# FactoryBot.define do factory :group_manager do + managed_group factory: :group + manager factory: :member end end diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 9884015e..0f52f521 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -1,4 +1,19 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: group_members +# +# id :bigint not null, primary key +# group_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# assignment :text +# FactoryBot.define do factory :group_member do + group + member end end diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 7222fb98..c317d186 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -2,15 +2,22 @@ # == Schema Information # -# Table name: missions +# Table name: groups +# +# id :bigint not null, primary key +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# roles :string # -# id :bigint(8) not null, primary key -# name :string not null -# roles :array[string] -# group_manager_mail :string FactoryBot.define do factory :group do - name { Faker::Lorem.word } + name { Faker::Lorem.unique.word } + + trait :with_members_and_managers do + group_managers { [association(:group_manager, managed_group: instance)] } + group_members { [association(:group_member, group: instance)] } + end end end diff --git a/spec/factories/history_of_generated_schedules.rb b/spec/factories/history_of_generated_schedules.rb index 0152d4fe..20013a01 100644 --- a/spec/factories/history_of_generated_schedules.rb +++ b/spec/factories/history_of_generated_schedules.rb @@ -1,3 +1,12 @@ +# == Schema Information +# +# Table name: history_of_generated_schedules +# +# id :bigint not null, primary key +# month_number :datetime +# created_at :datetime not null +# updated_at :datetime not null +# FactoryBot.define do factory :history_of_generated_schedule do month_number { "2020-12-13 22:31:01" } diff --git a/spec/factories/history_of_static_slot_selections.rb b/spec/factories/history_of_static_slot_selections.rb index 44ce41e6..cce5076e 100644 --- a/spec/factories/history_of_static_slot_selections.rb +++ b/spec/factories/history_of_static_slot_selections.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: history_of_static_slot_selections +# +# id :bigint not null, primary key +# member_id :bigint +# static_slot_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# FactoryBot.define do factory :history_of_static_slot_selection do member diff --git a/spec/factories/infos.rb b/spec/factories/infos.rb index cd4896ef..77491371 100644 --- a/spec/factories/infos.rb +++ b/spec/factories/infos.rb @@ -4,14 +4,14 @@ # # Table name: infos # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # content :text -# category :string -# published :boolean default: false # title :string # created_at :datetime not null # updated_at :datetime not null -# author_id :bigint(8) +# author_id :bigint +# category :string +# published :boolean default(FALSE) # FactoryBot.define do diff --git a/spec/factories/member_static_slots.rb b/spec/factories/member_static_slots.rb index 4548e14c..8f8b962b 100644 --- a/spec/factories/member_static_slots.rb +++ b/spec/factories/member_static_slots.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: member_static_slots +# +# id :bigint not null, primary key +# static_slot_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# FactoryBot.define do factory :member_static_slot do member diff --git a/spec/factories/members.rb b/spec/factories/members.rb index ef9ef51d..7c39d78f 100644 --- a/spec/factories/members.rb +++ b/spec/factories/members.rb @@ -4,7 +4,7 @@ # # Table name: members # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # email :string default(""), not null # encrypted_password :string default(""), not null # reset_password_token :string @@ -27,11 +27,12 @@ # invitation_accepted_at :datetime # invitation_limit :integer # invited_by_type :string -# invited_by_id :bigint(8) +# invited_by_id :bigint # invitations_count :integer default(0) # display_name :string # moderator :boolean default(FALSE) # cash_register_proficiency :integer default("untrained") +# register_id :integer # FactoryBot.define do @@ -45,16 +46,20 @@ biography { Faker::ChuckNorris.fact } phone_number { Faker::PhoneNumber.phone_number } email { Faker::Internet.email(name: first_name) } - password { "password" } - password_confirmation { "password" } + password { 'password' } + password_confirmation { 'password' } confirmed_at { Time.zone.today } - trait :admin do role { 'admin' } end - trait :super_admin do role { 'super_admin' } end + trait :admin do + role { 'admin' } + end + trait :super_admin do + role { 'super_admin' } + end after :create do |member, options| if options.redactor? - group = create :group, roles: ['redactor'] + group = create(:group, roles: ['redactor']) GroupMember.create(member: member, group: group) end end diff --git a/spec/factories/missions.rb b/spec/factories/missions.rb index 5b368a43..01e9230d 100644 --- a/spec/factories/missions.rb +++ b/spec/factories/missions.rb @@ -4,19 +4,20 @@ # # Table name: missions # -# id :bigint(8) not null, primary key -# name :string not null -# description :text not null -# due_date :datetime -# created_at :datetime not null -# updated_at :datetime not null -# author_id :bigint(8) -# start_date :datetime -# recurrent :boolean -# max_member_count :integer -# min_member_count :integer -# delivery_expected :boolean default(FALSE) -# genre :integer default: 0 +# id :bigint not null, primary key +# name :string not null +# description :text not null +# due_date :datetime +# created_at :datetime not null +# updated_at :datetime not null +# author_id :bigint +# start_date :datetime +# recurrent :boolean +# max_member_count :integer +# min_member_count :integer +# delivery_expected :boolean default(FALSE) +# genre :integer default("standard") +# cash_register_proficiency_requirement :integer default("untrained") # FactoryBot.define do diff --git a/spec/factories/productors.rb b/spec/factories/productors.rb index 30d01bd4..708fbb0d 100644 --- a/spec/factories/productors.rb +++ b/spec/factories/productors.rb @@ -4,7 +4,7 @@ # # Table name: productors # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # name :string # description :text # phone_number :string @@ -12,6 +12,7 @@ # updated_at :datetime not null # website_url :string # local :boolean default(FALSE) +# category :string # FactoryBot.define do diff --git a/spec/factories/static_slots.rb b/spec/factories/static_slots.rb index 2d9d2a0d..9bdc0846 100644 --- a/spec/factories/static_slots.rb +++ b/spec/factories/static_slots.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: static_slots +# +# id :bigint not null, primary key +# week_day :integer not null +# start_time :datetime not null +# week_type :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# FactoryBot.define do factory :static_slot do week_day { 'Monday' } diff --git a/spec/factory_bot_spec.rb b/spec/factory_bot_spec.rb new file mode 100644 index 00000000..c714af97 --- /dev/null +++ b/spec/factory_bot_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'rails_helper' + +BROKEN_FACTORIES = %i[ + messageboard + topic + post +].freeze + +RSpec.describe FactoryBot do + # Ideally, this list should be empty. When you work on a model, try to make its factory valid by default + let(:factories_to_check) do + described_class.factories.reject do |f| + f.name.in?(BROKEN_FACTORIES) + end + end + + it 'has valid factories' do # rubocop:disable RSpec/NoExpectationExample + described_class.lint(factories_to_check, traits: true) + end + + BROKEN_FACTORIES.each do |factory| + context "with the :#{factory} factory" do + pending 'does not build valid objects. See https://rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#best-practices' + end + end +end diff --git a/spec/features/document_uploads_at_document_page_section_spec.rb b/spec/features/document_uploads_at_document_page_section_spec.rb deleted file mode 100644 index 060714ed..00000000 --- a/spec/features/document_uploads_at_document_page_section_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.feature "DocumentUploadsAtInfoSections", type: :feature do - let(:admin) { create :member, :admin } - - context "when a regular member accesses the page" do - before { sign_in create :member } - - it "does not show the document upload form" do - visit infos_path(anchor: 'documents') - - expect(page).not_to have_content("Ajouter un document") - end - - it "does not show the delete button on a document" do - create :document, :with_file - - visit infos_path(anchor: 'documents') - - expect(page).not_to have_link(I18n.t('main_app.views.application.buttons.destroy')) - end - end - - context "when an admin uploads a document" do - before { - sign_in admin - visit documents_path(anchor: 'documents') - attach_file('document_file', Rails.root.join('spec', 'support', 'fixtures', 'erd.pdf')) - click_button "Ajouter" - } - - it "show the document on the documents/index#document view" do - expect(page).to have_content 'erd.pdf' - end - - context 'when javascript is enabled in the browser', js: true do - it "show the document on the documents/index#document view" do - expect(page).to have_content 'erd.pdf' - end - end - end - - context "when an admin deletes a document" do - before { - sign_in admin - create :document, :with_file - visit documents_path(anchor: 'documents') - click_on I18n.t("main_app.views.application.buttons.destroy") - } - - it "deletes the document from documents/index#document view" do - expect(page).not_to have_content 'erd.pdf' - end - - context 'when javascript is enabled in the browser', js: true do - it "deletes the document from from the documents/index#document view" do - page.driver.browser.switch_to.alert.accept - expect(page).not_to have_content 'erd.pdf' - end - end - end -end diff --git a/spec/features/factories_test.rb b/spec/features/factories_test.rb deleted file mode 100644 index e8c7fa33..00000000 --- a/spec/features/factories_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' -require 'spec_helper' - -RSpec.describe 'Factories :' do - FactoryBot.factories.map(&:name).each do |factory_name| - describe "the #{factory_name} factory" do - it 'is valid' do - expect(build(factory_name)).to be_valid - end - end - end -end diff --git a/spec/features/members/cash_register_proficiency_spec.rb b/spec/features/members/cash_register_proficiency_spec.rb deleted file mode 100644 index 72fee154..00000000 --- a/spec/features/members/cash_register_proficiency_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe "Members cash register proficiency", type: :feature do - let(:mission) { create :mission } - let(:jack) { create :member, cash_register_proficiency: :proficient } - - before { sign_in jack } - - context "when on a mission details page" do - before { - mission.members << jack - visit mission_path(mission.id) - } - - it "shows enrolled members proficiency" do - expect(page).to have_content( - I18n.translate(jack.cash_register_proficiency, - scope: "activerecord.attributes.member.cash_register_proficiencies") - ) - end - end - - context "when on the mission index page" do - before { - mission.members << create_list(:member, 3, cash_register_proficiency: :untrained) - visit missions_path - } - - it "shows missions without proficient members in purple", js: true do - expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) - .to eq 'rgb(128, 0, 128)' - end - end -end diff --git a/spec/features/members/member_invitations_spec.rb b/spec/features/members/member_invitations_spec.rb deleted file mode 100644 index bfa78635..00000000 --- a/spec/features/members/member_invitations_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.feature "MemberInvitations", type: :feature do - let(:super_admin) { create :member, :super_admin } - before { - sign_in super_admin - visit 'members/invitation/new' - } - - it "does not send an invitation to an invalid email" do - fill_in Member.human_attribute_name(:email), with: 'wrong_email' - click_button "Envoyer l'invitation" - end - - context "when using the invitation form," do - before { - fill_in Member.human_attribute_name(:email), with: 'test@test.com' - click_button "Envoyer l'invitation" - } - - it 'sends an invitation email' do - expect(Devise.mailer.deliveries.count).to eq 1 - expect(page).to have_content "Un e-mail d'invitation a été envoyé" - end - - it 'creates an unvalidated user' do - invalid_user = Member.last - expect(Member.count).to eq 2 - expect(invalid_user.email).to eq 'test@test.com' - expect(invalid_user.first_name).to eq nil - expect(invalid_user.last_name).to eq nil - end - - it "redirects to the same page when the invitation is sent, to allow quick multiple invitations" do - expect(current_path).to eq('/members/invitation/new') - end - end - - context "when following the invitation mail's link" do - before { - fill_in Member.human_attribute_name(:email), with: 'test@test.com' - click_button "Envoyer l'invitation" - click_link "Déconnexion" - open_email "test@test.com" - visit_in_email "Accepter l'invitation" - } - it "allows the user to finalize his account creation" do - fill_in "Prénom", with: 'first_name' - fill_in "Nom de famille", with: 'last_name' - fill_in "Mot de passe", with: "password" - fill_in "Confirmez votre mot de passe", with: "password" - click_button "Confirmer" - - expect(page).to have_content "Votre compte et votre mot de passe ont été créés. Vous êtes maintenant connecté." - end - end -end diff --git a/spec/features/mission_events_color_code_spec.rb b/spec/features/mission_events_color_code_spec.rb deleted file mode 100644 index 59b3c572..00000000 --- a/spec/features/mission_events_color_code_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe "Mission events color codes:", type: :feature do - before { sign_in create(:member) } - - context "when a delivery is expected at the shop" do - it "shows a truck icon on the mission event", js: true do - create :mission, delivery_expected: true - - visit missions_path - - expect(page).to have_css(".fas.fa-truck") - end - end - - context "when :min_member_count is set" do - let(:mission) { create :mission, min_member_count: 3 } - - context "with insufficient enrolled members", js: true do - it "shows the event colored in purple" do - mission.members << create_list(:member, 2) - - visit missions_path - - expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) - .to eq 'rgb(128, 0, 128)' - end - - it "shows the event in red if no member if enrolled" do - mission - - visit missions_path - - expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) - .to eq 'rgb(255, 0, 0)' - end - end - end - - context "when :event is set to true" do - it "shows the event colored in orange", js: true do - mission = create :mission, genre: 'event' - - visit missions_path - - expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) - .to eq 'rgb(255, 165, 0)' - end - end - - context "when a member enrolls for a smaller duration than the full mission duration" do - it "shows the member's name in light blue", js: true do - mission = create :mission - jack = create :member, first_name: 'Jack' - create :enrollment, :one_hour, mission: mission, member: jack - - visit mission_path(mission.id) - - expect(find("#member_#{jack.id}")).to have_css('.bg-info') - end - end -end diff --git a/spec/jobs/enroll_static_members_job_spec.rb b/spec/jobs/enroll_static_members_job_spec.rb index d7082eae..8220abc8 100644 --- a/spec/jobs/enroll_static_members_job_spec.rb +++ b/spec/jobs/enroll_static_members_job_spec.rb @@ -1,5 +1,24 @@ require 'rails_helper' RSpec.describe EnrollStaticMembersJob, type: :job do - pending "add some examples to (or delete) #{__FILE__}" + subject(:perform_job) do + allow(service).to receive_messages call: nil, reports: nil + described_class.perform_now(service) + end + + let(:service) { instance_double(StaticMembersRecruiter) } + + it 'calls the given service' do + perform_job + expect(service).to have_received :call + end + + it 'broadcasts the service result through websocket' do + allow(ActionCable.server).to receive :broadcast + + perform_job + + expect(ActionCable.server).to have_received(:broadcast) + .with('notifications', hash_including(:reports)) + end end diff --git a/spec/jobs/generate_schedule_job_spec.rb b/spec/jobs/generate_schedule_job_spec.rb index d2fe7c34..8d12f10a 100644 --- a/spec/jobs/generate_schedule_job_spec.rb +++ b/spec/jobs/generate_schedule_job_spec.rb @@ -1,5 +1,40 @@ require 'rails_helper' -RSpec.describe GenerateScheduleJob, type: :job do - pending "add some examples to (or delete) #{__FILE__}" +RSpec.describe GenerateScheduleJob do + include ActiveJob::TestHelper + + subject(:generate_job) do + described_class.perform_later(current_member: current_member, current_month: current_month.to_s) + end + + let(:current_member) { create(:member) } + let(:current_month) { Date.new(2024, 12, 27).beginning_of_month } + + it 'launches the ScheduleGenerator service' do + generator_double = instance_double(ScheduleGenerator, generate_schedule: nil, errors: []) + allow(ScheduleGenerator).to receive(:new).and_return(generator_double) + + perform_enqueued_jobs { generate_job } + + expect(generator_double).to have_received(:generate_schedule) + end + + it 'creates Missions for the given month' do + expect { perform_enqueued_jobs { generate_job } }.to change(Mission, :count).from(0).to(78) + end + + it 'creates HistoryOfGeneratedSchedule for the given month' do + expect { perform_enqueued_jobs { generate_job } }.to change(HistoryOfGeneratedSchedule, :count).from(0).to(1) + end + + context 'when the schedule genreration encountered some errors' do + before do + generator_double = instance_double(ScheduleGenerator, generate_schedule: nil, errors: ['BOOM']) + allow(ScheduleGenerator).to receive(:new).and_return(generator_double) + end + + it 'returns without creating any schedule generation history' do + expect { perform_enqueued_jobs { generate_job } }.not_to change(HistoryOfGeneratedSchedule, :count).from(0) + end + end end diff --git a/spec/models/address_spec.rb b/spec/models/address_spec.rb index 210b0fc3..7ec2ea66 100644 --- a/spec/models/address_spec.rb +++ b/spec/models/address_spec.rb @@ -4,15 +4,15 @@ # # Table name: addresses # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # postal_code :string # city :string not null # street_name_1 :string # street_name_2 :string # created_at :datetime not null # updated_at :datetime not null -# productor_id :bigint(8) -# member_id :bigint(8) +# productor_id :bigint +# member_id :bigint # coordinates :float is an Array # @@ -70,15 +70,11 @@ end describe "instance coordinates search" do - let(:address) { - create :address, street_name_1: "4 allée de la faïencerie", - street_name_2: "au bout de l'allée", - postal_code: "60100" - } - describe "#fetch coordinates" do - it "connects successfully to api-adresse.data.gouv.fr/search/" do - expect(address.send(:fetch_coordinates).code).to eq 200 - end + let(:address) do + create :address, + street_name_1: "4 allée de la faïencerie", + street_name_2: "au bout de l'allée", + postal_code: "60100" end describe "#assign_coordinates" do diff --git a/spec/models/document_spec.rb b/spec/models/document_spec.rb new file mode 100644 index 00000000..94b3a7a6 --- /dev/null +++ b/spec/models/document_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe Document, type: :model do + context 'with a file of a whitelisted content_type' do + subject { Document.new(file: fixture_file_upload('test.txt')) } + + it { is_expected.to be_valid } + end + + context 'with a file of a non-whitelisted content_type' do + subject { Document.new(file: fixture_file_upload('fixture.json')) } + + it { is_expected.not_to be_valid } + end + + context 'without any attached file' do + subject { Document.new(file: nil) } + + it { is_expected.not_to be_valid } + end +end diff --git a/spec/models/enrollment_spec.rb b/spec/models/enrollment_spec.rb index 227ffa4a..051d73c2 100644 --- a/spec/models/enrollment_spec.rb +++ b/spec/models/enrollment_spec.rb @@ -4,11 +4,13 @@ # # Table name: enrollments # -# member_id :bigint(8) not null -# mission_id :bigint(8) not null -# id :bigint(8) not null, primary key -# start_time :time -# end_time :time +# id :bigint not null, primary key +# member_id :bigint not null +# mission_id :bigint not null +# old_start_time :time +# old_end_time :time +# start_time :datetime +# end_time :datetime # require 'rails_helper' diff --git a/spec/models/group_manager_spec.rb b/spec/models/group_manager_spec.rb index 863539ee..2f1f8826 100644 --- a/spec/models/group_manager_spec.rb +++ b/spec/models/group_manager_spec.rb @@ -1,3 +1,13 @@ +# == Schema Information +# +# Table name: group_managers +# +# id :bigint not null, primary key +# managed_group_id :bigint +# manager_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# require 'rails_helper' RSpec.describe GroupManager, type: :model do diff --git a/spec/models/group_member_spec.rb b/spec/models/group_member_spec.rb index 52347559..224c0e19 100644 --- a/spec/models/group_member_spec.rb +++ b/spec/models/group_member_spec.rb @@ -1,3 +1,14 @@ +# == Schema Information +# +# Table name: group_members +# +# id :bigint not null, primary key +# group_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# assignment :text +# require 'rails_helper' RSpec.describe GroupMember, type: :model do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index b6876bc9..8714312b 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2,26 +2,34 @@ # == Schema Information # -# Table name: missions +# Table name: groups +# +# id :bigint not null, primary key +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# roles :string # -# id :bigint(8) not null, primary key -# name :string not null -# group_manager_mail :string require 'rails_helper' -RSpec.describe Group, type: :model do - describe 'Model instanciation' do - subject { described_class.new } +RSpec.describe Group do + describe 'validations' do + subject(:instance) { build(:group) } - describe 'validations' do - it { is_expected.to validate_presence_of(:name) } - it { validate_uniqueness_of(:name).case_insensitive } - end + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).case_insensitive } + end + + describe 'associations' do + it { is_expected.to have_many(:members).through(:group_members) } - describe 'associations' do - it { is_expected.to have_many(:members).through(:group_members) } - it { is_expected.to have_many(:managers).class_name('Member').inverse_of('managed_groups').through(:group_managers) } + it 'has many managers' do + expect(subject) + .to have_many(:managers) + .class_name('Member') + .inverse_of('managed_groups') + .through(:group_managers) end end end diff --git a/spec/models/history_of_generated_schedule_spec.rb b/spec/models/history_of_generated_schedule_spec.rb index 52a57e26..3fa887e2 100644 --- a/spec/models/history_of_generated_schedule_spec.rb +++ b/spec/models/history_of_generated_schedule_spec.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: history_of_generated_schedules +# +# id :bigint not null, primary key +# month_number :datetime +# created_at :datetime not null +# updated_at :datetime not null +# require 'rails_helper' RSpec.describe HistoryOfGeneratedSchedule, type: :model do diff --git a/spec/models/history_of_static_slot_selection_spec.rb b/spec/models/history_of_static_slot_selection_spec.rb index 70983387..61c91948 100644 --- a/spec/models/history_of_static_slot_selection_spec.rb +++ b/spec/models/history_of_static_slot_selection_spec.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: history_of_static_slot_selections +# +# id :bigint not null, primary key +# member_id :bigint +# static_slot_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# require 'rails_helper' RSpec.describe HistoryOfStaticSlotSelection, type: :model do diff --git a/spec/models/info_spec.rb b/spec/models/info_spec.rb index 9564f887..8b780ba0 100644 --- a/spec/models/info_spec.rb +++ b/spec/models/info_spec.rb @@ -4,17 +4,19 @@ # # Table name: infos # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # content :text # title :string # created_at :datetime not null # updated_at :datetime not null -# author_id :bigint(8) +# author_id :bigint +# category :string +# published :boolean default(FALSE) # require 'rails_helper' -RSpec.describe Info, type: :model do +RSpec.describe Info do describe 'Model instanciation' do subject { described_class.new } @@ -26,6 +28,10 @@ describe 'validations' do it { is_expected.to validate_presence_of(:title) } + + it 'has a valid factory' do # rubocop:disable RSpec/NoExpectationExample + FactoryBot.lint(FactoryBot.factories.select { |f| f.name == :info }) + end end describe 'associations' do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 6da546b7..aa29ac23 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -4,7 +4,7 @@ # # Table name: members # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # email :string default(""), not null # encrypted_password :string default(""), not null # reset_password_token :string @@ -27,11 +27,12 @@ # invitation_accepted_at :datetime # invitation_limit :integer # invited_by_type :string -# invited_by_id :bigint(8) +# invited_by_id :bigint # invitations_count :integer default(0) # display_name :string # moderator :boolean default(FALSE) # cash_register_proficiency :integer default("untrained") +# register_id :integer # require 'rails_helper' diff --git a/spec/models/member_static_slot_spec.rb b/spec/models/member_static_slot_spec.rb index 4ef706b2..b5059e3f 100644 --- a/spec/models/member_static_slot_spec.rb +++ b/spec/models/member_static_slot_spec.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: member_static_slots +# +# id :bigint not null, primary key +# static_slot_id :bigint +# member_id :bigint +# created_at :datetime not null +# updated_at :datetime not null +# require 'rails_helper' RSpec.describe MemberStaticSlot, type: :model do diff --git a/spec/models/mission_spec.rb b/spec/models/mission_spec.rb index 15188fe0..de9b5ac4 100644 --- a/spec/models/mission_spec.rb +++ b/spec/models/mission_spec.rb @@ -4,19 +4,20 @@ # # Table name: missions # -# id :bigint(8) not null, primary key -# name :string not null -# description :text not null -# due_date :datetime -# created_at :datetime not null -# updated_at :datetime not null -# author_id :bigint(8) -# start_date :datetime -# recurrent :boolean -# max_member_count :integer -# min_member_count :integer -# delivery_expected :boolean default(FALSE) -# event :boolean default(FALSE) +# id :bigint not null, primary key +# name :string not null +# description :text not null +# due_date :datetime +# created_at :datetime not null +# updated_at :datetime not null +# author_id :bigint +# start_date :datetime +# recurrent :boolean +# max_member_count :integer +# min_member_count :integer +# delivery_expected :boolean default(FALSE) +# genre :integer default("standard") +# cash_register_proficiency_requirement :integer default("untrained") # # A Mission is an activity that has to be done for the Supermaket Team to function properly. @@ -63,34 +64,78 @@ end describe '#selectable_time_slots' do - it 'returns the time slots with at least one slot available' do - mission = create :mission, genre: 'regulated' + subject(:selectable_time_slots) { mission.selectable_time_slots } - time_slots = mission.selectable_time_slots + let(:mission) { create(:mission, genre: 'regulated') } - expect(time_slots).to eq([mission.start_date, mission.start_date + 90.minutes]) + it 'returns the time slots that a member can enroll in' do + expect(selectable_time_slots).to eq([mission.start_date, mission.start_date + 90.minutes]) end context 'when all slots are already taken by other members' do + let(:mission) do + create(:mission, genre: 'regulated') do |mission| + create_list(:member, 4).each do |member| + create(:enrollment, + member: member, + mission: mission, + start_time: mission.start_date, + end_time: mission.start_date + 3.hours) + end + end + end + it 'returns no time slots' do - mission = create :mission, genre: 'regulated' - enroll_n_members_on_mission(mission, 4) + expect(selectable_time_slots).to be_empty + end + end - response = mission.selectable_time_slots + context 'with a non-regulated mission' do + let(:mission) { build(:mission) } - expect(response).to be_empty + it 'returns nil' do + expect(selectable_time_slots).to be_nil end end end - def enroll_n_members_on_mission(mission, members_count) - members = create_list :member, members_count - members.each do |member| - create :enrollment, - member: member, - mission: mission, - start_time: mission.start_date, - end_time: mission.start_date + 3.hours + describe '#time_slot_already_taken_by_member?' do + subject(:time_slot_already_taken_by_member?) do + mission.time_slot_already_taken_by_member?(time_slot, member) + end + + let(:mission) { build(:mission) } + let(:member) { build(:member) } + let(:time_slot) { DateTime.current } + + it { is_expected.to be false } + + context 'with a member enrolled on this mission during the given time slot' do + let(:mission) do + create(:mission, start_date: time_slot - 1.hour) do |mission| + create(:enrollment, + member: member, + mission: mission, + start_time: time_slot - 1.hour, + end_time: time_slot + 1.hour) + end + end + + it { is_expected.to be true } + end + + context 'with a member enrolled on this mission outside of the given time slot' do + let(:mission) do + create(:mission, start_date: time_slot - 2.hours) do |mission| + create(:enrollment, + member: member, + mission: mission, + start_time: time_slot - 1.hour, + end_time: time_slot) + end + end + + it { is_expected.to be false } end end end diff --git a/spec/models/productor_spec.rb b/spec/models/productor_spec.rb index 2029ce11..88b52306 100644 --- a/spec/models/productor_spec.rb +++ b/spec/models/productor_spec.rb @@ -4,7 +4,7 @@ # # Table name: productors # -# id :bigint(8) not null, primary key +# id :bigint not null, primary key # name :string # description :text # phone_number :string @@ -12,6 +12,7 @@ # updated_at :datetime not null # website_url :string # local :boolean default(FALSE) +# category :string # require 'rails_helper' diff --git a/spec/models/static_slot_spec.rb b/spec/models/static_slot_spec.rb index e4b16f00..5148ab91 100644 --- a/spec/models/static_slot_spec.rb +++ b/spec/models/static_slot_spec.rb @@ -1,5 +1,16 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: static_slots +# +# id :bigint not null, primary key +# week_day :integer not null +# start_time :datetime not null +# week_type :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# require 'rails_helper' RSpec.describe StaticSlot, type: :model do diff --git a/spec/policies/active_admin/page_policy_spec.rb b/spec/policies/active_admin/page_policy_spec.rb deleted file mode 100644 index c11b986b..00000000 --- a/spec/policies/active_admin/page_policy_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ActiveAdmin::PagePolicy, type: :policy do - subject { described_class } -end diff --git a/spec/policies/enrollment_policy_spec.rb b/spec/policies/enrollment_policy_spec.rb index 0aa8b412..1f344491 100644 --- a/spec/policies/enrollment_policy_spec.rb +++ b/spec/policies/enrollment_policy_spec.rb @@ -7,23 +7,17 @@ subject { described_class } - permissions ".scope" do - pending "add some examples to (or delete) #{__FILE__}" + permissions '.scope' do + subject(:scope) { described_class::Scope.new(nil, Enrollment).resolve } + + it 'returns everything' do + allow(Enrollment).to receive(:all) + scope + expect(Enrollment).to have_received(:all) + end end - permissions :show? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :create? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :update? do - pending "add some examples to (or delete) #{__FILE__}" - end - - permissions :destroy? do - pending "add some examples to (or delete) #{__FILE__}" + permissions :index?, :show?, :create?, :update?, :destroy? do + it { is_expected.to permit } end end diff --git a/spec/presenters/enrollment_presenter_spec.rb b/spec/presenters/enrollment_presenter_spec.rb new file mode 100644 index 00000000..ff6f523b --- /dev/null +++ b/spec/presenters/enrollment_presenter_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +describe EnrollmentPresenter do + describe '#default_start_time' do + subject(:default_start_time) { described_class.new(enrollment).default_start_time } + + let(:enrollment) { build_stubbed(:enrollment) } + + it 'return nil' do + expect(default_start_time).to be_nil + end + + context 'when there is a start time' do + let(:enrollment) { build_stubbed(:enrollment, start_time: Time.zone.parse('10h53')) } + + it 'formats it in the Hour:Minute format' do + expect(default_start_time).to eq '10:53' + end + end + end + + describe '#default_end_time' do + subject(:default_end_time) { described_class.new(enrollment).default_end_time } + + let(:enrollment) { build_stubbed(:enrollment) } + + it 'return nil' do + expect(default_end_time).to be_nil + end + + context 'when there is a start time' do + let(:enrollment) { build_stubbed(:enrollment, end_time: Time.zone.parse('19h35')) } + + it 'formats it in the Hour:Minute format' do + expect(default_end_time).to eq '19:35' + end + end + end +end diff --git a/spec/presenters/group_presenter_spec.rb b/spec/presenters/group_presenter_spec.rb new file mode 100644 index 00000000..051a3822 --- /dev/null +++ b/spec/presenters/group_presenter_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe GroupPresenter do + describe '#all_manager_links' do + subject(:all_manager_links) { described_class.new(group).all_manager_links } + let(:group) { create(:group, :with_members_and_managers) } + + it 'concatenates links to associated managers' do + expect(all_manager_links).to match(%Q{href="/members/#{group.managers.first.id}}) + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 89ae872f..14fe8806 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,7 +1,25 @@ # frozen_string_literal: true require 'simplecov' -SimpleCov.start 'rails' +SimpleCov.start 'rails' do + enable_coverage :branch + + add_filter '/vendor' + add_filter '/spec/support' + + add_group 'Admin', 'app/admin' + add_group 'Components', 'app/components' + add_group 'Decorators', 'app/decorators' + add_group 'Inputs', 'app/inputs' + add_group 'Policies', 'app/policies' + add_group 'QuerySets', 'app/query_sets' + add_group 'Presenters', 'app/presenters' + add_group 'Services', 'app/services' + add_group 'Transactions', 'app/transactions' + add_group 'Uploaders', 'app/uploaders' + add_group 'Validators', 'app/validators' + add_group 'Views', 'app/views' +end # This file is copied to spec/ when you run 'rails generate rspec:install' require 'spec_helper' @@ -15,6 +33,8 @@ require 'email_spec' require 'email_spec/rspec' +Rails.application.eager_load! # see https://github.com/simplecov-ruby/simplecov?tab=readme-ov-file#want-to-use-spring-with-simplecov + # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end @@ -40,7 +60,7 @@ end RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - # config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.fixture_path = Rails.root.join('spec/support/fixtures') # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false @@ -70,7 +90,7 @@ config.include FactoryBot::Syntax::Methods config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::ControllerHelpers, type: :view - config.include Devise::Test::IntegrationHelpers, type: :feature + config.include Devise::Test::IntegrationHelpers, type: :system config.include Devise::Test::IntegrationHelpers, type: :request end @@ -80,3 +100,5 @@ with.library :rails end end + +Capybara.javascript_driver = :selenium_headless diff --git a/spec/requests/admin/documents_spec.rb b/spec/requests/admin/documents_spec.rb new file mode 100644 index 00000000..b93b1e96 --- /dev/null +++ b/spec/requests/admin/documents_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +RSpec.describe 'Admin::Documents' do + before { sign_in build_stubbed(:member, :super_admin) } + + describe 'GET /admin/documents' do + subject(:index) do + create(:document) + create(:document, file: fixture_file_upload('test.txt')) + get admin_documents_path + end + + it 'has an :ok HTTP status' do + index + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /admin/documents/new' do + subject(:new) { get new_admin_document_path } + + it 'has an :ok HTTP status' do + new + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /admin/documents/:id/edit' do + subject(:edit) { get edit_admin_document_path(create(:document)) } + + it 'has an :ok HTTP status' do + edit + expect(response).to have_http_status(:ok) + end + end +end diff --git a/spec/requests/admin/groups_spec.rb b/spec/requests/admin/groups_spec.rb new file mode 100644 index 00000000..670c0c45 --- /dev/null +++ b/spec/requests/admin/groups_spec.rb @@ -0,0 +1,48 @@ +require 'rails_helper' + +RSpec.describe 'Admin::Groups' do + before { sign_in build_stubbed(:member, :super_admin) } + + describe 'GET /admin/groups' do + subject(:index) do + create_list(:group, 1) + get admin_groups_path + end + + it 'has an :ok HTTP status' do + index + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /admin/groups/:id' do + subject(:show) { get admin_group_path(group) } + + let(:group) { create(:group, :with_members_and_managers) } + + it 'has an :ok HTTP status' do + show + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /admin/groups/new' do + subject(:new) { get new_admin_group_path } + + it 'has an :ok HTTP status' do + new + expect(response).to have_http_status(:ok) + end + end + + describe 'GET /admin/groups/:id/edit' do + subject(:edit) { get edit_admin_group_path(group) } + + let(:group) { create(:group) } + + it 'has an :ok HTTP status' do + edit + expect(response).to have_http_status(:ok) + end + end +end diff --git a/spec/requests/admin/members_spec.rb b/spec/requests/admin/members_spec.rb new file mode 100644 index 00000000..978b751e --- /dev/null +++ b/spec/requests/admin/members_spec.rb @@ -0,0 +1,170 @@ +require 'rails_helper' + +RSpec.describe 'admin/members' do + before { sign_in build_stubbed(:member, :super_admin) } + + describe 'GET /' do + subject(:index) { get admin_members_path } + + let!(:members) { [create(:member)] } + + it 'has an :ok HTTP status' do + index + expect(response).to have_http_status(:ok) + end + + %i[first_name last_name email role cash_register_proficiency].each do |attribute| + it 'renders the expected columns' do + index + expect(response.body).to include(members.first.send(attribute)) + end + end + + context 'with the .csv format' do + subject(:csv_index) { get admin_members_path, params: {format: :csv} } + + it 'has an :ok HTTP status' do + csv_index + expect(response).to have_http_status(:ok) + end + + it 'returns a CSV format' do + csv_index + expect(response.header['Content-Type']).to include 'text/csv' + end + end + end + + describe 'GET /:id' do + subject(:show) { get admin_member_path(member) } + + let(:member) { create(:member) } + + it 'has an :ok HTTP status' do + show + expect(response).to have_http_status(:ok) + end + + context 'when the member belongs to a group' do + let(:member) { create(:group, :with_members_and_managers).members.first } + + it 'has an :ok HTTP status' do + show + expect(response).to have_http_status(:ok) + end + end + + context 'with static slots selection history' do + let(:member) { create(:history_of_static_slot_selection).member } + + it 'has an :ok HTTP status' do + show + expect(response).to have_http_status(:ok) + end + end + end + + describe 'GET /new' do + subject(:new) { get new_admin_member_path } + + it 'has an :ok HTTP status' do + new + expect(response).to have_http_status(:ok) + end + end + + describe 'POST /' do + subject(:create_member) { post admin_members_path, params: params } + + let(:params) do + {member: attributes_for(:member, cash_register_proficiency: 'beginner', email: 'update@update.com')} + end + + it 'creates a Member' do + expect { create_member }.to change(Member, :count).by(1) + end + + it 'redirects to the created member' do + create_member + expect(response).to redirect_to(admin_member_path(Member.last)) + end + + context 'with invalid params' do + let(:params) do + {member: {first_name: nil}} + end + + it 'does not create any Member' do + expect { create_member }.not_to change(Member, :count) + end + + it 'renders the :new form again' do + create_member + expect(response).to render_template :new + end + end + end + + describe 'GET /:id/edit' do + subject(:edit) { get edit_admin_member_path(member) } + + let(:member) { create(:member) } + + it 'has an :ok HTTP status' do + edit + expect(response).to have_http_status(:ok) + end + + context 'when the member belongs to a group' do + let(:member) { create(:group, :with_members_and_managers).members.first } + + it 'has an :ok HTTP status' do + edit + expect(response).to have_http_status(:ok) + end + end + end + + describe 'PUT /:id' do + subject(:update) { put admin_member_path(member), params: params } + + let(:member) { create(:member, first_name: 'patate') } + let(:params) { {member: {first_name: 'potato'}} } + + it 'updates the user with the given params' do + expect { update }.to change { member.reload.first_name }.from('patate').to('potato') + end + + it 'redirects to the member show page' do + update + expect(response).to redirect_to(admin_member_path(member)) + end + + context 'with invalid params' do + let(:params) { {member: {first_name: nil}} } + + it 'does not update the member' do + expect { update }.not_to(change { member.reload.first_name }) + end + end + end + + describe 'POST /enroll_static_members' do + subject(:enroll_static_members) { post enroll_static_members_admin_members_path } + + it 'launches an EnrollStaticMembersJob worker' do + expect { enroll_static_members }.to have_enqueued_job(EnrollStaticMembersJob) + end + end + + describe 'PUT /remove_static_slots_of_a_member' do + subject(:remove_static_slots_of_a_member) { put remove_static_slots_of_a_member_admin_members_path, params: params } + + let(:params) { {member_id: member.id} } + let(:member) { create(:member_static_slot).member } + + it 'deletes all static slots associated to the given member' do + expect { remove_static_slots_of_a_member }.to change(member.member_static_slots, :count).from(1).to(0) + end + end +end diff --git a/spec/requests/documents_spec.rb b/spec/requests/documents_spec.rb index 89da31af..8a183fe0 100644 --- a/spec/requests/documents_spec.rb +++ b/spec/requests/documents_spec.rb @@ -2,8 +2,8 @@ require 'rails_helper' -RSpec.describe 'Document request', type: :request do - let(:member) { create :member } +RSpec.describe 'Document request' do + let(:member) { create(:member) } describe 'GET index' do subject(:get_documents) { get documents_path } @@ -12,7 +12,7 @@ before { sign_in create :member } it 'renders the view' do - create_list :document, 3, :with_file + create_list(:document, 3) get_documents @@ -22,19 +22,19 @@ context 'when user is not signed ?' do it 'renders the view' do - create_list :document, 3, :with_file, published: true + create_list(:document, 3, published: true) get_documents expect(response).to be_successful end it "doesn't rend the documents with published attribute set to false" do - create_list :document, 3, :with_file, published: true - not_published_document = create :document, :with_file, published: false + create_list(:document, 3, published: true) + not_published_document = create(:document, published: false) get_documents - expect(controller.instance_variable_get('@documents')).not_to include(not_published_document) + expect(controller.instance_variable_get(:@documents)).not_to include(not_published_document) end end end diff --git a/spec/requests/forum_spec.rb b/spec/requests/forum_spec.rb new file mode 100644 index 00000000..cf1d822c --- /dev/null +++ b/spec/requests/forum_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe '/forum' do + subject(:index) { get thredded_path } + + before { sign_in create(:member) } + + it 'has an :ok HTTP status' do + index + expect(response).to have_http_status :ok + end +end diff --git a/spec/models/static_members_recruiter_service_spec.rb b/spec/services/static_members_recruiter_spec.rb similarity index 100% rename from spec/models/static_members_recruiter_service_spec.rb rename to spec/services/static_members_recruiter_spec.rb diff --git a/spec/support/fixtures/test.txt b/spec/support/fixtures/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/spec/features/cash_register_proficiency_checking_spec.rb b/spec/system/cash_register_proficiency_checking_spec.rb similarity index 60% rename from spec/features/cash_register_proficiency_checking_spec.rb rename to spec/system/cash_register_proficiency_checking_spec.rb index e85762fd..3ebd6179 100644 --- a/spec/features/cash_register_proficiency_checking_spec.rb +++ b/spec/system/cash_register_proficiency_checking_spec.rb @@ -2,15 +2,12 @@ require 'rails_helper' -RSpec.describe 'Cash register proficiency checking :', type: :feature do - let(:member) { create :member } - - before { sign_in member } +RSpec.describe 'Cash register proficiency checking :' do + before { sign_in create(:member) } context 'when the cash register proficiency is insufficient, and there is only one slot left on a given time_slot' do - subject(:enroll) do + subject(:enroll_current_user) do visit mission_path(mission.id) - I18n.locale = :fr check "enrollment_time_slots_#{mission.start_date.strftime('%F_%H%M%S_utc')}" click_button I18n.t('main_app.views.missions.show.button_enroll') end @@ -18,23 +15,21 @@ let(:expected_message) do I18n.t('activerecord.errors.models.enrollment.insufficient_cash_register_proficiency') end - let(:mission) { create :mission, genre: 'regulated', cash_register_proficiency_requirement: 'proficient' } + let(:mission) { create(:mission, genre: 'regulated', cash_register_proficiency_requirement: 'proficient') } - it "doesn't enroll", js: true do + it "doesn't enroll the current user", :js do enroll_members_on_mission(3, mission) - enroll + enroll_current_user expect(page).to have_content(expected_message) end end def enroll_members_on_mission(members_count, mission) - members = create_list :member, members_count + members = create_list(:member, members_count) members.each do |member| - create :enrollment, - mission: mission, - member: member + create(:enrollment, mission: mission, member: member) end end end diff --git a/spec/system/document_uploads_at_document_page_section_spec.rb b/spec/system/document_uploads_at_document_page_section_spec.rb new file mode 100644 index 00000000..67827c52 --- /dev/null +++ b/spec/system/document_uploads_at_document_page_section_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'DocumentUploadsAtInfoSections' do + context 'when a regular member accesses the page' do + before { sign_in create :member } + + it 'does not show the document upload form' do + visit infos_path(anchor: 'documents') + + expect(page).not_to have_content('Ajouter un document') + end + + it 'does not show the delete button on a document' do + create(:document) + + visit infos_path(anchor: 'documents') + + expect(page).not_to have_link(I18n.t('main_app.views.application.buttons.destroy')) + end + end + + context 'when an admin uploads a document' do + subject(:fill_in_upload_form) do + attach_file('document_file', Rails.root.join('spec/support/fixtures/erd.pdf')) + click_button 'Ajouter' + end + + before do + sign_in create(:member, :admin) + visit documents_path(anchor: 'documents') + end + + it 'shows the uploaded document on the documents/index#document view' do + fill_in_upload_form + expect(page).to have_content 'erd.pdf' + end + + context 'when javascript is enabled in the browser', :js do + it 'show the uploaded document on the documents/index#document view' do + fill_in_upload_form + expect(page).to have_content 'erd.pdf' + end + end + end + + context 'when an admin deletes a document' do + subject(:submit_document_destruction) do + click_on I18n.t('main_app.views.application.buttons.destroy') + page.driver.browser.switch_to.alert.accept + end + + before do + sign_in create(:member, :admin) + create(:document) + visit documents_path(anchor: 'documents') + end + + it 'deletes the document from documents/index#document view' do + submit_document_destruction + expect(page).not_to have_content 'erd.pdf' + end + + context 'when javascript is enabled in the browser', :js do + it 'deletes the document from from the documents/index#document view' do + submit_document_destruction + expect(page).not_to have_content 'erd.pdf' + end + end + end +end diff --git a/spec/features/member_count_limit_on_missions_spec.rb b/spec/system/member_count_limit_on_missions_spec.rb similarity index 82% rename from spec/features/member_count_limit_on_missions_spec.rb rename to spec/system/member_count_limit_on_missions_spec.rb index a800cfb3..a22576a6 100644 --- a/spec/features/member_count_limit_on_missions_spec.rb +++ b/spec/system/member_count_limit_on_missions_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' -RSpec.describe 'Member count limit on missions :', type: :feature do - let(:member) { create :member } - let(:mission) { create :mission } +RSpec.describe 'Member count limit on missions :' do + let(:member) { create(:member) } + let(:mission) { create(:mission) } before { sign_in member } @@ -19,7 +19,9 @@ expect(mission.reload.members).to include(member) end - it { expect(page).to have_content(I18n.t('enrollments.create.confirm_enroll')) } + it 'shows a confirmation flash message' do + expect(page).to have_content(I18n.t('enrollments.create.confirm_enroll')) + end end context 'when the enrolled Member count has been reached' do @@ -29,7 +31,6 @@ mission.save visit mission_path(mission.id) - I18n.locale = :fr click_button I18n.t('main_app.views.missions.show.button_enroll') end @@ -37,7 +38,7 @@ expect(mission.reload.members).not_to include(member) end - it 'sets a feedback message to the user', js: true do + it 'sets a feedback message to the user', :js do expect(page).to have_content(I18n.t('activerecord.errors.models.enrollment.full_mission')) end end diff --git a/spec/system/members/cash_register_proficiency_spec.rb b/spec/system/members/cash_register_proficiency_spec.rb new file mode 100644 index 00000000..3dd1847f --- /dev/null +++ b/spec/system/members/cash_register_proficiency_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Members cash register proficiency' do + let(:mission) { create(:mission) } + let(:jack) { create(:member, cash_register_proficiency: :proficient) } + + before { sign_in jack } + + context 'when on a mission details page' do + before do + mission.members << jack + visit mission_path(mission.id) + end + + it 'shows enrolled members proficiency' do + expect(page).to have_content( + I18n.t(jack.cash_register_proficiency, + scope: 'activerecord.attributes.member.cash_register_proficiencies') + ) + end + end + + context 'when on the mission index page' do + before do + mission.members << create_list(:member, 3, cash_register_proficiency: :untrained) + visit missions_path + end + + it 'shows missions without proficient members in purple', :js do + expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) + .to eq 'rgba(128, 0, 128, 1)' + end + end +end diff --git a/spec/system/members/member_invitations_spec.rb b/spec/system/members/member_invitations_spec.rb new file mode 100644 index 00000000..1fe4c695 --- /dev/null +++ b/spec/system/members/member_invitations_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'MemberInvitations' do + subject(:fill_email_and_submit) do + visit new_member_invitation_path + fill_in Member.human_attribute_name(:email), with: 'test@test.com' + click_button "Envoyer l'invitation" + end + + let(:super_admin) { create(:member, :super_admin) } + + before do + sign_in create(:member, :super_admin) + end + + it 'sends an invitation email' do + fill_email_and_submit + + expect(Devise.mailer.deliveries.count).to eq 1 + expect(page).to have_content "Un e-mail d'invitation a été envoyé" + end + + it 'creates an unvalidated user', :aggregate_failures do + fill_email_and_submit + + expect(Member.count).to eq 2 + expect(Member.last.email).to eq 'test@test.com' + expect(Member.last.first_name).to be_nil + expect(Member.last.last_name).to be_nil + end + + it 'redirects to the same page when the invitation is sent, to allow quick multiple invitations' do + fill_email_and_submit + expect(page).to have_current_path('/members/invitation/new', ignore_query: true) + end + + context 'when filling an invalid email' do + subject(:fill_invalid_email_and_submit) do + visit new_member_invitation_path + fill_in Member.human_attribute_name(:email), with: 'wrong_email' + click_button "Envoyer l'invitation" + end + + it 'does not send an invitation to an invalid email' do + fill_invalid_email_and_submit + expect(Devise.mailer.deliveries.count).to eq 0 + end + end + + context "when following the invitation mail's link" do + subject(:follow_invitation_link) do + open_email 'test@test.com' + visit_in_email "Accepter l'invitation" + end + + before do + visit new_member_invitation_path + fill_in Member.human_attribute_name(:email), with: 'test@test.com' + click_button "Envoyer l'invitation" + click_link 'Déconnexion' + end + + it 'allows the user to finalize his account creation' do + follow_invitation_link + fill_in 'Prénom', with: 'first_name' + fill_in 'Nom de famille', with: 'last_name' + fill_in 'Mot de passe', with: 'password' + fill_in 'Confirmez votre mot de passe', with: 'password' + click_button 'Confirmer' + + expect(page).to have_content 'Votre compte et votre mot de passe ont été créés. Vous êtes maintenant connecté.' + end + end +end diff --git a/spec/system/mission_events_color_code_spec.rb b/spec/system/mission_events_color_code_spec.rb new file mode 100644 index 00000000..40afce51 --- /dev/null +++ b/spec/system/mission_events_color_code_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Mission events color codes:' do + before { sign_in create(:member) } + + context 'when a delivery is expected at the shop' do + it 'shows a truck icon on the mission event', :js do + create(:mission, delivery_expected: true) + + visit missions_path + + expect(page).to have_css('.fas.fa-truck') + end + end + + context 'when :min_member_count is set' do + let(:mission) { create(:mission, min_member_count: 3) } + + context 'with insufficient enrolled members', :js do + it 'shows the event colored in purple' do + mission.members << create_list(:member, 2) + + visit missions_path + + expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) + .to eq 'rgba(128, 0, 128, 1)' + end + + it 'shows the event in red if no member if enrolled' do + mission + + visit missions_path + + expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) + .to eq 'rgba(255, 0, 0, 1)' + end + end + end + + context 'when :event is set to true' do + it 'shows the event colored in orange', :js do + mission = create(:mission, genre: 'event') + + visit missions_path + + expect(first("a[href='/missions/#{mission.id}']").native.style('background-color')) + .to eq 'rgba(255, 165, 0, 1)' + end + end + + context 'when a member enrolls for a smaller duration than the full mission duration' do + it "shows the member's name in light blue", :js do + mission = create(:mission) + jack = create(:member, first_name: 'Jack') + create(:enrollment, :one_hour, mission: mission, member: jack) + + visit mission_path(mission.id) + + expect(find("#member_#{jack.id}")).to have_css('.bg-info') + end + end +end diff --git a/spec/features/productors_recap_map_spec.rb b/spec/system/productors_recap_map_spec.rb similarity index 84% rename from spec/features/productors_recap_map_spec.rb rename to spec/system/productors_recap_map_spec.rb index ed594a8a..4502234e 100644 --- a/spec/features/productors_recap_map_spec.rb +++ b/spec/system/productors_recap_map_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' -RSpec.describe 'Productors Recap Maps address coordinates auto-search', type: :feature do +RSpec.describe 'Productors Recap Maps address coordinates auto-search' do context 'when a new productor is created,' do - let(:productor) { build :productor } + let(:productor) { build(:productor) } context 'when an address is given,' do it 'fetches coordinates if no coordinates are given' do @@ -28,25 +28,25 @@ end context 'when a productor is updated' do - let(:productor) { create :productor, address: build(:address, :coordinates) } + let(:productor) { create(:productor, address: build(:address, :coordinates)) } before { allow(productor.address).to receive(:assign_coordinates) } context 'when its address is also updated,' do it 'fetches coordinates if no new coordinates are given' do - new_address = attributes_for :address + new_address = attributes_for(:address) productor.update(address_attributes: new_address) expect(productor.address).to have_received(:assign_coordinates) end it 'fetches coordinates if empty coordinates are given' do - new_address = attributes_for :address, coordinates: ['', ''] + new_address = attributes_for(:address, coordinates: ['', '']) productor.update(address_attributes: new_address) expect(productor.address).to have_received(:assign_coordinates) end it 'does not fetch new coordinates if new coordinates are given' do - new_address = attributes_for :address, :coordinates + new_address = attributes_for(:address, :coordinates) productor.update(address_attributes: new_address) expect(productor.address).not_to have_received(:assign_coordinates) end @@ -61,10 +61,10 @@ end context "when an address that doesn't belong to a productor is saved," do - let(:address) { build :address, coordinates: nil } + let(:address) { build(:address, coordinates: nil) } it "doesn't launch Address#assign_coordinates for a Member" do - member = build :member + member = build(:member) member.address = address allow(member.address).to receive(:assign_coordinates) @@ -74,7 +74,7 @@ end it "doesn't launch Address#assign_coordinates for a Mission" do - mission = build :mission + mission = build(:mission) mission.addresses << address allow(mission.addresses[0]).to receive(:assign_coordinates) diff --git a/spec/features/time_slots_autocheck_spec.rb b/spec/system/time_slots_autocheck_spec.rb similarity index 68% rename from spec/features/time_slots_autocheck_spec.rb rename to spec/system/time_slots_autocheck_spec.rb index 11de83b6..7426b7ae 100644 --- a/spec/features/time_slots_autocheck_spec.rb +++ b/spec/system/time_slots_autocheck_spec.rb @@ -2,15 +2,15 @@ require 'rails_helper' -RSpec.describe 'Autocheck checkboxes in enrollments forms', type: :feature do - let(:member) { create :member } - let(:mission) { create :mission, genre: 'regulated' } +RSpec.describe 'Autocheck checkboxes in enrollments forms' do + let(:member) { create(:member) } + let(:mission) { create(:mission, genre: 'regulated') } before { sign_in member } context 'when the current member have already taken a timeslot in mission' do it 'autocheck the checkboxes of this timeslots in quick enrollment form' do - create :enrollment, member: member, mission: mission + create(:enrollment, member: member, mission: mission) visit mission_path(mission.id) @@ -19,7 +19,7 @@ end it 'autocheck the checkboxes of this timeslots in enrollment fields' do - create :enrollment, member: member, mission: mission + create(:enrollment, member: member, mission: mission) visit edit_mission_path(mission.id) diff --git a/spec/features/track_members_worked_hours_spec.rb b/spec/system/track_members_worked_hours_spec.rb similarity index 77% rename from spec/features/track_members_worked_hours_spec.rb rename to spec/system/track_members_worked_hours_spec.rb index c8c8fc8a..54155917 100644 --- a/spec/features/track_members_worked_hours_spec.rb +++ b/spec/system/track_members_worked_hours_spec.rb @@ -2,8 +2,8 @@ require 'rails_helper' -RSpec.describe 'Members worked hours tracking', type: :feature do - let(:member) { create :member } +RSpec.describe 'Members worked hours tracking' do + let(:member) { create(:member) } context 'when a member goes on his/her profile page,' do before do @@ -12,7 +12,7 @@ end it 'shows the number of worked hours this month' do - enrollment = create :enrollment, member: member + enrollment = create(:enrollment, member: member) visit edit_member_path member @@ -20,9 +20,9 @@ end it 'shows the number of worked hours during last month' do - last_month_enrollment = create :enrollment, + last_month_enrollment = create(:enrollment, member: member, - mission: create(:mission, start_date: 1.month.ago) + mission: create(:mission, start_date: 1.month.ago)) visit edit_member_path member @@ -30,9 +30,9 @@ end it 'shows the number of worked hours during last last month' do - last_last_month_enrollment = create :enrollment, + last_last_month_enrollment = create(:enrollment, member: member, - mission: create(:mission, start_date: 2.months.ago) + mission: create(:mission, start_date: 2.months.ago)) visit edit_member_path member @@ -54,7 +54,7 @@ visit admin_members_path - expect(page).to have_text "#{I18n.localize(current_time, format: :only_month)} : 3.0" + expect(page).to have_text "#{I18n.l(current_time, format: :only_month)} : 3.0" end it 'shows the number of worked hours during last month' do @@ -62,7 +62,7 @@ visit admin_members_path - expect(page).to have_text "#{I18n.localize(current_time - 1.month, format: :only_month)} : 3.0" + expect(page).to have_text "#{I18n.l(current_time - 1.month, format: :only_month)} : 3.0" end it 'shows the number of worked hours during last last month' do @@ -70,7 +70,7 @@ visit admin_members_path - expect(page).to have_text "#{I18n.localize(current_time - 2.months, format: :only_month)} : 3.0" + expect(page).to have_text "#{I18n.l(current_time - 2.months, format: :only_month)} : 3.0" end end @@ -84,7 +84,7 @@ def create_enrollments_for_the_last_three_months slot = 1.week.ago.clamp(current_time.at_beginning_of_month, current_time) 3.times do mission = create(:mission, start_date: slot.to_datetime, due_date: slot.to_datetime + 3.hours) - create :enrollment, mission: mission + create(:enrollment, mission: mission) slot -= 1.month end end