# frozen_string_literal: true # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE require 'rails_helper' describe InsertRecurringDonation do describe '.with_stripe' do before do Houdini.payment_providers.stripe.connect = true end include_context :shared_rd_donation_value_context it 'does basic validation' do validation_basic_validation { InsertRecurringDonation.with_stripe(designation: 34_124, dedication: 35_141, event_id: 'bad', campaign_id: 'bad') } end it 'does recurring donation validation' do expect do InsertRecurringDonation.with_stripe(amount: 1, nonprofit_id: 1, supporter_id: 1, token: fake_uuid, recurring_donation: { interval: 'not number', start_date: 'not_date', time_unit: 4, paydate: 'faf' }) end .to raise_error { |e| expect(e).to be_a ParamValidation::ValidationError expect_validation_errors(e.data, [ { key: :interval, name: :is_integer }, { key: :start_date, name: :can_be_date }, { key: :time_unit, name: :included_in }, { key: :paydate, name: :is_integer } ]) } end it 'does paydate validation min' do expect do InsertRecurringDonation.with_stripe(amount: 1, nonprofit_id: 1, supporter_id: 1, token: fake_uuid, recurring_donation: { paydate: '0' }) end .to raise_error { |e| expect(e).to be_a ParamValidation::ValidationError expect_validation_errors(e.data, [ { key: :paydate, name: :min } ]) } end it 'does paydate validation max' do expect do InsertRecurringDonation.with_stripe(amount: 1, nonprofit_id: 1, supporter_id: 1, token: fake_uuid, recurring_donation: { paydate: '29' }) end .to raise_error { |e| expect(e).to be_a ParamValidation::ValidationError expect_validation_errors(e.data, [ { key: :paydate, name: :max } ]) } end it 'errors out if token is invalid' do validation_invalid_token { InsertRecurringDonation.with_stripe(amount: 1, nonprofit_id: 1, supporter_id: 1, token: fake_uuid) } end it 'errors out if token is unauthorized' do validation_unauthorized { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: 1, supporter_id: 1, token: fake_uuid) } end it 'errors out if token is expired' do validation_expired { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: 1, supporter_id: 1, token: fake_uuid) } end describe 'errors during find if' do it 'supporter is invalid' do find_error_supporter { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: 55_555, token: source_token.token) } end it 'nonprofit is invalid' do find_error_nonprofit { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: 55_555, supporter_id: supporter.id, token: source_token.token) } end it 'campaign is invalid' do find_error_campaign { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: 5555) } end it 'event is invalid' do find_error_event { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: 5555) } end it 'profile is invalid' do find_error_profile { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: 5555) } end end describe 'errors during relationship comparison if' do it 'event is deleted' do validation_event_deleted { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id) } end it 'campaign is deleted' do validation_campaign_deleted { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id) } end it 'supporter is deleted' do validation_supporter_deleted { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token) } end it 'supporter doesnt belong to nonprofit' do validation_supporter_not_with_nonprofit { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: other_nonprofit_supporter.id, token: source_token.token) } end it 'campaign doesnt belong to nonprofit' do validation_campaign_not_with_nonprofit { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: other_campaign.id) } end it 'event doesnt belong to nonprofit' do validation_event_not_with_nonprofit { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: other_event.id) } end it 'card doesnt belong to supporter' do validation_card_not_with_supporter { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: other_source_token.token) } end end it 'charge returns failed' do handle_charge_failed { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token) } end describe 'success' do before(:each) do allow(SecureRandom).to receive(:uuid).and_return(default_edit_token) allow(Houdini.event_publisher).to receive(:announce) end describe 'charge happens' do before(:each) do before_each_success end it 'process event donation' do process_event_donation(recurring_donation: { paydate: nil, interval: 1, time_unit: 'year', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { time_unit: 'year' }) } end it 'process campaign donation' do expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args) process_campaign_donation(recurring_donation: { paydate: nil, interval: 2, time_unit: 'month', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { interval: 2 }) } end it 'processes general donation with no recurring donation hash' do process_general_donation(recurring_donation: { paydate: Time.now.day, interval: 1, time_unit: 'month', start_date: Time.now.beginning_of_day }) do InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: Time.now.to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') end end describe 'object event firing' do # all the same for all the types of donations; see #603 let(:created_time) { Time.current } let(:common_builder) do { 'supporter' => supporter.id, 'nonprofit' => nonprofit.id } end let(:common_builder_expanded) do { 'supporter' => supporter_builder_expanded, 'nonprofit' => np_builder_expanded } end let(:common_builder_with_trx_id) do common_builder.merge( { 'transaction' => match_houid('trx') } ) end let(:common_builder_with_trx) do common_builder.merge( { 'transaction' => transaction_builder } ) end let(:np_builder_expanded) do { 'id' => nonprofit.id, 'name' => nonprofit.name, 'object' => 'nonprofit' } end let(:supporter_builder_expanded) do supporter_to_builder_base.merge({ 'name' => 'Fake Supporter Name' }) end let(:transaction_builder) do common_builder.merge( { 'id' => match_houid('trx'), 'object' => 'transaction', 'amount' => { 'cents' => charge_amount, 'currency' => 'usd' }, 'created' => created_time.to_i, 'subtransaction' => stripe_transaction_id_only, 'subtransaction_payments' => [stripe_transaction_charge_id_only], 'transaction_assignments' => [donation_id_only] } ) end let(:transaction_builder_expanded) do transaction_builder.merge( common_builder_expanded, { 'subtransaction' => stripe_transaction_builder, 'subtransaction_payments' => [stripe_transaction_charge_builder], 'transaction_assignments' => [donation_builder] } ) end let(:stripe_transaction_id_only) do { 'id' => match_houid('stripetrx'), 'object' => 'stripe_transaction', 'type' => 'subtransaction' } end let(:stripe_transaction_builder) do stripe_transaction_id_only.merge( common_builder_with_trx_id, { 'initial_amount' => { 'cents' => charge_amount, 'currency' => 'usd' }, 'net_amount' => { 'cents' => 67, 'currency' => 'usd' }, 'payments' => [stripe_transaction_charge_id_only], 'created' => created_time.to_i } ) end let(:stripe_transaction_builder_expanded) do stripe_transaction_builder.merge( common_builder_with_trx, common_builder_expanded, { 'payments' => [stripe_transaction_charge_builder] } ) end let(:stripe_transaction_charge_id_only) do { 'id' => match_houid('stripechrg'), 'object' => 'stripe_transaction_charge', 'type' => 'payment' } end let(:stripe_transaction_charge_builder) do stripe_transaction_charge_id_only.merge( common_builder_with_trx_id, { 'gross_amount' => { 'cents' => charge_amount, 'currency' => 'usd' }, 'net_amount' => { 'cents' => 67, 'currency' => 'usd' }, 'fee_total' => { 'cents' => -33, 'currency' => 'usd' }, 'subtransaction' => stripe_transaction_id_only, 'stripe_id' => /test_ch_\d+/, 'created' => created_time.to_i } ) end let(:stripe_transaction_charge_builder_expanded) do stripe_transaction_charge_builder.merge( common_builder_with_trx, common_builder_expanded, { 'subtransaction' => stripe_transaction_builder } ) end let(:donation_id_only) do { 'id' => match_houid('don'), 'object' => 'donation', 'type' => 'trx_assignment' } end let(:donation_builder) do donation_id_only.merge(common_builder_with_trx_id, { 'amount' => { 'cents' => charge_amount, 'currency' => 'usd' }, 'designation' => 'designation', 'dedication' => { 'name' => 'a name', 'type' => 'honor' } }) end let(:donation_builder_expanded) do donation_builder.merge(common_builder_with_trx, common_builder_expanded) end describe 'general donations' do subject do process_general_donation(recurring_donation: { paydate: Time.now.day, interval: 1, time_unit: 'month', start_date: Time.now.beginning_of_day }) do described_class.with_stripe( { amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, dedication: { 'name' => 'a name', 'type' => 'honor' }, designation: 'designation' }.with_indifferent_access ) end end it 'has fired transaction.created' do expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with( :transaction_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'transaction.created', 'data' => { 'object' => transaction_builder_expanded } } ) subject end it 'has fired stripe_transaction_charge.created' do expect(Houdini.event_publisher).to receive(:announce).with( :stripe_transaction_charge_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'stripe_transaction_charge.created', 'data' => { 'object' => stripe_transaction_charge_builder_expanded } } ) expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) subject end it 'has fired payment.created' do expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with( :payment_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'payment.created', 'data' => { 'object' => stripe_transaction_charge_builder_expanded } } ) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) subject end it 'has fired stripe_transaction.created' do expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with( :stripe_transaction_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'stripe_transaction.created', 'data' => { 'object' => stripe_transaction_builder_expanded } } ) expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) subject end it 'has fired donation.created' do expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with( :donation_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'donation.created', 'data' => { 'object' => donation_builder_expanded } } ) expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) subject end it 'has fired trx_assignment.created' do expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) expect(Houdini.event_publisher).to receive(:announce).with( :trx_assignment_created, { 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'trx_assignment.created', 'data' => { 'object' => donation_builder_expanded } } ) expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) subject end end # describe 'campaign donations' do # subject do # expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args) # process_campaign_donation do # described_class.with_stripe( # { # amount: charge_amount, # nonprofit_id: nonprofit.id, # supporter_id: supporter.id, # token: source_token.token, # dedication: { 'name' => 'a name', 'type' => 'honor' }, # designation: 'designation', # campaign_id: campaign.id # }.with_indifferent_access # ) # end # end # before do # before_each_success # end # it 'has fired transaction.created' do # expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with( # :transaction_created, { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'transaction.created', # 'data' => { # 'object' => transaction_builder_expanded # } # } # ) # subject # end # it 'has fired stripe_transaction_charge.created' do # expect(Houdini.event_publisher).to receive(:announce).with( # :stripe_transaction_charge_created, # { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'stripe_transaction_charge.created', # 'data' => { # 'object' => stripe_transaction_charge_builder_expanded # } # } # ) # expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) # subject # end # it 'has fired payment.created' do # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with( # :payment_created, # { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'payment.created', # 'data' => { # 'object' => stripe_transaction_charge_builder_expanded # } # } # ) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) # subject # end # it 'has fired stripe_transaction.created' do # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with( # :stripe_transaction_created, # { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'stripe_transaction.created', # 'data' => { # 'object' => stripe_transaction_builder_expanded # } # } # ) # expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) # subject # end # it 'has fired donation.created' do # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with( # :donation_created, # { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'donation.created', # 'data' => { # 'object' => donation_builder_expanded # } # } # ) # expect(Houdini.event_publisher).to receive(:announce).with(:trx_assignment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) # subject # end # it 'has fired trx_assignment.created' do # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_charge_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:payment_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:stripe_transaction_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with(:donation_created, any_args) # expect(Houdini.event_publisher).to receive(:announce).with( # :trx_assignment_created, # { # 'id' => match_houid('objevt'), # 'object' => 'object_event', # 'type' => 'trx_assignment.created', # 'data' => { # 'object' => donation_builder_expanded # } # } # ) # expect(Houdini.event_publisher).to receive(:announce).with(:transaction_created, any_args) # subject # end # end end end describe 'future charge' do before(:each) do before_each_success(false) end it 'processes general donation' do process_general_donation(expect_payment: false, expect_charge: false, recurring_donation: { paydate: (Time.now + 5.days).day, interval: 1, time_unit: 'month', start_date: (Time.now + 5.days).beginning_of_day }) do InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { start_date: (Time.now + 5.days).to_s }) end end end end end describe '.convert_donation_to_recurring_donation' do describe 'wonderful testing Eric' do before(:each) { Timecop.freeze(2020, 4, 29) } after(:each) { Timecop.return } let(:nonprofit) { force_create(:nm_justice, state_code_slug: 'wi', city_slug: 'city', slug: 'sluggster') } let(:profile) { force_create(:profile, user: force_create(:user)) } let(:supporter) { force_create(:supporter, nonprofit: nonprofit) } let(:card) { force_create(:card, holder: supporter) } let(:campaign) { force_create(:campaign, profile: profile, nonprofit: nonprofit) } let(:event) { force_create(:event, profile: profile, nonprofit: nonprofit) } let!(:donation) { force_create(:donation, nonprofit: nonprofit, supporter: supporter, amount: 4000, card: card, campaign: campaign, event: event) } let!(:payment) { force_create(:payment, donation: donation, kind: 'Donation') } it 'param validation' do expect { InsertRecurringDonation.convert_donation_to_recurring_donation(nil) }.to(raise_error do |error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [{ key: :donation_id, name: :required }, { key: :donation_id, name: :is_integer }]) end) end it 'rejects invalid donation' do expect { InsertRecurringDonation.convert_donation_to_recurring_donation(5555) }.to(raise_error do |error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [{ key: :donation_id }]) end) end it 'accepts proper information' do Timecop.freeze(2020, 5, 4) do rd = InsertRecurringDonation.convert_donation_to_recurring_donation(donation.id) # this needs some serious improvement expected_rd = { id: rd.id, donation_id: donation.id, nonprofit_id: nonprofit.id, supporter_id: supporter.id, updated_at: Time.now, created_at: Time.now, active: true, n_failures: 0, interval: 1, time_unit: 'month', start_date: donation.created_at.beginning_of_day, paydate: 28, profile_id: nil, cancelled_at: nil, cancelled_by: nil, amount: 4000, anonymous: nil, card_id: nil, campaign_id: nil, failure_message: nil, end_date: nil, email: nil, origin_url: nil }.with_indifferent_access expect(rd.attributes.except('edit_token')).to eq(expected_rd) expect(rd.edit_token).to_not be_falsey expect(rd.donation.recurring).to eq true expect(rd.donation.payment.kind).to eq 'RecurringDonation' end end end describe 'test for earlier in the month' do before(:each) { Timecop.freeze(2020, 4, 5) } after(:each) { Timecop.return } let(:nonprofit) { force_create(:nm_justice, state_code_slug: 'wi', city_slug: 'city', slug: 'sluggster') } let(:profile) { force_create(:profile, user: force_create(:user)) } let(:supporter) { force_create(:supporter, nonprofit: nonprofit) } let(:card) { force_create(:card, holder: supporter) } let(:campaign) { force_create(:campaign, profile: profile, nonprofit: nonprofit) } let(:event) { force_create(:event, profile: profile, nonprofit: nonprofit) } let!(:donation) { force_create(:donation, nonprofit: nonprofit, supporter: supporter, amount: 4000, card: card, campaign: campaign, event: event) } let!(:payment) { force_create(:payment, donation: donation, kind: 'Donation') } it 'works when the date is earlier in the month' do Timecop.freeze(2020, 4, 29) do rd = InsertRecurringDonation.convert_donation_to_recurring_donation(donation.id) # this needs some serious improvement expected_rd = { id: rd.id, donation_id: donation.id, nonprofit_id: nonprofit.id, supporter_id: supporter.id, updated_at: Time.now, created_at: Time.now, active: true, n_failures: 0, interval: 1, time_unit: 'month', start_date: donation.created_at.beginning_of_day, paydate: 5, profile_id: nil, cancelled_at: nil, cancelled_by: nil, amount: 4000, anonymous: nil, card_id: nil, campaign_id: nil, failure_message: nil, end_date: nil, email: nil, origin_url: nil }.with_indifferent_access expect(rd.attributes.except('edit_token')).to eq(expected_rd) expect(rd.donation.recurring).to eq true expect(rd.donation.payment.kind).to eq 'RecurringDonation' expect(rd.edit_token).to_not be_falsey end end end end end