From 750c82a80f45f4c3d097aec55e38b65545cdde4a Mon Sep 17 00:00:00 2001 From: zmagod Date: Thu, 6 Jul 2017 09:07:05 +0200 Subject: [PATCH] setup testing tools [fixes SCI-1400] --- .gitignore | 3 + .rspec | 2 + .simplecov | 2 + Gemfile | 23 +- Gemfile.lock | 92 +- app/models/user.rb | 2 +- config/cucumber.yml | 8 + config/database.yml | 5 +- config/environments/test.rb | 3 + features/sign_in_sign_up/sign_up.feature | 29 + .../step_definitions/.gitkeep | 0 features/step_definitions/shared_steps.rb | 19 + features/step_definitions/users_steps.rb | 17 + features/support/env.rb | 68 + lib/tasks/cucumber.rake | 76 + lib/tasks/data.rake | 77 - lib/tasks/db_fake_data.rake | 1340 ----------------- lib/tasks/db_users.rake | 132 -- lib/tasks/exportable_items.rake | 9 - lib/tasks/i18n_missing_keys.rake | 97 -- lib/tasks/notifications.rake | 23 - lib/tasks/paperclip.rake | 53 - lib/tasks/sign_up_constraint.rake | 32 - lib/tasks/tiny_mce_asset.rake | 9 - lib/tasks/web_stats.rake | 65 - script/cucumber | 10 + spec/factories/users.rb | 9 + spec/models/user_spec.rb | 88 ++ spec/rails_helper.rb | 72 + spec/spec_helper.rb | 99 ++ 30 files changed, 617 insertions(+), 1847 deletions(-) create mode 100644 .rspec create mode 100644 .simplecov create mode 100644 config/cucumber.yml create mode 100644 features/sign_in_sign_up/sign_up.feature rename lib/tasks/.keep => features/step_definitions/.gitkeep (100%) create mode 100644 features/step_definitions/shared_steps.rb create mode 100644 features/step_definitions/users_steps.rb create mode 100644 features/support/env.rb create mode 100644 lib/tasks/cucumber.rake delete mode 100644 lib/tasks/data.rake delete mode 100644 lib/tasks/db_fake_data.rake delete mode 100644 lib/tasks/db_users.rake delete mode 100644 lib/tasks/exportable_items.rake delete mode 100644 lib/tasks/i18n_missing_keys.rake delete mode 100644 lib/tasks/notifications.rake delete mode 100644 lib/tasks/paperclip.rake delete mode 100644 lib/tasks/sign_up_constraint.rake delete mode 100644 lib/tasks/tiny_mce_asset.rake delete mode 100644 lib/tasks/web_stats.rake create mode 100755 script/cucumber create mode 100644 spec/factories/users.rb create mode 100644 spec/models/user_spec.rb create mode 100644 spec/rails_helper.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore index 3a6974583..873d76a64 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ rubocop_cache # Ignore ESLint local instalation node_modules + +# Ignore Coverage output +coverage diff --git a/.rspec b/.rspec new file mode 100644 index 000000000..5be63fcb0 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--require spec_helper +--format documentation diff --git a/.simplecov b/.simplecov new file mode 100644 index 000000000..8ac63351f --- /dev/null +++ b/.simplecov @@ -0,0 +1,2 @@ +require 'simplecov' +SimpleCov.start 'rails' diff --git a/Gemfile b/Gemfile index 8a86bfb71..e33b7df19 100644 --- a/Gemfile +++ b/Gemfile @@ -78,6 +78,11 @@ gem 'devise_security_extension', group :development, :test do gem 'listen', '~> 3.0' gem 'byebug' + gem 'pry' + gem 'pry-byebug' + gem 'pry-rails' + gem 'factory_girl_rails' + gem 'rspec-rails' gem 'better_errors' gem 'binding_of_caller' gem 'awesome_print' @@ -86,17 +91,23 @@ group :development, :test do gem 'starscope', require: false end +group :test do + gem 'minitest-reporters' + gem "shoulda-context" + gem 'shoulda-matchers' + gem 'cucumber-rails', require: false + gem 'database_cleaner' + gem 'capybara' + gem 'poltergeist' + gem 'phantomjs', :require => 'phantomjs/poltergeist' + gem 'simplecov', require: false +end + group :production do gem 'puma' gem 'rails_12factor' gem 'skylight' end -group :test do - gem 'minitest-reporters', '~> 1.1' - gem "shoulda-context" - gem "shoulda-matchers", ">= 3.0.1" -end - # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index 0ae455d1b..4b26a7fc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,6 +61,8 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) ajax-datatables-rails (0.3.1) railties (>= 3.1) ansi (1.5.0) @@ -101,7 +103,15 @@ GEM bootstrap_form (2.7.0) builder (3.2.3) byebug (9.0.6) + capybara (2.14.4) + addressable + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) climate_control (0.2.0) + cliver (0.3.2) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.1.1) @@ -115,6 +125,24 @@ GEM commit_param_routing (0.0.1) concurrent-ruby (1.0.5) crass (1.0.2) + cucumber (2.4.0) + builder (>= 2.1.2) + cucumber-core (~> 1.5.0) + cucumber-wire (~> 0.0.1) + diff-lcs (>= 1.1.3) + gherkin (~> 4.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (1.5.0) + gherkin (~> 4.0) + cucumber-rails (1.5.0) + capybara (>= 1.1.2, < 3) + cucumber (>= 1.3.8, < 4) + mime-types (>= 1.17, < 4) + nokogiri (~> 1.5) + railties (>= 4, < 5.2) + cucumber-wire (0.0.1) + database_cleaner (1.6.1) debug_inspector (0.0.3) deface (1.2.0) nokogiri (~> 1.6) @@ -140,9 +168,16 @@ GEM devise_invitable (1.7.2) actionmailer (>= 4.1.0) devise (>= 4.0.0) + diff-lcs (1.3) + docile (1.1.5) erubi (1.6.0) erubis (2.7.0) execjs (2.7.0) + factory_girl (4.8.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) + railties (>= 3.0.0) faker (1.7.3) i18n (~> 0.5) ffi (1.9.18) @@ -150,6 +185,7 @@ GEM thor (~> 0.14) font-awesome-rails (4.7.0.2) railties (>= 3.2, < 5.2) + gherkin (4.1.3) globalid (0.4.0) activesupport (>= 4.2.0) hammerjs-rails (2.0.4) @@ -210,6 +246,7 @@ GEM momentjs-rails (2.17.1) railties (>= 3.1) multi_json (1.12.1) + multi_test (0.1.2) nested_form_fields (0.8) coffee-rails (>= 3.2.1) jquery-rails @@ -232,8 +269,23 @@ GEM parser (2.4.0.0) ast (~> 2.2) pg (0.21.0) + phantomjs (2.1.1.0) + poltergeist (1.15.0) + capybara (~> 2.1) + cliver (~> 0.3.1) + websocket-driver (>= 0.2.0) polyglot (0.3.5) powerpack (0.1.1) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.4.2) + byebug (~> 9.0) + pry (~> 0.10) + pry-rails (0.3.6) + pry (>= 0.10.4) + public_suffix (2.0.5) puma (3.9.1) rack (2.0.3) rack-test (0.6.3) @@ -288,6 +340,23 @@ GEM roo (2.1.1) nokogiri (~> 1) rubyzip (~> 1.1, < 2.0.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-rails (3.6.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) rubocop (0.49.1) parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) @@ -324,8 +393,14 @@ GEM actionmailer (>= 3.2.6, < 6) actionpack (>= 3.2.6, < 6) devise (>= 3.2, < 6) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.1) skylight (1.3.1) activesupport (>= 3.0.0) + slop (3.6.0) sourcemap (0.1.1) spinjs-rails (1.4) rails (>= 3.1) @@ -365,6 +440,8 @@ GEM websocket-extensions (0.1.2) wicked_pdf (1.1.0) wkhtmltopdf-heroku (2.12.4.0) + xpath (2.1.0) + nokogiri (~> 1.3) yomu (0.2.4) json (~> 1.8) mime-types (~> 1.23) @@ -388,7 +465,10 @@ DEPENDENCIES bootstrap3-datetimepicker-rails (~> 4.15.35) bootstrap_form byebug + capybara commit_param_routing + cucumber-rails + database_cleaner deface (~> 1.0) delayed_job_active_record delayed_paperclip @@ -396,6 +476,7 @@ DEPENDENCIES devise-async devise_invitable devise_security_extension! + factory_girl_rails faker figaro font-awesome-rails (~> 4.7.0.2) @@ -410,13 +491,18 @@ DEPENDENCIES kaminari listen (~> 3.0) logging (~> 2.0.0) - minitest-reporters (~> 1.1) + minitest-reporters momentjs-rails (~> 2.17.1) nested_form_fields newrelic_rpm nokogiri paperclip (~> 5.1) pg + phantomjs + poltergeist + pry + pry-byebug + pry-rails puma rails (= 5.1.1) rails_12factor @@ -425,6 +511,7 @@ DEPENDENCIES remotipart (~> 1.2) rgl roo (~> 2.1.0) + rspec-rails rubocop ruby-graphviz (~> 1.2) rubyzip @@ -433,9 +520,10 @@ DEPENDENCIES scss_lint sdoc (~> 0.4.0) shoulda-context - shoulda-matchers (>= 3.0.1) + shoulda-matchers silencer simple_token_authentication (~> 1.15.1) + simplecov skylight sneaky-save! spinjs-rails diff --git a/app/models/user.rb b/app/models/user.rb index 78c487fa7..c7c5fb3dd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -197,7 +197,7 @@ class User < ApplicationRecord end def name=(name) - full_name = name + self.full_name = name end # Search all active users for username & email. Can diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 000000000..19b288df9 --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" +%> +default: <%= std_opts %> features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/database.yml b/config/database.yml index 663aec731..c11677eac 100644 --- a/config/database.yml +++ b/config/database.yml @@ -58,7 +58,7 @@ development: # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. -test: +test: &test <<: *default database: scinote_test @@ -83,3 +83,6 @@ test: # production: url: <%= ENV['DATABASE_URL'] %> + +cucumber: + <<: *test diff --git a/config/environments/test.rb b/config/environments/test.rb index 2fee112e6..8562e03ce 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -90,4 +90,7 @@ Rails.application.configure do # Enable user registrations config.x.enable_user_registration = true + + # prevents compile assets + config.assets.compile = false end diff --git a/features/sign_in_sign_up/sign_up.feature b/features/sign_in_sign_up/sign_up.feature new file mode 100644 index 000000000..51c291e36 --- /dev/null +++ b/features/sign_in_sign_up/sign_up.feature @@ -0,0 +1,29 @@ +Feature: Sign up + As a new User + I want to create a new account + So that I can use sciNote + + Background: + Given the following users are registered + | email | initials | + | night.slarker@gmail.com | NS | + | tusk@gmail.com | TU | + | juggernaut@gmail.com | JU | + + @javascript + Scenario: Sign up for an existing user + Given I visit the sign up page + Then I fill the sign up form with + | Full name | Email | Password | Password confirmation | Team name | + | Magnus | tusk@gmail.com | asdf1234 | asdf1234 | SpliceGirls | + And I click 'Sign up' button + Then I should see 'has already been taken' + + Scenario: Sign up for an non-existent user + Given I visit the sign up page + Then I fill the sign up form with + | Full name | Email | Password | Password confirmation | Team name | + | Magnus | magnus@gmail.com | asdf1234 | asdf1234 | SpliceGirls | + And I click 'Sign up' button + Then I should see 'SpliceGirls' + And I should be on homepage diff --git a/lib/tasks/.keep b/features/step_definitions/.gitkeep similarity index 100% rename from lib/tasks/.keep rename to features/step_definitions/.gitkeep diff --git a/features/step_definitions/shared_steps.rb b/features/step_definitions/shared_steps.rb new file mode 100644 index 000000000..a78c347e0 --- /dev/null +++ b/features/step_definitions/shared_steps.rb @@ -0,0 +1,19 @@ +When(/^I click '(.+)' button$/) do |button| + click_on button +end + +Then(/^I should be redirected to the homepage$/) do + current_path.should =~ /^\/$/ +end + +When(/^I click '(.+)' link$/) do |link| + click_link link +end + +Then(/^I should see '(.+)'$/) do |text| + expect(page).to have_content(text) +end + +Then(/^I should be on homepage$/) do + expect(page).to have_current_path(root_path) +end diff --git a/features/step_definitions/users_steps.rb b/features/step_definitions/users_steps.rb new file mode 100644 index 000000000..abdf6647f --- /dev/null +++ b/features/step_definitions/users_steps.rb @@ -0,0 +1,17 @@ +Given(/^the following users are registered$/) do |table| + table.hashes.each do |hash| + FactoryGirl.create(:user, hash) + end +end + +Given(/^I visit the sign up page$/) do + visit new_user_registration_path +end + +Then(/^I fill the sign up form with$/) do |table| + table.hashes.each do |hash| + hash.each do |k, v| + fill_in k, with: v + end + end +end diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 000000000..ff69adcb4 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,68 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' +require 'phantomjs' +require 'capybara/poltergeist' +require 'simplecov' + +Capybara.register_driver :poltergeist do |app| + Capybara::Poltergeist::Driver.new(app, + js_errors: true, + phantomjs: Phantomjs.path) +end + +Capybara.javascript_driver = :poltergeist +Capybara.default_max_wait_time = 5 +# Capybara defaults to CSS3 selectors rather than XPath. +# If you'd prefer to use XPath, just uncomment this line and adjust any +# selectors in your step definitions to use the XPath syntax. +# Capybara.default_selector = :xpath + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# # { :except => [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 000000000..23d9b3eb4 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,76 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + + task :annotations_setup do + Rails.application.configure do + if config.respond_to?(:annotations) + config.annotations.directories << 'features' + config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + end + end + end + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon. + task 'test:prepare' do + end + + task :stats => 'cucumber:statsetup' + + task :notes => 'cucumber:annotations_setup' +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/lib/tasks/data.rake b/lib/tasks/data.rake deleted file mode 100644 index 44be9f9b8..000000000 --- a/lib/tasks/data.rake +++ /dev/null @@ -1,77 +0,0 @@ -namespace :data do - Rails.logger = Logger.new(STDOUT) - - desc "Remove expired temporary files" - task clean_temp_files: :environment do - Rails.logger.info "Cleaning temporary files older than 3 days" - TempFile.where("created_at < ?", 3.days.ago).each do |tmp_file| - - TempFile.transaction do - begin - tmp_file.destroy! - rescue Exception => e - Rails.logger.error "Failed to destroy temporary file #{tmp_file.id}: #{e}" - raise ActiveRecord::Rollback - else - Rails.logger.info "Temporary file ##{tmp_file.id} removed" - end - end - end - end - - def destroy_users(users) - users.each do |user| - - User.transaction do - begin - # Destroy user_team, and possibly team - if user.teams.count > 0 - oids = user.teams.pluck(:id) - oids.each do |oid| - team = Team.find(oid) - user_team = user.user_teams.where(team: team).first - destroy_team = (team.users.count == 1 && team.created_by == user) - if !user_team.destroy(nil) then - raise Exception - end - team.destroy! if destroy_team - end - end - - user.destroy! - rescue Exception => e - Rails.logger.error "Failed to destroy unconfirmed user #{user.id}: #{e}" - raise ActiveRecord::Rollback - else - Rails.logger.info "Unconfirmed user ##{user.id} removed" - end - end - end - end - - desc "Remove unconfirmed user accounts" - task clean_unconfirmed_users: :environment do - Rails.logger.info "Cleaning unconfirmed users" - - # First, remove the users who signed up by themselves - users = User - .where(confirmed_at: nil) - .where.not(confirmation_token: nil) - .where(invitation_token: nil) - .where("created_at < ?", Devise.confirm_within.ago) - destroy_users(users) - - # Now, remove users who were invited - users = User - .where(confirmed_at: nil) - .where(invitation_accepted_at: nil) - .where(confirmation_token: nil) - .where.not(invitation_token: nil) - .where("created_at < ?", Devise.invite_for.ago) - destroy_users(users) - end - - desc "Remove temporary and obsolete data" - task clean: [:environment, :clean_temp_files, :clean_unconfirmed_users] - -end diff --git a/lib/tasks/db_fake_data.rake b/lib/tasks/db_fake_data.rake deleted file mode 100644 index 28a616168..000000000 --- a/lib/tasks/db_fake_data.rake +++ /dev/null @@ -1,1340 +0,0 @@ -require "#{Rails.root}/app/utilities/users_generator" -require "#{Rails.root}/app/utilities/renaming_util" -require "#{Rails.root}/test/helpers/fake_test_helper" -include UsersGenerator -include RenamingUtil -include FakeTestHelper - -namespace :db do - NR_TEAMS = 4 - NR_USERS = 100 - NR_SAMPLE_TYPES = 20 - NR_SAMPLE_GROUPS = 20 - NR_CUSTOM_FIELDS = 20 - NR_SAMPLES = 100 - NR_PROTOCOLS = 20 - NR_PROTOCOL_KEYWORDS = 20 - NR_PROJECTS = 3 - NR_EXPERIMENTS = 4 - NR_MODULE_GROUPS = 4 - NR_MODULES = 4 - NR_STEPS = 3 - NR_RESULTS = 4 - NR_REPORTS = 4 - NR_COMMENTS = 10 - RATIO_USER_TEAMS = 0.5 - NR_MAX_USER_TEAMS = 20 - RATIO_CUSTOM_FIELDS = 0.7 - RATIO_SAMPLE_CUSTOM_FIELDS = 0.6 - RATIO_PROTOCOL_KEYWORDS = 0.3 - THRESHOLD_PROTOCOL_IN_MODULE_LINKED = 0.5 - THRESHOLD_PROTOCOL_PUBLIC = 0.6 - THRESHOLD_PROTOCOL_ARCHIVED = 0.2 - NR_MAX_USER_PROJECTS = 15 - RATIO_USER_PROJECTS = 0.5 - RATIO_COMMENTS = 0.7 - NR_MAX_USER_MODULES = 15 - RATIO_USER_MODULES = 0.5 - NR_MAX_SAMPLE_MODULES = 10 - RATIO_SAMPLE_MODULES = 0.7 - RATIO_MODULE_MODULE_GROUPS = 0.8 - RATIO_EDGES = 0.7 - RATIO_STEP_COMPLETED = 0.5 - NR_MAX_STEP_ATTACHMENTS = 2 - RATIO_STEP_ATTACHMENTS = 0.2 - NR_MAX_CHECKLIST_ITEMS = 20 - RATIO_CHECKLIST_ITEM = 0.2 - RATIO_CHECKLIST_ITEM_CHECKED = 0.5 - RATIO_RESULT_ARCHIVED = 0.2 - RATIO_REPORT_ELEMENTS = 0.75 - - THRESHOLD_ARCHIVED = 0.2 - THRESHOLD_RESTORED = 0.9 - - MIN_FILE_SIZE = 0.01 - MAX_FILE_SIZE = 0.1 - - desc "Drops the database, sets it up and inserts fake data for " + - "the current RAILS_ENV. WARNING: THIS WILL ERASE ALL " + - "CURRENT DATA IN THE DATABASE." - task :fake => :environment do - Rake::Task["db:drop"].reenable - Rake::Task["db:drop"].invoke - Rake::Task["db:setup"].reenable - Rake::Task["db:setup"].invoke - Rake::Task["db:fake:generate"].reenable - Rake::Task["db:fake:generate"].invoke - end - - namespace :fake do - desc "Generates fake data & inserts it into database for the " + - "current RAILS_ENV." - task :generate => :environment do - require 'rgl/base' - require 'rgl/adjacency' - require 'rgl/topsort' - - puts "Verbose? (Y/n)" - res = $stdin.gets.to_s.downcase.strip - unless res.in?(["", "y", "n"]) then - puts "Invalid parameter, exiting" - return - end - verbose = res.in?(["", "y"]) - - puts "Simple seeding? (Y/n)" - res = $stdin.gets.to_s.downcase.strip - unless res.in?(["", "y", "n"]) then - puts "Invalid parameter, exiting" - return - end - simple = res.in?(["", "y"]) - - if simple - puts "Choose the size of generated dataset(T - tiny, " + - "s - small, m - medium, l - large, h -huge)" - res = $stdin.gets.to_s.downcase.strip - unless res.in?(["", "t", "s", "m", "l", "h"]) then - puts "Invalid parameter, exiting" - return - end - - case res - when "", "t" - factor = 0.5 - when "s" - factor = 1 - when "m" - factor = 5 - when "l" - factor = 20 - when "h" - factor = 100 - end - - nr_team = NR_TEAMS * factor - nr_users = NR_USERS * factor - nr_sample_types = NR_SAMPLE_TYPES * factor - nr_sample_groups = NR_SAMPLE_GROUPS * factor - nr_custom_fields = NR_CUSTOM_FIELDS * factor - nr_samples = NR_SAMPLES * factor - nr_protocols = NR_PROTOCOLS * factor - nr_protocol_keywords = NR_PROTOCOL_KEYWORDS * factor - nr_projects = NR_PROJECTS * factor - nr_experiments = NR_EXPERIMENTS * factor - nr_module_groups = NR_MODULE_GROUPS * factor - nr_modules = NR_MODULES * factor - nr_steps = NR_STEPS * factor - nr_results = NR_RESULTS * factor - nr_reports = NR_REPORTS * factor - nr_comments = NR_COMMENTS * factor - else - puts "Type in the number of seeded teams" - nr_team = $stdin.gets.to_i - puts "Type in the number of seeded users" - nr_users = $stdin.gets.to_i - puts "Type in the number of seeded sample types " + - "for each team" - nr_sample_types = $stdin.gets.to_i - puts "Type in the number of seeded sample groups for " + - "each team" - nr_sample_groups = $stdin.gets.to_i - puts "Type in the max. number of seeded custom fields " + - "for each team" - nr_custom_fields = $stdin.gets.to_i - puts "Type in the number of seeded samples for each team" - nr_samples = $stdin.gets.to_i - puts "Type in the number of seeded protocols for each team" - nr_protocols = $stdin.gets.to_i - puts "Type in the number of seeded protocol keywords for each team" - nr_protocol_keywords = $stdin.gets.to_i - puts "Type in the number of seeded projects for each team" - nr_projects = $stdin.gets.to_i - puts "Type in the number of seeded experiments for each project" - nr_experiments = $stdin.gets.to_i - puts "Type in the number of seeded workflows for each experiment" - nr_module_groups = $stdin.gets.to_i - puts "Type in the number of seeded modules for each experiment" - nr_modules = $stdin.gets.to_i - puts "Type in the number of seeded steps for each module" - nr_steps = $stdin.gets.to_i - puts "Type in the number of seeded results for each module" - nr_results = $stdin.gets.to_i - puts "Type in the number of seeded reports for each project" - nr_reports = $stdin.gets.to_i - puts "Type in the max. number of seeded comments for each " + - "commentable item" - nr_comments = $stdin.gets.to_i - end - - begin - ActiveRecord::Base.transaction do - - puts "Generating fake teams..." - taken_team_names = [] - for _ in 1..nr_team - begin - name = Faker::University.name - end while name.in? taken_team_names - taken_team_names << name - - Team.create( - name: name, - description: rand >= 0.7 ? Faker::Lorem.sentence : nil - ) - end - - all_teams = Team.all - - puts "Generating fake users..." - taken_emails = [] - for _ in 1..nr_users - begin - if rand >= 0.8 - name = generate_got_name - else - name = Faker::Name.name - end - email_name = name.downcase.remove(".").split(" ").join(".") - password = Faker::Internet.password(10, 20) - email = Faker::Internet.free_email(email_name) - end while email.in? taken_emails - taken_emails << email - - user = create_user( - name, - email, - password, - true, - nil, - [] - ) - user.update( - confirmed_at: Faker::Date.backward(30), - ) - if verbose then - puts " Generated user #{name} (email: #{email}, " + - "password: #{password})" - end - - # Randomly assign user to teams - taken_team_ids = [] - for _ in 1..[NR_MAX_USER_TEAMS, all_teams.count].min - if rand <= RATIO_USER_TEAMS then - begin - team = pluck_random(all_teams) - end while team.id.in? taken_team_ids - taken_team_ids << team.id - - UserTeam.create( - user: user, - team: team, - role: rand(0..2) - ) - end - end - end - - puts "Generating fake sample types..." - all_teams.find_each do |team| - for _ in 1..nr_sample_types - SampleType.create( - name: Faker::Commerce.department(4), - team: team - ) - end - end - - puts "Generating fake sample groups..." - all_teams.find_each do |team| - for _ in 1..nr_sample_groups - SampleGroup.create( - name: Faker::Commerce.color, - team: team, - color: generate_color - ) - end - end - - puts "Generating fake custom fields..." - all_teams.find_each do |team| - for _ in 1..nr_custom_fields - if rand <= RATIO_CUSTOM_FIELDS then - CustomField.create( - name: Faker::Team.state, - team: team, - user: pluck_random(team.users) - ) - end - end - end - - puts "Generating fake samples..." - all_teams.find_each do |team| - for _ in 1..nr_samples - sample = Sample.create( - name: Faker::Book.title, - team: team, - user: pluck_random(team.users), - sample_type: pluck_random(team.sample_types), - sample_group: pluck_random(team.sample_groups) - ) - - # Add some custom fields to sample - team.custom_fields.find_each do |cf| - if rand <= RATIO_SAMPLE_CUSTOM_FIELDS then - SampleCustomField.create( - sample: sample, - custom_field: cf, - value: Faker::Team.state - ) - end - end - end - end - - puts "Generating fake protocol keywords" - all_teams.find_each do |team| - taken_kw_names = [] - for _ in 1..nr_protocol_keywords - begin - name = Faker::Book.genre - end while name.in? taken_kw_names - taken_kw_names << name - ProtocolKeyword.create( - team: team, - name: name - ) - end - end - - puts "Generating fake repository protocols..." - all_teams.find_each do |team| - for _ in 1..nr_protocols - protocol = generate_fake_protocol(team, - nil, - nr_steps, - nr_comments) - - if verbose then - puts " Generated protocol #{protocol.name}" - end - end - end - - puts "Generating fake projects..." - all_teams.find_each do |team| - taken_project_names = [] - for _ in 1..nr_projects - begin - name = Faker::Company.name[0..29] - end while name.in? taken_project_names - taken_project_names << name - - author = pluck_random(team.users) - created_at = Faker::Time.backward(500) - last_modified_by = pluck_random(team.users) - archived_by = pluck_random(team.users) - archived_on = Faker::Time.between(created_at, DateTime.now) - restored_by = pluck_random(team.users) - restored_on = Faker::Time.between(archived_on, DateTime.now) - status = random_status - - project = Project.create( - visibility: rand(0..1), - name: name, - due_date: nil, - team: team, - created_by: author, - created_at: created_at, - last_modified_by: last_modified_by, - archived: status == :archived, - archived_by: status.in?([:archived, :restored]) ? - archived_by : nil, - archived_on: status.in?([:archived, :restored]) ? - archived_on : nil, - restored_by: status == :restored ? restored_by : nil, - restored_on: status == :restored ? restored_on : nil - ) - # Automatically assign project author onto project - UserProject.create( - user: author, - project: project, - role: 0, - created_at: created_at - ) - - # Activities - Activity.create( - type_of: :create_project, - user: author, - project: project, - message: I18n.t( - "activities.create_project", - user: author.full_name, - project: project.name - ), - created_at: created_at - ) - if status.in?([:archived, :restored]) then - Activity.create( - type_of: :archive_project, - user: archived_by, - project: project, - message: I18n.t( - "activities.archive_project", - user: archived_by.full_name, - project: project.name - ), - created_at: archived_on - ) - end - if status == :restored then - Activity.create( - type_of: :restore_project, - user: restored_by, - project: project, - message: I18n.t( - "activities.restore_project", - user: restored_by.full_name, - project: project.name - ), - created_at: restored_on - ) - end - - # Assign users onto the project - taken_user_ids = [] - for _ in 2..[NR_MAX_USER_PROJECTS, team.users.count].min - if rand <= RATIO_USER_PROJECTS - begin - user = pluck_random(team.users) - end while user.id.in? taken_user_ids - taken_user_ids << user.id - - assigned_on = Faker::Time.backward(500) - assigned_by = pluck_random(project.users) - up = UserProject.create( - user: user, - project: project, - role: rand(0..3), - created_at: assigned_on, - assigned_by: assigned_by - ) - Activity.create( - type_of: :assign_user_to_project, - user: assigned_by, - project: project, - message: I18n.t( - "activities.assign_user_to_project", - assigned_user: user.full_name, - role: up.role_str, - project: project.name, - assigned_by_user: assigned_by.full_name - ), - created_at: assigned_on - ) - end - end - - # Add some comments - for _ in 1..nr_comments - generate_fake_project_comment(project) if rand <= RATIO_COMMENTS - end - end - end - - puts "Generating fake experiments..." - Project.find_each do |project| - for _ in 1..nr_experiments - status = random_status - created_at = Faker::Time.backward(500) - archived_by = pluck_random(project.users) - archived_on = Faker::Time.between(created_at, DateTime.now) - restored_by = pluck_random(project.users) - restored_on = Faker::Time.between(archived_on, DateTime.now) - - author = pluck_random(project.users) - Experiment.create( - name: Faker::Hacker.noun, - description: Faker::Hipster.sentence, - project: project, - created_at: created_at, - created_by: author, - last_modified_by: author, - archived: status.in?([:active, :restored]), - archived_on: status.in?([:archived, :restored]) ? - archived_on : nil, - archived_by: status.in?([:archived, :restored]) ? - archived_by : nil, - restored_on: status == :restored ? restored_on : nil, - restored_by: status == :restored ? restored_by : nil - ) - end - end - - puts "Generating fake workflows..." - Experiment.find_each do |experiment| - for _ in 1..nr_module_groups - MyModuleGroup.create( - name: Faker::Hacker.noun, - experiment: experiment - ) - end - end - - puts "Generating fake modules..." - total_experiment = Experiment.count - Experiment.find_each.with_index do |experiment, i| - if verbose then - puts " Generating modules for experiment #{experiment.name} " + - "(#{i + 1} of #{total_experiment})..." - end - project = experiment.project - taken_pos = [] - for _ in 1..nr_modules - begin - x = rand(0..nr_modules) * 32 - y = rand(0..nr_modules) * 16 - end while [x, y].in? taken_pos - taken_pos << [x, y] - - status = random_status - author = pluck_random(team.users) - created_at = Faker::Time.backward(500) - archived_by = pluck_random(team.users) - archived_on = Faker::Time.between(created_at, DateTime.now) - restored_by = pluck_random(team.users) - restored_on = Faker::Time.between(archived_on, DateTime.now) - - my_module = MyModule.create( - name: Faker::Hacker.verb, - created_by: author, - created_at: created_at, - due_date: rand <= 0.5 ? - Faker::Time.forward(500) : nil, - description: rand <= 0.5 ? - Faker::Hacker.say_something_smart : nil, - x: x, - y: y, - experiment: experiment, - my_module_group: status == :archived ? - nil : - (rand <= RATIO_MODULE_MODULE_GROUPS ? - pluck_random(experiment.my_module_groups) : nil - ), - archived: status == :archived, - archived_on: status.in?([:archived, :restored]) ? - archived_on : nil, - archived_by: status.in?([:archived, :restored]) ? - archived_by : nil, - restored_on: status == :restored ? restored_on : nil, - restored_by: status == :restored ? restored_by : nil - ) - - # Activities - Activity.create( - type_of: :create_module, - user: author, - project: my_module.experiment.project, - my_module: my_module, - message: I18n.t( - "activities.create_module", - user: author.full_name, - module: my_module.name - ), - created_at: created_at - ) - if status.in?([:archived, :restored]) then - Activity.create( - type_of: :archive_module, - user: archived_by, - project: my_module.experiment.project, - my_module: my_module, - message: I18n.t( - "activities.archive_module", - user: archived_by.full_name, - module: my_module.name - ), - created_at: archived_on - ) - end - if status == :restored then - Activity.create( - type_of: :restore_module, - user: restored_by, - project: my_module.experiment.project, - my_module: my_module, - message: I18n.t( - "activities.restore_module", - user: restored_by.full_name, - module: my_module.name - ), - created_at: restored_on - ) - end - - if verbose then - puts " Generated module #{my_module.name}" - end - - # Assign some users onto module - taken_user_ids = [] - for _ in 1..[NR_MAX_USER_MODULES, project.users.count].min - if rand <= RATIO_USER_MODULES then - begin - user = pluck_random(project.users) - end while user.id.in? taken_user_ids - taken_user_ids << user.id - - assigned_on = Faker::Time.backward(500) - assigned_by = pluck_random(my_module.experiment.project.users) - UserMyModule.create( - user: user, - my_module: my_module, - assigned_by: pluck_random(project.users), - created_at: assigned_on - ) - Activity.create( - type_of: :assign_user_to_module, - user: assigned_by, - project: my_module.experiment.project, - my_module: my_module, - message: I18n.t( - "activities.assign_user_to_module", - assigned_user: user.full_name, - module: my_module.name, - assigned_by_user: assigned_by.full_name - ), - created_at: assigned_on - ) - end - end - - # Assign some samples onto module - taken_sample_ids = [] - for _ in 1..[ - NR_MAX_SAMPLE_MODULES, - project.team.samples.count - ].min - if rand <= RATIO_SAMPLE_MODULES then - begin - sample = pluck_random(project.team.samples) - end while sample.id.in? taken_sample_ids - taken_sample_ids << sample.id - - SampleMyModule.create( - sample: sample, - my_module: my_module - ) - end - end - - # Add some comments - for _ in 1..nr_comments - generate_fake_module_comment(my_module) if rand <= RATIO_COMMENTS - end - end - - # Generate some connections between modules - experiment.my_module_groups.find_each do |my_module_group| - if my_module_group.my_modules.empty? or - my_module_group.my_modules.count == 1 - # If any module group doesn't contain - # any modules (or has only 1 module), remove it - my_module_group.destroy - else - # Make connections between project modules, - # keeping in mind not to generate cycles - n = my_module_group.my_modules.count - max_edges = (n - 1) * n / 2 - - dg = RGL::DirectedAdjacencyGraph.new - for _ in 1..max_edges - if rand <= RATIO_EDGES - begin - m1 = pluck_random(my_module_group.my_modules) - m2 = pluck_random(my_module_group.my_modules) - end while ( - m1 == m2 or - dg.has_edge?(m1.id, m2.id) - ) - - # Only add edge if it won't make graph cyclic - dg.add_edge(m1.id, m2.id) - if dg.acyclic? - Connection.create( - input_id: m1.id, - output_id: m2.id - ) - else - dg.remove_edge(m1.id, m2.id) - end - end - end - - # Set order number for each module in group - topsort = dg.topsort_iterator.to_a - my_module_group.my_modules.each do |mm| - if topsort.include? mm.id - mm.workflow_order = topsort.find_index(mm.id) - mm.save! - end - end - end - end - end - - puts 'Generating fake module protocols...' - Experiment.find_each do |experiment| - experiment.my_modules.find_each do |my_module| - generate_fake_protocol(experiment.project.team, - my_module, - nr_steps, - nr_comments) - end - end - - puts "Generating fake module results..." - Experiment.find_each do |experiment| - experiment.my_modules.find_each do |my_module| - for _ in 1..nr_results - user = pluck_random(my_module.users) - created_at = Faker::Time.backward(500) - archived_on = Faker::Time.between(created_at, DateTime.now) - restored_on = Faker::Time.between(archived_on, DateTime.now) - status = random_status - - result = Result.new( - name: Faker::Hacker.abbreviation[0..49], - my_module: my_module, - user: user, - created_at: created_at, - archived: status == :archived, - archived_by: status.in?([:archived, :restored]) ? - user : nil, - archived_on: status.in?([:archived, :restored]) ? - archived_on : nil, - restored_by: status == :restored ? user : nil, - restored_on: status == :restored ? restored_on : nil - ) - - type = [:text, :asset, :table][rand(0..2)] - case type - when :text - result.result_text = ResultText.new( - text: Faker::Hipster.paragraph, - ) - str = "activities.add_text_result" - str2 = "activities.archive_text_result" - when :asset - result.asset = Asset.new( - file: generate_file(rand(MIN_FILE_SIZE..MAX_FILE_SIZE)), - created_by: result.user - ) - str = "activities.add_asset_result" - str2 = "activities.archive_asset_result" - when :table - result.table = Table.new( - contents: generate_table_contents(rand(30), rand(150)), - created_by: result.user - ) - str = "activities.add_table_result" - str2 = "activities.archive_table_result" - end - - result.save - - # Add activities - Activity.create( - type_of: :add_result, - project: experiment.project, - my_module: my_module, - user: user, - created_at: created_at, - message: I18n.t( - str, - user: user.full_name, - result: result.name - ) - ) - if status.in?([:archived, :restored]) then - Activity.create( - type_of: :archive_result, - user: user, - project: experiment.project, - my_module: my_module, - message: I18n.t( - str2, - user: user.full_name, - result: result.name - ), - created_at: archived_on - ) - end - # Currently, there is no way to restore archived results - - # Add some comments - for _ in 1..nr_comments - generate_fake_result_comment(result) if rand <= RATIO_COMMENTS - end - end - end - end - - puts "Generating fake reports..." - Experiment.find_each do |experiment| - project = experiment.project - for _ in 1..nr_reports - taken_project_names = [] - begin - name = Faker::Company.bs - user = pluck_random(project.users) - end while [user, name].in? taken_project_names - taken_project_names << [user, name] - - report = Report.create( - name: name, - description: Faker::Hipster.sentence, - project: project, - user: user - ) - - # Generate the oh-so-many report elements - ReportElement.create( - sort_order: 0, - position: 0, - report: report, - type_of: :project_header - ) - experiment.my_modules.each do |my_module| - if rand <= RATIO_REPORT_ELEMENTS then - re_my_module = ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :my_module, - my_module: my_module - ) - - my_module.protocol.completed_steps.each do |step| - if rand <= RATIO_REPORT_ELEMENTS then - re_step = ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :step, - step: step, - parent: re_my_module - ) - - step.checklists.each do |checklist| - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :step_checklist, - checklist: checklist, - parent: re_step - ) - end - end - step.assets.each do |asset| - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :step_asset, - asset: asset, - parent: re_step - ) - end - end - step.tables.each do |table| - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :step_table, - table: table, - parent: re_step - ) - end - end - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :step_comments, - step: step, - parent: re_step - ) - end - end - end - - my_module.results.each do |result| - if rand <= RATIO_REPORT_ELEMENTS then - if result.is_asset - type_of = :result_asset - elsif result.is_table - type_of = :result_table - else - type_of = :result_text - end - re_result = ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: type_of, - result: result, - parent: re_my_module - ) - - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :result_comments, - result: result, - parent: re_result - ) - end - end - end - - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :my_module_activity, - my_module: my_module, - parent: re_my_module - ) - end - - if rand <= RATIO_REPORT_ELEMENTS then - ReportElement.create( - sort_order: rand <= 0.5 ? 0 : 1, - position: 0, - report: report, - type_of: :my_module_samples, - my_module: my_module, - parent: re_my_module - ) - end - end - end - - # Shuffle the report - shuffle_report_elements( - report.report_elements.where(parent: nil) - ) - end - end - - end - - # Now, at the end, add additional "private" team - # to each user - User.find_each do |user| - create_private_user_team(user, Constants::DEFAULT_PRIVATE_TEAM_NAME) - end - - # Generate thumbnails of all experiments - Experiment.find_each do |experiment| - experiment.generate_workflow_img - end - - # Calculate space taken by each team; this must - # be done in a separate transaction because the estimated - # asset sizes are calculated in after_commit, which is done - # after the first transaction is completed - ActiveRecord::Base.transaction do - puts 'Calculating team sizes...' - Team.find_each do |team| - team.calculate_space_taken - team.save - end - end - rescue ActiveRecord::ActiveRecordError, - ArgumentError, ActiveRecord::RecordNotSaved => e - puts "Error seeding fake data, transaction reverted" - puts "Output: #{e.inspect}" - puts e.backtrace.join("\n") - end - end - end - - def generate_fake_protocol( - team, - my_module, - nr_steps, - nr_comments - ) - protocol = nil - if my_module.present? - protocol = my_module.protocol - users = my_module.experiment.project.users - author = pluck_random(users) - if rand <= THRESHOLD_PROTOCOL_IN_MODULE_LINKED && - (parent = pluck_random( - team.protocols.where(protocol_type: [ - Protocol.protocol_types[:in_repository_private], - Protocol.protocol_types[:in_repository_public] - ])) - ).present? - protocol.protocol_type = :linked - protocol.added_by = author - protocol.parent = parent - protocol.parent_updated_at = parent.updated_at - else - protocol.protocol_type = :unlinked - end - protocol.my_module = my_module - else - protocol = Protocol.new - users = team.users - author = pluck_random(users) - val = rand - if val > THRESHOLD_PROTOCOL_ARCHIVED - if val > THRESHOLD_PROTOCOL_PUBLIC - protocol.protocol_type = :in_repository_public - protocol.published_on = Faker::Time.backward(500) - else - protocol.protocol_type = :in_repository_private - end - else - protocol.protocol_type = :in_repository_archived - protocol.archived_by = author - protocol.archived_on = Faker::Time.backward(500) - end - protocol.added_by = author - protocol.authors = Faker::Book.author - protocol.description = Faker::Lorem.paragraph(2) - end - protocol.name = Faker::Hacker.ingverb - protocol.team = team - - if protocol.invalid? then - rename_record(protocol, :name) - end - - protocol.save! - - team.protocol_keywords.find_each do |kw| - if rand <= RATIO_PROTOCOL_KEYWORDS - ProtocolProtocolKeyword.create( - protocol: protocol, - protocol_keyword: kw - ) - end - end - - protocol.reload - if protocol.linked? - # For linked protocols, simply copy their parents' contents - protocol.load_from_repository(protocol.parent, author) - else - # Generate fake protocol data - for i in 1..nr_steps - created_at = Faker::Time.backward(500) - completed = protocol.in_repository? ? false : (rand <= RATIO_STEP_COMPLETED) - completed_on = completed ? - Faker::Time.between(created_at, DateTime.now) : nil - - step = Step.create( - created_at: created_at, - name: Faker::Hacker.ingverb, - description: Faker::Hacker.say_something_smart, - position: i - 1, - completed: completed, - user: pluck_random(users), - protocol: protocol, - completed_on: completed_on - ) - if protocol.in_module? - Activity.create( - type_of: :create_step, - project: my_module.experiment.project, - my_module: my_module, - user: step.user, - created_at: created_at, - message: I18n.t( - "activities.create_step", - user: step.user.full_name, - step: i, - step_name: step.name - ) - ) - end - if completed then - Activity.create( - type_of: :complete_step, - project: my_module.experiment.project, - my_module: my_module, - user: step.user, - created_at: completed_on, - message: I18n.t( - "activities.complete_step", - user: step.user.full_name, - step: i, - step_name: step.name, - completed: i, - all: i - ) - ) - end - - # Add checklists - for _ in 1..NR_MAX_STEP_ATTACHMENTS - if rand <= RATIO_STEP_ATTACHMENTS then - checklist = Checklist.create( - name: Faker::Hacker.noun, - step: step, - created_by: step.user, - ) - - # Add checklist items - for j in 1..NR_MAX_CHECKLIST_ITEMS - if rand <= RATIO_CHECKLIST_ITEM then - checked = protocol.in_repository? ? false : (rand <= RATIO_CHECKLIST_ITEM_CHECKED) - checked_on = Faker::Time.backward(500) - ci = ChecklistItem.create( - created_at: checked_on, - text: Faker::Hipster.sentence, - checklist: checklist, - checked: checked, - created_by: step.user - ) - if checked then - Activity.create( - type_of: :check_step_checklist_item, - project: my_module.experiment.project, - my_module: my_module, - user: step.user, - created_at: checked_on, - message: I18n.t( - "activities.check_step_checklist_item", - user: step.user.full_name, - checkbox: ci.text, - completed: j, - all: j, - step: i, - step_name: step.name - ) - ) - end - end - end - end - end - - # Add assets - for _ in 1..NR_MAX_STEP_ATTACHMENTS - if rand <= RATIO_STEP_ATTACHMENTS then - asset = Asset.create( - file: generate_file(rand(MIN_FILE_SIZE..MAX_FILE_SIZE)), - estimated_size: 0, - file_present: true, - created_by: step.user - ) - StepAsset.create( - step: step, - asset: asset - ) - end - end - - # Add tables - for _ in 1..NR_MAX_STEP_ATTACHMENTS - if rand <= RATIO_STEP_ATTACHMENTS then - table = Table.create( - contents: - generate_table_contents(rand(30), rand(150)), - created_by: step.user - ) - StepTable.create( - step: step, - table: table - ) - end - end - - # Add some comments (only on protocols on module) - if protocol.in_module? then - for _ in 1..nr_comments - if rand <= RATIO_COMMENTS - user = pluck_random(users) - created_at = Faker::Time.backward(500) - StepComment.create( - user: user, - message: Faker::Hipster.sentence, - created_at: created_at, - step: step - ) - Activity.create( - type_of: :add_comment_to_step, - project: my_module.experiment.project, - my_module: my_module, - user: user, - created_at: created_at, - message: I18n.t( - "activities.add_comment_to_step", - user: user.full_name, - step: i, - step_name: step.name - ) - ) - end - end - end - end - end - - return protocol.reload - end - - def shuffle_report_elements(report_elements) - if report_elements.blank? or report_elements.count == 0 - return - end - - header = report_elements.find_by(type_of: :project_header) - if header.present? - header.position = 0 - header.save - i = 1 - else - i = 0 - end - - ids_map = (i..(report_elements.count - 1)).to_a.shuffle - for i in i..(report_elements.count - 1) - re = report_elements[i] - re.position = ids_map[i] - re.save - end - - # Recursively shuffle children - report_elements.each do |re2| - if re2.children.count > 0 - shuffle_report_elements(re2.children) - end - end - end - - def generate_fake_project_comment(project) - user = pluck_random(project.users) - created_at = Faker::Time.backward(500) - ProjectComment.create( - user: user, - message: Faker::Hipster.sentence, - created_at: created_at, - project: project - ) - Activity.create( - type_of: :add_comment_to_project, - user: user, - project: project, - created_at: created_at, - message: I18n.t('activities.add_comment_to_project', - user: user.full_name, - project: project.name) - ) - end - - def generate_fake_module_comment(my_module) - user = pluck_random(my_module.experiment.project.users) - created_at = Faker::Time.backward(500) - TaskComment.create( - user: user, - message: Faker::Hipster.sentence, - created_at: created_at, - my_module: my_module - ) - Activity.create( - type_of: :add_comment_to_module, - user: user, - project: my_module.experiment.project, - my_module: my_module, - created_at: created_at, - message: I18n.t('activities.add_comment_to_module', - user: user.full_name, - module: my_module.name) - ) - end - - def generate_fake_result_comment(result) - user = pluck_random(result.my_module.experiment.project.users) - created_at = Faker::Time.backward(500) - ResultComment.create( - user: user, - message: Faker::Hipster.sentence, - created_at: created_at, - result: result - ) - Activity.create( - type_of: :add_comment_to_result, - project: result.my_module.experiment.project, - my_module: result.my_module, - user: user, - created_at: created_at, - message: I18n.t( - 'activities.add_comment_to_result', - user: user.full_name, - result: result.name - ) - ) - end - - def generate_fake_step_comment(step) - user = pluck_random(step.protocol.my_module.experiment.project.users) - created_at = Faker::Time.backward(500) - StepComment.create( - user: user, - message: Faker::Hipster.sentence, - created_at: created_at, - step: step - ) - Activity.create( - type_of: :add_comment_to_step, - project: step.protocol.my_module.experiment.project, - my_module: step.protocol.my_module, - user: user, - created_at: created_at, - message: I18n.t( - "activities.add_comment_to_step", - user: user.full_name, - step: step.position + 1, - step_name: step.name - ) - ) - end - - # WARNING: This only works on PostgreSQL - def pluck_random(scope) - scope.order("RANDOM()").first - end - - # Randomly determine whether project/module/result is active (0), - # archived (1), or already restored (2) - def random_status - val = rand - status = :active - if val > THRESHOLD_ARCHIVED - if val > THRESHOLD_RESTORED - status = :archived - end - else - status = :restored - end - status - end -end diff --git a/lib/tasks/db_users.rake b/lib/tasks/db_users.rake deleted file mode 100644 index 33a1b5d11..000000000 --- a/lib/tasks/db_users.rake +++ /dev/null @@ -1,132 +0,0 @@ -require "#{Rails.root}/app/utilities/users_generator" -include UsersGenerator - -namespace :db do - - desc "Load users into database from the provided YAML file" - task :load_users, [ :file_path, :create_teams ] => :environment do |task, args| - if args.blank? or args.empty? or args[:file_path].blank? - puts "No file provided" - return - end - - create_teams = false - if args[:create_teams].present? and args[:create_teams].downcase == "true" - create_teams = true - end - - # Parse file - yaml = YAML.load(File.read("#{args[:file_path]}")) - - begin - ActiveRecord::Base.transaction do - # Parse user & team hashes from YAML - teams = yaml.select { |k, v| /team_[0-9]+/ =~ k } - users = yaml.select { |k, v| /user_[0-9]+/ =~ k } - - # Create teams - teams.each do |k, team_hash| - team = Team.order(created_at: :desc) - .where(name: team_hash['name']) - .first - team = Team.create(name: team_hash['name'][0..99]) if team.blank? - team_hash['id'] = team.id - end - - # Create users - puts 'Created users' - users.each do |k, user_hash| - password = user_hash['password'] - password = generate_user_password if password.blank? - - user_teams = user_hash['teams'] - user_teams = '' if user_teams.blank? - - team_ids = - user_teams - .split(',') - .collect(&:strip) - .uniq - .select { |o| teams.include? o } - .collect { |o| teams[o]['id'] } - - user = create_user( - user_hash['full_name'], - user_hash['email'], - password, - true, - create_teams ? Constants::DEFAULT_PRIVATE_TEAM_NAME : nil, - team_ids - ) - - if user.id.present? - puts '' - print_user(user, password) - end - end - - puts '' - end - rescue ActiveRecord::ActiveRecordError, ArgumentError - puts 'Error creating all users, transaction reverted' - end - end - - desc 'Add a single user to the database' - task :add_user => :environment do - puts 'Type in user\'s full name (e.g. \'Steve Johnson\')' - full_name = $stdin.gets.to_s.strip - puts 'Type in user\'s email (e.g. \'steve.johnson@gmail.com\')' - email = $stdin.gets.to_s.strip - puts 'Type in user\'s password (e.g. \'password\'), or ' \ - 'leave blank to let Rails generate password' - password = $stdin.gets.to_s.strip - if password.empty? - password = generate_user_password - end - puts 'Do you want Rails to create default user\'s team? (T/F)' - create_team = $stdin.gets.to_s.strip == 'T' - puts 'Type names of any additional teams you want the user ' \ - 'to be admin of (delimited with \',\'), or leave blank' - team_names = $stdin.gets.to_s.strip - if team_names.empty? - team_names = [] - else - team_names = team_names.split(',').collect(&:strip) - end - - begin - ActiveRecord::Base.transaction do - # Add/fetch teams if needed - team_ids = [] - team_names.each do |team_name| - team = Team.order(created_at: :desc).where(name: team_name).first - team = Team.create(name: team_name[0..99]) if team.blank? - - team_ids << team.id - end - - user = create_user( - full_name, - email, - password, - true, - create_team ? Constants::DEFAULT_PRIVATE_TEAM_NAME : nil, - team_ids - ) - - if user.id.present? then - puts "Successfully created user" - puts "" - print_user(user, password) - else - puts "Error creating new user" - end - - puts "" - end - rescue ActiveRecord::ActiveRecordError, ArgumentError, ActiveRecord::RecordNotSaved => e - puts "Error creating user, transaction reverted: #{$!}" - end - end -end diff --git a/lib/tasks/exportable_items.rake b/lib/tasks/exportable_items.rake deleted file mode 100644 index c1775d87e..000000000 --- a/lib/tasks/exportable_items.rake +++ /dev/null @@ -1,9 +0,0 @@ -namespace :exportable_items do - desc 'Removes exportable zip files' - task cleanup: :environment do - num = Constants::EXPORTABLE_ZIP_EXPIRATION_DAYS - ZipExport.where('created_at < ?', num.days.ago).destroy_all - puts "All exportable zip files older than " \ - "'#{num.days.ago}' have been removed" - end -end diff --git a/lib/tasks/i18n_missing_keys.rake b/lib/tasks/i18n_missing_keys.rake deleted file mode 100644 index 86de02a04..000000000 --- a/lib/tasks/i18n_missing_keys.rake +++ /dev/null @@ -1,97 +0,0 @@ -require "yaml" - -namespace :i18n do - - desc "Find unused keys for given locale (default: 'en'); currently only works on OSs that support 'grep' command" - task :unused_keys, [ :lang ] => :environment do |_, args| - - def flatten_hash(my_hash, parent=[]) - my_hash.flat_map do |key, value| - case value - when Hash then flatten_hash(value, parent + [key]) - else [(parent + [key]).join("."), value] - end - end - end - - lang = args[:lang] || "en" - all_files = Dir.entries("config/locales").select { |f| f.ends_with?("#{lang}.yml") } - all_keys = [] - - all_files.each do |fname| - yml = YAML.load_file("config/locales/#{fname}") - res = Hash[*flatten_hash(yml)] - res.keys.each do |key| - all_keys << (key.start_with?("#{lang}.") ? key.sub("#{lang}.", "") : key) - end - end - - all_good = true - all_keys.each do |key| - output = `grep -rn #{key} .` - if !$?.success? - if all_good - all_good = false - puts "Following keys are unused (for locale #{lang}):" - end - puts " #{key}" - end - end - - if all_good - puts "No unused keys found!" - end - end - - desc "Find and list translation keys that do not exist in all locales" - task :missing_keys => :environment do - - def collect_keys(scope, translations) - full_keys = [] - translations.to_a.each do |key, translations| - new_scope = scope.dup << key - if translations.is_a?(Hash) - full_keys += collect_keys(new_scope, translations) - else - full_keys << new_scope.join('.') - end - end - return full_keys - end - - # Make sure we've loaded the translations - I18n.backend.send(:init_translations) - puts "#{I18n.available_locales.size} #{I18n.available_locales.size == 1 ? 'locale' : 'locales'} available: #{I18n.available_locales.to_sentence}" - - # Get all keys from all locales - all_keys = I18n.backend.send(:translations).collect do |check_locale, translations| - collect_keys([], translations).sort - end.flatten.uniq - puts "#{all_keys.size} #{all_keys.size == 1 ? 'unique key' : 'unique keys'} found." - - missing_keys = {} - all_keys.each do |key| - - I18n.available_locales.each do |locale| - I18n.locale = locale - begin - result = I18n.translate(key, :raise => true) - rescue I18n::MissingInterpolationArgument - # noop - rescue I18n::MissingTranslationData - if missing_keys[key] - missing_keys[key] << locale - else - missing_keys[key] = [locale] - end - end - end - end - - puts "#{missing_keys.size} #{missing_keys.size == 1 ? 'key is missing' : 'keys are missing'} from one or more locales:" - missing_keys.keys.sort.each do |key| - puts "'#{key}': Missing from #{missing_keys[key].join(', ')}" - end - - end -end \ No newline at end of file diff --git a/lib/tasks/notifications.rake b/lib/tasks/notifications.rake deleted file mode 100644 index 7b5677aee..000000000 --- a/lib/tasks/notifications.rake +++ /dev/null @@ -1,23 +0,0 @@ -namespace :notifications do - desc 'Creates new system notification for all active users' - task :new_system, [:title, :message] => :environment do |_, args| - include NotificationsHelper - - if args.blank? || - args.empty? || - args[:title].blank? || - args[:message].blank? - puts 'One or both of arguments are missing' - return - end - - title = args[:title] - message = args[:message] - - puts 'Creating following system notification:' - puts " *** #{title} ***" - puts " #{I18n.l(Time.now, format: :full)} | #{message}" - - create_system_notification(title, message) - end -end diff --git a/lib/tasks/paperclip.rake b/lib/tasks/paperclip.rake deleted file mode 100644 index eea765620..000000000 --- a/lib/tasks/paperclip.rake +++ /dev/null @@ -1,53 +0,0 @@ -namespace :paperclip do - - def trim_url(url) - url.split("?").first - end - - def print_model_files(model, file_attr) - - model.all.each do |a| - file = a.send file_attr - styles = file.options[:styles] - url = file.url - - puts trim_url(url) - - if styles - styles.each do |style, option| - url = file.url style - puts trim_url(url) - end - end - end - end - - desc 'List all paperclip files' - task files: :environment do - print_model_files Asset, :file - print_model_files User, :avatar - end - - desc 'Reprocess the Assets attachents styles' - task :reprocess, [:before] => :environment do |_, args| - error = false - assets = Asset - if args.present? && args[:before].present? - assets = assets.where('updated_at < ?', eval(args[:before])) - end - assets.find_each(batch_size: 100) do |asset| - next unless asset.is_image? - begin - asset.file.reprocess! :medium, :large - rescue - error = true - $stderr.puts "exception while processing #{asset.file_file_name} " \ - "ID: #{asset.id}:" - end - end - unless error - $stderr.puts 'All gone well! ' \ - 'You can grab a beer now and enjoy the evening.' - end - end -end diff --git a/lib/tasks/sign_up_constraint.rake b/lib/tasks/sign_up_constraint.rake deleted file mode 100644 index 11e0399cd..000000000 --- a/lib/tasks/sign_up_constraint.rake +++ /dev/null @@ -1,32 +0,0 @@ -namespace :sign_up_constraint do - desc 'Adds email domain constraint to the users table. '\ - 'E.g: scinote.net' - task :email_domain, [:domain] => :environment do |_, args| - include DatabaseHelper - - if args.blank? || - args.empty? || - args[:domain].blank? - puts 'Please add the email domain' - return - end - - domain = args[:domain] - domain = domain.strip.gsub(/\./, '\\.') - - add_check_constraint( - 'users', - 'email_must_be_company_email', - "email ~* '^[A-Za-z0-9._%-+]+@#{domain}'" - ) - puts "Created the following domain constraint: #{args[:domain]}" - end - - desc 'Remove email domain constraint from the users table.' - task remove_domain: :environment do - include DatabaseHelper - - drop_constraint('users', 'email_must_be_company_email') - puts 'Email constraint has been removed' - end -end diff --git a/lib/tasks/tiny_mce_asset.rake b/lib/tasks/tiny_mce_asset.rake deleted file mode 100644 index 275e0722b..000000000 --- a/lib/tasks/tiny_mce_asset.rake +++ /dev/null @@ -1,9 +0,0 @@ -namespace :tiny_mce_asset do - desc 'Remove obsolete images that were created on new steps or '\ - 'results and the step/result didn\'t get saved.' - task remove_obsolete_images: :environment do - TinyMceAsset.where('step_id IS ? AND ' \ - 'result_text_id IS ? AND created_at < ?', - nil, nil, 7.days.ago) - end -end diff --git a/lib/tasks/web_stats.rake b/lib/tasks/web_stats.rake deleted file mode 100644 index cc30274cf..000000000 --- a/lib/tasks/web_stats.rake +++ /dev/null @@ -1,65 +0,0 @@ -namespace :web_stats do - - desc "Report login statistics from the application" - task :login => :environment do - def print_splitter - puts "+--------------------------------------+-----------------+-----------------------+" - end - - def print_header - print_splitter - puts "| Username | Times signed in | Last signed in on |" - print_splitter - end - - def print_line(user) - last_signed_in = "never" - if user.last_sign_in_at.present? - last_signed_in = I18n.l(user.last_sign_in_at, format: :full) - end - puts "| #{user.email.ljust(36)} " \ - "| #{user.sign_in_count.to_s.ljust(15)} " \ - "| #{last_signed_in.ljust(21)} |" - end - - # Actual task code - print_header - User.all.each{ |u| print_line(u) } - print_splitter - - # Calculate total & avg - total = 0 - User.all.each{ |u| total += u.sign_in_count } - avg = total.to_f / User.count.to_f - - puts " Total times signed in: #{total} Avg. times signed in: #{avg.round(4)}" - puts "" - end - - desc "Report information on last login" - task :last_login => :environment do - ll_user = User.where.not(current_sign_in_at: nil).order(current_sign_in_at: :desc).first - ll_user2 = User.where.not(last_sign_in_at: nil).order(last_sign_in_at: :desc).first - - ll = ll_user.present? ? ll_user.current_sign_in_at : nil - ll2 = ll_user2.present? ? ll_user2.last_sign_in_at : nil - if ll.present? and ll2.present? - ll_real = ll > ll2 ? ll : ll2 - elsif ll.present? - ll_real = ll - elsif ll2.present - ll_real = ll2 - else - ll_real = nil - end - - puts "Current time: #{Time.now.to_s}" - if ll_real.present? - diff = ((Time.now - ll_real) / 1.hour).round - puts "Last login at: #{ll_real.to_s}" - puts " (#{diff} hours ago)" - else - puts "Woops, seems nobody has logged in yet!" - end - end -end \ No newline at end of file diff --git a/script/cucumber b/script/cucumber new file mode 100755 index 000000000..7fa5c9208 --- /dev/null +++ b/script/cucumber @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 000000000..07054f6c7 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do + factory :user do + full_name 'admin' + initials 'AD' + email 'admin@scinote.net' + password 'asdf1243' + password_confirmation 'asdf1243' + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 000000000..a9ae27c56 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,88 @@ +require 'rails_helper' + +describe User, type: :model do + it 'should be of class User' do + expect(subject.class).to eq User + end + + describe 'Database table' do + it { is_expected.to have_db_column :id } + it { is_expected.to have_db_column :full_name } + it { is_expected.to have_db_column :initials } + it { is_expected.to have_db_column :email } + it { is_expected.to have_db_column :encrypted_password } + it { is_expected.to have_db_column :reset_password_token } + it { is_expected.to have_db_column :reset_password_sent_at } + it { is_expected.to have_db_column :sign_in_count } + it { is_expected.to have_db_column :current_sign_in_at } + it { is_expected.to have_db_column :last_sign_in_at } + it { is_expected.to have_db_column :current_sign_in_ip } + it { is_expected.to have_db_column :last_sign_in_ip } + it { is_expected.to have_db_column :created_at } + it { is_expected.to have_db_column :updated_at } + it { is_expected.to have_db_column :avatar_file_name } + it { is_expected.to have_db_column :avatar_content_type } + it { is_expected.to have_db_column :avatar_file_size } + it { is_expected.to have_db_column :avatar_updated_at } + it { is_expected.to have_db_column :confirmation_token } + it { is_expected.to have_db_column :confirmed_at } + it { is_expected.to have_db_column :confirmation_sent_at } + it { is_expected.to have_db_column :unconfirmed_email } + it { is_expected.to have_db_column :time_zone } + it { is_expected.to have_db_column :invitation_token } + it { is_expected.to have_db_column :invitation_created_at } + it { is_expected.to have_db_column :invitation_sent_at } + it { is_expected.to have_db_column :invitation_accepted_at } + it { is_expected.to have_db_column :invitation_limit } + it { is_expected.to have_db_column :invited_by_id } + it { is_expected.to have_db_column :invited_by_type } + it { is_expected.to have_db_column :invitations_count } + it { is_expected.to have_db_column :tutorial_status } + it { is_expected.to have_db_column :assignments_notification } + it { is_expected.to have_db_column :recent_notification } + it { is_expected.to have_db_column :assignments_notification_email } + it { is_expected.to have_db_column :recent_notification_email } + it { is_expected.to have_db_column :current_team_id } + it { is_expected.to have_db_column :system_message_notification_email } + it { is_expected.to have_db_column :authentication_token } + end + + describe 'Should be a valid object' do + it { should validate_presence_of(:full_name) } + it { should validate_presence_of(:initials) } + it { should validate_presence_of(:email) } + it { should validate_presence_of(:time_zone) } + + it do + should validate_length_of(:full_name).is_at_most( + Constants::NAME_MAX_LENGTH + ) + end + + it do + should validate_length_of(:initials).is_at_most( + Constants::USER_INITIALS_MAX_LENGTH + ) + end + + it do + should validate_length_of(:email).is_at_most( + Constants::EMAIL_MAX_LENGTH + ) + end + end + + describe 'Should return a user name' do + let(:user) { build :user, full_name: 'Tinker' } + + it 'should return a user full name' do + expect(user.name).to eq 'Tinker' + end + + it 'should to be able to change full name' do + user.name = 'Axe' + expect(user.name).to_not eq 'Tinker' + expect(user.name).to eq 'Axe' + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 000000000..b0435ba1b --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,72 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +require 'shoulda-matchers' +require 'database_cleaner' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# 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 +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +# Checks for pending migration and applies them before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/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 + # instead of true. + config.use_transactional_fixtures = true + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") + + # includes FactoryGirl in rspec + config.include FactoryGirl::Syntax::Methods +end + +# config shoulda matchers to work with rspec +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 000000000..ffc1ac102 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,99 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require 'capybara/rspec' +require 'simplecov' + +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end