require 'rails_helper' module PandaPal PandaPal::Organization RSpec.describe OrganizationConcerns::SettingsValidation, type: :model do let!(:org) { create :panda_pal_organization } after :all do PandaPal.lti_options[:settings_structure] = nil end def set_test_settings_structure PandaPal.lti_options = { title: 'Test App', settings_structure: structure, } end RSpec.shared_examples "shared stuff" do it 'does not perform any validations if settings is not defined' do PandaPal.lti_options = {} expect(org.valid?).to be_truthy end it 'does not perform any validations if options is not defined' do PandaPal.lti_options = nil expect(org.valid?).to be_truthy end it 'does perform validations if settings_structure is defined' do set_test_settings_structure org.valid? expect(org.valid?).to be_falsey end it 'will fail if a required setting is not present' do set_test_settings_structure expect(org.valid?).to be_falsey errors = org.errors.messages[:settings] expect(errors[0]).to eq("Entry [:canvas] is required") end it 'will fail if a setting is supplied but data_type is wrong' do set_test_settings_structure org.settings = {canvas: "Dog", reports: {}} expect(org.valid?).to be_falsey errors = org.errors.messages[:settings] expect(errors[0]).to eq("Expected [:canvas] to be a Hash. Was a String") end it 'will fail if a required subsetting is missing' do set_test_settings_structure org.settings = {canvas: {base_url: 'http://'}, reports: {}} expect(org.valid?).to be_falsey errors = org.errors.messages[:settings] expect(errors[0]).to eq("Entry [:canvas][:api_token] is required") end it 'will fail if extra options are specified' do set_test_settings_structure org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"} expect(org.valid?).to be_falsey errors = org.errors.messages[:settings] expect(errors[0]).to eq("Did not expect [:] to contain [unknown_option]") end it 'will pass if all structure is maintained' do set_test_settings_structure org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {submissions_report_time_length: 30.minutes, max_recheck_time: 10.hours}} expect(org.valid?).to be_truthy end end context 'new settings_structure' do let!(:properties) do { canvas: { required: true, type: 'Hash', properties: { api_token: { type: 'String', required: true }, base_url: { type: 'String', required: true }, }, }, reports: { required: true, type: 'Hash', allow_additional: true, } } end let!(:structure) do { properties: properties, } end it_behaves_like("shared stuff") describe ':allow_additional' do it 'passes extra properties if allow_additional is a matching Hash' do structure[:allow_additional] = { type: 'String' } set_test_settings_structure org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"} expect(org).to be_valid end it 'fails extra properties if allow_additional is an unmatching Hash' do structure[:allow_additional] = { type: 'Hash' } set_test_settings_structure org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"} expect(org).not_to be_valid end it 'passes extra properties if allow_additional is a blank Hash' do structure[:allow_additional] = { } set_test_settings_structure org.settings = {canvas: {base_url: 'http://', api_token: 'TEST'}, reports: {}, unknown_option: "WHAT IS THIS?"} expect(org).to be_valid end end describe ':validate' do let!(:properties) do { blah: { type: 'String', validate: -> (v, *args) { v == 'blah' ? nil : ' failed validation' }, } } end it 'supports a custom validation proc' do set_test_settings_structure org.settings = { blah: 'blah' } expect(org).to be_valid end it 'replaces with the human readable path' do set_test_settings_structure org.settings = { blah: '' } expect(org.valid?).to be_falsey errors = org.errors.messages[:settings] expect(errors[0]).to eq("[:blah] failed validation") end end end context 'old settings_structure' do let!(:structure) do YAML.load(" canvas: is_required: true data_type: Hash api_token: is_required: true data_type: String base_url: is_required: true data_type: String reports: is_required: true data_type: Hash active_term_allowance: submissions_report_time_length: is_required: false data_type: ActiveSupport::Duration recheck_wait: data_type: ActiveSupport::Duration max_recheck_time: is_required: false ").deep_symbolize_keys end it_behaves_like("shared stuff") end end end