# frozen_string_literal: true # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later require 'rails_helper' describe InsertCustomFieldJoins do describe '.find_or_create' do let(:nonprofit) { force_create(:nonprofit) } let(:other_nonprofit) { force_create(:nonprofit) } let(:supporter) { force_create(:supporter, nonprofit: nonprofit) } let(:other_supporter) { force_create(:supporter, nonprofit: other_nonprofit) } let(:initial_custom_field_master) { force_create(:custom_field_master, nonprofit: nonprofit, name: 'CFM Name') } describe 'param validation' do it 'basic validation' do expect { InsertCustomFieldJoins.find_or_create(nil, nil, nil) }.to(raise_error do |error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ { key: :np_id, name: :required }, { key: :np_id, name: :is_integer }, { key: :supporter_ids, name: :required }, { key: :supporter_ids, name: :is_array }, { key: :supporter_ids, name: :min_length }, { key: :field_data, name: :required }, { key: :field_data, name: :is_array }, { key: :field_data, name: :min_length } ]) end) end it 'validate nonprofit existence' do expect { InsertCustomFieldJoins.find_or_create(5, [555], [[1, 1]]) }.to(raise_error do |error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ { key: :np_id } ]) expect(error.message).to eq '5 is not a valid non-profit' end) end it 'validate supporter in nonprofit' do expect { InsertCustomFieldJoins.find_or_create(nonprofit.id, [other_supporter.id], [[1, 1]]) }.to(raise_error do |error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ { key: :supporter_ids } ]) expect(error.message).to eq "#{other_supporter.id} is not a valid supporter for nonprofit #{nonprofit.id}" end) end end it 'run insert' do new_cf_name = 'new cf name' new_cf_value = 'value' old_cf_value = 'old_cf_value' expect(InsertCustomFieldJoins).to receive(:in_bulk) do |np_id, supporters_id, field_data| expect(np_id).to eq nonprofit.id expect(supporters_id).to eq [supporter.id] expect(field_data.length).to eq 2 expect(field_data).to include(custom_field_master_id: initial_custom_field_master.id, value: old_cf_value) expect(field_data).to include(custom_field_master_id: CustomFieldMaster.where(name: new_cf_name).first.id, value: new_cf_value) end result = InsertCustomFieldJoins.find_or_create(nonprofit.id, [supporter.id], [ [ initial_custom_field_master.name, old_cf_value ], [ new_cf_name, new_cf_value ] ]) expect(CustomFieldMaster.count).to eq 2 end end describe '.in_bulk' do context 'parameter validation' do it 'should validate parameters' do response = InsertCustomFieldJoins.in_bulk(nil, nil, nil) errors = response[:json][:errors] expect(errors.length).to eq(6) expect(response[:status]).to eq :unprocessable_entity expect_validation_errors(errors, [ { key: :np_id, name: :required }, { key: :np_id, name: :is_integer }, { key: :supporter_ids, name: :required }, { key: :supporter_ids, name: :is_array }, { key: :field_data, name: :is_array }, { key: :field_data, name: :required } ]) end context 'requiring db' do before(:each) do @nonprofit = force_create(:nonprofit) @bad_nonprofit = force_create(:nonprofit, id: 50) end it 'nonprofit must be valid' do response = InsertCustomFieldJoins.in_bulk(@nonprofit.id + 1, [], []) expect(response[:status]).to eq :unprocessable_entity expect(response[:json][:error]).to include("Nonprofit #{@nonprofit.id + 1} is not valid") end it 'supporters if empty should do nothing' do response = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [], []) expect(response).to eq(successful_json(0, 0)) end it 'supporters if empty should do nothing' do response = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [50], []) expect(response).to eq(successful_json(0, 0)) end end end context 'main testing' do before(:each) do @nonprofit = force_create(:nonprofit) @random_supporter = create(:supporter) @other_nonprofit = force_create(:nonprofit) @delete_cfm = [20, 40, 60] @add_cfm = [25, 35] @supporters = { np_supporter_with_add: { cfm_ids: [65, 75, 85] }, np_supporter_with_cfms_to_delete: { cfm_ids: [40, 75, 85] }, np_supporter_with_no_changes: { cfm_ids: @add_cfm }, np_supporter_with_some_of_both: { cfm_ids: [20, 35] }, supporter_from_other_np: { cfm_ids: [100, 150, 200], other_np: true } } @supporters.each_key do |k| i = @supporters[k] nonprofit_for_supporter = i[:other_np] ? @other_nonprofit : @nonprofit i[:entity] = create(:supporter, nonprofit: nonprofit_for_supporter) i[:cfm_ids].each do |j| cfm = CustomFieldMaster.exists?(id: j) ? CustomFieldMaster.find(j) : create(:custom_field_master, id: j, nonprofit: nonprofit_for_supporter, name: "CFM #{j}") create(:custom_field_join, :value_from_id, supporter_id: i[:entity].id, custom_field_master: cfm) end end end it 'invalid nonprofit-supporter combo returns okay' do results = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [@supporters[:supporter_from_other_np][:entity].id], []) expect(results).to eq(successful_json(0, 0)) end it 'strips cfms which dont belong to nonprofit' do results = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [@supporters[:np_supporter_with_add][:entity].id], create_cfm_data([100], [150])) expect(results).to eq(successful_json(0, 0)) expect(CustomFieldJoin.where('supporter_id = ? and custom_field_master_id = ?', @supporters[:np_supporter_with_add][:entity].id, 100).count).to eq 0 end it 'delete' do expect(CustomFieldJoin.count).to eq 13 @supporters[:np_supporter_with_some_of_both][:entity].id results = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [@supporters[:np_supporter_with_some_of_both][:entity].id, @supporters[:np_supporter_with_cfms_to_delete][:entity].id, @supporters[:np_supporter_with_add][:entity].id, @supporters[:supporter_from_other_np][:entity].id, @supporters[:np_supporter_with_no_changes][:entity].id], create_cfm_data(@add_cfm, @delete_cfm)) expect(CustomFieldJoin.where('supporter_id = ? ', @supporters[:np_supporter_with_some_of_both][:entity].id).count).to eq 2 expect(CustomFieldJoin.where('supporter_id = ?', @supporters[:np_supporter_with_add][:entity].id).count).to eq 5 expect(CustomFieldJoin.where('supporter_id = ?', @supporters[:np_supporter_with_cfms_to_delete][:entity].id).count).to eq 4 expect(CustomFieldJoin.where('supporter_id = ?', @supporters[:supporter_from_other_np][:entity].id).count).to eq 3 expect(CustomFieldJoin.where('supporter_id = ?', @supporters[:np_supporter_with_no_changes][:entity].id).count).to eq 2 expect(CustomFieldJoin.count).to eq 16 end it 'id, updated_at, created_at changes are stripped' do invalid_id = 10_000_000 Timecop.freeze(2020, 9, 1, 12, 0, 0) do results = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [@supporters[:np_supporter_with_add][:entity].id], [{ custom_field_master_id: 25, value: 'CFM value 25', id: invalid_id, created_at: Time.now.ago(3000), updated_at: Time.now.ago(2999) }]) expected = { custom_field_master_id: 25, value: 'CFM value 25', created_at: Time.now, updated_at: Time.now, supporter_id: @supporters[:np_supporter_with_add][:entity].id }.with_indifferent_access expect(results).to eq(successful_json(1, 0)) result_tag = @supporters[:np_supporter_with_add][:entity].custom_field_joins.where('custom_field_master_id = ?', 25).first expect(result_tag.attributes.with_indifferent_access.reject { |k, _| k == 'id' }).to eq(expected) expect(result_tag.attributes[:id]).to_not eq invalid_id end end it 'add_to_one' do expect(CustomFieldJoin.count).to eq 13 np_supporter_with_add_cfms = @supporters[:np_supporter_with_add][:entity].custom_field_joins.to_a np_supporter_with_some_of_both_cfms = @supporters[:np_supporter_with_some_of_both][:entity].custom_field_joins.to_a np_supporter_with_no_changes_cfms = @supporters[:np_supporter_with_no_changes][:entity].custom_field_joins.to_a Timecop.travel(20) do results = InsertCustomFieldJoins.in_bulk(@nonprofit.id, [ @supporters[:np_supporter_with_add][:entity].id, # add 2 @supporters[:np_supporter_with_no_changes][:entity], # update 2 @supporters[:np_supporter_with_some_of_both][:entity].id ], # add 2, delete 1 create_cfm_data(@add_cfm, @delete_cfm)) expect(results).to eq(successful_json(6, 1)) expect(@supporters[:np_supporter_with_no_changes][:entity].custom_field_joins).to match_array(np_supporter_with_no_changes_cfms) expect(CustomFieldJoin.where('supporter_id = ? ', @supporters[:np_supporter_with_add][:entity].id).count).to eq 5 original_db_pairs = get_original_and_db(np_supporter_with_add_cfms, CustomFieldJoin.where('supporter_id = ? and custom_field_master_id in (?)', @supporters[:np_supporter_with_add][:entity].id, @supporters[:np_supporter_with_add][:cfm_ids]).pluck(:id)) original_db_pairs.each do |orig, db| expect(db.attributes.length).to eq(orig.attributes.length) expect(db.attributes).to eq(orig.attributes) end expect(CustomFieldJoin.where('supporter_id = ?', @supporters[:np_supporter_with_some_of_both][:entity].id).count).to eq 2 original_db_pairs = get_original_and_db(np_supporter_with_some_of_both_cfms, CustomFieldJoin.where('supporter_id = ? and custom_field_master_id in (?)', @supporters[:np_supporter_with_some_of_both][:entity].id, [35]).pluck(:id)) skip_attribs = %w[updated_at value] original_db_pairs.each do |orig, db| expect(db.attributes.length).to eq(orig.attributes.length) expect(db.attributes.reject { |key, _value| skip_attribs.include?(key) }).to eq(orig.attributes.reject { |key, _value| skip_attribs.include?(key) }) expect(db.attributes['updated_at']).to be > orig.attributes['updated_at'] expect(db.attributes['value']).to eq 'CFM value 35' end expect(CustomFieldJoin.count).to eq 15 end end end end def successful_json(inserted, deleted) { json: { inserted_count: inserted, removed_count: deleted }, status: :ok } end def create_cfm_data(cfm_to_add = [], cfm_to_delete = []) use_nil = true cfm_to_add.map { |cfm| { custom_field_master_id: cfm, value: "CFM value #{cfm}" } } + cfm_to_delete.map do |cfm| value = use_nil ? nil : '' use_nil = !use_nil { custom_field_master_id: cfm, value: value } end end def get_original_and_db(original_items, ids_to_verify) ids_to_verify.map do |i| original_item = original_items.find { |oi| oi[:id] == i } db_item = CustomFieldJoin.find(i) [original_item, db_item] end end end