1
0
Fork 0

Isolate and specify template and update validation

Dieser Commit ist enthalten in:
Angus McLeod 2020-11-26 14:05:50 +11:00
Ursprung 4be7349d99
Commit f31054f75d
11 geänderte Dateien mit 220 neuen und 173 gelöschten Zeilen

Datei anzeigen

@ -1,5 +1,6 @@
module CustomWizardFieldExtension module CustomWizardFieldExtension
attr_reader :label, attr_reader :raw,
:label,
:description, :description,
:image, :image,
:key, :key,
@ -12,7 +13,7 @@ module CustomWizardFieldExtension
def initialize(attrs) def initialize(attrs)
super super
@attrs = attrs || {} @raw = attrs || {}
@description = attrs[:description] @description = attrs[:description]
@image = attrs[:image] @image = attrs[:image]
@key = attrs[:key] @key = attrs[:key]
@ -25,6 +26,6 @@ module CustomWizardFieldExtension
end end
def label def label
@label ||= PrettyText.cook(@attrs[:label]) @label ||= PrettyText.cook(@raw[:label])
end end
end end

Datei anzeigen

@ -24,19 +24,6 @@ class CustomWizard::Builder
@sorted_handlers.sort_by! { |h| -h[:priority] } @sorted_handlers.sort_by! { |h| -h[:priority] }
end end
def self.sorted_field_validators
@sorted_field_validators ||= []
end
def self.field_validators
sorted_field_validators.map { |h| { type: h[:type], block: h[:block] } }
end
def self.add_field_validator(priority = 0, type, &block)
sorted_field_validators << { priority: priority, type: type, block: block }
@sorted_field_validators.sort_by! { |h| -h[:priority] }
end
def mapper def mapper
CustomWizard::Mapper.new( CustomWizard::Mapper.new(
user: @wizard.user, user: @wizard.user,
@ -91,11 +78,7 @@ class CustomWizard::Builder
@updater = updater @updater = updater
user = @wizard.user user = @wizard.user
if step_template['fields'] && step_template['fields'].length updater.validate
step_template['fields'].each do |field|
validate_field(field, updater, step_template) if field['type'] != 'text_only'
end
end
next if updater.errors.any? next if updater.errors.any?
@ -107,10 +90,10 @@ class CustomWizard::Builder
next if updater.errors.any? next if updater.errors.any?
data = updater.fields submission = updater.submission
if submission = @wizard.current_submission if current_submission = @wizard.current_submission
data = submission.merge(data) submission = current_submission.merge(submission)
end end
final_step = updater.step.next.nil? final_step = updater.step.next.nil?
@ -125,19 +108,19 @@ class CustomWizard::Builder
wizard: @wizard, wizard: @wizard,
action: action, action: action,
user: user, user: user,
data: data data: submission
).perform ).perform
end end
end end
end end
if updater.errors.empty? if updater.errors.empty?
if route_to = data['route_to'] if route_to = submission['route_to']
data.delete('route_to') submission.delete('route_to')
end end
if @wizard.save_submissions if @wizard.save_submissions
save_submissions(data, final_step) save_submissions(submission, final_step)
end end
if final_step if final_step
@ -146,7 +129,7 @@ class CustomWizard::Builder
@wizard.user.save_custom_fields(true) @wizard.user.save_custom_fields(true)
end end
redirect_url = route_to || data['redirect_on_complete'] || data["redirect_to"] redirect_url = route_to || submission['redirect_on_complete'] || submission["redirect_to"]
updater.result[:redirect_on_complete] = redirect_url updater.result[:redirect_on_complete] = redirect_url
elsif route_to elsif route_to
updater.result[:redirect_on_next] = route_to updater.result[:redirect_on_next] = route_to
@ -261,101 +244,18 @@ class CustomWizard::Builder
end end
end end
def validate_field(field, updater, step_template)
value = updater.fields[field['id']]
min_length = false
label = field['label'] || I18n.t("#{field['key']}.label")
type = field['type']
required = field['required']
id = field['id'].to_s
min_length = field['min_length'] if is_text_type(field)
file_types = field['file_types']
format = field['format']
if required && !value
updater.errors.add(id, I18n.t('wizard.field.required', label: label))
end
if min_length && value.is_a?(String) && value.strip.length < min_length.to_i
updater.errors.add(id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
end
if is_url_type(field) && !check_if_url(value)
updater.errors.add(id, I18n.t('wizard.field.not_url', label: label))
end
if type === 'checkbox'
updater.fields[id] = standardise_boolean(value)
end
if type === 'upload' && value.present? && !validate_file_type(value, file_types)
updater.errors.add(id, I18n.t('wizard.field.invalid_file', label: label, types: file_types))
end
if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value)
updater.errors.add(id, I18n.t('wizard.field.invalid_date'))
end
if type === 'time' && value.present? && !validate_time(value)
updater.errors.add(id, I18n.t('wizard.field.invalid_time'))
end
CustomWizard::Builder.field_validators.each do |validator|
if type === validator[:type]
validator[:block].call(field, updater, step_template)
end
end
end
def validate_file_type(value, file_types)
file_types.split(',')
.map { |t| t.gsub('.', '') }
.include?(File.extname(value['original_filename'])[1..-1])
end
def validate_date(value)
begin
Date.parse(value)
true
rescue ArgumentError
false
end
end
def validate_time(value)
begin
Time.parse(value)
true
rescue ArgumentError
false
end
end
def is_text_type(field)
['text', 'textarea', 'composer'].include? field['type']
end
def is_url_type(field)
['url'].include? field['type']
end
def check_if_url(value)
value =~ URI::regexp
end
def standardise_boolean(value) def standardise_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value) ActiveRecord::Type::Boolean.new.cast(value)
end end
def save_submissions(data, final_step) def save_submissions(submission, final_step)
if final_step if final_step
data['submitted_at'] = Time.now.iso8601 submission['submitted_at'] = Time.now.iso8601
end end
if data.present? if submission.present?
@submissions.pop(1) if @wizard.unfinished? @submissions.pop(1) if @wizard.unfinished?
@submissions.push(data) @submissions.push(submission)
@wizard.set_submissions(@submissions) @wizard.set_submissions(@submissions)
end end
end end

Datei anzeigen

@ -1,14 +1,14 @@
class CustomWizard::StepUpdater class CustomWizard::StepUpdater
include ActiveModel::Model include ActiveModel::Model
attr_accessor :refresh_required, :fields, :result, :step attr_accessor :refresh_required, :submission, :result, :step
def initialize(current_user, wizard, step, fields) def initialize(current_user, wizard, step, submission)
@current_user = current_user @current_user = current_user
@wizard = wizard @wizard = wizard
@step = step @step = step
@refresh_required = false @refresh_required = false
@fields = fields.to_h.with_indifferent_access @submission = submission.to_h.with_indifferent_access
@result = {} @result = {}
end end
@ -38,4 +38,8 @@ class CustomWizard::StepUpdater
def refresh_required? def refresh_required?
@refresh_required @refresh_required
end end
def validate
CustomWizard::UpdateValidator.new(self).perform
end
end end

Datei anzeigen

@ -90,7 +90,7 @@ class CustomWizard::Template
end end
def validate_data def validate_data
validator = CustomWizard::Validator.new(@data, @opts) validator = CustomWizard::TemplateValidator.new(@data, @opts)
validator.perform validator.perform
add_errors_from(validator) add_errors_from(validator)
end end

Datei anzeigen

@ -1,4 +1,4 @@
class CustomWizard::Validator class CustomWizard::TemplateValidator
include HasErrors include HasErrors
include ActiveModel::Model include ActiveModel::Model
@ -49,7 +49,7 @@ class CustomWizard::Validator
private private
def check_required(object, type) def check_required(object, type)
CustomWizard::Validator.required[type].each do |property| CustomWizard::TemplateValidator.required[type].each do |property|
if object[property].blank? if object[property].blank?
errors.add :base, I18n.t("wizard.validation.required", property: property) errors.add :base, I18n.t("wizard.validation.required", property: property)
end end

Datei anzeigen

@ -0,0 +1,116 @@
class ::CustomWizard::UpdateValidator
attr_reader :updater
def initialize(updater)
@updater = updater
end
def perform
updater.step.fields.each do |field|
validate_field(field)
end
end
def validate_field(field)
return if field.type == 'text_only'
field_id = field.id.to_s
value = @updater.submission[field_id]
min_length = false
label = field.raw[:label] || I18n.t("#{field.key}.label")
type = field.type
required = field.required
min_length = field.min_length if is_text_type(field)
file_types = field.file_types
format = field.format
if required && !value
@updater.errors.add(field_id, I18n.t('wizard.field.required', label: label))
end
if min_length && value.is_a?(String) && value.strip.length < min_length.to_i
@updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
end
if is_url_type(field) && !check_if_url(value)
@updater.errors.add(field_id, I18n.t('wizard.field.not_url', label: label))
end
if type === 'checkbox'
@updater.submission[field_id] = standardise_boolean(value)
end
if type === 'upload' && value.present? && !validate_file_type(value, file_types)
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_file', label: label, types: file_types))
end
if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value)
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_date'))
end
if type === 'time' && value.present? && !validate_time(value)
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_time'))
end
self.class.field_validators.each do |validator|
if type === validator[:type]
validator[:block].call(field, value, @updater, @step_template)
end
end
end
def self.sorted_field_validators
@sorted_field_validators ||= []
end
def self.field_validators
sorted_field_validators.map { |h| { type: h[:type], block: h[:block] } }
end
def self.add_field_validator(priority = 0, type, &block)
sorted_field_validators << { priority: priority, type: type, block: block }
@sorted_field_validators.sort_by! { |h| -h[:priority] }
end
private
def validate_file_type(value, file_types)
file_types.split(',')
.map { |t| t.gsub('.', '') }
.include?(File.extname(value['original_filename'])[1..-1])
end
def validate_date(value)
begin
Date.parse(value)
true
rescue ArgumentError
false
end
end
def validate_time(value)
begin
Time.parse(value)
true
rescue ArgumentError
false
end
end
def is_text_type(field)
['text', 'textarea', 'composer'].include? field.type
end
def is_url_type(field)
['url'].include? field.type
end
def check_if_url(value)
value =~ URI::regexp
end
def standardise_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
end

Datei anzeigen

@ -107,10 +107,10 @@ class CustomWizard::Wizard
end end
end end
def create_updater(step_id, inputs) def create_updater(step_id, submission)
step = @steps.find { |s| s.id == step_id } step = @steps.find { |s| s.id == step_id }
wizard = self wizard = self
CustomWizard::StepUpdater.new(user, wizard, step, inputs) CustomWizard::StepUpdater.new(user, wizard, step, submission)
end end
def unfinished? def unfinished?

Datei anzeigen

@ -50,6 +50,8 @@ after_initialize do
../jobs/clear_after_time_wizard.rb ../jobs/clear_after_time_wizard.rb
../jobs/refresh_api_access_token.rb ../jobs/refresh_api_access_token.rb
../jobs/set_after_time_wizard.rb ../jobs/set_after_time_wizard.rb
../lib/custom_wizard/validators/template.rb
../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb ../lib/custom_wizard/action_result.rb
../lib/custom_wizard/action.rb ../lib/custom_wizard/action.rb
../lib/custom_wizard/builder.rb ../lib/custom_wizard/builder.rb
@ -59,7 +61,6 @@ after_initialize do
../lib/custom_wizard/log.rb ../lib/custom_wizard/log.rb
../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/validator.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/authorization.rb

Datei anzeigen

@ -316,48 +316,6 @@ describe CustomWizard::Builder do
).to eq(nil) ).to eq(nil)
end end
end end
context 'validation' do
it 'applies min length to fields that support min length' do
min_length = 3
@template[:steps][0][:fields][0][:min_length] = min_length
@template[:steps][0][:fields][1][:min_length] = min_length
@template[:steps][0][:fields][2][:min_length] = min_length
CustomWizard::Template.save(@template.as_json)
expect(
perform_update('step_1', step_1_field_1: 'Te')
.errors.messages[:step_1_field_1].first
).to eq(I18n.t('wizard.field.too_short', label: 'Text', min: min_length))
expect(
perform_update('step_1', step_1_field_2: 'Te')
.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length))
expect(
perform_update('step_1', step_1_field_3: 'Te')
.errors.messages[:step_1_field_3].first
).to eq(I18n.t('wizard.field.too_short', label: 'Composer', min: min_length))
end
it 'standardises boolean entries' do
perform_update('step_2', step_2_field_5: 'false')
expect(
CustomWizard::Wizard.submissions(@template[:id], user)
.first['step_2_field_5']
).to eq(false)
end
it 'requires required fields' do
@template[:steps][0][:fields][1][:required] = true
CustomWizard::Template.save(@template.as_json)
expect(
perform_update('step_1', step_1_field_2: nil)
.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
end
end
end end
end end
end end

Datei anzeigen

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
describe CustomWizard::Validator do describe CustomWizard::TemplateValidator do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { let(:template) {
@ -11,21 +11,21 @@ describe CustomWizard::Validator do
it "validates valid templates" do it "validates valid templates" do
expect( expect(
CustomWizard::Validator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(true) ).to eq(true)
end end
it "invalidates templates without required attributes" do it "invalidates templates without required attributes" do
template.delete(:id) template.delete(:id)
expect( expect(
CustomWizard::Validator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(false) ).to eq(false)
end end
it "invalidates templates with duplicate ids if creating a new template" do it "invalidates templates with duplicate ids if creating a new template" do
CustomWizard::Template.save(template) CustomWizard::Template.save(template)
expect( expect(
CustomWizard::Validator.new(template, create: true).perform CustomWizard::TemplateValidator.new(template, create: true).perform
).to eq(false) ).to eq(false)
end end
@ -33,7 +33,7 @@ describe CustomWizard::Validator do
template[:after_time] = true template[:after_time] = true
template[:after_time_scheduled] = (Time.now + 3.hours).iso8601 template[:after_time_scheduled] = (Time.now + 3.hours).iso8601
expect( expect(
CustomWizard::Validator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(true) ).to eq(true)
end end
@ -41,7 +41,7 @@ describe CustomWizard::Validator do
template[:after_time] = true template[:after_time] = true
template[:after_time_scheduled] = "not a time" template[:after_time_scheduled] = "not a time"
expect( expect(
CustomWizard::Validator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(false) ).to eq(false)
end end
end end

Datei anzeigen

@ -0,0 +1,67 @@
require 'rails_helper'
describe CustomWizard::UpdateValidator 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
}
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
def perform_validation(step_id, submission)
wizard = CustomWizard::Builder.new(@template[:id], user).build
updater = wizard.create_updater(step_id, submission)
updater.validate
updater
end
it 'applies min length to text type fields' do
min_length = 3
@template[:steps][0][:fields][0][:min_length] = min_length
@template[:steps][0][:fields][1][:min_length] = min_length
@template[:steps][0][:fields][2][:min_length] = min_length
CustomWizard::Template.save(@template)
updater = perform_validation('step_1', step_1_field_1: 'Te')
expect(
updater.errors.messages[:step_1_field_1].first
).to eq(I18n.t('wizard.field.too_short', label: 'Text', min: min_length))
updater = perform_validation('step_1', step_1_field_2: 'Te')
expect(
updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length))
updater = perform_validation('step_1', step_1_field_3: 'Te')
expect(
updater.errors.messages[:step_1_field_3].first
).to eq(I18n.t('wizard.field.too_short', label: 'Composer', min: min_length))
end
it 'standardises boolean entries' do
updater = perform_validation('step_2', step_2_field_5: 'false')
expect(updater.submission['step_2_field_5']).to eq(false)
end
it 'requires required fields' do
@template[:steps][0][:fields][1][:required] = true
CustomWizard::Template.save(@template)
updater = perform_validation('step_1', step_1_field_2: nil)
expect(
updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
end
end