require 'spec_helper' describe "Monthly Recurring Lesson Flow" do let(:user) { FactoryGirl.create(:user, remaining_test_drives: 0) } let(:teacher_user) { FactoryGirl.create(:teacher_user) } let(:teacher) { teacher_user.teacher } let(:lesson_booking_slot_single1) { FactoryGirl.build(:lesson_booking_slot_single) } let(:lesson_booking_slot_single2) { FactoryGirl.build(:lesson_booking_slot_single) } let(:lesson_booking_slot_recurring1) { FactoryGirl.build(:lesson_booking_slot_recurring) } let(:lesson_booking_slot_recurring2) { FactoryGirl.build(:lesson_booking_slot_recurring) } let(:valid_single_slots) { [lesson_booking_slot_single1, lesson_booking_slot_single2] } let(:valid_recurring_slots) { [lesson_booking_slot_recurring1, lesson_booking_slot_recurring2] } let(:affiliate_partner) { FactoryGirl.create(:affiliate_partner) } let(:affiliate_partner2) { FactoryGirl.create(:affiliate_partner, lesson_rate: 0.30) } let(:school) {FactoryGirl.create(:school)} after {Timecop.return} before { pending "lessons paused" teacher.stripe_account_id = stripe_account1_id teacher.save! } it "works normal" do # if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on Timecop.travel(Date.new(2016, 3, 20)) # user has no test drives, no credit card on file, but attempts to book a lesson booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60) booking.errors.any?.should be_false booking.card_presumed_ok.should be_false booking.user.should eql user booking.card_presumed_ok.should be_false booking.same_school.should be_false booking.same_school_free.should be_false booking.should eql user.unprocessed_normal_lesson booking.sent_notices.should be_false booking.booked_price.should eql 30.00 ########## Need validate their credit card token = create_stripe_token result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) booking.reload booking.card_presumed_ok.should be_true booking.errors.any?.should be_false booking = result[:lesson] lesson = booking.lesson_sessions[0] lesson.errors.any?.should be_false booking.sent_notices.should be_true lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) lesson.amount_charged.should be 0.0 lesson.reload user.reload user.stripe_customer_id.should_not be nil user.remaining_test_drives.should eql 0 user.lesson_purchases.length.should eql 0 customer = Stripe::Customer.retrieve(user.stripe_customer_id) customer.email.should eql user.email booking.lesson_sessions.length.should eql 1 lesson_session = booking.lesson_sessions[0] lesson_session.status.should eql LessonBooking::STATUS_REQUESTED booking.status.should eql LessonBooking::STATUS_REQUESTED ######### Teacher counters with new slot teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) booking.reload booking.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false lesson_session.lesson_booking_slots.length.should eql 1 lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last teacher_counter.should eql teacher_countered_slot teacher_counter.proposer.should eql teacher_user booking.lesson_booking_slots.length.should eql 3 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.message.should eql 'Does this work?' chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######### Student counters with new slot student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) lesson_session.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false #lesson_session.lesson_booking_slots.length.should eql 2 student_counter = booking.lesson_booking_slots.order(:created_at).last student_counter.proposer.should eql user booking.reload booking.lesson_booking_slots.length.should eql 4 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Does this work better?' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql user chat.target_user.should eql teacher_user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql false notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######## Teacher accepts slot UserMailer.deliveries.clear lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end # get acceptance emails, as well as 'your stuff is accepted' UserMailer.deliveries.length.should eql 2 lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter lesson_session.status.should eql LessonSession::STATUS_APPROVED booking.reload booking.default_slot.should eql student_counter lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Yeah I got this' chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'accept' notification.description.should eql NotificationTypes::LESSON_MESSAGE # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! Timecop.travel(end_time + 1) LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end # let user pay for it LessonBooking.hourly_check booked_price = booking.booked_price prorated = booked_price / 2 prorated_cents = (prorated * 100).to_i user.reload user.lesson_purchases.length.should eql 1 lesson_purchase = user.lesson_purchases[0] lesson_purchase.price.should eql prorated lesson_purchase.lesson_package_type.is_normal?.should eql true lesson_purchase.price_in_cents.should eql prorated_cents teacher_distribution = lesson_purchase.teacher_distribution teacher_distribution.amount_in_cents.should eql prorated_cents teacher_distribution.ready.should be_true teacher_distribution.distributed.should be_false user.sales.length.should eql 1 sale = user.sales.first sale.stripe_charge_id.should_not be_nil sale.recurly_tax_in_cents.should eql (100 * prorated * 0.0825).round.to_i sale.recurly_total_in_cents.should eql ((prorated * 100 * 0.0825).round + 100 * prorated).to_i sale.recurly_subtotal_in_cents.should eql prorated_cents sale.recurly_currency.should eql 'USD' sale.stripe_charge_id.should_not be_nil line_item = sale.sale_line_items[0] line_item.quantity.should eql 1 line_item.product_type.should eql SaleLineItem::LESSON line_item.product_id.should eq LessonPackageType.single.id line_item.lesson_package_purchase.should eql lesson_purchase lesson_purchase.sale_line_item.should eql line_item TeacherPayment.count.should eql 0 TeacherPayment.hourly_check teacher_distribution.reload teacher_distribution.distributed.should be_true TeacherPayment.count.should eql 1 payment = TeacherPayment.first payment.amount_in_cents.should eql prorated_cents payment.fee_in_cents.should eql (prorated_cents * 0.28).round payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * 0.28).round payment.teacher.should eql teacher_user payment.teacher_distribution.should eql teacher_distribution # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! UserMailer.deliveries.clear # background code comes around and analyses the session LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end lesson.amount_charged.should eql 0.0 lesson_session.billing_error_reason.should be_nil lesson_session.sent_billing_notices.should be nil user.reload user.remaining_test_drives.should eql 0 UserMailer.deliveries.length.should eql 0 # one for student booking.remaining_roll_forward_amount_in_cents.should eql 0 lesson_purchase.remaining_roll_forward_amount_in_cents.should be_nil lesson_purchase.expected_session_times.should eql 2 lesson_purchase.actual_session_times.should be_nil Timecop.travel(Date.new(2016, 4, 1)) LessonBooking.adjust_for_missed_sessions # we should find remaining/totals in lesson purcahse as well as booking rolled up booking.reload lesson_purchase.reload lesson_purchase.actual_session_times.should eql 1 lesson_purchase.expected_session_times.should eql 2 booking.remaining_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round lesson_purchase.remaining_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round lesson_purchase.total_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round # but once we bill out, we'll not credit as heavily LessonBooking.bill_monthlies booking.reload lesson_purchase.reload lesson_purchase.actual_session_times.should eql 1 lesson_purchase.expected_session_times.should eql 2 booking.remaining_roll_forward_amount_in_cents.should eql 0 lesson_purchase.remaining_roll_forward_amount_in_cents.should eql 0 lesson_purchase.total_roll_forward_amount_in_cents.should eql (lesson_purchase.price * 100 * (1.0/2.0)).round booking.lesson_package_purchases.count.should eql 2 next_purchase = booking.lesson_package_purchases.order(:created_at)[1] next_purchase.remaining_roll_forward_amount_in_cents.should be nil next_purchase.total_roll_forward_amount_in_cents.should be nil next_purchase.expected_session_times.should eql 4 next_purchase.actual_session_times.should be_nil next_purchase.teacher_distributions.count.should eql 1 distribution = next_purchase.teacher_distributions[0] distribution.amount_in_cents.should eql (3000 - 750) # booked price is 30. one lesson is 7.50 distribution.teacher_fee_in_cents.should eql ((3000 - 750) * 0.28).round # booked price is 30. one lesson is 7.50. take out .28 distribution.reduced_roll_forward_amount_in_cents.should eql 750 next_purchase.reduced_roll_forward_amount_in_cents.should eql 750 end it "works (school on school education)" do # make sure teacher can get payments teacher.stripe_account_id = stripe_account1_id school.user.stripe_account_id = stripe_account2_id # get user and teacher into same school school.education = true school.save! user.school = school user.save! teacher.school = school teacher.save! # if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on Timecop.travel(Date.new(2016, 3, 20)) # user has no test drives, no credit card on file, but attempts to book a lesson booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60) booking.errors.any?.should be_false booking.card_presumed_ok.should be_false booking.user.should eql user booking.card_presumed_ok.should be_false booking.same_school.should be_true booking.same_school_free.should be_false booking.should eql user.unprocessed_normal_lesson booking.sent_notices.should be_false booking.booked_price.should eql 30.00 ########## Need validate their credit card token = create_stripe_token result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) booking.reload booking.card_presumed_ok.should be_true booking.errors.any?.should be_false booking = result[:lesson] lesson = booking.lesson_sessions[0] lesson.errors.any?.should be_false booking.sent_notices.should be_true lesson.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) lesson.amount_charged.should be 0.0 lesson.reload user.reload user.stripe_customer_id.should_not be nil user.remaining_test_drives.should eql 0 user.lesson_purchases.length.should eql 0 customer = Stripe::Customer.retrieve(user.stripe_customer_id) customer.email.should eql user.email booking.lesson_sessions.length.should eql 1 lesson_session = booking.lesson_sessions[0] lesson_session.status.should eql LessonBooking::STATUS_REQUESTED booking.status.should eql LessonBooking::STATUS_REQUESTED ######### Teacher counters with new slot teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) booking.reload booking.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false lesson_session.lesson_booking_slots.length.should eql 1 lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last teacher_counter.should eql teacher_countered_slot teacher_counter.proposer.should eql teacher_user booking.lesson_booking_slots.length.should eql 3 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.message.should eql 'Does this work?' chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######### Student counters with new slot student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) lesson_session.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false #lesson_session.lesson_booking_slots.length.should eql 2 student_counter = booking.lesson_booking_slots.order(:created_at).last student_counter.proposer.should eql user booking.reload booking.lesson_booking_slots.length.should eql 4 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Does this work better?' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql user chat.target_user.should eql teacher_user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql false notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######## Teacher accepts slot UserMailer.deliveries.clear lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end # get acceptance emails, as well as 'your stuff is accepted' UserMailer.deliveries.length.should eql 2 lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter lesson_session.status.should eql LessonSession::STATUS_APPROVED booking.reload booking.default_slot.should eql student_counter lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Yeah I got this' chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'accept' notification.description.should eql NotificationTypes::LESSON_MESSAGE # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! Timecop.travel(end_time + 1) LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end # let user pay for it LessonBooking.hourly_check booked_price = booking.booked_price prorated = booked_price / 2 prorated_cents = (prorated * 100).to_i user.reload user.lesson_purchases.length.should eql 1 lesson_purchase = user.lesson_purchases[0] lesson_purchase.price.should eql prorated lesson_purchase.lesson_package_type.is_normal?.should eql true lesson_purchase.price_in_cents.should eql prorated_cents teacher_distribution = lesson_purchase.teacher_distribution teacher_distribution.amount_in_cents.should eql prorated_cents teacher_distribution.ready.should be_true teacher_distribution.distributed.should be_false education_distribution = lesson_purchase.education_distribution education_distribution.amount_in_cents.should eql (prorated_cents * 0.0625).round education_distribution.ready.should be_true education_distribution.distributed.should be_false user.sales.length.should eql 1 sale = user.sales.first sale.stripe_charge_id.should_not be_nil sale.recurly_tax_in_cents.should eql (100 * prorated * 0.0825).round.to_i sale.recurly_total_in_cents.should eql ((prorated * 100 * 0.0825).round + 100 * prorated).to_i sale.recurly_subtotal_in_cents.should eql prorated_cents sale.recurly_currency.should eql 'USD' sale.stripe_charge_id.should_not be_nil line_item = sale.sale_line_items[0] line_item.quantity.should eql 1 line_item.product_type.should eql SaleLineItem::LESSON line_item.product_id.should eq LessonPackageType.single.id line_item.lesson_package_purchase.should eql lesson_purchase lesson_purchase.sale_line_item.should eql line_item TeacherPayment.count.should eql 0 TeacherPayment.hourly_check teacher_distribution.reload teacher_distribution.distributed.should be_true TeacherPayment.count.should eql 2 payment = teacher_distribution.teacher_payment payment.amount_in_cents.should eql prorated_cents payment.fee_in_cents.should eql (prorated_cents * (school.base_rate + APP_CONFIG.stripe[:charge_fee])).round payment.teacher_payment_charge.amount_in_cents.should eql (payment.real_distribution_in_cents + payment.real_distribution_in_cents * APP_CONFIG.stripe[:ach_pct]).round + 1 # we are off by 1 in rounding error somewhere. I'm OK with that payment.teacher_payment_charge.fee_in_cents.should eql (prorated_cents * (school.base_rate + 0.03)).round payment.teacher.should eql teacher_user payment.teacher_distribution.should eql teacher_distribution education_distribution.reload education_distribution.distributed.should be_true education_amt = (prorated_cents * 0.0625).round payment = education_distribution.teacher_payment payment.amount_in_cents.should eql education_amt payment.fee_in_cents.should eql 0 payment.teacher_payment_charge.amount_in_cents.should eql (education_amt + education_amt * APP_CONFIG.stripe[:ach_pct]).round payment.teacher_payment_charge.fee_in_cents.should eql 0 payment.teacher.should eql teacher_user payment.teacher_distribution.should eql education_distribution # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! UserMailer.deliveries.clear # background code comes around and analyses the session LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end lesson.amount_charged.should eql 0.0 lesson_session.billing_error_reason.should be_nil lesson_session.sent_billing_notices.should be nil user.reload user.remaining_test_drives.should eql 0 UserMailer.deliveries.length.should eql 0 # one for student end it "works (school on school normal)" do # get user and teacher into same school user.school = school user.save! teacher.school = school teacher.save! # if it's later in the month, we'll make 2 lesson_package_purchases (prorated one, and next month's), which can throw off some assertions later on Timecop.travel(Date.new(2016, 3, 20)) # user has no test drives, no credit card on file, but attempts to book a lesson booking = LessonBooking.book_normal(user, teacher_user, valid_recurring_slots, "Hey I've heard of you before.", true, LessonBooking::PAYMENT_STYLE_MONTHLY, 60) booking.errors.any?.should be_false booking.card_presumed_ok.should be_false booking.user.should eql user booking.sent_notices.should be_false token = create_stripe_token result = user.payment_update({token: token, zip: '78759', normal: true, booking_id: booking.id}) booking.reload booking.card_presumed_ok.should be_true booking.errors.any?.should be_false booking.sent_notices.should be_true booking.booked_price.should eql 30.00 user.reload user.stripe_customer_id.should_not be nil user.remaining_test_drives.should eql 0 user.lesson_purchases.length.should eql 0 booking.lesson_sessions.length.should eql 1 lesson_session = booking.lesson_sessions[0] lesson_session.status.should eql LessonBooking::STATUS_REQUESTED booking.status.should eql LessonBooking::STATUS_REQUESTED ######### Teacher counters with new slot teacher_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 14, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: teacher_user, slot: teacher_countered_slot, message: 'Does this work?'}) lesson_session.errors.any?.should be_false booking.reload booking.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false lesson_session.lesson_booking_slots.length.should eql 1 lesson_session.lesson_booking_slots[0].proposer.should eql teacher_user teacher_counter = lesson_session.lesson_booking_slots.order(:created_at).last teacher_counter.should eql teacher_countered_slot teacher_counter.proposer.should eql teacher_user booking.lesson_booking_slots.length.should eql 3 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.message.should eql 'Does this work?' chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######### Student counters with new slot student_countered_slot = FactoryGirl.build(:lesson_booking_slot_recurring, hour: 16, update_all: true) UserMailer.deliveries.clear lesson_session.counter({proposer: user, slot: student_countered_slot, message: 'Does this work better?'}) lesson_session.errors.any?.should be false lesson_session.lesson_booking.errors.any?.should be false #lesson_session.lesson_booking_slots.length.should eql 2 student_counter = booking.lesson_booking_slots.order(:created_at).last student_counter.proposer.should eql user booking.reload booking.lesson_booking_slots.length.should eql 4 UserMailer.deliveries.length.should eql 1 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Does this work better?' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql user chat.target_user.should eql teacher_user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql false notification.purpose.should eql 'counter' notification.description.should eql NotificationTypes::LESSON_MESSAGE ######## Teacher accepts slot UserMailer.deliveries.clear lesson_session.accept({message: 'Yeah I got this', slot: student_counter.id, update_all: false, accepter: teacher_user}) UserMailer.deliveries.each do |del| # puts del.inspect end # get acceptance emails, as well as 'your stuff is accepted' UserMailer.deliveries.length.should eql 2 lesson_session.errors.any?.should be_false lesson_session.reload lesson_session.slot.should eql student_counter lesson_session.status.should eql LessonSession::STATUS_APPROVED booking.reload booking.default_slot.should eql student_counter lesson_session.music_session.scheduled_start.should eql booking.default_slot.scheduled_time(0) booking.status.should eql LessonBooking::STATUS_APPROVED UserMailer.deliveries.length.should eql 2 chat = ChatMessage.unscoped.order(:created_at).last chat.message.should eql 'Yeah I got this' chat.purpose.should eql 'Lesson Approved' chat.channel.should eql ChatMessage::CHANNEL_LESSON chat.user.should eql teacher_user chat.target_user.should eql user notification = Notification.unscoped.order(:created_at).last notification.session_id.should eql lesson_session.music_session.id notification.student_directed.should eql true notification.purpose.should eql 'accept' notification.description.should eql NotificationTypes::LESSON_MESSAGE # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! Timecop.travel(end_time + 1) LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end # let user pay for it LessonBooking.hourly_check LessonPaymentCharge.count.should eql 0 TeacherDistribution.count.should eql 0 user.reload user.lesson_purchases.length.should eql 0 user.sales.length.should eql 0 TeacherPayment.count.should eql 0 TeacherPayment.hourly_check TeacherPayment.count.should eql 0 # teacher & student get into session start = lesson_session.scheduled_start end_time = lesson_session.scheduled_start + (60 * lesson_session.duration) uh2 = FactoryGirl.create(:music_session_user_history, user: teacher_user, history: lesson_session.music_session, created_at: start, session_removed_at: end_time) # artificially end the session, which is covered by other background jobs lesson_session.music_session.session_removed_at = end_time lesson_session.music_session.save! UserMailer.deliveries.clear # background code comes around and analyses the session LessonSession.hourly_check lesson_session.reload lesson_session.analysed.should be_true analysis = lesson_session.analysis analysis["reason"].should eql LessonSessionAnalyser::STUDENT_FAULT analysis["student"].should eql LessonSessionAnalyser::NO_SHOW if lesson_session.billing_error_detail puts "monthly recurring lesson flow #{lesson_session.billing_error_detail}" # this should not occur, but helps a great deal if a regression occurs and running all the tests end lesson_session.amount_charged.should eql 0.0 lesson_session.billing_error_reason.should be_nil lesson_session.sent_billing_notices.should be_nil user.reload user.remaining_test_drives.should eql 0 UserMailer.deliveries.length.should eql 0 # one for student LessonPaymentCharge.count.should eql 0 TeacherDistribution.count.should eql 0 end it "affiliate gets their cut" do Timecop.travel(2016, 05, 15) user.affiliate_referral = affiliate_partner user.save! teacher_user.affiliate_referral = affiliate_partner2 teacher_user.save! user.lesson_purchases.count.should eql 0 lesson = monthly_lesson(user, teacher_user, {finish:true, accept:true}) user.reload user.lesson_purchases.count.should eql 1 lesson_package_purchase = user.lesson_purchases.first teacher_distribution = lesson_package_purchase.teacher_distribution user.sales.count.should eql 1 user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 affiliate_partner.affiliate_distributions.count.should eql 1 affiliate_partner2.affiliate_distributions.count.should eql 1 partner1_distribution = affiliate_partner.affiliate_distributions.first partner2_distribution = affiliate_partner2.affiliate_distributions.first partner1_distribution.sale_line_item.should eql partner2_distribution.sale_line_item partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round partner2_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner2.lesson_rate).round end it "school affiliate gets nothing when teacher school is involved" do Timecop.travel(2016, 05, 15) teacher.school = school teacher.save! user.affiliate_referral = affiliate_partner user.save! lesson = monthly_lesson(user, teacher_user, {finish:true, accept:true }) user.sales.count.should eql 1 user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 1 user.lesson_purchases.count.should eql 1 lesson_package_purchase = user.lesson_purchases.first teacher_distribution = lesson_package_purchase.teacher_distribution affiliate_partner.affiliate_distributions.count.should eql 1 partner1_distribution = affiliate_partner.affiliate_distributions.first partner1_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * affiliate_partner.lesson_rate).round school.affiliate_partner.affiliate_distributions.count.should eql 0 end it "student school affiliates gets cut when student school is involved. so does teacher's" do # in the middle of the month so that we don't get the next month's in-advance purchase put on us Timecop.travel(2016, 05, 15) user.affiliate_referral = school.affiliate_partner user.save! teacher_user.affiliate_referral = affiliate_partner teacher_user.save! lesson = monthly_lesson(user, teacher_user, {finish: true, accept: true}) user.sales.count.should eql 1 user.sales[0].sale_line_items[0].affiliate_distributions.count.should eql 2 user.lesson_purchases.count.should eql 1 lesson_package_purchase = user.lesson_purchases.first teacher_distribution = lesson_package_purchase.teacher_distribution affiliate_partner.affiliate_distributions.count.should eql 1 partner1_distribution = affiliate_partner.affiliate_distributions.count.should eql 1 school.affiliate_partner.affiliate_distributions.count.should eql 1 school_partner_distribution = school.affiliate_partner.affiliate_distributions.first school_partner_distribution.affiliate_referral_fee_in_cents.should eql (teacher_distribution.jamkazam_margin_in_cents * school.affiliate_partner.lesson_rate).round end end