diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5e3c8109da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly \ No newline at end of file diff --git a/Gemfile b/Gemfile index 4b264a6be2..ab509ebad7 100644 --- a/Gemfile +++ b/Gemfile @@ -26,13 +26,12 @@ gem 'puma_worker_killer' gem 'knockoutjs-rails' # Using this branch in pattern library due to multiselect (until it's merged to master) -gem 'pattern-library', git: 'https://github.com/openstax/pattern-library.git', ref: 'c3dd0b2c8ed987f9089b7da302fb02d2fc4cd840' +gem 'pattern-library', github: 'openstax/pattern-library', ref: 'c3dd0b2c8ed987f9089b7da302fb02d2fc4cd840' -# Lev framework -# - introduces two new concepts: Routines and Handlers -gem 'lev', github: 'lml/lev', ref: 'a33c93c83afea63c80b5da6317efec4bfd357df5' -gem 'openstax_transaction_retry', github: 'openstax/transaction_retry', ref: '36e26d0a068756c334b9d1c671d40773bbfc9300' -gem 'openstax_transaction_isolation', github: 'openstax/transaction_isolation', ref: '7387fa091462118704967a7c4b2821cd0f5d9e01' +# Lev framework - introduces two new concepts: Routines and Handlers +gem 'lev' +gem 'openstax_transaction_retry' +gem 'openstax_transaction_isolation' # SCSS stylesheets gem 'sass-rails' @@ -123,7 +122,7 @@ gem 'representable' gem 'keyword_search' # ToS/PP management -gem 'fine_print', github: 'lml/fine_print', ref: '636023f68e95196dffaf295bfad3ad8051c23542' +gem 'fine_print' # Send users back to the correct page after login gem 'action_interceptor' @@ -170,7 +169,7 @@ gem 'openstax_path_prefixer', github: 'openstax/path_prefixer', ref: 'e3edfc7058 gem 'json-jwt' # international country codes javascript plugin -gem 'intl-tel-input-rails', git: 'https://github.com/openstax/intl-tel-input-rails.git', branch: 'master' +gem 'intl-tel-input-rails', github: 'openstax/intl-tel-input-rails', branch: 'master' # internationalization based on the `HTTP_ACCEPT_LANGUAGE` header sent by browsers gem 'http_accept_language' @@ -225,10 +224,6 @@ group :development, :test do # Codecov integration gem 'codecov', require: false - # Speedup Rails boot time - gem 'spring' - gem 'spring-commands-rspec' - # Run specs when files change gem 'guard-rspec' gem 'guard-livereload', require: false diff --git a/Gemfile.lock b/Gemfile.lock index a1fc2c2227..a2bac63920 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,3 @@ -GIT - remote: https://github.com/lml/fine_print.git - revision: 636023f68e95196dffaf295bfad3ad8051c23542 - ref: 636023f68e95196dffaf295bfad3ad8051c23542 - specs: - fine_print (6.0.1) - action_interceptor - jquery-rails - rails (< 7) - responders - -GIT - remote: https://github.com/lml/lev.git - revision: a33c93c83afea63c80b5da6317efec4bfd357df5 - ref: a33c93c83afea63c80b5da6317efec4bfd357df5 - specs: - lev (13.0.0) - actionpack (>= 4.2) - active_attr - activejob - activemodel (>= 6.1) - activerecord (>= 4.2) - hashie - openstax_transaction_isolation - openstax_transaction_retry - GIT remote: https://github.com/openstax/intl-tel-input-rails.git revision: 3a63a495822d90bc9ca93e9541ef252fd7a0b50b @@ -49,23 +23,6 @@ GIT specs: pattern-library (1.1.18) -GIT - remote: https://github.com/openstax/transaction_isolation.git - revision: 7387fa091462118704967a7c4b2821cd0f5d9e01 - ref: 7387fa091462118704967a7c4b2821cd0f5d9e01 - specs: - openstax_transaction_isolation (2.0.0) - activerecord (>= 6) - -GIT - remote: https://github.com/openstax/transaction_retry.git - revision: 36e26d0a068756c334b9d1c671d40773bbfc9300 - ref: 36e26d0a068756c334b9d1c671d40773bbfc9300 - specs: - openstax_transaction_retry (2.0.0) - activerecord (>= 6) - openstax_transaction_isolation (>= 2) - GEM remote: https://rubygems.org/ specs: @@ -269,7 +226,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) + date (3.4.1) db-query-matchers (0.13.0) activesupport (>= 4.0, < 7.3) rspec (>= 3.0) @@ -297,7 +254,7 @@ GEM eventmachine (>= 0.12.9) http_parser.rb (~> 0) error_page_assets (0.4) - erubi (1.13.0) + erubi (1.13.1) eventmachine (1.2.7) exception_notification (4.5.0) actionmailer (>= 5.2, < 8) @@ -342,6 +299,11 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) ffi (1.16.3) + fine_print (6.0.3) + action_interceptor + jquery-rails + rails (< 7) + responders font-awesome-rails (4.7.0.8) railties (>= 3.2, < 8.0) formatador (1.1.0) @@ -417,6 +379,15 @@ GEM launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) + lev (13.0.0) + actionpack (>= 4.2) + active_attr + activejob + activemodel (>= 6.1) + activerecord (>= 4.2) + hashie + openstax_transaction_isolation + openstax_transaction_retry libv8-node (18.19.0.0) libv8-node (18.19.0.0-x86_64-darwin) libv8-node (18.19.0.0-x86_64-linux) @@ -429,7 +400,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.23.0) + loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) lumberjack (1.2.10) @@ -446,16 +417,16 @@ GEM railties (>= 3.0.0, < 8) method_source (1.1.0) mini_mime (1.1.5) - mini_portile2 (2.8.7) + mini_portile2 (2.8.8) mini_racer (0.16.0) libv8-node (~> 18.19.0.0) - minitest (5.25.1) + minitest (5.25.4) msgpack (1.7.3) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.4.1) nenv (0.3.0) - net-imap (0.5.0) + net-imap (0.5.5) date net-protocol net-pop (0.1.2) @@ -464,13 +435,13 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) - nokogiri (1.16.7) + nio4r (2.7.4) + nokogiri (1.18.1) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.18.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.18.1-x86_64-linux-gnu) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -539,6 +510,11 @@ GEM openstax_active_force rails (>= 5.0, < 7.0) restforce + openstax_transaction_isolation (2.0.0) + activerecord (>= 6) + openstax_transaction_retry (2.0.0) + activerecord (>= 6) + openstax_transaction_isolation (>= 2) openstax_utilities (5.1.2) aws-sdk-autoscaling faraday @@ -584,7 +560,7 @@ GEM rack (>= 2.0.0) rack-session (1.0.2) rack (< 3) - rack-test (2.1.0) + rack-test (2.2.0) rack (>= 1.3) rails (6.1.7.8) actioncable (= 6.1.7.8) @@ -614,9 +590,9 @@ GEM activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (7.0.9) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) @@ -761,9 +737,6 @@ GEM snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) - spring (4.2.1) - spring-commands-rspec (1.0.4) - spring (>= 0.9.1) sprockets (3.7.5) base64 concurrent-ruby (~> 1.0) @@ -780,7 +753,7 @@ GEM thor (1.3.2) tilt (2.4.0) timecop (0.9.10) - timeout (0.4.1) + timeout (0.4.3) trailblazer-option (0.1.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -805,7 +778,8 @@ GEM crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.11) - websocket-driver (0.7.6) + websocket-driver (0.7.7) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) whenever (1.0.0) @@ -858,7 +832,7 @@ DEPENDENCIES faraday faraday_middleware ffi (< 1.17) - fine_print! + fine_print font-awesome-rails guard-livereload guard-rspec @@ -872,7 +846,7 @@ DEPENDENCIES keyword_search knockoutjs-rails launchy - lev! + lev listen lograge maruku @@ -890,8 +864,8 @@ DEPENDENCIES openstax_path_prefixer! openstax_rescue_from openstax_salesforce - openstax_transaction_isolation! - openstax_transaction_retry! + openstax_transaction_isolation + openstax_transaction_retry openstax_utilities p3p parallel_tests @@ -923,8 +897,6 @@ DEPENDENCIES sentry-ruby shoulda-matchers smarter_csv - spring - spring-commands-rspec timecop uglifier vcr diff --git a/app/handlers/create_external_user_credentials.rb b/app/handlers/create_external_user_credentials.rb index 1394662e61..4e12e4b719 100644 --- a/app/handlers/create_external_user_credentials.rb +++ b/app/handlers/create_external_user_credentials.rb @@ -53,7 +53,7 @@ def handle password_confirmation: signup_params.password ) - run Newflow::CreateEmailForUser, email: signup_params.email, user: outputs.user + run Newflow::CreateEmailForUser, email: signup_params.email, user: outputs.user, show_pin: false agree_to_terms if signup_params.terms_accepted end diff --git a/app/mailers/newflow_mailer.rb b/app/mailers/newflow_mailer.rb index 75e327e8ed..47afe9c9be 100644 --- a/app/mailers/newflow_mailer.rb +++ b/app/mailers/newflow_mailer.rb @@ -15,8 +15,9 @@ def reset_password_email(user:, email_address:) subject: "Reset your OpenStax password" end - def signup_email_confirmation(email_address:) - @should_show_pin = ConfirmByPin.sequential_failure_for(email_address).attempts_remaining? + def signup_email_confirmation(email_address:, show_pin: true) + @should_show_pin = show_pin != false && + ConfirmByPin.sequential_failure_for(email_address).attempts_remaining? @email_value = email_address.value @confirmation_pin = email_address.confirmation_pin @confirmation_code = email_address.confirmation_code diff --git a/app/models/user.rb b/app/models/user.rb index d32591a2bd..32bef11f4c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -80,10 +80,7 @@ class User < ApplicationRecord before_validation(:strip_fields) before_validation(:remove_special_chars) - before_validation( - :generate_uuid, :generate_support_identifier, - on: :create - ) + before_validation(:generate_uuid, on: :create) validate(:ensure_names_continue_to_be_present) validate( @@ -125,7 +122,7 @@ class User < ApplicationRecord validates(:login_token, uniqueness: { allow_nil: true }) - validates(:uuid, :support_identifier, presence: true, uniqueness: true) + validates(:uuid, presence: true, uniqueness: true) validate :books_used_details_valid @@ -160,7 +157,7 @@ class User < ApplicationRecord delegate_to_routine :destroy - attr_readonly :uuid, :support_identifier + attr_readonly :uuid attribute :is_not_gdpr_location, :boolean, default: nil @@ -450,10 +447,6 @@ def generate_uuid self.uuid ||= SecureRandom.uuid end - def generate_support_identifier(length: 4) - self.support_identifier ||= "cs_#{SecureRandom.hex(length)}" - end - protected def make_first_user_an_admin diff --git a/app/representers/api/v1/find_or_create_user_representer.rb b/app/representers/api/v1/find_or_create_user_representer.rb index 008e1d4cda..3f946ecece 100644 --- a/app/representers/api/v1/find_or_create_user_representer.rb +++ b/app/representers/api/v1/find_or_create_user_representer.rb @@ -12,11 +12,6 @@ class FindOrCreateUserRepresenter < Roar::Decorator readable: true, writeable: false - property :support_identifier, - type: String, - readable: true, - writeable: false - property :external_id, type: String, readable: false, diff --git a/app/representers/api/v1/find_user_representer.rb b/app/representers/api/v1/find_user_representer.rb index a73208017f..c68cd4287c 100644 --- a/app/representers/api/v1/find_user_representer.rb +++ b/app/representers/api/v1/find_user_representer.rb @@ -12,11 +12,6 @@ class FindUserRepresenter < Roar::Decorator readable: true, writeable: true - property :support_identifier, - type: String, - readable: true, - writeable: false - property :external_id, type: String, readable: false, diff --git a/app/representers/api/v1/user_representer.rb b/app/representers/api/v1/user_representer.rb index ece0f265c4..3031e2bdc7 100644 --- a/app/representers/api/v1/user_representer.rb +++ b/app/representers/api/v1/user_representer.rb @@ -50,11 +50,6 @@ class UserRepresenter < Roar::Decorator readable: true, writeable: false - property :support_identifier, - type: String, - readable: true, - writeable: false - property :consent_preferences, type: JSON, readable: true, diff --git a/app/routines/confirm_by_pin.rb b/app/routines/confirm_by_pin.rb index 9aecfa5a16..456697c914 100644 --- a/app/routines/confirm_by_pin.rb +++ b/app/routines/confirm_by_pin.rb @@ -9,6 +9,8 @@ def self.sequential_failure_for(contact_info) :value when PreAuthState :contact_info_value + else + :value end SequentialFailure.confirm_by_pin diff --git a/app/routines/newflow/create_email_for_user.rb b/app/routines/newflow/create_email_for_user.rb index 05014c85f3..e6ef5d37d4 100644 --- a/app/routines/newflow/create_email_for_user.rb +++ b/app/routines/newflow/create_email_for_user.rb @@ -5,7 +5,7 @@ class CreateEmailForUser protected ############### - def exec(email:, user:, is_school_issued: nil) + def exec(email:, user:, is_school_issued: nil, show_pin: true) @email = EmailAddress.find_or_create_by(value: email&.downcase, user_id: user.id) @email.is_school_issued = is_school_issued @@ -17,7 +17,9 @@ def exec(email:, user:, is_school_issued: nil) event_type: :email_added_to_user, event_data: { email: @email } ) - NewflowMailer.signup_email_confirmation(email_address: @email).deliver_later + NewflowMailer.signup_email_confirmation( + email_address: @email, show_pin: show_pin + ).deliver_later end @email.save diff --git a/app/routines/newflow/create_or_update_salesforce_lead.rb b/app/routines/newflow/create_or_update_salesforce_lead.rb index d186fc30a4..4302aa9510 100644 --- a/app/routines/newflow/create_or_update_salesforce_lead.rb +++ b/app/routines/newflow/create_or_update_salesforce_lead.rb @@ -146,11 +146,23 @@ def build_book_adoption_json_for_salesforce(user) return nil unless user.books_used_details user.books_used_details.each do |book| - books_json << { - name: book[0], + book_value = book[0] + if book_value.match(/\[.*\]/) + book_name = book_value.gsub(/\[.*\]/, '').strip # Calculus Volume 1 + book_language = book_value[/\[(.*?)\]/, 1] # Spanish (no brackets) + books_json << { + name: book_name, + students: book[1]["num_students_using_book"], + howUsing: book[1]["how_using_book"], + language: book_language, + } + else + books_json << { + name: book_value, students: book[1]["num_students_using_book"], howUsing: book[1]["how_using_book"] } + end end adoption_json['Books'] = books_json diff --git a/app/routines/search_users.rb b/app/routines/search_users.rb index 49061c24cc..73e219c199 100644 --- a/app/routines/search_users.rb +++ b/app/routines/search_users.rb @@ -96,16 +96,6 @@ def exec(query, options={}) users = users.where(uuid: uuids_queries) end - if options[:admin] - with.keyword :support_identifier do |identifiers| - sanitized_identifiers = sanitize_strings( - identifiers, append_wildcard: true, prepend_wildcard: true - ) - - users = users.where( table[:support_identifier].matches_any(sanitized_identifiers) ) - end - end - with.keyword :id do |ids| users = users.where(id: ids) end @@ -160,7 +150,6 @@ def exec(query, options={}) matches_last_name = table[:last_name].matches_any(sanitized_names) matches_full_name = full_name.matches_any(sanitized_names) matches_id = table[:id].in(terms) - matches_support_identifier = table[:support_identifier].matches_any(sanitized_names) users = User.where(matches_first_name) .or( @@ -169,8 +158,6 @@ def exec(query, options={}) User.where(matches_full_name) ).or( User.where(matches_id) - ).or( - User.where(matches_support_identifier) ) end end diff --git a/app/views/newflow_mailer/signup_email_confirmation.html.erb b/app/views/newflow_mailer/signup_email_confirmation.html.erb index 3afbd76129..2ccd542acd 100644 --- a/app/views/newflow_mailer/signup_email_confirmation.html.erb +++ b/app/views/newflow_mailer/signup_email_confirmation.html.erb @@ -89,7 +89,7 @@

- Verify your email by clicking the button below or use your pin: <%= @confirmation_pin %> + Verify your email by clicking the button below<% if @should_show_pin %> or use your pin: <%= @confirmation_pin %><% end %>

diff --git a/config/initializers/active_job.rb b/config/initializers/active_job.rb index 6627277b2d..c508b387de 100644 --- a/config/initializers/active_job.rb +++ b/config/initializers/active_job.rb @@ -1,2 +1,2 @@ # Use delayed_job for background jobs -Rails.application.config.active_job.queue_adapter = :delayed_job +ActiveSupport.on_load(:active_job) { ActiveJob::Base.queue_adapter = :delayed_job } diff --git a/config/initializers/delayed_job.rb b/config/initializers/delayed_job.rb index 114db75540..d3126d4b4f 100644 --- a/config/initializers/delayed_job.rb +++ b/config/initializers/delayed_job.rb @@ -65,7 +65,7 @@ def handle_failed_job(job, exception) job.fail! if fail_proc.present? && fail_proc.call(exception) || exception.try(:instantly_fail_if_in_background_job?) - super(job, exception) + super(job, exception) end end diff --git a/db/migrate/20241107193019_remove_support_identifier_from_users.rb b/db/migrate/20241107193019_remove_support_identifier_from_users.rb new file mode 100644 index 0000000000..6802f6ecbd --- /dev/null +++ b/db/migrate/20241107193019_remove_support_identifier_from_users.rb @@ -0,0 +1,17 @@ +class RemoveSupportIdentifierFromUsers < ActiveRecord::Migration[6.1] + def up + remove_column :users, :support_identifier + end + + def down + add_column :users, :support_identifier, :citext + + add_index :users, :support_identifier, unique: true + + User.find_each do |user| + User.where(id: user.id).update_all(support_identifier: user.generate_support_identifier) + end + + change_column_null :users, :support_identifier, false + end +end diff --git a/db/schema.rb b/db/schema.rb index e9fe3357f4..ae95da60cc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_10_24_162218) do +ActiveRecord::Schema.define(version: 2024_11_07_193019) do # These are extensions that must be enabled in order to support this database enable_extension "citext" @@ -437,7 +437,6 @@ t.datetime "login_token_expires_at" t.integer "role", default: 0, null: false t.jsonb "signed_external_data" - t.citext "support_identifier", null: false t.boolean "is_test" t.integer "school_type", default: 0, null: false t.boolean "using_openstax", default: false @@ -484,7 +483,6 @@ t.index ["school_id"], name: "index_users_on_school_id" t.index ["school_type"], name: "index_users_on_school_type" t.index ["source_application_id"], name: "index_users_on_source_application_id" - t.index ["support_identifier"], name: "index_users_on_support_identifier", unique: true t.index ["username"], name: "index_users_on_username", unique: true t.index ["uuid"], name: "index_users_on_uuid", unique: true end diff --git a/lib/fetch_book_data.rb b/lib/fetch_book_data.rb index b297ed95a0..3381798bef 100644 --- a/lib/fetch_book_data.rb +++ b/lib/fetch_book_data.rb @@ -19,7 +19,7 @@ def fetch_titles items = results.fetch('items', []) # See https://github.com/openstax/openstax-cms/blob/main/books/constants.py#L7 for book states - books = items.reject{ %w[retired unlisted].include?(i['book_state']) } + books = items.reject { |i| %w[retired unlisted].include?(i['book_state']) } books_with_subject = [] diff --git a/lib/import_users.rb b/lib/import_users.rb index c039fa81d6..11c5d11911 100644 --- a/lib/import_users.rb +++ b/lib/import_users.rb @@ -72,7 +72,6 @@ def create_user(username, password_digest, title, first_name, last_name, email_a @user.first_name = first_name @user.last_name = last_name @user.generate_uuid - @user.generate_support_identifier @user.save(validate: false) identity = @user.build_identity diff --git a/spec/controllers/api/v1/users_controller_spec.rb b/spec/controllers/api/v1/users_controller_spec.rb index 75880e7cb1..bd0a9abcfc 100644 --- a/spec/controllers/api/v1/users_controller_spec.rb +++ b/spec/controllers/api/v1/users_controller_spec.rb @@ -302,7 +302,6 @@ external_ids: [ external_id.external_id ], id: user.id, sso: kind_of(String), - support_identifier: user.support_identifier, uuid: user.uuid ) end @@ -315,7 +314,6 @@ expect(response.body_as_hash).to match( external_ids: [ external_id.external_id ], id: user.id, - support_identifier: user.support_identifier, uuid: user.uuid ) end @@ -360,7 +358,7 @@ expect(response.code).to eq('201') new_user = User.order(:id).last expect(response.body_as_hash).to eq( - id: new_user.id, uuid: new_user.uuid, external_ids: [], support_identifier: new_user.support_identifier + id: new_user.id, uuid: new_user.uuid, external_ids: [] ) end @@ -448,8 +446,7 @@ expect(response.body_as_hash).to eq( id: unclaimed_user.id, uuid: unclaimed_user.uuid, - external_ids: [], - support_identifier: unclaimed_user.support_identifier + external_ids: [] ) end @@ -459,7 +456,7 @@ body: { email: user_2.contact_infos.first.value } expect(response.code).to eq('201') expect(response.body_as_hash).to eq( - id: user_2.id, uuid: user_2.uuid, external_ids: [], support_identifier: user_2.support_identifier + id: user_2.id, uuid: user_2.uuid, external_ids: [] ) end end diff --git a/spec/fixtures/users.json b/spec/fixtures/users.json index c01cce385f..1c2abf70f4 100644 --- a/spec/fixtures/users.json +++ b/spec/fixtures/users.json @@ -1 +1 @@ -[{"username":"jstrav","created_at":"2018-05-17T19:49:42.469Z","updated_at":"2018-05-17T19:49:42.469Z","is_administrator":false,"first_name":"John","last_name":"Stravinsky","title":null,"uuid":"f1f96deb-919b-4183-9367-0cb713672172","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"support_identifier":"cs_5d1edbb0","is_test":null,"school_type":0,"identity":{"password_digest":"$2a$04$s48G.v95rrx3vQ9Hvld2fus2fuNBRBhel99Io30.mTJOivtSgSxRu","created_at":"2018-05-17T19:49:42.524Z","updated_at":"2018-05-17T19:49:42.524Z","password_expires_at":null},"authentications":[{"provider":"identity","created_at":"2018-05-17T19:49:42.538Z","updated_at":"2018-05-17T19:49:42.538Z","login_hint":null}],"contact_infos":[{"type":"EmailAddress","value":"8f30b4@7cc1fa.223256","verified":false,"confirmation_code":null,"created_at":"2018-05-17T19:49:42.477Z","updated_at":"2018-05-17T19:49:42.477Z","confirmation_sent_at":null,"is_searchable":true,"confirmation_pin":null},{"type":"EmailAddress","value":"a467a2@4634bc.981344","verified":false,"confirmation_code":null,"created_at":"2018-05-17T19:49:42.485Z","updated_at":"2018-05-17T19:49:42.485Z","confirmation_sent_at":null,"is_searchable":true,"confirmation_pin":null}]},{"username":"mary","created_at":"2018-05-17T19:49:42.497Z","updated_at":"2018-05-17T19:49:42.497Z","is_administrator":false,"first_name":"Mary","last_name":"Mighty","title":null,"uuid":"f3145396-9b36-4080-a66d-ff3d4c0c1393","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"support_identifier":"cs_6cf1cf5e","is_test":null,"school_type":0,"identity":{"password_digest":"$2a$04$nghthHpquanPYYJRfUk6n.TwkNtoP6h.aFFWJl9DHblRze9F1teMa","created_at":"2018-05-17T19:49:42.545Z","updated_at":"2018-05-17T19:49:42.545Z","password_expires_at":null},"authentications":[{"provider":"identity","created_at":"2018-05-17T19:49:42.551Z","updated_at":"2018-05-17T19:49:42.551Z","login_hint":null}],"contact_infos":[]},{"username":"jstead","created_at":"2018-05-17T19:49:42.507Z","updated_at":"2018-05-17T19:49:42.507Z","is_administrator":false,"first_name":"John","last_name":"Stead","title":null,"uuid":"1ced2cd8-4bb5-4bc6-a034-29410bb6e86d","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"support_identifier":"cs_fa68f2a5","is_test":null,"school_type":0,"identity":null,"authentications":[],"contact_infos":[]}] \ No newline at end of file +[{"username":"jstrav","created_at":"2018-05-17T19:49:42.469Z","updated_at":"2018-05-17T19:49:42.469Z","is_administrator":false,"first_name":"John","last_name":"Stravinsky","title":null,"uuid":"f1f96deb-919b-4183-9367-0cb713672172","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"is_test":null,"school_type":0,"identity":{"password_digest":"$2a$04$s48G.v95rrx3vQ9Hvld2fus2fuNBRBhel99Io30.mTJOivtSgSxRu","created_at":"2018-05-17T19:49:42.524Z","updated_at":"2018-05-17T19:49:42.524Z","password_expires_at":null},"authentications":[{"provider":"identity","created_at":"2018-05-17T19:49:42.538Z","updated_at":"2018-05-17T19:49:42.538Z","login_hint":null}],"contact_infos":[{"type":"EmailAddress","value":"8f30b4@7cc1fa.223256","verified":false,"confirmation_code":null,"created_at":"2018-05-17T19:49:42.477Z","updated_at":"2018-05-17T19:49:42.477Z","confirmation_sent_at":null,"is_searchable":true,"confirmation_pin":null},{"type":"EmailAddress","value":"a467a2@4634bc.981344","verified":false,"confirmation_code":null,"created_at":"2018-05-17T19:49:42.485Z","updated_at":"2018-05-17T19:49:42.485Z","confirmation_sent_at":null,"is_searchable":true,"confirmation_pin":null}]},{"username":"mary","created_at":"2018-05-17T19:49:42.497Z","updated_at":"2018-05-17T19:49:42.497Z","is_administrator":false,"first_name":"Mary","last_name":"Mighty","title":null,"uuid":"f3145396-9b36-4080-a66d-ff3d4c0c1393","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"is_test":null,"school_type":0,"identity":{"password_digest":"$2a$04$nghthHpquanPYYJRfUk6n.TwkNtoP6h.aFFWJl9DHblRze9F1teMa","created_at":"2018-05-17T19:49:42.545Z","updated_at":"2018-05-17T19:49:42.545Z","password_expires_at":null},"authentications":[{"provider":"identity","created_at":"2018-05-17T19:49:42.551Z","updated_at":"2018-05-17T19:49:42.551Z","login_hint":null}],"contact_infos":[]},{"username":"jstead","created_at":"2018-05-17T19:49:42.507Z","updated_at":"2018-05-17T19:49:42.507Z","is_administrator":false,"first_name":"John","last_name":"Stead","title":null,"uuid":"1ced2cd8-4bb5-4bc6-a034-29410bb6e86d","suffix":null,"state":"activated","salesforce_contact_id":null,"faculty_status":0,"self_reported_school":null,"login_token":null,"login_token_expires_at":null,"role":0,"signed_external_data":null,"is_test":null,"school_type":0,"identity":null,"authentications":[],"contact_infos":[]}] diff --git a/spec/handlers/create_external_user_credentials_spec.rb b/spec/handlers/create_external_user_credentials_spec.rb index 43f82faacf..9eb9da7fde 100644 --- a/spec/handlers/create_external_user_credentials_spec.rb +++ b/spec/handlers/create_external_user_credentials_spec.rb @@ -49,7 +49,7 @@ it 'sends a confirmation email' do expect_any_instance_of(NewflowMailer).to( receive(:signup_email_confirmation).with( - hash_including({ email_address: an_instance_of(EmailAddress) }) + hash_including({ email_address: an_instance_of(EmailAddress), show_pin: false }) ) ) handler_call diff --git a/spec/mailers/newflow_mailer_spec.rb b/spec/mailers/newflow_mailer_spec.rb index 681ed8fa13..aabb49abb1 100644 --- a/spec/mailers/newflow_mailer_spec.rb +++ b/spec/mailers/newflow_mailer_spec.rb @@ -2,45 +2,56 @@ module Newflow describe NewflowMailer, type: :mailer do - # let(:user) { FactoryBot.create :user, first_name: 'John', last_name: 'Doe', suffix: 'Jr.' } - # let(:email) { FactoryBot.create :email_address, value: 'to@example.org', - # user_id: user.id, confirmation_code: '1234', confirmation_pin: '123456' } - - describe '' do - # it 'has basic header and from info and greeting' do - # mail = ConfirmationMailer.instructions email_address: email - - # expect(mail.header['to'].to_s).to eq('"John Doe Jr." ') - # expect(mail.from).to eq(["noreply@openstax.org"]) - # expect(mail.body.encoded).to include("Hi #{user.casual_name}") - # end - - # it 'does not include PIN when directed not to' do - # mail = ConfirmationMailer.instructions email_address: email, send_pin: false - - # expect(mail.subject).to eq("[OpenStax] Confirm your email address") - # expect(mail.body.encoded).not_to include('Your PIN') - # end - - # it "has PIN info when PIN attempts remain" do - # allow(ConfirmByPin).to receive(:sequential_failure_for) { Hashie::Mash.new('attempts_remaining?' => true)} - - # mail = ConfirmationMailer.instructions email_address: email, send_pin: true - - # expect(mail.subject).to eq("[OpenStax] Use PIN 123456 to confirm your email address") - # expect(mail.body.encoded).to include('Enter your 6-digit') - # expect(mail.body.encoded).to include('Your PIN: 123456') - # end - - # it "has just link when no PIN attempts remain" do - # allow(ConfirmByPin).to receive(:sequential_failure_for) { Hashie::Mash.new('attempts_remaining?' => false)} - - # mail = ConfirmationMailer.instructions email_address: email, send_pin: true - - # expect(mail.subject).to eq("[OpenStax] Confirm your email address") - # expect(mail.body.encoded).to include('Click on the link below') - # expect(mail.body.encoded).not_to include('Your PIN') - # end + let(:pin) { '123456' } + let(:code) { '1234' } + let(:confirm_url) { "http://localhost:2999/i/verify_email_by_code/#{code}" } + let(:user) { FactoryBot.create :user, first_name: 'John', last_name: 'Doe', suffix: 'Jr.' } + let(:email) { + FactoryBot.create :email_address, + value: 'to@example.org', + user_id: user.id, + confirmation_code: code, + confirmation_pin: pin + } + + describe 'sends email confirmation' do + it 'has basic header and from info and greeting' do + mail = NewflowMailer.signup_email_confirmation email_address: email + + expect(mail.header['to'].to_s).to eq('to@example.org') + expect(mail.from).to eq(["noreply@openstax.org"]) + expect(mail.body.encoded).to include("Welcome to OpenStax!") + end + + context 'when show_pin is not sent' do + it 'includes PIN info in the email' do + mail = NewflowMailer.signup_email_confirmation(email_address: email) + + expect(mail.subject).to eq("[OpenStax] Your OpenStax account PIN has arrived: #{pin}") + expect(mail.body.encoded).to include("#{pin}") + end + end + + context 'when show_pin is nil' do + it 'includes PIN info in the email' do + mail = NewflowMailer.signup_email_confirmation(email_address: email, show_pin: nil) + + expect(mail.subject).to eq("[OpenStax] Your OpenStax account PIN has arrived: #{pin}") + expect(mail.body.encoded).to include("#{pin}") + end + end + + context 'when show_pin is false' do + it 'excludes the pin code from the email' do + mail = NewflowMailer.signup_email_confirmation(email_address: email, show_pin: false) + + expect(mail.subject).to eq("[OpenStax] Confirm your email address") + expect(mail.body.encoded).to include("#{pin}") + end + end end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b570bd38cd..fe9450846e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -9,8 +9,7 @@ it { is_expected.to validate_presence_of(:role ) } it { is_expected.to validate_presence_of(:school_type ) } - it { is_expected.to validate_uniqueness_of(:uuid ).case_insensitive } - it { is_expected.to validate_uniqueness_of(:support_identifier).case_insensitive } + it { is_expected.to validate_uniqueness_of(:uuid).case_insensitive } context 'when the user is activated' do let(:user) { User.new.tap {|u| u.state = 'activated'} } @@ -135,27 +134,6 @@ end end - context 'support_identifier' do - it 'is generated when created' do - user = FactoryBot.create :user - expect(user.support_identifier).to start_with('cs') - expect(user.support_identifier.length).to eq(11) - end - - it 'cannot be updated' do - user = FactoryBot.create :user - old_identifier = user.support_identifier - user.update(first_name: 'New') - expect(user.reload.first_name).to eq('New') - expect(user.support_identifier).to eq(old_identifier) - - new_identifier = "cs_#{SecureRandom.hex(4)}" - user.support_identifier = new_identifier - user.save - expect(user.reload.support_identifier).to eq(old_identifier) - end - end - context 'username' do it 'must be unique (case-insensitive) on creation, if provided' do user_1 = FactoryBot.create :user, username: "MyUs3Rn4M3" diff --git a/spec/representers/api/v1/find_user_representer_spec.rb b/spec/representers/api/v1/find_user_representer_spec.rb index 9e74130e33..773a7d7640 100644 --- a/spec/representers/api/v1/find_user_representer_spec.rb +++ b/spec/representers/api/v1/find_user_representer_spec.rb @@ -30,20 +30,6 @@ end end - context 'support_identifier' do - it 'can be read' do - payload.support_identifier = SecureRandom.uuid - expect(representer.to_hash['support_identifier']).to eq payload.support_identifier - end - - it 'cannot be written (attempts are silently ignored)' do - hash = { 'support_identifier' => SecureRandom.uuid } - - expect(payload).not_to receive(:support_identifier=) - expect { representer.from_hash(hash) }.not_to change { payload.support_identifier } - end - end - context 'external_id' do it 'cannot be read' do payload.external_id = SecureRandom.uuid diff --git a/spec/representers/api/v1/user_representer_spec.rb b/spec/representers/api/v1/user_representer_spec.rb index 66f3e0f8aa..18232a4ec4 100644 --- a/spec/representers/api/v1/user_representer_spec.rb +++ b/spec/representers/api/v1/user_representer_spec.rb @@ -27,11 +27,10 @@ expect(representer.to_hash['consent_preferences']).to eq user.consent_preferences end - it 'cannot be written (attempts are silently ignored)' do - hash = { 'support_identifier' => "cs_#{SecureRandom.hex(4)}" } - - expect(user).not_to receive(:support_identifier=) - expect { representer.from_hash(hash) }.not_to change { user.reload.support_identifier } + it 'can be written' do + hash = { 'consent_preferences' => {} } + expect(user).to receive(:consent_preferences=).with({}).and_call_original + expect { representer.from_hash(hash) }.to change { user.consent_preferences } end end diff --git a/spec/routines/admin/import_users_spec.rb b/spec/routines/admin/import_users_spec.rb index e0aff29814..20d9e907a9 100644 --- a/spec/routines/admin/import_users_spec.rb +++ b/spec/routines/admin/import_users_spec.rb @@ -32,7 +32,6 @@ expect(user_1.login_token_expires_at).to be_nil expect(user_1.role).to eq 'unknown_role' expect(user_1.signed_external_data).to be_nil - expect(user_1.support_identifier).to eq 'cs_5d1edbb0' expect(user_1.is_test).to be_nil expect(user_1.school_type).to eq 'unknown_school_type' expect(user_1.identity.password_digest).to( @@ -78,7 +77,6 @@ expect(user_2.login_token_expires_at).to be_nil expect(user_2.role).to eq 'unknown_role' expect(user_2.signed_external_data).to be_nil - expect(user_2.support_identifier).to eq 'cs_6cf1cf5e' expect(user_2.is_test).to be_nil expect(user_2.school_type).to eq 'unknown_school_type' expect(user_2.identity.password_digest).to( @@ -111,7 +109,6 @@ expect(user_3.login_token_expires_at).to be_nil expect(user_3.role).to eq 'unknown_role' expect(user_3.signed_external_data).to be_nil - expect(user_3.support_identifier).to eq 'cs_fa68f2a5' expect(user_3.is_test).to be_nil expect(user_3.school_type).to eq 'unknown_school_type' expect(user_3.identity).to be_nil diff --git a/spec/routines/admin/search_users_spec.rb b/spec/routines/admin/search_users_spec.rb index 0187c38230..edeb445a2a 100644 --- a/spec/routines/admin/search_users_spec.rb +++ b/spec/routines/admin/search_users_spec.rb @@ -56,20 +56,6 @@ expect(outcome).to eq [user_1] end - it "should match on full support_identifier" do - support_identifier = user_1.support_identifier - outcome = described_class.call("support_identifier:#{support_identifier}").outputs.items.to_a - expect(outcome).to eq [user_1] - end - - it "should match on partial support_identifier" do - partial_support_identifier = user_1.support_identifier.first(10).last(9) - outcome = described_class.call( - "support_identifier:#{partial_support_identifier}" - ).outputs.items.to_a - expect(outcome).to eq [user_1] - end - it "should match based on a partial email address" do email = user_1.contact_infos.email_addresses.order(:value).first.value.split('@').first outcome = described_class.call("email:#{email}").outputs.items.to_a @@ -84,17 +70,11 @@ end it "should match any fields when no prefix given" do - [ user_1, user_2, user_3, user_4 ].each_with_index do |user, index| - User.where(id: user.id).update_all(support_identifier: "cs_#{index}") - end outcome = described_class.call("st").outputs.items.to_a expect(outcome).to eq [user_4, user_3, user_1] end it "should match any fields when no prefix given and intersect when prefix given" do - [ user_1, user_2, user_3, user_4 ].each_with_index do |user, index| - User.where(id: user.id).update_all(support_identifier: "cs_#{index}") - end outcome = described_class.call("John first_name:John").outputs.items.to_a expect(outcome).to eq [user_3, user_1] end @@ -105,9 +85,6 @@ end it "should gather space-separated unprefixed search terms" do - [ user_1, user_2, user_3, user_4 ].each_with_index do |user, index| - User.where(id: user.id).update_all(support_identifier: "cs_#{index}") - end outcome = described_class.call("john strav").outputs.items.to_a expect(outcome).to eq [user_1] end diff --git a/spec/routines/search_users_spec.rb b/spec/routines/search_users_spec.rb index 11b3ffad83..92f587f3f3 100644 --- a/spec/routines/search_users_spec.rb +++ b/spec/routines/search_users_spec.rb @@ -145,25 +145,11 @@ expect(outcome).to eq [user_4] end - it 'should not match by support_identifier' do - outcome = described_class.call( - "support_identifier:#{user_3.support_identifier}" - ).outputs.items.to_a - expect(outcome).to eq [] - end - it 'should match by external_id' do outcome = described_class.call("external_id:#{user_4.external_ids.first.external_id}").outputs.items.to_a expect(outcome).to eq [user_4] end - it 'should not match by support_identifier' do - outcome = described_class.call( - "support_identifier:#{user_3.support_identifier}" - ).outputs.items.to_a - expect(outcome).to eq [] - end - context "sorting" do let!(:bob_brown) do diff --git a/spec/support/user_hash.rb b/spec/support/user_hash.rb index 10946035f9..63e9ccb743 100644 --- a/spec/support/user_hash.rb +++ b/spec/support/user_hash.rb @@ -11,7 +11,6 @@ def user_matcher(user, include_private_data: false) title: user.title, suffix: user.suffix, uuid: user.uuid, - support_identifier: user.support_identifier, consent_preferences: user.consent_preferences, is_test: user.is_test?, is_administrator: user.is_administrator?,