From fc7c5b9b34f69ceab90956326c7abade90d28973 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 3 Nov 2020 11:24:20 +1100 Subject: [PATCH] Complete tests --- config/locales/server.en.yml | 5 + config/routes.rb | 6 +- controllers/custom_wizard/admin/admin.rb | 1 + controllers/custom_wizard/admin/logs.rb | 2 +- .../custom_wizard/admin/submissions.rb | 54 ++-- .../custom_wizard/{ => admin}/transfer.rb | 56 ++-- controllers/custom_wizard/admin/wizard.rb | 19 +- controllers/custom_wizard/steps.rb | 18 +- controllers/custom_wizard/wizard.rb | 20 +- jobs/set_after_time_wizard.rb | 2 +- lib/custom_wizard/action.rb | 10 +- lib/custom_wizard/builder.rb | 53 ++-- lib/custom_wizard/field.rb | 6 +- lib/custom_wizard/log.rb | 9 +- lib/custom_wizard/mapper.rb | 2 +- lib/custom_wizard/template.rb | 126 ++++++--- lib/custom_wizard/validator.rb | 100 ++----- lib/custom_wizard/wizard.rb | 131 +++++---- plugin.rb | 5 +- spec/components/custom_wizard/action_spec.rb | 86 ++++-- spec/components/custom_wizard/builder_spec.rb | 30 +- spec/components/custom_wizard/field_spec.rb | 30 ++ spec/components/custom_wizard/log_spec.rb | 27 ++ spec/components/custom_wizard/mapper_spec.rb | 250 +++++++++++++++++ .../components/custom_wizard/template_spec.rb | 111 ++++++++ .../custom_wizard/validator_spec.rb | 53 ++++ spec/components/custom_wizard/wizard_spec.rb | 230 +++++++++++++++ .../extra_locales_controller_spec.rb | 23 ++ spec/extensions/invites_controller_spec.rb | 24 ++ spec/extensions/users_controller_spec.rb | 21 ++ spec/extensions/wizard_field_spec.rb | 21 ++ spec/extensions/wizard_step_spec.rb | 23 ++ spec/fixtures/field/field.json | 10 + spec/fixtures/mapper/data.json | 4 + spec/fixtures/mapper/inputs.json | 264 ++++++++++++++++++ .../{wizard => step}/required_data.json | 0 spec/fixtures/step/step.json | 10 + spec/fixtures/wizard.json | 3 +- spec/fixtures/wizard/after_time.json | 4 + spec/jobs/clear_after_time_wizard_spec.rb | 37 +++ spec/jobs/set_after_time_wizard_spec.rb | 42 +++ spec/plugin_helper.rb | 4 +- .../admin/logs_controller_spec.rb | 22 ++ .../admin/submissions_controller_spec.rb | 45 +++ .../admin/transfer_controller_spec.rb | 52 ++++ .../admin/wizard_controller_spec.rb | 66 +++++ .../application_controller_spec.rb | 61 ++++ .../custom_wizard/steps_controller_spec.rb | 34 +++ .../custom_wizard/wizard_controller_spec.rb | 63 +++++ .../basic_wizard_serializer_spec.rb | 21 ++ .../custom_wizard/log_serializer_spec.rb | 19 ++ .../wizard_field_serializer_spec.rb | 37 +++ .../custom_wizard/wizard_serializer_spec.rb | 86 ++++++ .../custom_wizard/wizard_step_serializer.rb | 59 ++++ .../components/custom_wizard/api_spec.rb | 21 -- .../components/custom_wizard/mapper_spec.rb | 13 - .../custom_wizard/admin_controller_spec.rb | 5 - .../application_controller_spec.rb | 5 - .../custom_wizard/wizard_controller_spec.rb | 31 -- .../custom_wizard/wizard_serializer_spec.rb | 45 --- 60 files changed, 2138 insertions(+), 479 deletions(-) rename controllers/custom_wizard/{ => admin}/transfer.rb (50%) create mode 100644 spec/components/custom_wizard/field_spec.rb create mode 100644 spec/components/custom_wizard/log_spec.rb create mode 100644 spec/components/custom_wizard/mapper_spec.rb create mode 100644 spec/components/custom_wizard/template_spec.rb create mode 100644 spec/components/custom_wizard/validator_spec.rb create mode 100644 spec/components/custom_wizard/wizard_spec.rb create mode 100644 spec/extensions/extra_locales_controller_spec.rb create mode 100644 spec/extensions/invites_controller_spec.rb create mode 100644 spec/extensions/users_controller_spec.rb create mode 100644 spec/extensions/wizard_field_spec.rb create mode 100644 spec/extensions/wizard_step_spec.rb create mode 100644 spec/fixtures/field/field.json create mode 100644 spec/fixtures/mapper/data.json create mode 100644 spec/fixtures/mapper/inputs.json rename spec/fixtures/{wizard => step}/required_data.json (100%) create mode 100644 spec/fixtures/step/step.json create mode 100644 spec/fixtures/wizard/after_time.json create mode 100644 spec/jobs/clear_after_time_wizard_spec.rb create mode 100644 spec/jobs/set_after_time_wizard_spec.rb create mode 100644 spec/requests/custom_wizard/admin/logs_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/submissions_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/transfer_controller_spec.rb create mode 100644 spec/requests/custom_wizard/admin/wizard_controller_spec.rb create mode 100644 spec/requests/custom_wizard/application_controller_spec.rb create mode 100644 spec/requests/custom_wizard/steps_controller_spec.rb create mode 100644 spec/requests/custom_wizard/wizard_controller_spec.rb create mode 100644 spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/log_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_field_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/wizard_step_serializer.rb delete mode 100644 spec_offload/components/custom_wizard/api_spec.rb delete mode 100644 spec_offload/components/custom_wizard/mapper_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/admin_controller_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/application_controller_spec.rb delete mode 100644 spec_offload/requests/custom_wizard/wizard_controller_spec.rb delete mode 100644 spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2fcfe687..4f9ef820 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -26,6 +26,11 @@ en: file_large: "File too large" invalid_json: "File is not a valid json file" no_valid_wizards: "File doesn't contain any valid wizards" + + validation: + required: "%{property} is required" + conflict: "Wizard with %{wizard_id} already exists" + after_time: "After time setting is invalid" site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/config/routes.rb b/config/routes.rb index 908e9656..8f754d08 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,8 +33,8 @@ Discourse::Application.routes.append do get 'admin/wizards/logs' => 'admin_logs#index' - get 'admin/wizards/transfer' => 'transfer#index' - get 'admin/wizards/transfer/export' => 'transfer#export' - post 'admin/wizards/transfer/import' => 'transfer#import' + get 'admin/wizards/transfer' => 'admin_transfer#index' + get 'admin/wizards/transfer/export' => 'admin_transfer#export' + post 'admin/wizards/transfer/import' => 'admin_transfer#import' end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 2dd87e86..fe438e5d 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -9,5 +9,6 @@ class CustomWizard::AdminController < ::Admin::AdminController def find_wizard params.require(:wizard_id) @wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore) + raise Discourse::InvalidParameters.new(:wizard_id) unless @wizard end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/logs.rb b/controllers/custom_wizard/admin/logs.rb index 60303af7..005de8dc 100644 --- a/controllers/custom_wizard/admin/logs.rb +++ b/controllers/custom_wizard/admin/logs.rb @@ -1,7 +1,7 @@ class CustomWizard::AdminLogsController < CustomWizard::AdminController def index render_serialized( - CustomWizard::Log.list(params[:page].to_i), + CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i), CustomWizard::LogSerializer ) end diff --git a/controllers/custom_wizard/admin/submissions.rb b/controllers/custom_wizard/admin/submissions.rb index b7d45e44..69088492 100644 --- a/controllers/custom_wizard/admin/submissions.rb +++ b/controllers/custom_wizard/admin/submissions.rb @@ -1,29 +1,23 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController skip_before_action :preload_json, :check_xhr, only: [:download] - - before_action :find_wizard + before_action :find_wizard, except: [:index] def index render json: ActiveModel::ArraySerializer.new( - CustomWizard::Wizard.list, + CustomWizard::Wizard.list(current_user), each_serializer: CustomWizard::BasicWizardSerializer ) end def show - result = {} - - if wizard = @wizard - submissions = build_submissions(wizard.id) - result[:wizard] = CustomWizard::BasicWizardSerializer.new(wizard, root: false) - result[:submissions] = submissions.as_json - end - - render_json_dump(result) + render_json_dump( + wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), + submissions: build_submissions.as_json + ) end def download - send_data build_submissions(@wizard.id).to_json, + send_data build_submissions.to_json, filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", content_type: "application/json", disposition: "attachment" @@ -31,23 +25,21 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController private - def build_submissions(wizard_id) - rows = PluginStoreRow.where(plugin_name: "#{wizard_id}_submissions").order('id DESC') - - submissions = [*rows].map do |row| - value = ::JSON.parse(row.value) - - if user = User.find_by(id: row.key) - username = user.username - else - username = I18n.t('admin.wizard.submissions.no_user', id: row.key) - end - - value.map do |submission| - { - username: username - }.merge!(submission.except("redirect_to")) - end - end.flatten + def build_submissions + PluginStoreRow.where(plugin_name: "#{@wizard.id}_submissions") + .order('id DESC') + .map do |row| + value = ::JSON.parse(row.value) + + if user = User.find_by(id: row.key) + username = user.username + else + username = I18n.t('admin.wizard.submissions.no_user', id: row.key) + end + + value.map do |v| + { username: username }.merge!(v.except("redirect_to")) + end + end.flatten end end \ No newline at end of file diff --git a/controllers/custom_wizard/transfer.rb b/controllers/custom_wizard/admin/transfer.rb similarity index 50% rename from controllers/custom_wizard/transfer.rb rename to controllers/custom_wizard/admin/transfer.rb index f1bf0bba..b55fe872 100644 --- a/controllers/custom_wizard/transfer.rb +++ b/controllers/custom_wizard/admin/transfer.rb @@ -1,25 +1,22 @@ -class CustomWizard::TransferController < ::ApplicationController - before_action :ensure_logged_in - before_action :ensure_admin +class CustomWizard::AdminTransferController < CustomWizard::AdminController skip_before_action :check_xhr, :only => [:export] - def index - end - def export - wizards = params['wizards'] - wizard_objects = [] + wizard_ids = params['wizards'] + templates = [] - if wizards.nil? + if wizard_ids.nil? render json: { error: I18n.t('wizard.export.error.select_one') } return end - wizards.each do |w| - wizard_objects.push(PluginStore.get('custom_wizard', w.tr('-', '_'))) + wizard_ids.each do |wizard_id| + if template = CustomWizard::Template.find(wizard_id) + templates.push(template) + end end - send_data wizard_objects.to_json, + send_data templates.to_json, type: "application/json", disposition: 'attachment', filename: 'wizards.json' @@ -27,45 +24,42 @@ class CustomWizard::TransferController < ::ApplicationController def import file = File.read(params['file'].tempfile) - + if file.nil? render json: { error: I18n.t('wizard.import.error.no_file') } return end - fileSize = file.size - maxFileSize = 512 * 1024 - - if maxFileSize < fileSize + file_size = file.size + max_file_size = 512 * 1024 + + if max_file_size < file_size render json: { error: I18n.t('wizard.import.error.file_large') } return end - + begin - jsonObject = JSON.parse file + template_json = JSON.parse file rescue JSON::ParserError render json: { error: I18n.t('wizard.import.error.invalid_json') } return end - countValid = 0 success_ids = [] failed_ids = [] - jsonObject.each do |o| - if !CustomWizard::Wizard.new(o) - failed_ids.push o['id'] - next + template_json.each do |t_json| + template = CustomWizard::Template.new(t_json) + template.save(skip_jobs: true) + + if template.errors.any? + failed_ids.push t_json['id'] + else + success_ids.push t_json['id'] end - - countValid += 1 - pluginStoreEntry = PluginStore.new 'custom_wizard' - saved = pluginStoreEntry.set(o['id'], o) unless pluginStoreEntry.get(o['id']) - success_ids.push o['id'] if !!saved - failed_ids.push o['id'] if !saved end - if countValid == 0 + if success_ids.length == 0 render json: { error: I18n.t('wizard.import.error.no_valid_wizards') } else render json: { success: success_ids, failed: failed_ids } diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 48beea67..0594de5e 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -4,7 +4,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController def index render_json_dump( wizard_list: ActiveModel::ArraySerializer.new( - CustomWizard::Template.list, + CustomWizard::Wizard.list(current_user), each_serializer: CustomWizard::BasicWizardSerializer ), field_types: CustomWizard::Field.types @@ -30,20 +30,13 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController end def save - opts = {} - opts[:create] = params[:create] if params[:create] + template = CustomWizard::Template.new(save_wizard_params.to_h) + wizard_id = template.save(create: params[:create]) - validator = CustomWizard::Validator.new(save_wizard_params.to_h, opts) - validation = validator.perform - - if validation[:error] - render json: { error: validation[:error] } + if template.errors.any? + render json: failed_json.merge(errors: result.errors.full_messages) else - if wizard_id = CustomWizard::Template.save(validation[:wizard]) - render json: success_json.merge(wizard_id: wizard_id) - else - render json: failed_json - end + render json: success_json.merge(wizard_id: wizard_id) end end diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb index 4d338ab8..778707a6 100644 --- a/controllers/custom_wizard/steps.rb +++ b/controllers/custom_wizard/steps.rb @@ -15,22 +15,24 @@ class CustomWizard::StepsController < ::ApplicationController field_ids = step.fields.map(&:id) + update = update_params.to_h + if params[:fields] - permitted_fields = params[:fields].select { |k, v| field_ids.include? k } - update_params[:fields] = permitted_fields - update_params.permit! + update[:fields] = {} + + params[:fields].each do |k, v| + update[:fields][k] = v if field_ids.include? k + end end - updater = wizard.create_updater( - update_params[:step_id], - update_params[:fields] - ) + updater = wizard.create_updater(update[:step_id], update[:fields]) updater.update - + if updater.success? result = success_json result.merge!(updater.result) if updater.result result[:refresh_required] = true if updater.refresh_required? + render json: result else errors = [] diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index bf2b3ed4..aa786510 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -1,7 +1,8 @@ class CustomWizard::WizardController < ::ApplicationController prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) layout 'wizard' - + + before_action :ensure_plugin_enabled helper_method :wizard_page_title helper_method :theme_ids @@ -38,7 +39,7 @@ class CustomWizard::WizardController < ::ApplicationController def skip params.require(:wizard_id) - + if wizard.required && !wizard.completed? && wizard.permitted? return render json: { error: I18n.t('wizard.no_skip') } end @@ -47,16 +48,11 @@ class CustomWizard::WizardController < ::ApplicationController user = current_user if user - submission = wizard.submissions.last - + submission = wizard.current_submission if submission && submission['redirect_to'] result.merge!(redirect_to: submission['redirect_to']) end - if submission && !wizard.save_submissions - PluginStore.remove("#{wizard.id}_submissions", user.id) - end - if user.custom_fields['redirect_to_wizard'] === wizard.id user.custom_fields.delete('redirect_to_wizard') user.save_custom_fields(true) @@ -65,4 +61,12 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + + private + + def ensure_plugin_enabled + unless SiteSetting.custom_wizard_enabled + redirect_to path("/") + end + end end diff --git a/jobs/set_after_time_wizard.rb b/jobs/set_after_time_wizard.rb index 80f8f5e5..38d0eebe 100644 --- a/jobs/set_after_time_wizard.rb +++ b/jobs/set_after_time_wizard.rb @@ -3,7 +3,7 @@ module Jobs def execute(args) if SiteSetting.custom_wizard_enabled wizard = CustomWizard::Wizard.create(args[:wizard_id]) - + if wizard && wizard.after_time user_ids = [] diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index a28498e6..18252f85 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -267,7 +267,7 @@ class CustomWizard::Action url += "&body=#{params[:raw]}" if category_id = action_category - if category_id && category = Category.find(category_id) + if category = Category.find_by(id: category_id) url += "&category=#{category.full_slug('/')}" end end @@ -412,6 +412,8 @@ class CustomWizard::Action data: data, user: user ).perform + + return false unless output.present? if output.is_a?(Array) output.first @@ -428,6 +430,8 @@ class CustomWizard::Action data: data, user: user, ).perform + + return false unless output.present? if output.is_a?(Array) output.flatten @@ -483,11 +487,11 @@ class CustomWizard::Action def public_topic_params params = {} - if (category = action_category) + if category = action_category params[:category] = category end - if (tags = action_tags) + if tags = action_tags params[:tags] = tags end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 4e8ec8b5..66d2f5df 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -49,7 +49,6 @@ class CustomWizard::Builder return @wizard if !@wizard.can_access? build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit - reset_submissions if build_opts[:reset] @steps.each do |step_template| @wizard.append_step(step_template['id']) do |step| @@ -110,9 +109,7 @@ class CustomWizard::Builder data = updater.fields - ## if the wizard has data from the previous steps make that accessible to the actions. - if @submissions && @submissions.last && !@submissions.last.key?("submitted_at") - submission = @submissions.last + if submission = @wizard.current_submission data = submission.merge(data) end @@ -133,29 +130,31 @@ class CustomWizard::Builder end end end - - if route_to = data['route_to'] - data.delete('route_to') - end - - if @wizard.save_submissions && updater.errors.empty? - save_submissions(data, final_step) - elsif final_step - PluginStore.remove("#{@wizard.id}_submissions", @wizard.user.id) - end - - if final_step && @wizard.id === @wizard.user.custom_fields['redirect_to_wizard'] - @wizard.user.custom_fields.delete('redirect_to_wizard'); - @wizard.user.save_custom_fields(true) - end - + if updater.errors.empty? + if route_to = data['route_to'] + data.delete('route_to') + end + + if @wizard.save_submissions + save_submissions(data, final_step) + end + if final_step + if @wizard.id == @wizard.user.custom_fields['redirect_to_wizard'] + @wizard.user.custom_fields.delete('redirect_to_wizard'); + @wizard.user.save_custom_fields(true) + end + redirect_url = route_to || data['redirect_on_complete'] || data["redirect_to"] updater.result[:redirect_on_complete] = redirect_url elsif route_to updater.result[:redirect_on_next] = route_to end + + true + else + false end end end @@ -177,10 +176,8 @@ class CustomWizard::Builder params[:key] = field_template['key'] if field_template['key'] params[:min_length] = field_template['min_length'] if field_template['min_length'] params[:value] = prefill_field(field_template, step_template) - - ## Load previously submitted values - if !build_opts[:reset] && @submissions.last && !@submissions.last.key?("submitted_at") - submission = @submissions.last + + if !build_opts[:reset] && (submission = @wizard.current_submission) params[:value] = submission[field_template['id']] if submission[field_template['id']] end @@ -359,15 +356,9 @@ class CustomWizard::Builder if data.present? @submissions.pop(1) if @wizard.unfinished? @submissions.push(data) - PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions) + @wizard.set_submissions(@submissions) end end - - def reset_submissions - @submissions.pop(1) if @wizard.unfinished? - PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions) - @wizard.reset - end def save_permitted_params(permitted_params, params) permitted_data = {} diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index 534e4824..cbb08e9c 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -57,10 +57,10 @@ class CustomWizard::Field @require_assets ||= {} end - def self.add_assets(type, plugin = nil, asset_paths = [], opts={}) + def self.register(type, plugin = nil, asset_paths = [], opts={}) if type - types[type] ||= {} - types[type] = opts[:type_opts] if opts[:type_opts].present? + types[type.to_sym] ||= {} + types[type.to_sym] = opts[:type_opts] if opts[:type_opts].present? end if plugin && asset_paths diff --git a/lib/custom_wizard/log.rb b/lib/custom_wizard/log.rb index 56430949..7dca2093 100644 --- a/lib/custom_wizard/log.rb +++ b/lib/custom_wizard/log.rb @@ -29,9 +29,12 @@ class CustomWizard::Log ").order("value::json->>'date' DESC") end - def self.list(page = 0) - self.list_query.limit(PAGE_LIMIT) - .offset(page * PAGE_LIMIT) + def self.list(page = 0, limit = nil) + limit = limit.to_i > 0 ? limit.to_i : PAGE_LIMIT + page = page.to_i + + self.list_query.limit(limit) + .offset(page * limit) .map { |r| self.new(JSON.parse(r.value)) } end end \ No newline at end of file diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 97791703..d28daed9 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -144,7 +144,7 @@ class CustomWizard::Mapper end if operator == '=~' - result == 0 ? true : false + result.nil? ? false : true else result end diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb index 39e4b3e5..fc79e91d 100644 --- a/lib/custom_wizard/template.rb +++ b/lib/custom_wizard/template.rb @@ -1,53 +1,48 @@ class CustomWizard::Template - def self.add(obj) - wizard = obj.is_a?(String) ? ::JSON.parse(json) : obj - PluginStore.set('custom_wizard', wizard["id"], wizard) + include HasErrors + + attr_reader :data, + :opts + + def initialize(data) + @data = data end - + + def save(opts={}) + @opts = opts + + normalize_data + validate_data + prepare_data + + return false if errors.any? + + ActiveRecord::Base.transaction do + schedule_save_jobs unless opts[:skip_jobs] + PluginStore.set('custom_wizard', @data[:id], @data) + end + + @data[:id] + end + + def self.save(data, opts={}) + new(data).save(opts) + end + def self.find(wizard_id) PluginStore.get('custom_wizard', wizard_id) end - def self.save(data) - data = data.with_indifferent_access - existing = self.find(data[:id]) - - data[:steps].each do |step| - if step[:raw_description] - step[:description] = PrettyText.cook(step[:raw_description]) - end - end - - data = data.slice!(:create) - - ActiveRecord::Base.transaction do - PluginStore.set('custom_wizard', data[:id], data) - - if data[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) - enqueue_at = Time.parse(data[:after_time_scheduled]).utc - Jobs.enqueue_at(enqueue_at, :set_after_time_wizard, wizard_id: data[:id]) - end - - if existing && existing[:after_time] && !data[:after_time] - Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: data[:id]) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: data[:id]) - end - end - - data[:id] - end - def self.remove(wizard_id) - wizard = self.create(wizard_id) + wizard = CustomWizard::Wizard.create(wizard_id) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do + PluginStore.remove('custom_wizard', wizard.id) + if wizard.after_time Jobs.cancel_scheduled_job(:set_after_time_wizard) - Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard.id) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) end - - PluginStore.remove('custom_wizard', wizard.id) end end @@ -55,8 +50,11 @@ class CustomWizard::Template PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: wizard_id) end - def self.list(user=nil) - PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id) + def self.list(setting: nil, order: :id) + query = "plugin_name = 'custom_wizard'" + query += "AND (value::json ->> '#{setting}')::boolean IS TRUE" if setting + + PluginStoreRow.where(query).order(order) .reduce([]) do |result, record| attrs = JSON.parse(record.value) @@ -72,10 +70,46 @@ class CustomWizard::Template end end - def self.setting_enabled(attr) - PluginStoreRow.where(" - plugin_name = 'custom_wizard' AND - (value::json ->> '#{attr}')::boolean IS TRUE - ") + private + + def normalize_data + @data = ::JSON.parse(@data) if @data.is_a?(String) + @data = @data.with_indifferent_access + end + + def prepare_data + @data[:steps].each do |step| + if step[:raw_description] + step[:description] = PrettyText.cook(step[:raw_description]) + end + end + end + + def validate_data + validator = CustomWizard::Validator.new(@data, @opts) + validator.perform + add_errors_from(validator) + end + + def schedule_save_jobs + if @data[:after_time] && @data[:after_time_scheduled] + wizard_id = @data[:id] + old_data = CustomWizard::Template.find(data[:id]) + + begin + enqueue_wizard_at = Time.parse(@data[:after_time_scheduled]).utc + rescue ArgumentError + errors.add :validation, I18n.t("wizard.validation.after_time") + raise ActiveRecord::Rollback.new + end + + if enqueue_wizard_at + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) + Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id) + elsif old_data && old_data[:after_time] + Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) + Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id) + end + end end end \ No newline at end of file diff --git a/lib/custom_wizard/validator.rb b/lib/custom_wizard/validator.rb index b75b295d..e98023d0 100644 --- a/lib/custom_wizard/validator.rb +++ b/lib/custom_wizard/validator.rb @@ -1,52 +1,38 @@ class CustomWizard::Validator + include HasErrors - def initialize(params, opts={}) - @params = params + def initialize(data, opts={}) + @data = data @opts = opts - @error = nil end def perform - params = @params + data = @data - check_id(params, :wizard) - check_required(params, :wizard) - check_depdendent(params, :wizard) - - after_time = nil - - if !@error && @params[:after_time] - validate_after_time - end + check_id(data, :wizard) + check_required(data, :wizard) + validate_after_time - if !@error - params[:steps].each do |step| - check_required(step, :step) - check_depdendent(step, :step) - break if @error.present? - - if params[:fields].present? - params[:fields].each do |field| - check_required(field, :field) - check_depdendent(field, :field) - break if @error.present? - end - end - end + data[:steps].each do |step| + check_required(step, :step) - if params[:actions].present? - params[:actions].each do |action| - check_required(action, :action) - check_depdendent(action, :action) - break if @error.present? + if data[:fields].present? + data[:fields].each do |field| + check_required(field, :field) end end end + + if data[:actions].present? + data[:actions].each do |action| + check_required(action, :action) + end + end - if @error - { error: @error } + if errors.any? + false else - { wizard: params } + true end end @@ -59,54 +45,28 @@ class CustomWizard::Validator } end - def self.dependent - { - wizard: { - after_time: 'after_time_scheduled' - }, - step: {}, - field: {}, - action: {} - } - end - private def check_required(object, type) CustomWizard::Validator.required[type].each do |property| if object[property].blank? - @error = { - type: 'required', - params: { type: type, property: property } - } + errors.add :validation, I18n.t("wizard.validation.required", property: property) end end end - - def check_depdendent(object, type) - CustomWizard::Validator.dependent[type].each do |property, dependent| - if object[property] && object[dependent].blank? - @error = { - type: 'dependent', - params: { property: property, dependent: dependent } - } - end - end - end - + def check_id(object, type) if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id]) - @error = { - type: 'conflict', - params: { type: type, property: 'id', value: object[:id] } - } + errors.add :validation, I18n.t("wizard.validation.conflict", id: object[:id]) end end - def validate_after_time - wizard = CustomWizard::Wizard.create(@params[:id]) if !@opts[:create] + def validate_after_time + return unless @data[:after_time] + + wizard = CustomWizard::Wizard.create(@data[:id]) if !@opts[:create] current_time = wizard.present? ? wizard.after_time_scheduled : nil - new_time = @params[:after_time_scheduled] + new_time = @data[:after_time_scheduled] begin active_time = Time.parse(new_time.present? ? new_time : current_time).utc @@ -115,7 +75,7 @@ class CustomWizard::Validator end if invalid_time || active_time.blank? || active_time < Time.now.utc - @error = { type: 'after_time' } + errors.add :validation, I18n.t("wizard.validation.after_time") end end end \ No newline at end of file diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index b4f41089..96ae4e0e 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -26,22 +26,24 @@ class CustomWizard::Wizard :steps, :step_ids, :actions, - :user + :user, + :first_step def initialize(attrs = {}, user=nil) @user = user + attrs = attrs.with_indifferent_access @id = attrs['id'] @name = attrs['name'] @background = attrs['background'] - @save_submissions = attrs['save_submissions'] || false - @multiple_submissions = attrs['multiple_submissions'] || false - @prompt_completion = attrs['prompt_completion'] || false - @restart_on_revisit = attrs['restart_on_revisit'] || false - @after_signup = attrs['after_signup'] - @after_time = attrs['after_time'] + @save_submissions = cast_bool(attrs['save_submissions']) + @multiple_submissions = cast_bool(attrs['multiple_submissions']) + @prompt_completion = cast_bool(attrs['prompt_completion']) + @restart_on_revisit = cast_bool(attrs['restart_on_revisit']) + @after_signup = cast_bool(attrs['after_signup']) + @after_time = cast_bool(attrs['after_time']) @after_time_scheduled = attrs['after_time_scheduled'] - @required = attrs['required'] || false + @required = cast_bool(attrs['required']) @permitted = attrs['permitted'] || nil @needs_categories = false @needs_groups = false @@ -53,10 +55,17 @@ class CustomWizard::Wizard end @first_step = nil - @step_ids = attrs['steps'].map { |s| s['id'] } @steps = [] + if attrs['steps'].present? + @step_ids = attrs['steps'].map { |s| s['id'] } + end + @actions = [] end + + def cast_bool(val) + val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val) + end def create_step(step_name) ::Wizard::Step.new(step_name) @@ -69,9 +78,9 @@ class CustomWizard::Wizard last_step = steps.last steps << step - + if steps.size == 1 - first_step = step + @first_step = step step.index = 0 elsif last_step.present? last_step.next = step @@ -94,14 +103,14 @@ class CustomWizard::Wizard last_index = steps.index { |s| s.id == step_id } steps[last_index + 1] else - first_step + @first_step end end - def create_updater(step_id, fields) + def create_updater(step_id, inputs) step = @steps.find { |s| s.id == step_id } wizard = self - CustomWizard::StepUpdater.new(user, wizard, step, fields) + CustomWizard::StepUpdater.new(user, wizard, step, inputs) end def unfinished? @@ -116,7 +125,7 @@ class CustomWizard::Wizard if most_recent && most_recent.subject == "reset" false elsif most_recent - most_recent.subject != steps.last.id + most_recent.subject != steps.last.id else true end @@ -134,7 +143,7 @@ class CustomWizard::Wizard if after_time history = history.where("updated_at > ?", after_time_scheduled) end - + completed = history.distinct.order(:subject).pluck(:subject) (step_ids - completed).empty? end @@ -166,6 +175,7 @@ class CustomWizard::Wizard end def can_access? + return false unless user return true if user.admin return permitted? && (multiple_submissions || !completed?) end @@ -191,63 +201,76 @@ class CustomWizard::Wizard Array.wrap(PluginStore.get("#{id}_submissions", user.id)) end + def current_submission + if submissions.present? && !submissions.last.key?("submitted_at") + submissions.last + else + nil + end + end + + def set_submissions(submissions) + PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions)) + end + + def self.submissions(wizard_id, user) + new({ id: wizard_id }, user).submissions + end + + def self.set_submissions(wizard_id, user, submissions) + new({ id: wizard_id }, user).set_submissions(submissions) + end + def self.create(wizard_id, user = nil) if template = CustomWizard::Template.find(wizard_id) - self.new(template.to_h, user) + new(template.to_h, user) else false end end - def self.list(user=nil) - CustomWizard::Template.list.map { |template| self.new(template.to_h, user) } + def self.list(user, template_opts: {}) + return [] unless user + + CustomWizard::Template.list(template_opts).reduce([]) do |result, template| + wizard = new(template, user) + result.push(wizard) if wizard.can_access? + result + end end def self.after_signup(user) - if (records = CustomWizard::Template.setting_enabled('after_signup')).any? - result = false - - records - .sort_by { |record| record.value['permitted'].present? ? 0 : 1 } - .each do |record| - wizard = self.new(JSON.parse(record.value), user) - - if wizard.permitted? - result = wizard - break - end - end - - result - else - false - end + wizards = list( + user, + template_opts: { + setting: 'after_signup', + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + } + ) + wizards.any? ? wizards.first : false end def self.prompt_completion(user) - if (records = CustomWizard::Template.setting_enabled('prompt_completion')).any? - records.reduce([]) do |result, record| - wizard = self.new(::JSON.parse(record.value), user) - - if wizard.permitted? && !wizard.completed? - result.push(id: wizard.id, name: wizard.name) - end - - result + wizards = list( + user, + template_opts: { + setting: 'prompt_completion', + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + } + ) + + if wizards.any? + wizards.map do |w| + { + id: w.id, + name: w.name + } end else false end end - def self.restart_on_revisit - if (records = CustomWizard::Template.setting_enabled('restart_on_revisit')).any? - records.first.key - else - false - end - end - def self.set_submission_redirect(user, wizard_id, url) PluginStore.set("#{wizard_id.underscore}_submissions", user.id, [{ redirect_to: url }]) end diff --git a/plugin.rb b/plugin.rb index 3a8d48e0..544db890 100644 --- a/plugin.rb +++ b/plugin.rb @@ -42,9 +42,9 @@ after_initialize do ../controllers/custom_wizard/admin/submissions.rb ../controllers/custom_wizard/admin/api.rb ../controllers/custom_wizard/admin/logs.rb + ../controllers/custom_wizard/admin/transfer.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb - ../controllers/custom_wizard/transfer.rb ../jobs/clear_after_time_wizard.rb ../jobs/refresh_api_access_token.rb ../jobs/set_after_time_wizard.rb @@ -128,8 +128,7 @@ after_initialize do if request.referer !~ /\/w\// && request.referer !~ /\/invites\// CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer) end - - if CustomWizard::Wizard.exists?(wizard_id) + if CustomWizard::Template.exists?(wizard_id) redirect_to "/w/#{wizard_id.dasherize}" end end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 12308ef2..890dde12 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -7,31 +7,62 @@ describe CustomWizard::Action do before do Group.refresh_automatic_group!(:trust_level_2) - CustomWizard::Template.add( + CustomWizard::Template.save( JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - ) + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end - it 'creates a topic' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - - updater = wizard.create_updater( - wizard.steps[0].id, - step_1_field_1: "Topic Title", - step_1_field_2: "topic body" - ).update - updater2 = wizard.create_updater(wizard.steps[1].id, {}).update + context "creating a topic" do + + end + + context 'creating a topic' do + it "works" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater( + wizard.steps.first.id, + step_1_field_1: "Topic Title", + step_1_field_2: "topic body" + ).update + wizard.create_updater(wizard.steps.second.id, {}).update + wizard.create_updater(wizard.steps.last.id, + step_3_field_3: category.id + ).update + + topic = Topic.where( + title: "Topic Title", + category_id: category.id + ) + expect(topic.exists?).to eq(true) + expect(Post.where( + topic_id: topic.pluck(:id), + raw: "topic body" + ).exists?).to eq(true) + end - topic = Topic.where(title: "Topic Title") - - expect(topic.exists?).to eq(true) - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "topic body" - ).exists?).to eq(true) + it "fails silently without basic topic inputs" do + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater( + wizard.steps.first.id, + step_1_field_2: "topic body" + ).update + wizard.create_updater(wizard.steps.second.id, {}).update + updater = wizard.create_updater(wizard.steps.last.id, {}) + updater.update + + expect(updater.success?).to eq(true) + expect(UserHistory.where( + acting_user_id: user.id, + context: "super_mega_fun_wizard", + subject: "step_3" + ).exists?).to eq(true) + expect(Post.where( + raw: "topic body" + ).exists?).to eq(false) + end end it 'sends a message' do @@ -79,8 +110,7 @@ describe CustomWizard::Action do updater = wizard.create_updater(wizard.steps[1].id, {}) updater.update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - category = Category.find_by(id: submissions.first['action_8']) + category = Category.find_by(id: wizard.current_submission['action_8']) expect(updater.result[:redirect_on_next]).to eq( "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus@email.com&category=#{category.slug}/#{category.id}&tags=tag1" @@ -91,24 +121,21 @@ describe CustomWizard::Action do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - expect(Category.where(id: submissions.first['action_8']).exists?).to eq(true) + expect(Category.where(id: wizard.current_submission['action_8']).exists?).to eq(true) end it 'creates a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build step_id = wizard.steps[0].id updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - expect(Group.where(name: submissions.first['action_9']).exists?).to eq(true) + expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true) end it 'adds a user to a group' do wizard = CustomWizard::Builder.new(@template[:id], user).build step_id = wizard.steps[0].id updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) - group = Group.find_by(name: submissions.first['action_9']) + group = Group.find_by(name: wizard.current_submission['action_9']) expect(group.users.first.username).to eq('angus') end @@ -116,9 +143,8 @@ describe CustomWizard::Action do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - submissions = PluginStore.get("super_mega_fun_wizard_submissions", user.id) expect(CategoryUser.where( - category_id: submissions.first['action_8'], + category_id: wizard.current_submission['action_8'], user_id: user.id ).first.notification_level).to eq(2) expect(CategoryUser.where( diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 341cf045..699f5ce9 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -18,7 +18,7 @@ describe CustomWizard::Builder do let(:required_data_json) { JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/required_data.json" + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" ).read) } @@ -36,11 +36,11 @@ describe CustomWizard::Builder do before do Group.refresh_automatic_group!(:trust_level_3) - CustomWizard::Template.add( + CustomWizard::Template.save( JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - ) + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end @@ -175,9 +175,8 @@ describe CustomWizard::Builder do context "user has partially completed" do before do - PluginStore.set("super_mega_fun_wizard_submissions", user.id, - step_1_field_1: 'I am a user submission' - ) + wizard = CustomWizard::Wizard.new(@template, user) + wizard.set_submissions(step_1_field_1: 'I am a user submission') end it 'returns saved submissions' do @@ -241,7 +240,7 @@ describe CustomWizard::Builder do end it 'is permitted if required data is present' do - PluginStore.set('super_mega_fun_wizard_submissions', user.id, + CustomWizard::Wizard.set_submissions('super_mega_fun_wizard', user, required_data: "required_value" ) expect( @@ -259,13 +258,10 @@ describe CustomWizard::Builder do end it 'saves permitted params' do - CustomWizard::Builder.new(@template[:id], user).build({}, + wizard = CustomWizard::Builder.new(@template[:id], user).build({}, param: 'param_value' ) - expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) - .first['saved_param'] - ).to eq('param_value') + expect(wizard.current_submission['saved_param']).to eq('param_value') end end end @@ -302,7 +298,7 @@ describe CustomWizard::Builder do it 'saves submissions' do perform_update('step_1', step_1_field_1: 'Text input') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user) .first['step_1_field_1'] ).to eq('Text input') end @@ -316,7 +312,7 @@ describe CustomWizard::Builder do it "does not save submissions" do perform_update('step_1', step_1_field_1: 'Text input') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user).first ).to eq(nil) end end @@ -332,7 +328,7 @@ describe CustomWizard::Builder do it 'standardises boolean entries' do perform_update('step_2', step_2_field_5: 'false') expect( - PluginStore.get("super_mega_fun_wizard_submissions", user.id) + CustomWizard::Wizard.submissions(@template[:id], user) .first['step_2_field_5'] ).to eq(false) end diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb new file mode 100644 index 00000000..d6d6e61a --- /dev/null +++ b/spec/components/custom_wizard/field_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe CustomWizard::Field do + before do + CustomWizard::Field.register( + 'location', + 'discourse-locations', + ['components', 'helpers', 'lib', 'stylesheets', 'templates'], + type_opts: { + prefill: { "coordinates": [35.3082, 149.1244] } + } + ) + end + + it "registers custom field types" do + expect(CustomWizard::Field.types[:location].present?).to eq(true) + end + + it "allows custom field types to set default attributes" do + expect( + CustomWizard::Field.types[:location][:prefill] + ).to eq({ "coordinates": [35.3082, 149.1244] }) + end + + it "registers custom field assets" do + expect( + CustomWizard::Field.require_assets['discourse-locations'] + ).to eq(['components', 'helpers', 'lib', 'stylesheets', 'templates']) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/log_spec.rb b/spec/components/custom_wizard/log_spec.rb new file mode 100644 index 00000000..9c8b570d --- /dev/null +++ b/spec/components/custom_wizard/log_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe CustomWizard::Log do + before do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + CustomWizard::Log.create("Third log message") + end + + it "creates logs" do + expect( + CustomWizard::Log.list.length + ).to eq(3) + end + + it "lists logs by time created" do + expect( + CustomWizard::Log.list.first.message + ).to eq("Third log message") + end + + it "paginates logs" do + expect( + CustomWizard::Log.list(0, 2).length + ).to eq(2) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb new file mode 100644 index 00000000..4ddbee57 --- /dev/null +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -0,0 +1,250 @@ +require 'rails_helper' + +describe CustomWizard::Mapper do + fab!(:user1) { + Fabricate(:user, + name: "Angus", + username: "angus", + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + fab!(:user2) { + Fabricate(:user, + name: "Patrick", + username: "patrick", + email: "patrick@email2.com", + trust_level: TrustLevel[1] + ) + } + fab!(:user_field) { + field = Fabricate(:user_field, + id: 3, + name: 'dropdown_field', + description: 'field desc', + field_type: 'dropdown', + user_field_options_attributes: [ + { value: "a" }, + { value: "b" }, + { value: "c" } + ] + ) + } + let(:inputs) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/inputs.json" + ).read) + } + let(:data) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/data.json" + ).read) + } + + it "maps values" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment'], + data: data, + user: user1 + ).perform).to eq([13]) + end + + it "maps associations" do + association = CustomWizard::Mapper.new( + inputs: inputs['association'], + data: data, + user: user1 + ).perform + expect(association.length).to eq(3) + expect(association.first[:value]).to eq("Choice 1") + end + + context "conditional mapping" do + it "maps when the condition is met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional'], + data: data, + user: user1 + ).perform).to eq("true") + end + + it "does not map when the condition is not met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional'], + data: data, + user: user2 + ).perform).to eq(nil) + end + + it "maps when multiple conditions are met" do + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional_multiple_pairs'], + data: data, + user: user1 + ).perform).to eq("true") + end + + it "does not map when one of multiple conditions are not met" do + user1.email = "angus@other-email.com" + expect(CustomWizard::Mapper.new( + inputs: inputs['conditional_multiple_pairs'], + data: data, + user: user1 + ).perform).to eq(nil) + end + end + + it "validates valid data" do + expect(CustomWizard::Mapper.new( + inputs: inputs['validation'], + data: data, + user: user1 + ).perform).to eq(true) + end + + it "does not validate invalid data" do + data["input_2"] = "value 3" + expect(CustomWizard::Mapper.new( + inputs: inputs['validation'], + data: data, + user: user1 + ).perform).to eq(false) + end + + it "maps text fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_text'], + data: data, + user: user1 + ).perform).to eq("Value") + end + + it "maps user fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_user_field'], + data: data, + user: user1 + ).perform).to eq("Angus") + end + + it "maps user field options" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_user_field_options'], + data: data, + user: user1 + ).perform).to eq(["a", "b", "c"]) + end + + it "maps wizard fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_wizard_field'], + data: data, + user: user1 + ).perform).to eq("value 1") + end + + it "maps wizard actions" do + expect(CustomWizard::Mapper.new( + inputs: inputs['assignment_wizard_action'], + data: data, + user: user1 + ).perform).to eq("value 2") + end + + it "interpolates user fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_user_field'], + data: data, + user: user1 + ).perform).to eq("Name: Angus") + end + + it "interpolates wizard fields" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_wizard_field'], + data: data, + user: user1 + ).perform).to eq("Input 1: value 1") + end + + it "interpolates date" do + expect(CustomWizard::Mapper.new( + inputs: inputs['interpolate_timestamp'], + data: data, + user: user1 + ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}") + end + + it "handles greater than pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_pair'], + data: data, + user: user2 + ).perform).to eq(false) + end + + it "handles less than pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_pair'], + data: data, + user: user1 + ).perform).to eq(false) + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles greater than or equal pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_or_equal_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['greater_than_or_equal_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles less than or equal pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_or_equal_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['less_than_or_equal_pair'], + data: data, + user: user2 + ).perform).to eq(true) + end + + it "handles regex pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['regex_pair'], + data: data, + user: user1 + ).perform).to eq(true) + expect(CustomWizard::Mapper.new( + inputs: inputs['regex_pair'], + data: data, + user: user2 + ).perform).to eq(false) + end + + it "handles shorthand pairs" do + expect(CustomWizard::Mapper.new( + inputs: inputs['shorthand_pair'], + data: data, + user: user1 + ).perform).to eq(false) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb new file mode 100644 index 00000000..594009a8 --- /dev/null +++ b/spec/components/custom_wizard/template_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' + +describe CustomWizard::Template do + fab!(:user) { Fabricate(:user) } + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + let(:permitted_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read) + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + before do + CustomWizard::Template.save(template_json, skip_jobs: true) + end + + it "saves wizard templates" do + expect( + PluginStoreRow.exists?( + plugin_name: 'custom_wizard', + key: 'super_mega_fun_wizard' + ) + ).to eq(true) + end + + it "finds wizard templates" do + expect( + CustomWizard::Template.find('super_mega_fun_wizard')['id'] + ).to eq('super_mega_fun_wizard') + end + + it "removes wizard templates" do + CustomWizard::Template.remove('super_mega_fun_wizard') + expect( + CustomWizard::Template.find('super_mega_fun_wizard') + ).to eq(nil) + end + + it "checks for wizard template existence" do + expect( + CustomWizard::Template.exists?('super_mega_fun_wizard') + ).to eq(true) + end + + context "wizard template list" do + before do + template_json_2 = template_json.dup + template_json_2["id"] = 'super_mega_fun_wizard_2' + template_json_2["permitted"] = permitted_json['permitted'] + CustomWizard::Template.save(template_json_2, skip_jobs: true) + + template_json_3 = template_json.dup + template_json_3["id"] = 'super_mega_fun_wizard_3' + template_json_3["after_signup"] = true + CustomWizard::Template.save(template_json_3, skip_jobs: true) + end + + it "works" do + expect( + CustomWizard::Template.list.length + ).to eq(3) + end + + it "can be filtered by wizard settings" do + expect( + CustomWizard::Template.list(setting: "after_signup").length + ).to eq(1) + end + + it "can be ordered" do + expect( + CustomWizard::Template.list( + order: "(value::json ->> 'permitted') IS NOT NULL DESC" + ).first['id'] + ).to eq('super_mega_fun_wizard_2') + end + end + + context "after time setting" do + before do + freeze_time Time.now + @after_time_template = template_json.dup + @after_time_template["after_time"] = after_time['after_time'] + @after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + end + + it 'if enabled queues jobs after wizard is saved' do + expect_enqueued_with(job: :set_after_time_wizard, at: Time.parse(after_time['after_time_scheduled']).utc) do + CustomWizard::Template.save(@after_time_template) + end + end + + it 'if disabled clears jobs after wizard is saved' do + CustomWizard::Template.save(@after_time_template) + @after_time_template['after_time'] = false + + expect_not_enqueued_with(job: :set_after_time_wizard) do + CustomWizard::Template.save(@after_time_template) + end + end + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/validator_spec.rb b/spec/components/custom_wizard/validator_spec.rb new file mode 100644 index 00000000..5833127e --- /dev/null +++ b/spec/components/custom_wizard/validator_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +describe CustomWizard::Validator do + fab!(:user) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "validates valid templates" do + expect( + CustomWizard::Validator.new(template).perform + ).to eq(true) + end + + it "invalidates templates without required attributes" do + template.delete(:id) + expect( + CustomWizard::Validator.new(template).perform + ).to eq(false) + end + + it "invalidates templates with duplicate ids if creating a new template" do + CustomWizard::Template.save(template) + expect( + CustomWizard::Validator.new(template, create: true).perform + ).to eq(false) + end + + it "validates after time settings" do + template[:after_time] = after_time[:after_time] + template[:after_time_scheduled] = after_time[:after_time_scheduled] + expect( + CustomWizard::Validator.new(template).perform + ).to eq(true) + end + + it "invalidates invalid after time settings" do + template[:after_time] = after_time[:after_time] + template[:after_time_scheduled] = "not a time" + expect( + CustomWizard::Validator.new(template).perform + ).to eq(false) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb new file mode 100644 index 00000000..7724cc64 --- /dev/null +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -0,0 +1,230 @@ +require 'rails_helper' + +describe CustomWizard::Wizard do + fab!(:user) { Fabricate(:user) } + fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3])} + fab!(:admin_user) { Fabricate(:user, admin: true)} + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read) + } + + let(:permitted_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" + ).read) + } + + before do + Group.refresh_automatic_group!(:trust_level_3) + + @permitted_template = template_json.dup + @permitted_template["permitted"] = permitted_json["permitted"] + + @wizard = CustomWizard::Wizard.new(template_json, user) + template_json['steps'].each do |step_template| + @wizard.append_step(step_template['id']) + end + end + + def progress_step(step_id, acting_user = user) + UserHistory.create( + action: UserHistory.actions[:custom_wizard_step], + acting_user_id: acting_user.id, + context: @wizard.id, + subject: step_id + ) + end + + it "appends steps from a template" do + expect(@wizard.steps.length).to eq(3) + end + + it "determines the user's current step" do + expect(@wizard.start.id).to eq('step_1') + progress_step('step_1') + expect(@wizard.start.id).to eq('step_2') + end + + it "creates a step updater" do + expect( + @wizard.create_updater('step_1', step_1_field_1: "Text input") + .class + ).to eq(CustomWizard::StepUpdater) + end + + it "determines whether a wizard is unfinished" do + expect(@wizard.unfinished?).to eq(true) + progress_step("step_1") + expect(@wizard.unfinished?).to eq(true) + progress_step("step_2") + expect(@wizard.unfinished?).to eq(true) + progress_step("step_3") + expect(@wizard.unfinished?).to eq(false) + end + + it "determines whether a wizard has been completed by a user" do + expect(@wizard.completed?).to eq(false) + progress_step("step_1") + progress_step("step_2") + progress_step("step_3") + expect(@wizard.completed?).to eq(true) + end + + it "is not completed if steps submitted before after time" do + progress_step("step_1") + progress_step("step_2") + progress_step("step_3") + + template_json['after_time'] = after_time['after_time'] + template_json['after_time_scheduled'] = after_time['after_time_scheduled'] + + wizard = CustomWizard::Wizard.new(template_json, user) + + expect(wizard.completed?).to eq(false) + end + + it "permits admins" do + expect( + CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? + ).to eq(true) + end + + it "permits permitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted? + ).to eq(true) + end + + it "does not permit unpermitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).permitted? + ).to eq(false) + end + + it "does not let an unpermitted user access a wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).can_access? + ).to eq(false) + end + + it "lets a permitted user access an incomplete wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end + + it "lets a permitted user access a complete wizard with multiple submissions" do + progress_step("step_1", trusted_user) + progress_step("step_2", trusted_user) + progress_step("step_3", trusted_user) + + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end + + it "does not let an unpermitted user access a complete wizard without multiple submissions" do + progress_step("step_1", trusted_user) + progress_step("step_2", trusted_user) + progress_step("step_3", trusted_user) + + @permitted_template['multiple_submissions'] = false + + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(false) + end + + it "lists the site groups" do + expect(@wizard.groups.length).to eq(8) + end + + it "lists the site categories" do + expect(@wizard.categories.length).to eq(1) + end + + context "submissions" do + before do + @wizard.set_submissions(step_1_field_1: 'I am a user submission') + end + + it "sets the user's submission" do + expect( + PluginStore.get("#{template_json['id']}_submissions", user.id) + .first['step_1_field_1'] + ).to eq('I am a user submission') + end + + it "lists the user's submissions" do + expect(@wizard.submissions.length).to eq(1) + end + + it "returns the user's current submission" do + expect(@wizard.current_submission['step_1_field_1']).to eq('I am a user submission') + end + end + + it "provides class methods to set and list submissions" do + CustomWizard::Wizard.set_submissions(template_json['id'], user, + step_1_field_1: 'I am a user submission' + ) + expect( + CustomWizard::Wizard.submissions(template_json['id'], user) + .first['step_1_field_1'] + ).to eq('I am a user submission') + end + + context do + before do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + + template_json_2 = template_json.dup + template_json_2["id"] = 'super_mega_fun_wizard_2' + template_json_2["prompt_completion"] = true + CustomWizard::Template.save(template_json_2, skip_jobs: true) + + template_json_3 = template_json.dup + template_json_3["id"] = 'super_mega_fun_wizard_3' + template_json_3["after_signup"] = true + CustomWizard::Template.save(template_json_3, skip_jobs: true) + end + + it "lists wizards the user can see" do + expect(CustomWizard::Wizard.list(user).length).to eq(2) + expect(CustomWizard::Wizard.list(trusted_user).length).to eq(3) + end + + it "returns the first after signup wizard" do + expect(CustomWizard::Wizard.after_signup(user).id).to eq('super_mega_fun_wizard_3') + end + + it "lists prompt completion wizards" do + expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(2) + end + end + + it "sets wizard redirects if user is permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', trusted_user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq("super_mega_fun_wizard") + end + + it "does not set a wizard redirect if user is not permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq(nil) + end +end \ No newline at end of file diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb new file mode 100644 index 00000000..bac0f2c6 --- /dev/null +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe ExtraLocalesControllerCustomWizard, type: :request do + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + end + + before do + @controller = ExtraLocalesController.new + end + + it "returns locales when requested by wizard" do + expect( + ExtraLocalesController.url("wizard") + ).to eq( + "#{Discourse.base_path}/extra-locales/wizard?v=#{ExtraLocalesController.bundle_js_hash("wizard")}" + ) + end +end \ No newline at end of file diff --git a/spec/extensions/invites_controller_spec.rb b/spec/extensions/invites_controller_spec.rb new file mode 100644 index 00000000..05fa58a5 --- /dev/null +++ b/spec/extensions/invites_controller_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe InvitesControllerCustomWizard, type: :request do + fab!(:topic) { Fabricate(:topic) } + let(:invite) do + Invite.invite_by_email("angus@email.com", topic.user, topic) + end + let(:template) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + end + + before do + @controller = InvitesController.new + end + + it "redirects a user to wizard after invite if after signup is enabled" do + template['after_signup'] = true + CustomWizard::Template.save(template, skip_jobs: true) + put "/invites/show/#{invite.invite_key}.json" + expect(response.parsed_body["redirect_to"]).to eq("/w/super-mega-fun-wizard") + end +end \ No newline at end of file diff --git a/spec/extensions/users_controller_spec.rb b/spec/extensions/users_controller_spec.rb new file mode 100644 index 00000000..5a219dee --- /dev/null +++ b/spec/extensions/users_controller_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe CustomWizardUsersController, type: :request do + let(:template) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + end + + before do + @controller = UsersController.new + end + + it "redirects a user to wizard after sign up if after signup is enabled" do + template['after_signup'] = true + CustomWizard::Template.save(template, skip_jobs: true) + sign_in(Fabricate(:user)) + get "/u/account-created" + expect(response).to redirect_to("/w/super-mega-fun-wizard") + end +end \ No newline at end of file diff --git a/spec/extensions/wizard_field_spec.rb b/spec/extensions/wizard_field_spec.rb new file mode 100644 index 00000000..d13b3c80 --- /dev/null +++ b/spec/extensions/wizard_field_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe CustomWizardFieldExtension do + let(:field_hash) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json" + ).read).with_indifferent_access + end + + it "adds custom field attributes" do + field = Wizard::Field.new(field_hash) + expect(field.id).to eq("field_id") + expect(field.label).to eq("

Field Label

") + expect(field.image).to eq("field_image_url.png") + expect(field.description).to eq("Field description") + expect(field.required).to eq(true) + expect(field.key).to eq("field.locale.key") + expect(field.type).to eq("field_type") + expect(field.content).to eq([]) + end +end \ No newline at end of file diff --git a/spec/extensions/wizard_step_spec.rb b/spec/extensions/wizard_step_spec.rb new file mode 100644 index 00000000..7d1fbb68 --- /dev/null +++ b/spec/extensions/wizard_step_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe CustomWizardStepExtension do + let(:step_hash) do + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json" + ).read).with_indifferent_access + end + + it "adds custom step attributes" do + step = Wizard::Step.new(step_hash[:id]) + [ + :title, + :description, + :key, + :permitted, + :permitted_message + ].each do |attr| + step.send("#{attr.to_s}=", step_hash[attr]) + expect(step.send(attr)).to eq(step_hash[attr]) + end + end +end \ No newline at end of file diff --git a/spec/fixtures/field/field.json b/spec/fixtures/field/field.json new file mode 100644 index 00000000..c2de266d --- /dev/null +++ b/spec/fixtures/field/field.json @@ -0,0 +1,10 @@ +{ + "id": "field_id", + "label": "Field Label", + "image": "field_image_url.png", + "description": "Field description", + "required": true, + "key": "field.locale.key", + "type": "field_type", + "content": [] +} \ No newline at end of file diff --git a/spec/fixtures/mapper/data.json b/spec/fixtures/mapper/data.json new file mode 100644 index 00000000..eca75a1a --- /dev/null +++ b/spec/fixtures/mapper/data.json @@ -0,0 +1,4 @@ +{ + "input_1": "value 1", + "input_2": "value 2" +} \ No newline at end of file diff --git a/spec/fixtures/mapper/inputs.json b/spec/fixtures/mapper/inputs.json new file mode 100644 index 00000000..8faf2435 --- /dev/null +++ b/spec/fixtures/mapper/inputs.json @@ -0,0 +1,264 @@ +{ + "assignment": [ + { + "type": "assignment", + "output_type": "group", + "output_connector": "set", + "output": [ + 13 + ] + } + ], + "assignment_text": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Value" + } + ], + "assignment_user_field": [ + { + "type": "assignment", + "output_type": "user_field", + "output_connector": "set", + "output": "name" + } + ], + "assignment_user_field_options": [ + { + "type": "assignment", + "output_type": "user_field_options", + "output_connector": "set", + "output": "user_field_3" + } + ], + "assignment_wizard_field": [ + { + "type": "assignment", + "output_type": "wizard_field", + "output_connector": "set", + "output": "input_1" + } + ], + "assignment_wizard_action": [ + { + "type": "assignment", + "output_type": "wizard_action", + "output_connector": "set", + "output": "input_2" + } + ], + "interpolate_user_field": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Name: u{name}" + } + ], + "interpolate_wizard_field": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Input 1: w{input_1}" + } + ], + "interpolate_timestamp": [ + { + "type": "assignment", + "output_type": "text", + "output_connector": "set", + "output": "Time: v{time}" + } + ], + "validation": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "input_1", + "key_type": "wizard_field", + "value": "value 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "input_2", + "key_type": "wizard_field", + "value": "value 2", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "association": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "choice1", + "key_type": "text", + "value": "Choice 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "choice2", + "key_type": "text", + "value": "Choice 2", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "choice3", + "key_type": "text", + "value": "Choice 3", + "value_type": "text", + "connector": "association" + } + ] + } + ], + "conditional": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "conditional_multiple_pairs": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "email", + "key_type": "user_field", + "value": "angus@email.com", + "value_type": "text", + "connector": "equal" + } + ] + } + ], + "greater_than_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "2", + "value_type": "text", + "connector": "greater" + } + ] + } + ], + "less_than_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "2", + "value_type": "text", + "connector": "less" + } + ] + } + ], + "greater_than_or_equal_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "1", + "value_type": "text", + "connector": "greater_or_equal" + } + ] + } + ], + "less_than_or_equal_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "trust_level", + "key_type": "user_field", + "value": "3", + "value_type": "text", + "connector": "less_or_equal" + } + ] + } + ], + "regex_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "email", + "key_type": "user_field", + "value": "@email.com", + "value_type": "text", + "connector": "regex" + } + ] + } + ], + "shorthand_pair": [ + { + "type": "validation", + "pairs": [ + { + "index": 0, + "key": "bio_raw", + "key_type": "user_field", + "value": "present", + "value_type": "text", + "connector": "is" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard/required_data.json b/spec/fixtures/step/required_data.json similarity index 100% rename from spec/fixtures/wizard/required_data.json rename to spec/fixtures/step/required_data.json diff --git a/spec/fixtures/step/step.json b/spec/fixtures/step/step.json new file mode 100644 index 00000000..5c6c5f58 --- /dev/null +++ b/spec/fixtures/step/step.json @@ -0,0 +1,10 @@ +{ + "id": "step_1", + "title": "Text", + "description": "Step description", + "image": "step_image_url.png", + "key": "step.locale.key", + "fields": [], + "required_data": [], + "permitted": [] +} \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index 1d5e1bc7..287f3b60 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -16,6 +16,7 @@ { "id": "step_1_field_1", "label": "Text", + "description": "Text field description.", "type": "text", "min_length": "3", "prefill": [ @@ -351,7 +352,7 @@ }, { "id": "action_1", - "run_after": "step_2", + "run_after": "step_3", "type": "create_topic", "skip_redirect": true, "post": "step_1_field_2", diff --git a/spec/fixtures/wizard/after_time.json b/spec/fixtures/wizard/after_time.json new file mode 100644 index 00000000..691af470 --- /dev/null +++ b/spec/fixtures/wizard/after_time.json @@ -0,0 +1,4 @@ +{ + "after_time": true, + "after_time_scheduled": "2020-11-20T00:59:00.000Z" +} \ No newline at end of file diff --git a/spec/jobs/clear_after_time_wizard_spec.rb b/spec/jobs/clear_after_time_wizard_spec.rb new file mode 100644 index 00000000..b6d675fe --- /dev/null +++ b/spec/jobs/clear_after_time_wizard_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Jobs::ClearAfterTimeWizard do + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + fab!(:user3) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "clears wizard redirect for all users " do + after_time_template = template.dup + after_time_template["after_time"] = after_time['after_time'] + after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + + CustomWizard::Template.save(after_time_template) + + described_class.new.execute(wizard_id: 'super_mega_fun_wizard') + + expect( + UserCustomField.where(" + name = 'redirect_to_wizard' AND + value = 'super_mega_fun_wizard' + ").exists? + ).to eq(false) + end +end \ No newline at end of file diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb new file mode 100644 index 00000000..49ec211f --- /dev/null +++ b/spec/jobs/set_after_time_wizard_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Jobs::SetAfterTimeWizard do + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } + fab!(:user3) { Fabricate(:user) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read).with_indifferent_access + } + let(:after_time) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/after_time.json" + ).read).with_indifferent_access + } + + it "sets wizard redirect for all users " do + after_time_template = template.dup + after_time_template["after_time"] = after_time['after_time'] + after_time_template["after_time_scheduled"] = after_time['after_time_scheduled'] + + CustomWizard::Template.save(after_time_template) + + messages = MessageBus.track_publish("/redirect_to_wizard") do + described_class.new.execute(wizard_id: 'super_mega_fun_wizard') + end + + expect( + UserCustomField.where( + name: 'redirect_to_wizard', + value: 'super_mega_fun_wizard' + ).length + ).to eq(3) + + expect(messages.first.data).to eq("super_mega_fun_wizard") + expect(messages.first.user_ids).to match_array([user1.id,user2.id,user3.id]) + end +end \ No newline at end of file diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 47368da5..55deb6a2 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -3,6 +3,8 @@ require 'simplecov' SimpleCov.configure do add_filter do |src| src.filename !~ /discourse-custom-wizard/ || - src.filename =~ /spec/ + src.filename =~ /spec/ || + src.filename =~ /db/ || + src.filename =~ /api/ ## API features are currently experimental end end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/logs_controller_spec.rb b/spec/requests/custom_wizard/admin/logs_controller_spec.rb new file mode 100644 index 00000000..37a83e90 --- /dev/null +++ b/spec/requests/custom_wizard/admin/logs_controller_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +describe CustomWizard::AdminLogsController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + before do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + CustomWizard::Log.create("Third log message") + sign_in(admin_user) + end + + it "returns a list of logs" do + get "/admin/wizards/logs.json" + expect(response.parsed_body.length).to eq(3) + end + + it "paginates" do + get "/admin/wizards/logs.json", params: { page: 1, limit: 2 } + expect(response.parsed_body.length).to eq(1) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb new file mode 100644 index 00000000..51628ea2 --- /dev/null +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +describe CustomWizard::AdminSubmissionsController do + fab!(:admin_user) {Fabricate(:user, admin: true)} + fab!(:user1) {Fabricate(:user)} + fab!(:user2) {Fabricate(:user)} + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template, skip_jobs: true) + CustomWizard::Wizard.set_submissions(template['id'], user1, + step_1_field_1: "I am a user1's submission" + ) + CustomWizard::Wizard.set_submissions(template['id'], user2, + step_1_field_1: "I am a user2's submission" + ) + sign_in(admin_user) + end + + it "returns a basic list of wizards" do + get "/admin/wizards/submissions.json" + expect(response.parsed_body.length).to eq(1) + expect(response.parsed_body.first['id']).to eq(template['id']) + end + + it "returns the all user's submissions for a wizard" do + get "/admin/wizards/submissions/#{template['id']}.json" + expect(response.parsed_body['submissions'].length).to eq(2) + end + + it "returns the all user's submissions for a wizard" do + get "/admin/wizards/submissions/#{template['id']}.json" + expect(response.parsed_body['submissions'].length).to eq(2) + end + + it "downloads all user submissions" do + get "/admin/wizards/submissions/#{template['id']}/download" + expect(response.parsed_body.length).to eq(2) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/transfer_controller_spec.rb b/spec/requests/custom_wizard/admin/transfer_controller_spec.rb new file mode 100644 index 00000000..217311c2 --- /dev/null +++ b/spec/requests/custom_wizard/admin/transfer_controller_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +describe CustomWizard::AdminTransferController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + sign_in(admin_user) + + CustomWizard::Template.save(template, skip_jobs: true) + + template_2 = template.dup + template_2["id"] = 'super_mega_fun_wizard_2' + CustomWizard::Template.save(template_2, skip_jobs: true) + + template_3 = template.dup + template_3["id"] = 'super_mega_fun_wizard_3' + template_3["after_signup"] = true + CustomWizard::Template.save(template_3, skip_jobs: true) + + @template_array = [template, template_2, template_3] + + FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder) + @tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json') + File.write(@tmp_file_path, @template_array.to_json) + end + + it 'exports all the wizard templates' do + get '/admin/wizards/transfer/export.json', params: { + wizards: [ + 'super_mega_fun_wizard', + 'super_mega_fun_wizard_2', + 'super_mega_fun_wizard_3' + ] + } + expect(response.status).to eq(200) + expect(response.parsed_body).to match_array(@template_array) + end + + it 'imports wizard a template' do + post '/admin/wizards/transfer/import.json', params: { + file: fixture_file_upload(File.open(@tmp_file_path)) + } + expect(response.status).to eq(200) + expect(response.parsed_body['success']).to eq(@template_array.map { |t| t['id'] }) + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb new file mode 100644 index 00000000..d33762e4 --- /dev/null +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +describe CustomWizard::AdminWizardController do + fab!(:admin_user) {Fabricate(:user, admin: true)} + fab!(:user1) {Fabricate(:user)} + fab!(:user2) {Fabricate(:user)} + + let(:template) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template, skip_jobs: true) + + template_2 = template.dup + template_2["id"] = 'super_mega_fun_wizard_2' + template_2["permitted"] = template_2['permitted'] + CustomWizard::Template.save(template_2, skip_jobs: true) + + template_3 = template.dup + template_3["id"] = 'super_mega_fun_wizard_3' + template_3["after_signup"] = true + CustomWizard::Template.save(template_3, skip_jobs: true) + + sign_in(admin_user) + end + + it "returns a basic list of wizard templates and wizard field types" do + get "/admin/wizards/wizard.json" + expect( + response.parsed_body['wizard_list'].map { |w| w['id'] } + ).to match_array(['super_mega_fun_wizard', 'super_mega_fun_wizard_2', 'super_mega_fun_wizard_3']) + expect( + response.parsed_body['field_types'].keys + ).to eq(CustomWizard::Field.types.keys.map(&:to_s)) + end + + it "returns a wizard template" do + get "/admin/wizards/wizard/#{template['id']}.json" + expect(response.parsed_body['id']).to eq(template['id']) + expect(response.parsed_body['steps'].length).to eq(3) + end + + it "removes wizard templates" do + delete "/admin/wizards/wizard/#{template['id']}.json" + expect(response.status).to eq(200) + expect(CustomWizard::Template.exists?(template['id'])).to eq(false) + end + + it "saves wizard templates" do + template_updated = template.dup + template_updated['name'] = "Super Mega Fun Wizard 2" + template_updated['multiple_submissions'] = false + template_updated['steps'][0]['fields'][0]['label'] = "Text 2" + + put "/admin/wizards/wizard/#{template['id']}.json", params: { wizard: template_updated } + expect(response.status).to eq(200) + + updated_template = CustomWizard::Template.find('super_mega_fun_wizard') + expect(updated_template['name']).to eq("Super Mega Fun Wizard 2") + expect(updated_template['multiple_submissions']).to eq("false") + expect(updated_template['steps'][0]['fields'][0]['label']).to eq("Text 2") + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb new file mode 100644 index 00000000..4c67cc41 --- /dev/null +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +describe ApplicationController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find('super_mega_fun_wizard') + end + + context "with signed in user" do + before do + sign_in(user) + end + + context "who is required to complete wizard" do + before do + user.custom_fields['redirect_to_wizard'] = 'super_mega_fun_wizard' + user.save_custom_fields(true) + end + + it "redirects if user is required to complete a wizard" do + get "/" + expect(response).to redirect_to("/w/super-mega-fun-wizard") + end + + it "saves original destination of user" do + get '/', headers: { 'REFERER' => "/t/2" } + expect( + CustomWizard::Wizard.submissions(@template['id'], user) + .first['redirect_to'] + ).to eq("/t/2") + end + end + + context "who is not required to complete wizard" do + it "does nothing" do + get "/" + expect(response.status).to eq(200) + end + end + end + + context "with guest" do + it "does nothing" do + get "/" + expect(response.status).to eq(200) + end + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb new file mode 100644 index 00000000..eeea6c17 --- /dev/null +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +describe CustomWizard::StepsController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + sign_in(user) + end + + it 'performs a step update' do + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Text input" + } + } + expect(response.status).to eq(200) + + wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + expect(wizard.current_submission['step_1_field_1']).to eq("Text input") + expect(wizard.start.id).to eq("step_2") + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb new file mode 100644 index 00000000..a9833381 --- /dev/null +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +describe CustomWizard::WizardController do + fab!(:user) { + Fabricate( + :user, + username: 'angus', + email: "angus@email.com", + trust_level: TrustLevel[3] + ) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find("super_mega_fun_wizard") + sign_in(user) + end + + context 'plugin disabled' do + before do + SiteSetting.custom_wizard_enabled = false + end + + it 'redirects to root' do + get '/w/super-mega-fun-wizard', xhr: true + expect(response).to redirect_to("/") + end + end + + it 'returns wizard' do + get '/w/super-mega-fun-wizard.json' + expect(response.parsed_body["id"]).to eq("super_mega_fun_wizard") + end + + it 'returns missing message if no wizard exists' do + get '/w/super-mega-fun-wizards.json' + expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.") + end + + it 'skips a wizard if user is allowed to skip' do + put '/w/super-mega-fun-wizard/skip.json' + expect(response.status).to eq(200) + end + + it 'returns a no skip message if user is not allowed to skip' do + @template['required'] = 'true' + CustomWizard::Template.save(@template) + put '/w/super-mega-fun-wizard/skip.json' + expect(response.parsed_body['error']).to eq("Wizard can't be skipped") + end + + it 'skip response contains a redirect_to if in users submissions' do + CustomWizard::Wizard.set_submissions(@template['id'], user, + redirect_to: '/t/2' + ) + put '/w/super-mega-fun-wizard/skip.json' + expect(response.parsed_body['redirect_to']).to eq('/t/2') + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb new file mode 100644 index 00000000..0e088e2d --- /dev/null +++ b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::BasicWizardSerializer do + fab!(:user) { Fabricate(:user) } + + it 'should return basic wizard attributes' do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + json = CustomWizard::BasicWizardSerializer.new( + CustomWizard::Builder.new("super_mega_fun_wizard", user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:basic_wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:basic_wizard][:name]).to eq("Super Mega Fun Wizard") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/log_serializer_spec.rb b/spec/serializers/custom_wizard/log_serializer_spec.rb new file mode 100644 index 00000000..2ea39871 --- /dev/null +++ b/spec/serializers/custom_wizard/log_serializer_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::LogSerializer do + fab!(:user) { Fabricate(:user) } + + it 'should return log attributes' do + CustomWizard::Log.create("First log message") + CustomWizard::Log.create("Second log message") + + json_array = ActiveModel::ArraySerializer.new( + CustomWizard::Log.list(0), + each_serializer: CustomWizard::LogSerializer + ).as_json + expect(json_array.length).to eq(2) + expect(json_array[0][:message]).to eq("Second log message") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb new file mode 100644 index 00000000..02069a9b --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::FieldSerializer do + fab!(:user) { Fabricate(:user) } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + end + + it "should return basic field attributes" do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps.first.fields, + each_serializer: CustomWizard::FieldSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array.length).to eq(4) + expect(json_array[0][:label]).to eq("

Text

") + expect(json_array[0][:description]).to eq("Text field description.") + end + + it "should return optional field attributes" do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps.second.fields, + each_serializer: CustomWizard::FieldSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:format]).to eq("YYYY-MM-DD") + expect(json_array[5][:file_types]).to eq(".jpg,.png") + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb new file mode 100644 index 00000000..16734f83 --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::WizardSerializer do + fab!(:user) { Fabricate(:user) } + fab!(:category) { Fabricate(:category) } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @template = CustomWizard::Template.find('super_mega_fun_wizard') + end + + it 'should return the wizard attributes' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:id]).to eq("super_mega_fun_wizard") + expect(json[:wizard][:name]).to eq("Super Mega Fun Wizard") + expect(json[:wizard][:background]).to eq("#333333") + expect(json[:wizard][:required]).to eq(false) + end + + it 'should return the wizard steps' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:steps].length).to eq(3) + end + + it "should return the wizard user attributes" do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect( + json[:wizard][:user] + ).to eq(BasicUserSerializer.new(user, root: false).as_json) + end + + it "should not return categories if there are no category fields" do + @template[:steps][2][:fields].delete_at(2) + CustomWizard::Template.save(@template) + + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(false) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(false) + end + + it "should return categories if there is a category selector field" do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(true) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) + end + + it 'should return groups if there is a group selector field' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].length).to eq(8) + end + + it 'should not return groups if there is not a group selector field' do + @template[:steps][2][:fields].delete_at(3) + CustomWizard::Template.save(@template) + + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].present?).to eq(false) + end +end \ No newline at end of file diff --git a/spec/serializers/custom_wizard/wizard_step_serializer.rb b/spec/serializers/custom_wizard/wizard_step_serializer.rb new file mode 100644 index 00000000..30e8cc5f --- /dev/null +++ b/spec/serializers/custom_wizard/wizard_step_serializer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CustomWizard::StepSerializer do + fab!(:user) { Fabricate(:user) } + + let(:required_data_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" + ).read) + } + + before do + CustomWizard::Template.save( + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read), + skip_jobs: true) + @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build + end + + it 'should return basic step attributes' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:title]).to eq("Text") + expect(json_array[0][:wizard_step][:description]).to eq("Text inputs!") + end + + it 'should return fields' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:fields].length).to eq(4) + end + + context 'with required data' do + before do + @template[:steps][0][:required_data] = required_data_json['required_data'] + @template[:steps][0][:required_data_message] = required_data_json['required_data_message'] + CustomWizard::Template.save(@template.as_json) + end + + it 'should return permitted attributes' do + json_array = ActiveModel::ArraySerializer.new( + @wizard.steps, + each_serializer: CustomWizard::StepSerializer, + scope: Guardian.new(user) + ).as_json + expect(json_array[0][:wizard_step][:permitted]).to eq(false) + expect(json_array[0][:wizard_step][:permitted_message]).to eq("Missing required data") + end + end +end \ No newline at end of file diff --git a/spec_offload/components/custom_wizard/api_spec.rb b/spec_offload/components/custom_wizard/api_spec.rb deleted file mode 100644 index a7a8ba1b..00000000 --- a/spec_offload/components/custom_wizard/api_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizard::Api do - context 'authorization' do - it 'authorizes with an oauth2 api' do - - end - - it 'refreshes the api access token' do - - end - end - - context 'endpoint' do - it 'requests an api endpoint' do - - end - end -end diff --git a/spec_offload/components/custom_wizard/mapper_spec.rb b/spec_offload/components/custom_wizard/mapper_spec.rb deleted file mode 100644 index b186ddb0..00000000 --- a/spec_offload/components/custom_wizard/mapper_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -describe CustomWizard::Mapper do - fab!(:user) { Fabricate(:user, name: 'Angus', username: 'angus', email: "angus@email.com") } - - it 'interpolates user data' do - expect( - CustomWizard::Mapper.fill_placeholders( - "My name is u{name}", - user, - {} - ) - ).to eq('My name is Angus') - end -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/admin_controller_spec.rb b/spec_offload/requests/custom_wizard/admin_controller_spec.rb deleted file mode 100644 index 731d0de5..00000000 --- a/spec_offload/requests/custom_wizard/admin_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -describe CustomWizard::AdminController do - -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/application_controller_spec.rb b/spec_offload/requests/custom_wizard/application_controller_spec.rb deleted file mode 100644 index 1315748d..00000000 --- a/spec_offload/requests/custom_wizard/application_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -describe ApplicationController do - -end \ No newline at end of file diff --git a/spec_offload/requests/custom_wizard/wizard_controller_spec.rb b/spec_offload/requests/custom_wizard/wizard_controller_spec.rb deleted file mode 100644 index b457eba7..00000000 --- a/spec_offload/requests/custom_wizard/wizard_controller_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'rails_helper' - -describe CustomWizard::WizardController do - it 'returns a wizard if enabled' do - - end - - it 'returns a disabled message if disabled' do - - end - - it 'returns a missing message if no wizard exists' do - - end - - it 'returns a custom wizard theme' do - - end - - it 'updates the page title' do - - end - - it 'skips a wizard if user is allowed to skip' do - - end - - it 'returns a no skip message if user is not allowed to skip' do - - end -end \ No newline at end of file diff --git a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb b/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb deleted file mode 100644 index 1498ede9..00000000 --- a/spec_offload/serializers/custom_wizard/wizard_serializer_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe CustomWizard::WizardSerializer do - fab!(:user) { Fabricate(:user) } - fab!(:category) { Fabricate(:category) } - - before do - template = JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - CustomWizard::Template.add(template) - @wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user) - end - - it 'should return the wizard attributes' do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:id]).to eq("super_mega_fun_wizard") - expect(json[:custom_wizard][:name]).to eq("Super Mega Fun Wizard") - expect(json[:custom_wizard][:background]).to eq("#333333") - expect(json[:custom_wizard][:required]).to eq(false) - end - - it "should return the wizard user attributes" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:user]).to eq(BasicUserSerializer.new(user, root: false).as_json) - end - - it "should not return category attributes if there are no category fields" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(false) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(false) - end - - it "should return category attributes if there is a category selector field" do - built_wizard = CustomWizard::Builder.new(@wizard.id, user).build - json = CustomWizard::WizardSerializer.new(built_wizard, scope: Guardian.new(user)).as_json - expect(json[:custom_wizard][:categories].present?).to eq(true) - expect(json[:custom_wizard][:uncategorized_category_id].present?).to eq(true) - end -end \ No newline at end of file