diff --git a/.env.example b/.env.example index 1ed569dbd..9768429be 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,12 @@ # # ------------------------------------------------------------------------------ +# +DEV_EMAIL=your.email@education.gov.uk +DEV_GOV_ONE_TOKEN=urn:fdc:gov.uk:2022:23-random-alpha-numeric +DEV_FIRST_NAME=First +DEV_LAST_NAME=Last + # Display console information VERBOSE=true # Display debugging information diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af1366b39..615e9b8ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,11 +84,11 @@ jobs: name: Run rubocop run: bundle exec rubocop # Answer migration feature test - # - - # name: Run test suite - # run: bundle exec rspec - # env: - # DISABLE_USER_ANSWER: true + - + name: Run test suite + run: bundle exec rspec + env: + DISABLE_USER_ANSWER: true # Gov One feature test - name: Run test suite diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a34d6e740..26f2b366a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-auto-gen-timestamp` -# using RuboCop version 1.59.0. +# using RuboCop version 1.60.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -11,11 +11,12 @@ Lint/ShadowedException: Exclude: - 'app/models/concerns/to_csv.rb' -# Offense count: 1 +# Offense count: 2 # Configuration parameters: AllowComments, AllowNil. Lint/SuppressedException: Exclude: - 'lib/tasks/cms.rake' + - 'spec/lib/migrate_training_spec.rb' # Offense count: 2 # Configuration parameters: Database, Include. @@ -25,13 +26,14 @@ Rails/BulkChangeTable: Exclude: - 'db/migrate/20220617020303_add_summative_assessment_fields_to_user_answers.rb' -# Offense count: 3 +# Offense count: 4 # Configuration parameters: Include. # Include: db/**/*.rb Rails/CreateTableWithTimestamps: Exclude: - 'db/migrate/20220419121105_create_ahoy_visits_and_events.rb' - 'db/migrate/20230316130014_create_releases.rb' + - 'db/migrate/20240119091634_create_assessments.rb' # Offense count: 4 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -44,7 +46,7 @@ Rails/Output: - 'app/services/content_integrity.rb' - 'app/services/dashboard.rb' -# Offense count: 10 +# Offense count: 17 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: AllowImplicitReturn, AllowedReceivers. Rails/SaveBang: @@ -52,8 +54,11 @@ Rails/SaveBang: - 'app/controllers/registrations_controller.rb' - 'app/controllers/training/responses_controller.rb' - 'app/forms/registration/setting_type_form.rb' + - 'app/models/user.rb' + - 'app/services/assessment_progress.rb' - 'config/sitemap.rb' - 'lib/content_seed.rb' + - 'lib/migrate_training.rb' - 'lib/seed_images.rb' - 'spec/forms/registration/setting_type_form_spec.rb' @@ -65,3 +70,9 @@ Rails/Validation: Exclude: - 'app/models/course.rb' - 'app/models/training/module.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/IdenticalConditionalBranches: + Exclude: + - 'app/services/module_progress.rb' diff --git a/README.md b/README.md index f50582b0a..a4329f396 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,23 @@ settings the following classes can be added: - `data-hj-suppress` to redact additional user information - `data-hj-allow` to allow data that is automatically redacted +## Azure + +Production console access + +- https://eyrecovery-dev.scm.azurewebsites.net/webssh/host +- https://eyrecovery-stage.scm.azurewebsites.net/webssh/host +- https://eyrecovery-prod.scm.azurewebsites.net/webssh/host + + +## Programmatic testing + +```ruby +bravo = Training::Module.by_name('bravo') +data = ContentTestSchema.new(mod: bravo).call(pass: false).compact +file = Rails.root.join('spec/support/ast/bravo-fail.yml') +File.open(file, 'w') { |file| file.write(data.to_yaml) } +``` --- diff --git a/app/controllers/training/assessments_controller.rb b/app/controllers/training/assessments_controller.rb index a30115712..b34844da0 100644 --- a/app/controllers/training/assessments_controller.rb +++ b/app/controllers/training/assessments_controller.rb @@ -13,7 +13,7 @@ class AssessmentsController < ApplicationController layout 'hero' def new - assessment.archive! + assessment.retake! # TODO: permit testers to try again redirect_to training_module_page_path(mod.name, mod.assessment_intro_page.name) end diff --git a/app/controllers/training/questions_controller.rb b/app/controllers/training/questions_controller.rb index ff6b35a8b..c03641773 100644 --- a/app/controllers/training/questions_controller.rb +++ b/app/controllers/training/questions_controller.rb @@ -1,3 +1,14 @@ +# Question flavours: +# +# Tests: +# - formative question (immediate feedback) +# - summative question (grouped by assessment) +# +# Opinions: +# - confidence question (static options) +# - feedback question (dynamic options) +# +# module Training class QuestionsController < ApplicationController include Learning diff --git a/app/controllers/training/responses_controller.rb b/app/controllers/training/responses_controller.rb index 47b9a30a8..c24a6c88b 100644 --- a/app/controllers/training/responses_controller.rb +++ b/app/controllers/training/responses_controller.rb @@ -1,3 +1,8 @@ +# +# TODO: Logic around potential FK question_name changes that could cause an assessment to contain more than 10 responses +# TODO: Checks that scores are numeric, otherwise zero is recorded +# +# module Training class ResponsesController < ApplicationController include Learning @@ -25,7 +30,7 @@ def update # @note migrate from user_answer to response # @see User#response_for def response_params - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? params.require(:response).permit! else params.require(:user_answer).permit! @@ -38,8 +43,8 @@ def response_params def save_response! correct_answers = content.confidence_question? ? true : content.correct_answers.eql?(user_answers) - if ENV['DISABLE_USER_ANSWER'].present? - current_user_response.update(answers: user_answers, correct: correct_answers, schema: content.schema) + if Rails.application.migrated_answers? + current_user_response.update(answers: user_answers, correct: correct_answers) else current_user_response.update(answer: user_answers, correct: correct_answers) end @@ -51,7 +56,7 @@ def user_answers end def redirect - assessment.complete! if content.last_assessment? + assessment.grade! if content.last_assessment? if content.formative_question? redirect_to training_module_question_path(mod.name, content.name) @@ -62,7 +67,7 @@ def redirect # @return [Event] Update action def track_question_answer - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? track('questionnaire_answer', uid: content.id, mod_uid: mod.id, diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/authentication_helper.rb similarity index 97% rename from app/helpers/gov_one_helper.rb rename to app/helpers/authentication_helper.rb index 23cd93f05..d8792e1ad 100644 --- a/app/helpers/gov_one_helper.rb +++ b/app/helpers/authentication_helper.rb @@ -1,4 +1,4 @@ -module GovOneHelper +module AuthenticationHelper # @return [URI] def login_uri params = { diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb index b1f835674..3868f9164 100644 --- a/app/helpers/content_helper.rb +++ b/app/helpers/content_helper.rb @@ -1,6 +1,7 @@ module ContentHelper # @see [CustomMarkdown] # @param key [String] + # @param args [Hash] # @return [String] def m(key, headings_start_with: 'l', **args) markdown = I18n.exists?(key, scope: args[:scope]) ? t(key, **args) : key.to_s @@ -41,15 +42,13 @@ def icon(icon, size: 2, **) aria: { label: "#{icon} icon" } end - # @param success [Boolean] - # @param score [Integer] + # @param assessment [AssessmentProgress] # @return [String] - def results_banner(success:, score:) - state = success ? :pass : :fail - title = t(".#{state}.heading") - text = t(".#{state}.text", score: score) - - govuk_notification_banner(title_text: title, text: m(text)) + def assessment_banner(assessment) + govuk_notification_banner( + title_text: t("training.assessments.show.#{assessment.status}.heading"), + text: m("training.assessments.show.#{assessment.status}.text", score: assessment.score.to_i), + ) end # @param status [String, Symbol] diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb index d07dad60c..082f2bd31 100644 --- a/app/helpers/link_helper.rb +++ b/app/helpers/link_helper.rb @@ -62,12 +62,16 @@ def link_to_previous # @param mod [Training::Module] # @return [String, nil] def link_to_retake_or_results(mod) - return unless assessment_progress_service(mod).attempted? + if Rails.application.migrated_answers? + return unless assessment_progress_service(mod).graded? + else + return unless assessment_progress_service(mod).attempted? + end if assessment_progress_service(mod).failed? govuk_link_to 'Retake end of module test', new_training_module_assessment_path(mod.name), no_visited_state: true, class: 'card-link--retake' else - govuk_link_to 'View previous test result', training_module_assessment_path(mod.name, mod.assessment_results_page.name) + govuk_link_to 'View previous test result', training_module_assessment_path(mod.name, mod.assessment_results_page.name), no_visited_state: true, class: 'card-link--retake' end end end diff --git a/app/models/assessment.rb b/app/models/assessment.rb new file mode 100644 index 000000000..05e149aba --- /dev/null +++ b/app/models/assessment.rb @@ -0,0 +1,22 @@ +class Assessment < ApplicationRecord + include ToCsv + + belongs_to :user + has_many :responses, -> { where(question_type: 'summative') } + + scope :incomplete, -> { where(completed_at: nil) } + scope :complete, -> { where.not(completed_at: nil) } + + scope :passed, -> { where(passed: true) } + scope :failed, -> { where(passed: false) } + + # @return [Boolean] + def passed? + passed + end + + # @return [Boolean] + def graded? + score.present? + end +end diff --git a/app/models/data_analysis/average_pass_scores.rb b/app/models/data_analysis/average_pass_scores.rb index 24b80f934..f19815aad 100644 --- a/app/models/data_analysis/average_pass_scores.rb +++ b/app/models/data_analysis/average_pass_scores.rb @@ -11,14 +11,22 @@ def column_names ] end - # TODO: Upcoming changes to UserAssessment will make this type coercion unnecessary # @return [Array Mixed}>] def dashboard - UserAssessment.summative.passes.group(:module).average('CAST(score AS float)').map do |module_name, score| - { - module_name: module_name, - pass_score: score, - } + if Rails.application.migrated_answers? + Assessment.passed.group(:training_module).average(:score).map do |module_name, score| + { + module_name: module_name, + pass_score: score, + } + end + else + UserAssessment.summative.passes.group(:module).average('CAST(score AS float)').map do |module_name, score| + { + module_name: module_name, + pass_score: score, + } + end end end end diff --git a/app/models/data_analysis/high_fail_questions.rb b/app/models/data_analysis/high_fail_questions.rb index ccd37cf0f..f40392964 100644 --- a/app/models/data_analysis/high_fail_questions.rb +++ b/app/models/data_analysis/high_fail_questions.rb @@ -27,7 +27,7 @@ def dashboard # @return [Hash{Array => Integer}] def question_attempts - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? Response.summative.group(:training_module, :question_name).count else UserAnswer.summative.group(:module, :name).count @@ -36,8 +36,8 @@ def question_attempts # @return [Hash{Array => Integer}] def question_failures - if ENV['DISABLE_USER_ANSWER'].present? - Response.summative.where(correct: false).group(:training_module, :question_name).count + if Rails.application.migrated_answers? + Response.summative.incorrect.group(:training_module, :question_name).count else UserAnswer.summative.where(correct: false).group(:module, :name).count end diff --git a/app/models/data_analysis/modules_per_month.rb b/app/models/data_analysis/modules_per_month.rb index bac289e11..cefe79fa5 100644 --- a/app/models/data_analysis/modules_per_month.rb +++ b/app/models/data_analysis/modules_per_month.rb @@ -30,12 +30,20 @@ def dashboard # @return [Hash] def assessments_by_month - UserAssessment.summative.group_by { |assessment| assessment.created_at.strftime('%B %Y') } + if Rails.application.migrated_answers? + Assessment.all.group_by { |assessment| assessment.completed_at.strftime('%B %Y') } + else + UserAssessment.all.group_by { |assessment| assessment.created_at.strftime('%B %Y') } + end end # @return [Hash] def assessments_by_module_by_month - assessments_by_month.transform_values { |assessments| assessments.group_by(&:module) } + if Rails.application.migrated_answers? + assessments_by_month.transform_values { |assessments| assessments.group_by(&:training_module) } + else + assessments_by_month.transform_values { |assessments| assessments.group_by(&:module) } + end end end end diff --git a/app/models/data_analysis/resits_per_user.rb b/app/models/data_analysis/resits_per_user.rb index e68fba64c..d08008f13 100644 --- a/app/models/data_analysis/resits_per_user.rb +++ b/app/models/data_analysis/resits_per_user.rb @@ -34,7 +34,11 @@ def user_roles # @return [Hash{Array => Integer}] def assessments - UserAssessment.summative.group(:module, :user_id).count + if Rails.application.migrated_answers? + Assessment.order(:user_id).group(:training_module, :user_id).count + else + UserAssessment.summative.group(:module, :user_id).count + end end # @return [Hash{Array => Integer}] diff --git a/app/models/data_analysis/users_not_passing.rb b/app/models/data_analysis/users_not_passing.rb index 623418cee..79484cf35 100644 --- a/app/models/data_analysis/users_not_passing.rb +++ b/app/models/data_analysis/users_not_passing.rb @@ -25,12 +25,21 @@ def dashboard # @return [Hash{String => Integer}] def total_users_not_passing_per_module - UserAssessment.summative - .group(:module, :user_id) - .count - .reject { |(module_name, user_id), _| UserAssessment.summative.where(module: module_name, user_id: user_id).passes.exists? } - .group_by { |(module_name, _), _| module_name } - .transform_values(&:size) + if Rails.application.migrated_answers? + Assessment.all + .group(:training_module, :user_id) + .count + .reject { |(module_name, user_id), _| Assessment.all.where(training_module: module_name, user_id: user_id).passed.exists? } + .group_by { |(module_name, _), _| module_name } + .transform_values(&:size) + else + UserAssessment.all + .group(:module, :user_id) + .count + .reject { |(module_name, user_id), _| UserAssessment.all.where(module: module_name, user_id: user_id).passes.exists? } + .group_by { |(module_name, _), _| module_name } + .transform_values(&:size) + end end end end diff --git a/app/models/response.rb b/app/models/response.rb index 18c84ebfe..f902d7990 100644 --- a/app/models/response.rb +++ b/app/models/response.rb @@ -1,17 +1,24 @@ # -# Persisted answers to CMS Questions +# Submitted response to Training::Question # class Response < ApplicationRecord include ToCsv belongs_to :user + belongs_to :assessment, optional: true validates :answers, presence: true - scope :unarchived, -> { where(archived: false) } - scope :formative, -> { where('schema->>4 = ?', 'formative') } - scope :summative, -> { where('schema->>4 = ?', 'summative') } - scope :confidence, -> { where('schema->>4 = ?', 'confidence') } + scope :incorrect, -> { where(correct: false) } + scope :correct, -> { where(correct: true) } + + scope :ungraded, -> { where(graded: false) } + scope :graded, -> { where(graded: true) } + + scope :formative, -> { where(question_type: 'formative') } + scope :summative, -> { where(question_type: 'summative') } + scope :confidence, -> { where(question_type: 'confidence') } + scope :feedback, -> { where(question_type: 'feedback') } delegate :to_partial_path, :legend, to: :question @@ -25,16 +32,20 @@ def question mod.page_by_name(question_name) end - # Disable if already answered unless confidence check # @return [Array] def options - if question.confidence_question? - question.options(checked: answers) - else + if question.formative_question? || assessment&.graded? question.options(checked: answers, disabled: responded?) + else + question.options(checked: answers) end end + # @return [Boolean] + def archived? + archived + end + # @return [Boolean] def responded? answers.any? diff --git a/app/models/training/content.rb b/app/models/training/content.rb index 5350ce9ce..c6efe8d50 100644 --- a/app/models/training/content.rb +++ b/app/models/training/content.rb @@ -22,7 +22,7 @@ def debug_summary uid: #{id} module uid: #{parent.id} module name: #{parent.name} - published at: #{published_at} + published at: #{published_at || 'Management Key Missing'} page type: #{page_type} --- diff --git a/app/models/training/question.rb b/app/models/training/question.rb index a18d5b70e..d01b2e61e 100644 --- a/app/models/training/question.rb +++ b/app/models/training/question.rb @@ -48,7 +48,7 @@ def true_false? answer.options.map(&:label).sort.eql? %w[False True] end - # TODO: remove once user_answers is removed + # TODO: Non longer required if Rails.application.migrated_answers? # @return [String] def assessments_type { @@ -58,11 +58,16 @@ def assessments_type }.fetch(page_type.to_sym) end + # TODO: remove once CMS model page_types have suffix removed + # @return [String] def question_type { formative_questionnaire: 'formative', + formative: 'formative', summative_questionnaire: 'summative', + summative: 'summative', confidence_questionnaire: 'confidence', + confidence: 'confidence', }.fetch(page_type.to_sym) end diff --git a/app/models/user.rb b/app/models/user.rb index 8775968f0..e3ce9df3b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -68,7 +68,12 @@ def self.find_or_create_from_gov_one(email:, gov_one_id:) has_many :responses has_many :user_answers - has_many :user_assessments + if Rails.application.migrated_answers? + has_many :assessments + else + has_many :user_assessments + end + has_many :visits has_many :events has_many :notes @@ -111,8 +116,13 @@ def self.find_or_create_from_gov_one(email:, gov_one_id:) scope :without_notes, -> { where.not(id: with_notes) } # assessments - scope :with_assessments, -> { joins(:user_assessments) } - scope :with_passing_assessments, -> { with_assessments.merge(UserAssessment.passes) } + if Rails.application.migrated_answers? + scope :with_assessments, -> { joins(:assessments) } + scope :with_passing_assessments, -> { with_assessments.merge(Assessment.passed) } + else + scope :with_assessments, -> { joins(:user_assessments) } + scope :with_passing_assessments, -> { with_assessments.merge(UserAssessment.passes) } + end # events scope :with_events, -> { joins(:events) } @@ -185,11 +195,20 @@ def update_with_password(params) # @param content [Training::Question] # @return [UserAnswer, Response] def response_for(content) - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? + if content.summative_question? + # creates new assessment on first summative_question + assessment = + assessments.passed.find_by(training_module: content.parent.name) || + assessments.incomplete.find_by(training_module: content.parent.name) || # needed? + assessments.create(training_module: content.parent.name, started_at: Time.zone.now) + end + responses.find_or_initialize_by( - question_name: content.name, + assessment_id: assessment&.id, training_module: content.parent.name, - archived: false, + question_name: content.name, + question_type: content.question_type, # TODO: RENAME options for Question#page_type removing "questionnaire" suffix ) else user_answers.find_or_initialize_by( diff --git a/app/models/user_answer.rb b/app/models/user_answer.rb index 3108df9a9..ec038439e 100644 --- a/app/models/user_answer.rb +++ b/app/models/user_answer.rb @@ -1,3 +1,4 @@ +# TODO: to be deprecated class UserAnswer < ApplicationRecord include ToCsv diff --git a/app/models/user_assessment.rb b/app/models/user_assessment.rb index 6bdc3f292..a7690434d 100644 --- a/app/models/user_assessment.rb +++ b/app/models/user_assessment.rb @@ -1,7 +1,9 @@ +# TODO: to be deprecated class UserAssessment < ApplicationRecord include ToCsv has_many :user_answers + scope :summative, -> { where(assessments_type: 'summative_assessment') } scope :passes, -> { where(status: 'passed') } scope :fails, -> { where(status: 'failed') } diff --git a/app/services/assessment_progress.rb b/app/services/assessment_progress.rb index 192905622..ff289d16b 100644 --- a/app/services/assessment_progress.rb +++ b/app/services/assessment_progress.rb @@ -1,10 +1,11 @@ -# Summative assessment evaluation +# Assessment grading # +# An assessment is created when the first summative question is answered? class AssessmentProgress extend Dry::Initializer - # @return [Integer] - THRESHOLD = 70 + # @return [Float] + THRESHOLD = 70.0 # @!attribute [r] user # @return [User] @@ -15,33 +16,48 @@ class AssessmentProgress # @see Training::ResponsesController#redirect # - # @return [Array] - def complete! - update_responses(user_assessment_id: user_assessment.id) + # @return [Array, Assessment, nil] + def grade! + if Rails.application.migrated_answers? + unless graded? + assessment.update( + score: score, + passed: passed?, + completed_at: Time.zone.now, + ) + end + else + update_responses(user_assessment_id: assessment.id) + end end # @see Training::AssessmentsController#new # - # @return [Array] - def archive! - update_responses(archived: true) - end - - # @see ContentHelper#results_banner - # - # @return [Hash] - def result - { success: passed?, score: score.to_i } + # @return [Array] deprecate + def retake! + if Rails.application.migrated_answers? + :no_op + else + update_responses(archived: true) + end end - # @return [Symbol] + # @see ContentHelper#assessment_banner + # @return [Symbol] I18n key def status - passed? ? :passed : :failed + passed? ? :pass : :fail end + # @return [Boolean, nil] + delegate :graded?, to: :assessment, allow_nil: true + # @return [Boolean] def passed? - score >= THRESHOLD + if Rails.application.migrated_answers? + assessment&.passed? || score >= THRESHOLD + else + score >= THRESHOLD + end end # @return [Boolean] @@ -49,17 +65,24 @@ def failed? !passed? end - # TODO: assessments_type is a useless field and should be removed - # + # deprecate see track_events # @return [Boolean] CTA failed_attempt state def attempted? - user.user_assessments.where(assessments_type: 'summative_assessment', module: mod.name).any? + if Rails.application.migrated_answers? + assessment.present? + else + user.user_assessments.where(assessments_type: 'summative_assessment', module: mod.name).any? + end end # @return [Float] percentage of correct responses def score - (correct_responses.count.to_f / mod.summative_questions.count) * 100 - rescue ZeroDivisionError + if Rails.application.migrated_answers? + assessment.score || (correct_responses.count.to_f / mod.summative_questions.count) * 100 + else + (correct_responses.count.to_f / mod.summative_questions.count) * 100 + end + rescue ZeroDivisionError, NoMethodError 0.0 end @@ -68,14 +91,11 @@ def incorrect_responses assessment_responses.reject(&:correct?) end -private - + # delegate :responses, to: :assessment # swap to delegation # @return [Array, Array] def assessment_responses - if ENV['DISABLE_USER_ANSWER'].present? - user.responses - .unarchived.where(training_module: mod.name) - .select { |response| response.question.summative_question? } + if Rails.application.migrated_answers? + assessment.responses else user.user_answers .not_archived.where(module: mod.name) @@ -83,25 +103,30 @@ def assessment_responses end end - # @return [Array] + # @return [Array] deprecate def update_responses(params) assessment_responses.each { |response| response.update!(params) } end - # @note #correct? uses answers validate against question + # @note #correct? validates against current question options # @return [Array] def correct_responses assessment_responses.select(&:correct?) end - # @return [UserAssessment] - def user_assessment - @user_assessment ||= UserAssessment.create!( - user_id: user.id, - score: score.to_i, - status: status, - module: mod.name, - assessments_type: 'summative_assessment', - ) + # TODO: drop memoisation + # @return [UserAssessment, Assessment] + def assessment + @assessment ||= if Rails.application.migrated_answers? + user.assessments.where(training_module: mod.name).order(:started_at).last + else + UserAssessment.create!( + user_id: user.id, + score: score.to_i, + status: (passed? ? 'passed' : 'failed'), + module: mod.name, + assessments_type: 'summative_assessment', + ) + end end end diff --git a/app/services/course_progress.rb b/app/services/course_progress.rb index 15f30f77b..acad196df 100644 --- a/app/services/course_progress.rb +++ b/app/services/course_progress.rb @@ -34,20 +34,21 @@ def course_completed? # @return [Array] def debug_summary - training_modules.map do |mod| + Training::Module.ordered.map { |mod| <<~SUMMARY + --- title: #{mod.title} - published at: #{mod.published_at} + published at: #{mod.published_at || 'Management Key Missing'} position: #{mod.position} name: #{mod.name} draft: #{mod.draft?} started: #{started?(mod)} completed: #{completed?(mod)} - last: #{mod.thankyou_page&.name unless mod.draft?} - certificate: #{mod.certificate_page&.name unless mod.draft?} - milestone: #{module_progress(mod).milestone} + last: #{mod.thankyou_page&.name || 'N/A'} + certificate: #{mod.certificate_page&.name || 'N/A'} + milestone: #{module_progress(mod).milestone || 'N/A'} SUMMARY - end + }.join end # @param mod [Training::Module] diff --git a/app/services/module_progress.rb b/app/services/module_progress.rb index 404278620..344615198 100644 --- a/app/services/module_progress.rb +++ b/app/services/module_progress.rb @@ -104,13 +104,21 @@ def none?(items) # @see AssessmentProgress # @return [Boolean] def failed_attempt? - summative_assessment.attempted? && summative_assessment.failed? + if Rails.application.migrated_answers? + summative_assessment.attempted? && summative_assessment.failed? + else + summative_assessment.attempted? && summative_assessment.failed? + end end # @see AssessmentProgress # @return [Boolean] def successful_attempt? - summative_assessment.attempted? && summative_assessment.passed? + if Rails.application.migrated_answers? + summative_assessment.passed? + else + summative_assessment.attempted? && summative_assessment.passed? + end end # In progress modules with new pages that have been skipped diff --git a/app/views/training/assessments/show.html.slim b/app/views/training/assessments/show.html.slim index 83849a3d6..16350d541 100644 --- a/app/views/training/assessments/show.html.slim +++ b/app/views/training/assessments/show.html.slim @@ -7,14 +7,14 @@ - if debug? pre.debug_dump hr - | Class: #{ENV['DISABLE_USER_ANSWER'].present? ? 'Response' : 'UserAnswer'} + | Class: #{Rails.application.migrated_answers? ? 'Response' : 'UserAnswer'} .govuk-grid-row #assessment-results.govuk-grid-column-full h1.govuk-heading-l = t('.heading') - = results_banner(**assessment.result) + = assessment_banner(assessment) - assessment.incorrect_responses.each do |incorrect_response| = form_with model: incorrect_response, url: training_module_response_path(mod.name, content.name), method: :patch do |f| diff --git a/app/views/training/questions/_debug.html.slim b/app/views/training/questions/_debug.html.slim index f18c2b222..ac7358407 100644 --- a/app/views/training/questions/_debug.html.slim +++ b/app/views/training/questions/_debug.html.slim @@ -3,7 +3,7 @@ = content.debug_summary hr - | Class: #{ENV['DISABLE_USER_ANSWER'].present? ? 'Response' : 'UserAnswer'} + | Class: #{Rails.application.migrated_answers? ? 'Response' : 'UserAnswer'} hr | Question Options: - (content.answers || content.class::CONFIDENCE_OPTIONS).map do |option| diff --git a/app/views/training/questions/show.html.slim b/app/views/training/questions/show.html.slim index 03a847baf..15241f3a3 100644 --- a/app/views/training/questions/show.html.slim +++ b/app/views/training/questions/show.html.slim @@ -25,8 +25,8 @@ .govuk-button-group = link_to_previous - - if content.confidence_question? || !current_user_response.responded? + - unless content.formative_question? && current_user_response.responded? = f.govuk_submit content.next_button_text - else = f.govuk_submit 'Responded', class: 'govuk-visually-hidden', disabled: true - = link_to_next + = link_to_next \ No newline at end of file diff --git a/cms/bulk_edit/question_types.js b/cms/bulk_edit/question_types.js new file mode 100644 index 000000000..0f55fc95d --- /dev/null +++ b/cms/bulk_edit/question_types.js @@ -0,0 +1,8 @@ +module.exports = function(migration) { + + const course = migration.createContentType('course', { + name: 'Course', + displayField: 'service_name', + description: 'Top-level site-wide configuration' + }) +} \ No newline at end of file diff --git a/cms/migrate/02-create-question.js b/cms/migrate/02-create-question.js index 8e69e3047..bec12dac8 100644 --- a/cms/migrate/02-create-question.js +++ b/cms/migrate/02-create-question.js @@ -26,14 +26,14 @@ module.exports = function(migration) { type: 'Symbol', required: true, defaultValue: { - 'en-US': 'formative_questionnaire', + 'en-US': 'formative', }, validations: [ { in: [ - 'formative_questionnaire', - 'summative_questionnaire', - 'confidence_questionnaire', + 'formative', + 'summative', + 'confidence', ] } ] diff --git a/config/application.rb b/config/application.rb index fe7fc6c08..29a814306 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,18 +1,11 @@ require_relative 'boot' - require 'rails/all' -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) - -require 'grover' - -ALLOWED_TAGS = %w[p ul li div ol strong].freeze +# require 'grover' module EarlyYearsFoundationRecovery class Application < Rails::Application - # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 # @see ErrorsController config.exceptions_app = routes @@ -21,18 +14,13 @@ class Application < Rails::Application g.test_framework :rspec end - # Configuration for the application, engines, and railties goes here. - # - # These settings can be overridden in specific environments using the files - # in config/environments, which are processed later. - # # config.eager_load_paths << Rails.root.join("extras") # config.time_zone = ENV.fetch('TZ', 'Europe/London') config.service_url = (Rails.env.production? ? 'https://' : 'http://') + ENV.fetch('DOMAIN', 'child-development-training') config.middleware.use Grover::Middleware config.active_record.yaml_column_permitted_classes = [Symbol] - config.action_view.sanitized_allowed_tags = ALLOWED_TAGS + config.action_view.sanitized_allowed_tags = %w[p ul li div ol strong].freeze # Background Jobs config.active_job.queue_adapter = :que @@ -99,11 +87,23 @@ def maintenance? Types::Params::Bool[ENV.fetch('MAINTENANCE', false)] end + # + # Feature flags + # + # @return [Boolean] def gov_one_login? Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)] end + def migrated_answers? + Types::Params::Bool[ENV.fetch('DISABLE_USER_ANSWER', false)] + end + + # + # Significant dates + # + # @return [ActiveSupport::TimeWithZone] def public_beta_launch_date Time.zone.local(2023, 2, 9, 15, 0, 0) diff --git a/db/migrate/20220623005225_create_user_assessments.rb b/db/migrate/20220623005225_create_user_assessments.rb index 359cfb02c..82faae683 100644 --- a/db/migrate/20220623005225_create_user_assessments.rb +++ b/db/migrate/20220623005225_create_user_assessments.rb @@ -3,14 +3,11 @@ def up create_table :user_assessments do |t| t.references :user, null: false, foreign_key: true t.string :score - # TODO: make type Boolean - t.string :status # passed | failed - # TODO: rename :training_module_name + t.string :status t.string :module t.string :assessments_type t.boolean :archived - # TODO: rename :completed_at - t.datetime :completed # date time module completed not sure if we need this + t.datetime :completed t.index %i[score status] t.timestamps end diff --git a/db/migrate/20240119083907_remove_schema_from_responses.rb b/db/migrate/20240119083907_remove_schema_from_responses.rb new file mode 100644 index 000000000..c49dd13c5 --- /dev/null +++ b/db/migrate/20240119083907_remove_schema_from_responses.rb @@ -0,0 +1,7 @@ +class RemoveSchemaFromResponses < ActiveRecord::Migration[7.0] + def change + change_table :responses, bulk: true do |t| + t.remove :schema, type: :jsonb + end + end +end diff --git a/db/migrate/20240119091634_create_assessments.rb b/db/migrate/20240119091634_create_assessments.rb new file mode 100644 index 000000000..a04eab104 --- /dev/null +++ b/db/migrate/20240119091634_create_assessments.rb @@ -0,0 +1,14 @@ +class CreateAssessments < ActiveRecord::Migration[7.0] + def change + create_table :assessments do |t| + t.references :user, null: false, foreign_key: true + t.string :training_module, null: false + t.float :score + t.boolean :passed + t.datetime :started_at + t.datetime :completed_at + + t.index %i[score passed] + end + end +end diff --git a/db/migrate/20240119105439_add_question_type_to_responses.rb b/db/migrate/20240119105439_add_question_type_to_responses.rb new file mode 100644 index 000000000..a0a3c0f77 --- /dev/null +++ b/db/migrate/20240119105439_add_question_type_to_responses.rb @@ -0,0 +1,5 @@ +class AddQuestionTypeToResponses < ActiveRecord::Migration[7.0] + def change + add_column :responses, :question_type, :string + end +end diff --git a/db/migrate/20240119120916_rename_user_assessment_id_to_assessment_id_in_responses.rb b/db/migrate/20240119120916_rename_user_assessment_id_to_assessment_id_in_responses.rb new file mode 100644 index 000000000..20826af19 --- /dev/null +++ b/db/migrate/20240119120916_rename_user_assessment_id_to_assessment_id_in_responses.rb @@ -0,0 +1,6 @@ +class RenameUserAssessmentIdToAssessmentIdInResponses < ActiveRecord::Migration[7.0] + def change + remove_column :responses, :user_assessment_id, :integer + add_reference :responses, :assessment, foreign_key: true, null: true + end +end diff --git a/db/migrate/20240119163001_remove_archived_from_responses.rb b/db/migrate/20240119163001_remove_archived_from_responses.rb new file mode 100644 index 000000000..10d8fb874 --- /dev/null +++ b/db/migrate/20240119163001_remove_archived_from_responses.rb @@ -0,0 +1,7 @@ +class RemoveArchivedFromResponses < ActiveRecord::Migration[7.0] + def change + change_table :responses, bulk: true do |t| + t.remove :archived, type: :boolean + end + end +end diff --git a/db/schema.rb b/db/schema.rb index f8e6f3c01..ce18569a4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,6 +14,17 @@ # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "assessments", force: :cascade do |t| + t.bigint "user_id", null: false + t.string "training_module", null: false + t.float "score" + t.boolean "passed" + t.datetime "started_at" + t.datetime "completed_at" + t.index ["score", "passed"], name: "index_assessments_on_score_and_passed" + t.index ["user_id"], name: "index_assessments_on_user_id" + end + create_table "events", force: :cascade do |t| t.bigint "visit_id" t.bigint "user_id" @@ -122,13 +133,12 @@ t.string "training_module", null: false t.string "question_name", null: false t.jsonb "answers", default: [] - t.boolean "archived", default: false t.boolean "correct" - t.bigint "user_assessment_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.jsonb "schema" - t.index ["user_assessment_id"], name: "index_responses_on_user_assessment_id" + t.string "question_type" + t.bigint "assessment_id" + t.index ["assessment_id"], name: "index_responses_on_assessment_id" t.index ["user_id", "training_module", "question_name"], name: "user_question" t.index ["user_id"], name: "index_responses_on_user_id" end @@ -238,10 +248,11 @@ t.index ["visit_token"], name: "index_visits_on_visit_token", unique: true end + add_foreign_key "assessments", "users" add_foreign_key "module_releases", "releases" add_foreign_key "notes", "users" add_foreign_key "que_scheduler_audit_enqueued", "que_scheduler_audit", column: "scheduler_job_id", primary_key: "scheduler_job_id", name: "que_scheduler_audit_enqueued_scheduler_job_id_fkey" - add_foreign_key "responses", "user_assessments" + add_foreign_key "responses", "assessments" add_foreign_key "responses", "users" add_foreign_key "user_answers", "user_assessments" add_foreign_key "user_answers", "users" diff --git a/db/seeds/users.yml b/db/seeds/users.yml index c4bf2d284..2de92d945 100644 --- a/db/seeds/users.yml +++ b/db/seeds/users.yml @@ -20,3 +20,30 @@ completed@example.com: early_years_emails: true training_emails: true registration_complete: true + +<% if ENV['DEV_EMAIL'] %> +<%= ENV['DEV_EMAIL'] %>: + gov_one_id: <%= ENV['DEV_GOV_ONE_TOKEN'] %> + first_name: <%= ENV['DEV_FIRST_NAME'] %> + last_name: <%= ENV['DEV_LAST_NAME'] %> + + password: <%= Rails.configuration.user_password %> + confirmed_at: <%= Time.zone.now %> + created_at: <%= Time.zone.now %> + updated_at: <%= Time.zone.now %> + terms_and_conditions_agreed_at: <%= Time.zone.now %> + + registration_complete: true + private_beta_registration_complete: false + display_whats_new: false + training_emails: true + early_years_emails: true + + setting_type: other + setting_type_other: DfE + local_authority: + role_type: other + role_type_other: Developer + setting_type_id: other + early_years_experience: Not applicable +<% end %> diff --git a/lib/content_test_schema.rb b/lib/content_test_schema.rb index 1a5ae4fdd..9da0114a0 100644 --- a/lib/content_test_schema.rb +++ b/lib/content_test_schema.rb @@ -133,7 +133,7 @@ def question_answers # @param option [Integer] # @return [String] def field_name(option) - model = ENV['DISABLE_USER_ANSWER'].present? ? 'response' : 'user-answer' + model = Rails.application.migrated_answers? ? 'response' : 'user-answer' "#{model}-answers-#{option}-field" end diff --git a/lib/migrate_training.rb b/lib/migrate_training.rb new file mode 100644 index 000000000..c91072fdc --- /dev/null +++ b/lib/migrate_training.rb @@ -0,0 +1,154 @@ +# BEFORE +# +# UserAnswer is assigned UserAssessment upon answering the last question meaning: +# - foreign keys may be missing and not every UserAnswer has a UserAssessment +# +# AFTER +# +# Assessment is created with the first Response meaning: +# - every Assessment has a minimum of 1 Response +# - every Assessment has a maximum score of 100% +# +# +class MigrateTraining + class Error < StandardError + end + + extend Dry::Initializer + + # @return [Boolean] + option :truncate, Types::Bool, default: proc { false }, reader: :private + # @return [Boolean] + option :simulate, Types::Bool, default: proc { false }, reader: :private + # @return [Boolean] + option :verbose, Types::Bool, default: proc { true }, reader: :private + # @return [Integer, nil] + option :resume, Types::Integer, optional: true, reader: :private + + # @return [?] + def call + ActiveRecord::Base.transaction do + log "Migration started: #{Time.zone.now}" + truncate! if truncate + migrate! + log "Migration finished: #{Time.zone.now}" + raise Error if simulate + end + end + +private + + # @return [PG::Result] + def truncate! + log 'Truncate responses and assessments' + ActiveRecord::Base.connection.execute 'TRUNCATE responses, assessments RESTART IDENTITY' + end + + # @return [nil] + def migrate! + UserAnswer.find_each(start: resume) do |user_answer| + if valid?(user_answer) + response = process_user_answer(user_answer) + log response.attributes.to_json, alert: false + else + log "User: #{user_answer.user_id} UserAnswer: #{user_answer.id}" + end + end + end + + # @param user_answer [UserAnswer] + # @return [Boolean] + def valid?(user_answer) + if user_answer.name.blank? || + user_answer.module.blank? || + user_answer.assessments_type.blank? || + user_answer.question.nil? || + !user_answer.question.respond_to?(:question_type) + false + else + true + end + end + + # @param user_answer [UserAnswer] + # @return [Response] + def process_user_answer(user_answer) + assessment = process_user_assessment(user_answer) + params = response_params(user_answer) + + Response.create(**params, assessment_id: assessment&.id) + end + + # @param user_answer [UserAnswer] + # @return [Assessment, nil] + def process_user_assessment(user_answer) + return unless user_answer.question.summative_question? + + if user_answer.user_assessment_id.nil? + assessment = + Assessment + .create_with(started_at: user_answer.created_at) + .find_or_create_by(user_id: user_answer.user_id, training_module: user_answer.module) + else + user_assessment = UserAssessment.find(user_answer.user_assessment_id) + params = assessment_params(user_assessment) + assessment = Assessment.find_or_create_by(params) + + if assessment.score.nil? + if user_assessment.score.to_i > 100 + assessment.update(score: 100) + else + assessment.update(score: user_assessment.score) + end + end + end + + assessment + end + + # @param user_answer [UserAssessment] + # @return [HashMixed>] + def assessment_params(user_assessment) + { + id: user_assessment.id, # int primary key + user_id: user_assessment.user_id, # int foreign key + training_module: user_assessment.module, # string foreign key + passed: user_assessment.status.eql?('passed'), # bool + started_at: user_assessment.user_answers.order(:created_at).first.created_at, # datetime + completed_at: user_assessment.created_at, # datetime + } + end + + # @param user_answer [UserAnswer] + # @return [HashMixed>] + def response_params(user_answer) + { + id: user_answer.id, # int db primary key + user_id: user_answer.user_id, # int db foreign key + assessment_id: user_answer.user_assessment_id, # int db foreign key + training_module: user_answer.module, # string cms key + question_name: user_answer.name, # string cms key + question_type: user_answer.question.question_type, # string cms filter + answers: user_answer.answer, # array + correct: user_answer.correct, # bool + created_at: user_answer.created_at, # datetime + updated_at: user_answer.updated_at, # datetime + } + end + + # @param message [String] + # @param alert [Boolean] Send to Sentry + # @return [String, nil] + def log(message, alert: true) + if ENV['RAILS_LOG_TO_STDOUT'].present? + Rails.logger.info(message) + + if alert + alert_message = "#{self.class.name}: #{ENV['ENVIRONMENT']} - #{message}" + Sentry.capture_message(alert_message, level: :info) + end + elsif verbose + puts message + end + end +end diff --git a/lib/tasks/cms.rake b/lib/tasks/cms.rake index 2bf584cd1..c50696f8e 100644 --- a/lib/tasks/cms.rake +++ b/lib/tasks/cms.rake @@ -18,22 +18,30 @@ namespace :eyfs do desc 'Define Contentful entry models' task migrate: :environment do - migrations = Dir[Rails.root.join('cms/migrate/*')] - token = ContentfulRails.configuration.management_token - space = ContentfulRails.configuration.space - env = ContentfulRails.configuration.environment - - migrations.each do |file| + Dir[Rails.root.join('cms/migrate/*')].each do |file| system <<~CMD contentful space migration \ - --management-token #{token} \ - --space-id #{space} \ - --environment-id #{env} \ + --management-token #{ContentfulRails.configuration.management_token} \ + --space-id #{ContentfulRails.configuration.space} \ + --environment-id #{ContentfulRails.configuration.environment} \ --yes #{file} CMD end end + desc 'Change multiple Contentful entry values' + task bulk_edit: :environment do + Dir[Rails.root.join('cms/bulk_edit/*')].each do |file| + # system <<~CMD + # contentful space migration \ + # --management-token #{ContentfulRails.configuration.management_token} \ + # --space-id #{ContentfulRails.configuration.space} \ + # --environment-id #{ContentfulRails.configuration.environment} \ + # --yes #{file} + # CMD + end + end + desc 'Upload asset files to Contentful' task seed_images: :environment do require 'seed_images' diff --git a/lib/tasks/data.rake b/lib/tasks/data.rake deleted file mode 100644 index 8c0eab367..000000000 --- a/lib/tasks/data.rake +++ /dev/null @@ -1,33 +0,0 @@ -namespace :data do - desc 'Run all migrations' - task migrations: :environment do - Rake.application.in_namespace(:data) do |namespace| - namespace.tasks.each do |task| - next if task.name == 'data:migrations' - - puts "Invoking #{task.name}" - task.invoke - end - end - end - - desc 'Migrate user answers to responses' - task migrate_user_answers_to_responses: :environment do - puts 'Truncate responses' - ActiveRecord::Base.connection.execute('TRUNCATE responses RESTART IDENTITY CASCADE') - puts 'Migrating user answers to responses' - UserAnswer.all.find_each do |user_answer| - response = Response.create!( - user_id: user_answer.user_id, - training_module: user_answer.module, - question_name: user_answer.name, - answers: user_answer.answer, - correct: user_answer.correct, - schema: user_answer.question.schema, - user_assessment_id: user_answer.user_assessment_id, - ) - response.save!(validate: false) - end - puts 'Migrated user answers to responses' - end -end diff --git a/lib/tasks/eyfs.rake b/lib/tasks/eyfs.rake index d03212ddf..3f4b10c82 100644 --- a/lib/tasks/eyfs.rake +++ b/lib/tasks/eyfs.rake @@ -95,4 +95,10 @@ namespace :eyfs do end end end + + desc 'Migrate training data to response and assessment models' + task migrate_training_data: :environment do + require 'migrate_training' + MigrateTraining.new.call + end end diff --git a/spec/controllers/training/responses_controller_spec.rb b/spec/controllers/training/responses_controller_spec.rb index 3cfe6ad87..d7b9ab17d 100644 --- a/spec/controllers/training/responses_controller_spec.rb +++ b/spec/controllers/training/responses_controller_spec.rb @@ -4,7 +4,7 @@ before do sign_in create(:user, :registered) - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? patch :update, params: { training_module_id: 'alpha', id: question_name, @@ -20,7 +20,7 @@ end let(:records) do - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? Response.count else UserAnswer.count diff --git a/spec/decorators/module_overview_decorator_spec.rb b/spec/decorators/module_overview_decorator_spec.rb index e5c7e5b7c..4716256a8 100644 --- a/spec/decorators/module_overview_decorator_spec.rb +++ b/spec/decorators/module_overview_decorator_spec.rb @@ -36,7 +36,11 @@ context 'when the assessment was failed' do before do - fail_summative_assessment(bravo) + if Rails.application.migrated_answers? + create :assessment, :failed, user: user, training_module: 'bravo' + else + create :user_assessment, user_id: user.id, module: 'bravo' + end end it 'retakes the assessment' do diff --git a/spec/factories/assessment.rb b/spec/factories/assessment.rb new file mode 100644 index 000000000..e071ea36b --- /dev/null +++ b/spec/factories/assessment.rb @@ -0,0 +1,20 @@ +FactoryBot.define do + factory :assessment do + association :user, :registered + + training_module { 'alpha' } + started_at { Time.zone.now } + + trait :failed do + passed { false } + score { 0.0 } + completed_at { Time.zone.now } + end + + trait :passed do + passed { true } + score { 100.0 } + completed_at { Time.zone.now } + end + end +end diff --git a/spec/factories/response.rb b/spec/factories/response.rb index 41a23addb..537cf580f 100644 --- a/spec/factories/response.rb +++ b/spec/factories/response.rb @@ -3,57 +3,7 @@ user training_module { 'alpha' } question_name { '1-1-4-1' } + question_type { 'formative' } answers { [(1..5).to_a.sample] } - - trait :correct do - correct { true } - end - - trait :incorrect do - correct { false } - end - - trait :confidence_check do - schema do - [ - '9-9-9', - 'confidence_check', - 'How confident are you? - Select from following', - { - "correct": [1, 2, 3, 4, 5], - "options": [ - { "id": 1, "label": 'Strongly agree' }, - { "id": 2, "label": 'Agree' }, - { "id": 3, "label": 'Neither agree nor disagree' }, - { "id": 4, "label": 'Disagree' }, - { "id": 5, "label": 'Strongly Disagree' }, - ], - "incorrect": [], - }, - 'confidence', - ] - end - end - - trait :summative do - schema do - [ - '1-1-4', - 'summative_questionnaire', - 'Question - Select from following', - { - "correct": [2, 3], - "options": [ - { "id": 1, "label": 'Wrong answer 1' }, - { "id": 2, "label": 'Correct answer 1' }, - { "id": 3, "label": 'Correct answer 2' }, - { "id": 4, "label": 'Wrong answer 2' }, - ], - "incorrect": [1, 4], - }, - 'summative', - ] - end - end end end diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/authentication_helper_spec.rb similarity index 97% rename from spec/helpers/gov_one_helper_spec.rb rename to spec/helpers/authentication_helper_spec.rb index c3e333e24..51d63f773 100644 --- a/spec/helpers/gov_one_helper_spec.rb +++ b/spec/helpers/authentication_helper_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'GovOneHelper', type: :helper do +describe 'AuthenticationHelper', type: :helper do describe '#login_uri' do subject(:login_uri) { helper.login_uri } diff --git a/spec/helpers/link_helper_spec.rb b/spec/helpers/link_helper_spec.rb index 435820a53..899642184 100644 --- a/spec/helpers/link_helper_spec.rb +++ b/spec/helpers/link_helper_spec.rb @@ -86,7 +86,11 @@ context 'with a failed assessment' do before do - create :user_assessment, :failed, user_id: user.id, score: 0, module: mod.name + if Rails.application.migrated_answers? + create :assessment, :failed, user: user, training_module: mod.name + else + create :user_assessment, :failed, user_id: user.id, score: 0, module: mod.name + end end it 'links to retake' do @@ -97,13 +101,18 @@ context 'with a passed assessment' do before do - create :user_assessment, user_id: user.id, module: mod.name + if Rails.application.migrated_answers? + create :assessment, :passed, user: user, training_module: mod.name + else + create :user_assessment, user_id: user.id, module: mod.name + end mod.summative_questions.map do |question| - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? create :response, user: user, training_module: mod.name, question_name: question.name, + question_type: 'summative', answers: question.correct_answers else create :user_answer, user: user, @@ -157,7 +166,11 @@ context 'with failed assessment' do before do - create :user_assessment, :failed, user_id: user.id, score: 0, module: mod.name + if Rails.application.migrated_answers? + create :assessment, :failed, user: user, training_module: mod.name + else + create :user_assessment, :failed, user_id: user.id, score: 0, module: mod.name + end end it 'targets new assessment attempt' do diff --git a/spec/lib/content_test_schema_spec.rb b/spec/lib/content_test_schema_spec.rb index f750b495a..674459a06 100644 --- a/spec/lib/content_test_schema_spec.rb +++ b/spec/lib/content_test_schema_spec.rb @@ -10,7 +10,7 @@ end before do - skip 'WIP' if ENV['DISABLE_USER_ANSWER'].present? + skip 'WIP' if Rails.application.migrated_answers? end context 'when pass is true' do diff --git a/spec/lib/migrate_training_spec.rb b/spec/lib/migrate_training_spec.rb new file mode 100644 index 000000000..e0a5c2d1f --- /dev/null +++ b/spec/lib/migrate_training_spec.rb @@ -0,0 +1,249 @@ +require 'rails_helper' +require 'migrate_training' + +# UserAnswer names like '1-3-2-1' must be genuine and exist in the CMS env +# +RSpec.describe MigrateTraining do + subject(:operation) { described_class.new(verbose: false) } + + let(:user) { create(:user, :registered) } + + context 'with truncate' do + subject(:operation) { described_class.new(verbose: false, truncate: true) } + + let(:assessment) { create :assessment, user: user } + + before do + create_list :response, 10, user: user, question_type: 'summative', assessment: assessment + operation.call + end + + it 'preserves user records' do + expect(Assessment.count).to eq 0 + expect(Response.count).to eq 0 + expect(User.count).to eq 1 + end + end + + context 'with simulate' do + subject(:operation) { described_class.new(verbose: false, simulate: true) } + + before do + create_list :response, 10 + operation.call + rescue MigrateTraining::Error + end + + it 'reverts changes' do + expect(Response.count).to eq 10 + end + end + + context 'when data is valid' do + before do + create :user_answer, + user_id: user.id, + name: '1-1-4-1', # genuine key + module: 'alpha', + correct: false, + assessments_type: 'formative_assessment', + user_assessment_id: nil, + questionnaire_id: 0, + created_at: Time.zone.local(2023, 1, 1) + + # Alpha (finished) + alpha_assessment = + create :user_assessment, + user_id: user.id, + module: 'alpha', + completed: true, + status: 'passed', + score: '70', + created_at: Time.zone.local(2023, 1, 2) # matches last question (approx) + + create :user_answer, + user_id: user.id, + name: '1-3-2-1', # genuine key + module: 'alpha', + correct: true, + assessments_type: 'summative_assessment', + user_assessment_id: alpha_assessment.id, + questionnaire_id: 0, + created_at: Time.zone.local(2023, 1, 1) + + create :user_answer, + user_id: user.id, + name: '1-3-2-2', # genuine key + module: 'alpha', + correct: true, + assessments_type: 'summative_assessment', + user_assessment_id: alpha_assessment.id, + questionnaire_id: 0, + created_at: Time.zone.local(2023, 1, 2) + + # Bravo (started therefore no association) + create :user_answer, + user_id: user.id, + name: '1-3-2-1', # genuine key + module: 'bravo', + correct: true, + assessments_type: 'summative_assessment', + user_assessment_id: nil, + questionnaire_id: 0, + created_at: Time.zone.local(2024, 2, 1) + + create :user_answer, + user_id: user.id, + name: '1-3-2-2', # genuine key + module: 'bravo', + correct: true, + assessments_type: 'summative_assessment', + user_assessment_id: nil, + questionnaire_id: 0, + created_at: Time.zone.local(2024, 2, 2) + end + + it 'migrates everything' do + expect(Assessment.count).to be 0 + expect(Response.count).to be 0 + + operation.call + + expect(Assessment.count).to be 2 + expect(Response.formative.count).to be 1 + expect(Response.summative.count).to be 4 + + # ALPHA + alpha_assessment = Assessment.find_by(training_module: 'alpha') + + expect(alpha_assessment.training_module).to eq 'alpha' + expect(alpha_assessment.score).to eq 70.0 + expect(alpha_assessment.passed).to eq true + + # first question + expect(alpha_assessment.started_at.year).to eq 2023 + expect(alpha_assessment.started_at.month).to eq 1 + expect(alpha_assessment.started_at.day).to eq 1 + + # last question + expect(alpha_assessment.completed_at.year).to eq 2023 + expect(alpha_assessment.completed_at.month).to eq 1 + expect(alpha_assessment.completed_at.day).to eq 2 + + # BRAVO + bravo_assessment = Assessment.find_by(training_module: 'bravo') + + expect(bravo_assessment.training_module).to eq 'bravo' + expect(bravo_assessment.score).to be_nil + expect(bravo_assessment.passed).to be_nil + + # first question + expect(bravo_assessment.started_at.year).to eq 2024 + expect(bravo_assessment.started_at.month).to eq 2 + expect(bravo_assessment.started_at.day).to eq 1 + + # last question + expect(bravo_assessment.completed_at).to be_nil + end + + it 'preserves keys' do + operation.call + + user_assessment = UserAssessment.find_by(module: 'alpha') + assessment = Assessment.find_by(training_module: 'alpha') + + expect(assessment.id).to eq user_assessment.id + + user_answer = UserAnswer.find_by(name: '1-3-2-1') + response = Response.find_by(question_name: '1-3-2-1') + + expect(response.id).to eq user_answer.id + end + end + + context 'when assessment score is invalid' do + before do + # Charlie (inconsistent) + charlie_assessment = + create :user_assessment, + user_id: user.id, + module: 'charlie', + completed: true, + status: 'passed', + score: '150' + + create_list :user_answer, 20, + user_id: user.id, + name: '1-3-2-1', # genuine key + module: 'charlie', + assessments_type: 'summative_assessment', + user_assessment_id: charlie_assessment.id, + questionnaire_id: 0, + created_at: Time.zone.now + + operation.call + end + + it 'is corrected' do + expect(UserAssessment.count).to eq 1 + expect(Assessment.count).to be 1 + + expect(UserAnswer.count).to be 20 + expect(Response.count).to be 20 + + expect(UserAssessment.first.user_answers.count).to eq 20 + expect(Assessment.first.responses.count).to eq 20 + + expect(Assessment.first.score).to eq 100.0 + end + end + + context 'with resume' do + before do + assessment_1 = + create :user_assessment, + user_id: user.id, + module: 'alpha' + + create_list :user_answer, 10, + user_id: user.id, + name: '1-3-2-1', + module: 'alpha', + assessments_type: 'summative_assessment', + user_assessment_id: assessment_1.id, + questionnaire_id: 0, + created_at: Time.zone.local(2023, 1, 1) + + operation.call + end + + it 'migrates remaining data' do + last_user_answer_id = UserAnswer.last.id + + assessment_2 = + create :user_assessment, + user_id: user.id, + module: 'charlie' + + create_list :user_answer, 10, + user_id: user.id, + name: '1-3-2-1', + module: 'charlie', + assessments_type: 'summative_assessment', + user_assessment_id: assessment_2.id, + questionnaire_id: 0, + created_at: Time.zone.now + + described_class.new(verbose: false, resume: last_user_answer_id + 1).call + + expect(UserAnswer.pluck(:id)[9]).to eq last_user_answer_id + expect(UserAnswer.pluck(:id)[19]).to eq UserAnswer.last.id + + expect(UserAssessment.count).to eq 2 + expect(Assessment.count).to be 2 + + expect(UserAnswer.count).to be 20 + expect(Response.count).to be 20 + end + end +end diff --git a/spec/models/assessment_spec.rb b/spec/models/assessment_spec.rb new file mode 100644 index 000000000..fa0fe2ab0 --- /dev/null +++ b/spec/models/assessment_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe Assessment, type: :model do + subject(:assessment) do + create :assessment + end + + before do + skip unless Rails.application.migrated_answers? + end + + specify { expect(assessment).not_to be_passed } + specify { expect(assessment).not_to be_graded } +end diff --git a/spec/models/course_spec.rb b/spec/models/course_spec.rb index d5c1dceed..114624d05 100644 --- a/spec/models/course_spec.rb +++ b/spec/models/course_spec.rb @@ -18,6 +18,8 @@ it 'feedback' do expect(course.feedback).to be_empty + # - only one question type + # - number of questions end end end diff --git a/spec/models/data_analysis/average_pass_scores_spec.rb b/spec/models/data_analysis/average_pass_scores_spec.rb index b29e300f4..6a0730aca 100644 --- a/spec/models/data_analysis/average_pass_scores_spec.rb +++ b/spec/models/data_analysis/average_pass_scores_spec.rb @@ -11,7 +11,7 @@ let(:rows) do [ { - module_name: 'module_1', + module_name: 'alpha', pass_score: 100.0, }, ] @@ -20,8 +20,13 @@ let(:user) { create(:user, :registered) } before do - create :user_assessment, :passed, user_id: user.id, score: 100, module: 'module_1' - create :user_assessment, :failed, user_id: user.id, score: 0, module: 'module_1' + if Rails.application.migrated_answers? + create :assessment, :passed + create :assessment, :failed + else + create :user_assessment, :passed, user_id: user.id, score: 100, module: 'alpha' + create :user_assessment, :failed, user_id: user.id, score: 0, module: 'alpha' + end end it_behaves_like 'a data export model' diff --git a/spec/models/data_analysis/confidence_check_scores_spec.rb b/spec/models/data_analysis/confidence_check_scores_spec.rb index 31e41a28a..add96392d 100644 --- a/spec/models/data_analysis/confidence_check_scores_spec.rb +++ b/spec/models/data_analysis/confidence_check_scores_spec.rb @@ -2,10 +2,11 @@ RSpec.describe DataAnalysis::ConfidenceCheckScores do before do - skip if ENV['DISABLE_USER_ANSWER'].blank? - create(:response, :confidence_check, training_module: 'module_1', question_name: 'q1', answers: [1]) - create(:response, :confidence_check, training_module: 'module_1', question_name: 'q2', answers: [2]) - create(:response, :confidence_check, training_module: 'module_1', question_name: 'q2', answers: [2]) + skip unless Rails.application.migrated_answers? + + create(:response, question_type: 'confidence', training_module: 'module_1', question_name: 'q1', answers: [1]) + create(:response, question_type: 'confidence', training_module: 'module_1', question_name: 'q2', answers: [2]) + create(:response, question_type: 'confidence', training_module: 'module_1', question_name: 'q2', answers: [2]) end let(:headers) do diff --git a/spec/models/data_analysis/high_fail_questions_spec.rb b/spec/models/data_analysis/high_fail_questions_spec.rb index 943c1f2eb..39506515d 100644 --- a/spec/models/data_analysis/high_fail_questions_spec.rb +++ b/spec/models/data_analysis/high_fail_questions_spec.rb @@ -25,11 +25,35 @@ end before do - if ENV['DISABLE_USER_ANSWER'].present? - create(:response, :correct, :summative, training_module: 'module_1', question_name: 'q1', answers: [1]) - create(:response, :correct, :summative, training_module: 'module_2', question_name: 'q1', answers: [1]) - create(:response, :incorrect, :summative, training_module: 'module_1', question_name: 'q2', answers: [2]) - create(:response, :incorrect, :summative, training_module: 'module_1', question_name: 'q2', answers: [2]) + if Rails.application.migrated_answers? + create :response, + question_type: 'summative', + training_module: 'module_1', + question_name: 'q1', + answers: [1], + correct: true + + create :response, + question_type: 'summative', + training_module: 'module_2', + question_name: 'q1', + answers: [1], + correct: true + + create :response, + question_type: 'summative', + training_module: 'module_1', + question_name: 'q2', + answers: [2], + correct: false + + create :response, + question_type: 'summative', + training_module: 'module_1', + question_name: 'q2', + answers: [2], + correct: false + else create(:user_answer, :correct, :questionnaire, :summative, module: 'module_1', name: 'q1') create(:user_answer, :correct, :questionnaire, :summative, module: 'module_2', name: 'q1') diff --git a/spec/models/data_analysis/modules_per_month_spec.rb b/spec/models/data_analysis/modules_per_month_spec.rb index 7c18b831c..0b03535af 100644 --- a/spec/models/data_analysis/modules_per_month_spec.rb +++ b/spec/models/data_analysis/modules_per_month_spec.rb @@ -16,7 +16,7 @@ [ { month: 'January 2023', - module_name: 'module_1', + module_name: 'alpha', pass_percentage: 0.5, pass_count: 1, fail_percentage: 0.5, @@ -24,7 +24,7 @@ }, { month: 'February 2023', - module_name: 'module_1', + module_name: 'alpha', pass_percentage: 0.0, pass_count: 0, fail_percentage: 1.0, @@ -32,7 +32,7 @@ }, { month: 'March 2023', - module_name: 'module_1', + module_name: 'alpha', pass_percentage: 1.0, pass_count: 1, fail_percentage: 0.0, @@ -45,10 +45,17 @@ let(:user_2) { create :user, :registered } before do - create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1', created_at: Time.zone.local(2023, 1, 1)) - create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1', created_at: Time.zone.local(2023, 2, 1)) - create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1', created_at: Time.zone.local(2023, 3, 1)) - create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1', created_at: Time.zone.local(2023, 1, 1)) + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1, completed_at: Time.zone.local(2023, 1, 1) + create :assessment, :passed, user: user_1, completed_at: Time.zone.local(2023, 1, 1) + create :assessment, :failed, user: user_1, completed_at: Time.zone.local(2023, 2, 1) + create :assessment, :passed, user: user_2, completed_at: Time.zone.local(2023, 3, 1) + else + create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'alpha', created_at: Time.zone.local(2023, 1, 1)) + create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha', created_at: Time.zone.local(2023, 2, 1)) + create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'alpha', created_at: Time.zone.local(2023, 3, 1)) + create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha', created_at: Time.zone.local(2023, 1, 1)) + end end it_behaves_like 'a data export model' diff --git a/spec/models/data_analysis/resits_per_user_spec.rb b/spec/models/data_analysis/resits_per_user_spec.rb index 1c478342e..0da410805 100644 --- a/spec/models/data_analysis/resits_per_user_spec.rb +++ b/spec/models/data_analysis/resits_per_user_spec.rb @@ -13,13 +13,13 @@ let(:rows) do [ { - module_name: 'module_1', + module_name: 'alpha', user_id: user_1.id, role_type: 'resit', resit_attempts: 1, }, { - module_name: 'module_1', + module_name: 'alpha', user_id: user_3.id, role_type: 'failing', resit_attempts: 2, @@ -27,19 +27,30 @@ ] end - let(:user_1) { create(:user, :registered, role_type: 'resit') } - let(:user_2) { create(:user, :registered, role_type: 'hole-in-one') } - let(:user_3) { create(:user, :registered, role_type: 'failing') } + let(:user_1) { create :user, :registered, role_type: 'resit' } + let(:user_2) { create :user, :registered, role_type: 'hole-in-one' } + let(:user_3) { create :user, :registered, role_type: 'failing' } before do - create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1') - create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1') + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1 + create :assessment, :passed, user: user_1 - create(:user_assessment, :passed, user_id: user_2.id, score: 90, module: 'module_1') + create :assessment, :passed, user: user_2 - create(:user_assessment, :failed, user_id: user_3.id, score: 10, module: 'module_1') - create(:user_assessment, :failed, user_id: user_3.id, score: 20, module: 'module_1') - create(:user_assessment, :failed, user_id: user_3.id, score: 30, module: 'module_1') + create :assessment, :failed, user: user_3 + create :assessment, :failed, user: user_3 + create :assessment, :failed, user: user_3 + else + create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha') + create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'alpha') + + create(:user_assessment, :passed, user_id: user_2.id, score: 90, module: 'alpha') + + create(:user_assessment, :failed, user_id: user_3.id, score: 10, module: 'alpha') + create(:user_assessment, :failed, user_id: user_3.id, score: 20, module: 'alpha') + create(:user_assessment, :failed, user_id: user_3.id, score: 30, module: 'alpha') + end end it_behaves_like 'a data export model' diff --git a/spec/models/data_analysis/role_pass_rate_spec.rb b/spec/models/data_analysis/role_pass_rate_spec.rb index 35ee5a243..13e6f9909 100644 --- a/spec/models/data_analysis/role_pass_rate_spec.rb +++ b/spec/models/data_analysis/role_pass_rate_spec.rb @@ -23,13 +23,19 @@ ] end - let(:user_1) { create(:user, :registered, role_type: 'childminder') } - let(:user_2) { create(:user, :registered, role_type: 'childminder') } + let(:user_1) { create :user, :registered, role_type: 'childminder' } + let(:user_2) { create :user, :registered, role_type: 'childminder' } before do - create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1') - create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1') - create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1') + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1 + create :assessment, :passed, user: user_1 + create :assessment, :passed, user: user_2 + else + create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'alpha') + create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha') + create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'alpha') + end end it_behaves_like 'a data export model' diff --git a/spec/models/data_analysis/setting_pass_rate_spec.rb b/spec/models/data_analysis/setting_pass_rate_spec.rb index 2feaa4464..99fb090d2 100644 --- a/spec/models/data_analysis/setting_pass_rate_spec.rb +++ b/spec/models/data_analysis/setting_pass_rate_spec.rb @@ -23,13 +23,19 @@ ] end - let(:user_1) { create(:user, :agency_childminder) } - let(:user_2) { create(:user, :agency_childminder) } + let(:user_1) { create :user, :agency_childminder } + let(:user_2) { create :user, :agency_childminder } before do - create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1') - create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1') - create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1') + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1 + create :assessment, :passed, user: user_1 + create :assessment, :passed, user: user_2 + else + create(:user_assessment, :passed, user_id: user_1.id, score: 100, module: 'alpha') + create(:user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha') + create(:user_assessment, :passed, user_id: user_2.id, score: 80, module: 'alpha') + end end it_behaves_like 'a data export model' diff --git a/spec/models/data_analysis/summative_quiz_spec.rb b/spec/models/data_analysis/summative_quiz_spec.rb index e66adfb16..938aa11f7 100644 --- a/spec/models/data_analysis/summative_quiz_spec.rb +++ b/spec/models/data_analysis/summative_quiz_spec.rb @@ -5,10 +5,17 @@ let(:user_2) { create :user, :agency_childminder } before do - create :user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1' - create :user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1' - create :user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1' - create :user_assessment, :failed, user_id: user_2.id, score: 0, module: 'module_1' + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1 + create :assessment, :passed, user: user_1 + create :assessment, :failed, user: user_2 + create :assessment, :passed, user: user_2 + else + create :user_assessment, :passed, user_id: user_1.id, score: 100, module: 'module_1' + create :user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1' + create :user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1' + create :user_assessment, :failed, user_id: user_2.id, score: 0, module: 'module_1' + end end describe '.pass_rate' do diff --git a/spec/models/data_analysis/users_not_passing_spec.rb b/spec/models/data_analysis/users_not_passing_spec.rb index e3bc76013..a164dd6ca 100644 --- a/spec/models/data_analysis/users_not_passing_spec.rb +++ b/spec/models/data_analysis/users_not_passing_spec.rb @@ -1,9 +1,6 @@ require 'rails_helper' RSpec.describe DataAnalysis::UsersNotPassing do - let(:user_1) { create(:user, :registered) } - let(:user_2) { create(:user, :registered) } - let(:headers) do [ 'Module', @@ -14,16 +11,25 @@ let(:rows) do [ { - module_name: 'module_1', + module_name: 'alpha', count: 1, }, ] end + let(:user_1) { create :user, :registered } + let(:user_2) { create :user, :registered } + before do - create :user_assessment, :failed, user_id: user_1.id, score: 0, module: 'module_1' - create :user_assessment, :passed, user_id: user_2.id, score: 80, module: 'module_1' - create :user_assessment, :failed, user_id: user_2.id, score: 0, module: 'module_1' + if Rails.application.migrated_answers? + create :assessment, :failed, user: user_1 + create :assessment, :failed, user: user_2 + create :assessment, :passed, user: user_2 + else + create :user_assessment, :failed, user_id: user_1.id, score: 0, module: 'alpha' + create :user_assessment, :passed, user_id: user_2.id, score: 80, module: 'alpha' + create :user_assessment, :failed, user_id: user_2.id, score: 0, module: 'alpha' + end end it_behaves_like 'a data export model' diff --git a/spec/models/response_spec.rb b/spec/models/response_spec.rb index ccb495ec6..629c61da0 100644 --- a/spec/models/response_spec.rb +++ b/spec/models/response_spec.rb @@ -1,34 +1,37 @@ require 'rails_helper' RSpec.describe Response, type: :model do + subject(:response) { user.response_for(question) } + before do - skip if ENV['DISABLE_USER_ANSWER'].blank? + skip unless Rails.application.migrated_answers? + response.update!(answers: [1], correct: true) end - describe 'dashboard' do - let(:user) { create :user } - let(:question) do - # uncached - # described_class.find_by(name: , training_module: { name: 'alpha' }).load.first - - # cached - Training::Module.by_name('alpha').page_by_name('1-1-4') - end - - let(:response) do - user.response_for(question).tap do |response| - response.update(answers: [1], correct: true, schema: question.schema) - end - end + let(:user) { create :user } - let(:headers) do - %w[id user_id training_module question_name answers archived correct user_assessment_id created_at updated_at schema] - end + let(:headers) do + %w[ + id + user_id + training_module + question_name + answers + correct + created_at + updated_at + question_type + assessment_id + ] + end - let(:rows) do - [response] - end + let(:rows) do + [response] + end - it_behaves_like 'a data export model' + let(:question) do + Training::Module.by_name('alpha').page_by_name('1-1-4-1') end + + it_behaves_like 'a data export model' end diff --git a/spec/models/training/page_spec.rb b/spec/models/training/page_spec.rb index ada617786..20d6033ea 100644 --- a/spec/models/training/page_spec.rb +++ b/spec/models/training/page_spec.rb @@ -21,4 +21,36 @@ expect(page.schema).to eq ['1-1-1-1', 'text_page', 'text_page', {}] end end + + describe '#debug_summary' do + it 'summarises information' do + expect(page.debug_summary).to eq( + <<~SUMMARY, + uid: 1lD5q4W5MfV2MkBqCGbMA6 + module uid: 2eghGRABMvuDKfpIZBjRAT + module name: charlie + published at: Management Key Missing + page type: text_page + + --- + previous: 1-1-1 + current: 1-1-1-1 + next: 1-1-1-2 + + --- + submodule: 1 + topic: 1 + + --- + position in module: 4th + position in submodule: 3rd + position in topic: 2nd + + --- + pages in submodule: 4 + pages in topic: 4 + SUMMARY + ) + end + end end diff --git a/spec/models/training/question_spec.rb b/spec/models/training/question_spec.rb index 62a7c6c3f..9a837f13c 100644 --- a/spec/models/training/question_spec.rb +++ b/spec/models/training/question_spec.rb @@ -97,4 +97,36 @@ end end end + + describe '#debug_summary' do + it 'summarises information' do + expect(question.debug_summary).to eq( + <<~SUMMARY, + uid: 49Z7xDMPfGAnIY8jzyD4ia + module uid: 6EczqUOpieKis8imYPc6mG + module name: alpha + published at: Management Key Missing + page type: formative_questionnaire + + --- + previous: 1-1-4 + current: 1-1-4-1 + next: 1-2 + + --- + submodule: 1 + topic: 4 + + --- + position in module: 8th + position in submodule: 7th + position in topic: 2nd + + --- + pages in submodule: 6 + pages in topic: 2 + SUMMARY + ) + end + end end diff --git a/spec/models/training/video_spec.rb b/spec/models/training/video_spec.rb index ef4644abd..1e4c7164e 100644 --- a/spec/models/training/video_spec.rb +++ b/spec/models/training/video_spec.rb @@ -38,4 +38,36 @@ expect(video.transcript).to start_with "Today's subject is based on" end end + + describe '#debug_summary' do + it 'summarises information' do + expect(video.debug_summary).to eq( + <<~SUMMARY, + uid: LaZ22OwFuaFuXRjVvNLwy + module uid: 4u49zTRJzYAWsBI6CitwN4 + module name: bravo + published at: Management Key Missing + page type: video_page + + --- + previous: 1-2-1-1 + current: 1-2-1-2 + next: 1-2-1-3 + + --- + submodule: 2 + topic: 1 + + --- + position in module: 9th + position in submodule: 4th + position in topic: 3rd + + --- + pages in submodule: 4 + pages in topic: 4 + SUMMARY + ) + end + end end diff --git a/spec/services/assessment_progress_spec.rb b/spec/services/assessment_progress_spec.rb index c242f8e5e..d5c7750fc 100644 --- a/spec/services/assessment_progress_spec.rb +++ b/spec/services/assessment_progress_spec.rb @@ -5,12 +5,18 @@ include_context 'with progress' + let(:attempt) do + create :assessment, training_module: 'alpha', user: user + end + before do questions.each do |question_name, answers| - if ENV['DISABLE_USER_ANSWER'].present? + if Rails.application.migrated_answers? create :response, user: user, + assessment: attempt, training_module: 'alpha', question_name: question_name, + question_type: 'summative', answers: answers else create :user_answer, user: user, diff --git a/spec/services/course_progress_spec.rb b/spec/services/course_progress_spec.rb index 67326c467..9dc54e15a 100644 --- a/spec/services/course_progress_spec.rb +++ b/spec/services/course_progress_spec.rb @@ -93,4 +93,57 @@ end end end + + describe '#debug_summary' do + it 'summarises information' do + expect(course.debug_summary).to eq( + <<~SUMMARY, + --- + title: First Training Module + published at: Management Key Missing + position: 1 + name: alpha + draft: false + started: false + completed: false + last: 1-3-3-5 + certificate: 1-3-4 + milestone: N/A + --- + title: Second Training Module + published at: Management Key Missing + position: 2 + name: bravo + draft: false + started: false + completed: false + last: 1-3-3-5 + certificate: 1-3-4 + milestone: N/A + --- + title: Third Training Module + published at: Management Key Missing + position: 3 + name: charlie + draft: false + started: false + completed: false + last: 1-3-3-5 + certificate: 1-3-4 + milestone: N/A + --- + title: Fourth Training Module + published at: Management Key Missing + position: 4 + name: delta + draft: true + started: false + completed: false + last: N/A + certificate: N/A + milestone: N/A + SUMMARY + ) + end + end end diff --git a/spec/support/ast/bravo-fail.yml b/spec/support/ast/bravo-fail.yml new file mode 100644 index 000000000..6bb0e0d3e --- /dev/null +++ b/spec/support/ast/bravo-fail.yml @@ -0,0 +1,160 @@ +--- +- :path: "/modules/bravo/content-pages/what-to-expect" + :text: What to expect during the training + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-1" + :text: sub_module_intro + :inputs: + - - :click_on + - Start section +- :path: "/modules/bravo/content-pages/1-1-1" + :text: text_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-1-2" + :text: video_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-1-2-1" + :text: text_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-2" + :text: sub_module_intro + :inputs: + - - :click_on + - Start section +- :path: "/modules/bravo/content-pages/1-2-1" + :text: text_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-2-1-1" + :text: text_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-2-1-2" + :text: video_page + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/questionnaires/1-2-1-3" + :text: Binary choice + :inputs: + - - :choose + - user-answer-answers-2-field + - - :click_on + - Next + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-3" + :text: Summary and next steps + :inputs: + - - :click_on + - Start section +- :path: "/modules/bravo/content-pages/1-3-1" + :text: Recap + :inputs: + - - :click_on + - Next +- :path: "/modules/bravo/content-pages/1-3-2" + :text: End of module test + :inputs: + - - :click_on + - Start test +- :path: "/modules/bravo/questionnaires/1-3-2-1" + :text: Question One - Select from following + :inputs: + - - :check + - user-answer-answers-2-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-2" + :text: Question Two - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-3" + :text: Question Three - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-4" + :text: Question Four - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-5" + :text: Question Five - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-6" + :text: Question Six - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-7" + :text: Question Seven - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-8" + :text: Question Eight - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-9" + :text: Question Nine - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: "/modules/bravo/questionnaires/1-3-2-10" + :text: Question Ten - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Finish test diff --git a/spec/support/ast/training-migration.yml b/spec/support/ast/training-migration.yml new file mode 100644 index 000000000..c21d6f5a3 --- /dev/null +++ b/spec/support/ast/training-migration.yml @@ -0,0 +1,458 @@ +# +# 1. PASS ALPHA +# 2. FAIL BRAVO +# 3. PASS BRAVO +# 4. PARTIAL CHARLIE +# +--- +# +# +# +# +# Prepare to start Alpha +# +- :path: /my-modules + :text: My modules + :inputs: + - - :click_on + - 'Module 1: First Training Module' +- :path: /modules/alpha + :text: 'Module 1: First Training Module' + :inputs: + - - :click_on + - Start module +# +# +# +# +# Alpha Pass +# +- :path: /modules/alpha/content-pages/what-to-expect + :text: What to expect during the training + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-1 + :text: The first submodule + :inputs: + - - :click_on + - Start section +- :path: /modules/alpha/content-pages/1-1-1 + :text: 1-1-1 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-1-2 + :text: 1-1-2 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-1-3 + :text: 1-1-3 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-1-3-1 + :text: 1-1-3-1 + :inputs: + - - :click_on + - Save and continue +- :path: /modules/alpha/content-pages/1-1-4 + :text: 1-1-4 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-1-4-1 + :text: Question One - Select from following + :inputs: + - - :choose + - user-answer-answers-1-field + - - :click_on + - Next + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-2 + :text: The second submodule + :inputs: + - - :click_on + - Start section +- :path: /modules/alpha/content-pages/1-2-1 + :text: 1-2-1 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-2-1-1 + :text: Question Two - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-3-field + - - :click_on + - Next + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-2-1-2 + :text: 1-2-1-2 + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-2-1-3 + :text: Question Three - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-3-field + - - :click_on + - Next + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-3 + :text: Summary and next steps + :inputs: + - - :click_on + - Start section +- :path: /modules/alpha/content-pages/1-3-1 + :text: Recap + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-3-2 + :text: End of module test + :inputs: + - - :click_on + - Start test +- :path: /modules/alpha/questionnaires/1-3-2-1 + :text: Question One - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-2 + :text: Question Two - Select from following + :inputs: + - - :check + - user-answer-answers-2-field + - - :check + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-3 + :text: Question Three - Select from following + :inputs: + - - :check + - user-answer-answers-3-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-4 + :text: Question Four - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-5 + :text: Question Five - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-6 + :text: Question Six - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-7 + :text: Question Seven - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-8 + :text: Question Eight - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-9 + :text: Question Nine - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Save and continue +- :path: /modules/alpha/questionnaires/1-3-2-10 + :text: Question Ten - Select from following + :inputs: + - - :choose + - user-answer-answers-3-field + - - :click_on + - Finish test +- :path: /modules/alpha/assessment-result/1-3-2-11 + :text: Assessment results + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-3-3 + :text: Reflect on your learning + :inputs: + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-3-3-1 + :text: Question One - Select from 1 to 5 + :inputs: + - - :choose + - user-answer-answers-5-field + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-3-3-2 + :text: Question Two - Select from 1 to 5 + :inputs: + - - :choose + - user-answer-answers-5-field + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-3-3-3 + :text: Question Three - Select from 1 to 5 + :inputs: + - - :choose + - user-answer-answers-5-field + - - :click_on + - Next +- :path: /modules/alpha/questionnaires/1-3-3-4 + :text: Question Four - Select from 1 to 5 + :inputs: + - - :choose + - user-answer-answers-5-field + - - :click_on + - Next +- :path: /modules/alpha/content-pages/1-3-3-5 + :text: Thank you + :inputs: + - - :click_on + - Finish +- :path: /modules/alpha/content-pages/1-3-4 + :text: Congratulations! + :inputs: + - - :click_on + - My modules +# +# +# +# +# Prepare to start Bravo +# +- :path: /my-modules + :text: My modules + :inputs: + - - :click_on + - 'Module 2: Second Training Module' +- :path: /modules/bravo + :text: 'Module 2: Second Training Module' + :inputs: + - - :click_on + - Start module +# +# +# +# +# Bravo Pass +# +# - :path: /modules/bravo/content-pages/what-to-expect +# :text: What to expect during the training +# :inputs: +# - - :click_on +# - Next +# - :path: /modules/bravo/content-pages/1-1 +# :text: first sub intro +# :inputs: +# - - :click_on +# - Start section +# - :path: /modules/bravo/content-pages/1-1-1 +# :text: page 1 of sub 1 (topic 1) +# :inputs: +# - - :click_on +# - Next +# - :path: /modules/bravo/content-pages/1-1-2 +# :text: page 2 of sub 1 (topic 2) +# :inputs: +# - - :click_on +# - Next +# - :path: /modules/bravo/content-pages/1-1-2-1 +# :text: page 3 of sub 1 (topic 2 page 2) +# :inputs: +# - - :click_on +# - Next + + + +- :path: /modules/bravo/content-pages/what-to-expect + :text: What to expect during the training + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-1 + :text: sub_module_intro + :inputs: + - - :click_on + - Start section +- :path: /modules/bravo/content-pages/1-1-1 + :text: text_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-1-2 + :text: video_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-1-2-1 + :text: text_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-2 + :text: sub_module_intro + :inputs: + - - :click_on + - Start section +- :path: /modules/bravo/content-pages/1-2-1 + :text: text_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-2-1-1 + :text: text_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-2-1-2 + :text: video_page + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/questionnaires/1-2-1-3 + :text: Binary choice + :inputs: + - - :choose + - user-answer-answers-2-field + - - :click_on + - Next + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-3 + :text: Summary and next steps + :inputs: + - - :click_on + - Start section +- :path: /modules/bravo/content-pages/1-3-1 + :text: Recap + :inputs: + - - :click_on + - Next +- :path: /modules/bravo/content-pages/1-3-2 + :text: End of module test + :inputs: + - - :click_on + - Start test +- :path: /modules/bravo/questionnaires/1-3-2-1 + :text: Question One - Select from following + :inputs: + - - :check + - user-answer-answers-2-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-2 + :text: Question Two - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-3 + :text: Question Three - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-4 + :text: Question Four - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-5 + :text: Question Five - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-6 + :text: Question Six - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-7 + :text: Question Seven - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-8 + :text: Question Eight - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-9 + :text: Question Nine - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Save and continue +- :path: /modules/bravo/questionnaires/1-3-2-10 + :text: Question Ten - Select from following + :inputs: + - - :check + - user-answer-answers-1-field + - - :check + - user-answer-answers-4-field + - - :click_on + - Finish test diff --git a/spec/support/shared/with_progress.rb b/spec/support/shared/with_progress.rb index 98dfaaf86..fa8170392 100644 --- a/spec/support/shared/with_progress.rb +++ b/spec/support/shared/with_progress.rb @@ -41,11 +41,6 @@ def start_summative_assessment(mod) view_pages_upto(mod, 'summative_questionnaire') end - # @param mod [Training::Module] - def fail_summative_assessment(mod) - create(:user_assessment, user_id: user.id, module: mod.name) - end - # @param mod [Training::Module] def view_pages_upto_formative_question(mod, count = 1) view_pages_upto(mod, 'formative_questionnaire', count) diff --git a/spec/system/assessment_results_spec.rb b/spec/system/assessment_results_spec.rb index 5abebb9fe..ff92ab31f 100644 --- a/spec/system/assessment_results_spec.rb +++ b/spec/system/assessment_results_spec.rb @@ -3,6 +3,10 @@ RSpec.describe 'Assessment results page' do include_context 'with user' + before do + create :assessment, :passed, user: user + end + describe 'back link' do it 'targets the module overview' do visit 'modules/alpha/assessment-result/1-3-2-11' diff --git a/spec/system/event_log_spec.rb b/spec/system/event_log_spec.rb index dc7735da2..c3e9961ed 100644 --- a/spec/system/event_log_spec.rb +++ b/spec/system/event_log_spec.rb @@ -31,8 +31,11 @@ end it 'tracks answers and completion' do - # TODO: type will change to 'confidence' when CMS 'page_type' is updated - expect(events.where(name: 'questionnaire_answer').where_properties(type: 'confidence_check').size).to eq 4 + if Rails.application.migrated_answers? + expect(events.where(name: 'questionnaire_answer').where_properties(type: 'confidence').size).to eq 4 + else + expect(events.where(name: 'questionnaire_answer').where_properties(type: 'confidence_check').size).to eq 4 + end expect(events.where(name: 'confidence_check_complete').size).to eq 1 end end @@ -73,7 +76,11 @@ end it 'tracks answers and successful attempt' do - expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'summative_assessment').size).to eq 10 + if Rails.application.migrated_answers? + expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'summative').size).to eq 10 + else + expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'summative_assessment').size).to eq 10 + end expect(events.where(name: 'summative_assessment_complete').where_properties(score: 100, success: true).size).to eq 1 end end @@ -89,8 +96,12 @@ end it 'tracks answers and failed attempt' do - # TODO: type will change to 'summative' when CMS 'page_type' is updated - expect(events.where(name: 'questionnaire_answer').where_properties(success: false, type: 'summative_assessment').size).to eq 10 + if Rails.application.migrated_answers? + expect(events.where(name: 'questionnaire_answer').where_properties(success: false, type: 'summative').size).to eq 10 + else + expect(events.where(name: 'questionnaire_answer').where_properties(success: false, type: 'summative_assessment').size).to eq 10 + end + expect(events.where(name: 'summative_assessment_complete').where_properties(score: 0, success: false).size).to eq 1 end end @@ -107,8 +118,11 @@ end it 'tracks answers' do - # TODO: type will change to 'formative' when CMS 'page_type' is updated - expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'formative_assessment').size).to eq 3 + if Rails.application.migrated_answers? + expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'formative').size).to eq 3 + else + expect(events.where(name: 'questionnaire_answer').where_properties(success: true, type: 'formative_assessment').size).to eq 3 + end end end diff --git a/spec/system/migrate_training_spec.rb b/spec/system/migrate_training_spec.rb new file mode 100644 index 000000000..768900447 --- /dev/null +++ b/spec/system/migrate_training_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' +require 'migrate_training' + +RSpec.describe 'Training response data migration', type: :system do + subject(:operation) { MigrateTraining.new(verbose: true) } + + include_context 'with user' + include_context 'with progress' + + let(:alpha_assessment) { Assessment.where(training_module: 'alpha').first } + let(:bravo_assessment) { Assessment.where(training_module: 'bravo').first } + + let(:ast) do + YAML.load_file Rails.root.join('spec/support/ast/training-migration.yml') + end + + before do + skip if Rails.application.migrated_answers? + + visit '/my-modules' + + ast.each do |content| + # NB: Help debugging YAML + # expect(page).to have_current_path content[:path] + # expect(page).to have_content content[:text] + + content[:inputs].each { |args| send(*args) } + end + end + + it 'is successful' do + expect(UserAssessment.count).to eq 2 + expect(Assessment.count).to be 0 + + expect(UserAssessment.first.user_answers.count).to eq 10 + + expect(UserAnswer.where(module: 'alpha').count).to be 17 + expect(UserAnswer.where(module: 'bravo').count).to be 11 + expect(Response.count).to be 0 + + operation.call + + expect(UserAssessment.count).to eq 2 + expect(Assessment.count).to be 2 + + expect(UserAnswer.count).to be 28 + expect(Response.count).to be 28 + + expect(alpha_assessment.passed).to eq true + expect(alpha_assessment.score).to eq 100.0 + expect(alpha_assessment.responses.count).to eq 10 + + expect(bravo_assessment.passed).to eq false + expect(bravo_assessment.score).to eq 0.0 + expect(bravo_assessment.responses.count).to eq 10 + end +end diff --git a/spec/system/page_title_spec.rb b/spec/system/page_title_spec.rb index cc797dbd2..3043c453e 100644 --- a/spec/system/page_title_spec.rb +++ b/spec/system/page_title_spec.rb @@ -60,6 +60,10 @@ include_context 'with progress' include_context 'with user' + before do + create :assessment, :passed, user: user + end + it { expect(root_path).to have_page_title('Home page') } it { expect(user_path).to have_page_title('My account') } it { expect(my_modules_path).to have_page_title('My modules') } @@ -123,7 +127,7 @@ ].each do |page, title| describe "/modules/alpha/content-pages/#{page}" do let(:path) do - training_module_page_path(training_module_id: alpha.name, id: page) + training_module_page_path(training_module_id: 'alpha', id: page) end it { expect(path).to have_page_title(title) } diff --git a/spec/system/training_module_completion_spec.rb b/spec/system/training_module_completion_spec.rb index 8ad6bbe91..79f38e2e8 100644 --- a/spec/system/training_module_completion_spec.rb +++ b/spec/system/training_module_completion_spec.rb @@ -9,7 +9,7 @@ let(:schema) { ContentTestSchema.new(mod: mod) } before do - skip 'WIP' if ENV['DISABLE_USER_ANSWER'].present? + skip 'WIP' if Rails.application.migrated_answers? visit "/modules/#{mod.name}" click_on 'Start module'