require 'spec_helper'

describe Account do
  subject { Factory(:account) }

  it { should have_many(:memberships) }
  it { should have_many(:users).through(:memberships) }
  it { should have_many(:projects) }
  it { should belong_to(:plan) }
  it { should belong_to(:coupon) }

  it { should validate_uniqueness_of(:keyword) }
  it { should validate_presence_of(:name) }
  it { should validate_presence_of(:keyword) }
  it { should validate_presence_of(:plan_id) }

  it { should allow_value("hello").for(:keyword) }
  it { should allow_value("0123").for(:keyword) }
  it { should allow_value("hello_world").for(:keyword) }
  it { should allow_value("hello-world").for(:keyword) }
  it { should_not allow_value("HELLO").for(:keyword) }
  it { should_not allow_value("hello world").for(:keyword) }

  it { should_not allow_mass_assignment_of(:id) }
  it { should_not allow_mass_assignment_of(:updated_at) }
  it { should_not allow_mass_assignment_of(:created_at) }
  it { should allow_mass_assignment_of(:keyword) }

  [nil, "", "a b", "a.b", "a%b"].each do |value|
    it { should_not allow_value(value).for(:keyword).with_message(/letters/i) }
  end

  ["foo", "f00", "37signals"].each do |value|
    it { should allow_value(value).for(:keyword) }
  end

  it "should give its keyword for to_param" do
    subject.to_param.should == subject.keyword
  end

  it "finds admin users" do
    admins = [Factory(:user), Factory(:user)]
    non_admin = Factory(:user)
    non_member = Factory(:user)
    admins.each do |admin|
      Factory(:membership, :user => admin, :account => subject, :admin => true)
    end
    Factory(:membership, :user => non_admin, :account => subject, :admin => false)

    result = subject.admins

    result.to_a.should =~ admins
  end

  it "finds non admin users" do
    non_admins = [Factory(:user), Factory(:user)]
    admin = Factory(:user)
    non_member = Factory(:user)
    non_admins.each do |non_admin|
      Factory(:membership, :user => non_admin, :account => subject, :admin => false)
    end
    Factory(:membership, :user => admin, :account => subject, :admin => true)

    result = subject.non_admins

    result.to_a.should =~ non_admins
  end

  it "finds emails for admin users" do
    admins = [Factory(:user), Factory(:user)]
    non_admin = Factory(:user)
    non_member = Factory(:user)
    admins.each do |admin|
      Factory(:membership, :user => admin, :account => subject, :admin => true)
    end
    Factory(:membership, :user => non_admin, :account => subject, :admin => false)

    subject.admin_emails.should == admins.map(&:email)
  end

  it "has a member with a membership" do
    membership = Factory(:membership, :account => subject)
    should have_member(membership.user)
  end

  it "has a count of users" do
    membership = Factory(:membership, :account => subject)
    subject.users_count.should == 1
  end

  it "has a count of active projects" do
    Factory(:project, :account => subject, :archived => false)
    Factory(:project, :account => subject, :archived => true)
    subject.projects_count.should == 1
  end

  it "doesn't have a member without a membership" do
    membership = Factory(:membership, :account => subject)
    should_not have_member(Factory(:user))
  end

  it "finds memberships by name" do
    expected = 'expected result'
    memberships = stub('memberships', :by_name => expected)
    account = Factory.stub(:account)
    account.stubs(:memberships => memberships)

    result = account.memberships_by_name

    result.should == expected
  end

  it "is expired with a trial plan after 30 days" do
    trial = Factory(:plan, :trial => true)
    Factory(:account, :created_at => 30.days.ago, :plan => trial).should be_expired
  end

  it "isn't expired with a trial plan before 30 days" do
    trial = Factory(:plan, :trial => true)
    Factory(:account, :created_at => 29.days.ago, :plan => trial).should_not be_expired
  end

  it "isn't expired with a non-trial plan after 30 days" do
    forever = Factory(:plan, :trial => false)
    Factory(:account, :created_at => 30.days.ago, :plan => forever).should_not be_expired
  end

  it "isn't expired without an expiration date after 30 days" do
    trial = Factory(:plan, :trial => true)
    account = Factory(:account, :created_at => 30.days.ago, :plan => trial)
    account.trial_expires_at = nil
    account.save!
    account.should_not be_expired
  end

  context "with expiring accounts" do
    before do
      trial   = Factory(:plan, :trial => true)
      forever = Factory(:plan, :trial => false)

      created_23_days  = Factory(:account, :plan => trial, :created_at => 23.days.ago)
      expires_7_days   = Factory(:account, :plan => trial, :created_at => 1.day.ago)
      @expiring        = [created_23_days, expires_7_days]
      forever          = Factory(:account, :plan => forever, :created_at => 23.days.ago)
      new_trial        = Factory(:account, :plan => trial, :created_at => 22.days.ago)
      already_notified = Factory(:account, :plan                   => trial,
                                           :created_at             => 24.days.ago,
                                           :notified_of_expiration => true)

      expires_7_days.trial_expires_at = 7.days.from_now
      expires_7_days.save!

      @mail = stub('mail', :deliver => true)
      BillingMailer.stubs(:expiring_trial => @mail)
      Airbrake.stubs(:notify => true)
    end

    it "sends notifications for expiring accounts" do
      Account.deliver_expiring_trial_notifications

      @expiring.each do |account|
        BillingMailer.should have_received(:expiring_trial).with(account)
      end

      @mail.should have_received(:deliver).twice

      @expiring.each { |account| account.reload.should be_notified_of_expiration }
    end

    it "notifies Airbrake if expiring account notifications fail" do
      @mail.stubs(:deliver).raises(RuntimeError).then.returns(true)
      Account.deliver_expiring_trial_notifications
      Airbrake.should have_received(:notify).once()
    end

    it "delivers the rest of the emails even if one fails" do
      @mail.stubs(:deliver).raises(RuntimeError)
      Account.deliver_expiring_trial_notifications
      @mail.should have_received(:deliver).twice()
    end
  end

  context "with completed trials" do
    before do
      trial   = Factory(:plan, :trial => true)
      forever = Factory(:plan, :trial => false)

      unexpired_trial          = Factory(:account, :plan => trial, :created_at => 29.days.ago)
      unnotified_expired_trial = Factory(:account, :plan => trial, :created_at => 31.days.ago)
      expiring_now             = Factory(:account, :plan => trial, :created_at => 1.day.ago)
      notified_expired_trial   = Factory(:account, :plan                         => trial,
                                                   :created_at                   => 31.days.ago,
                                                   :notified_of_completed_trial  => true)
      forever                  = Factory(:account, :plan => forever, :created_at => 31.days.ago)

      expiring_now.trial_expires_at = 1.day.ago
      expiring_now.save!

      @requires_notification = [unnotified_expired_trial, expiring_now]

      @mail = stub('mail', :deliver => true)
      BillingMailer.stubs(:completed_trial => @mail)
      Airbrake.stubs(:notify => true)
    end

    it "sends notifications for completed trials" do
      Account.deliver_completed_trial_notifications

      @requires_notification.each do |account|
        BillingMailer.should have_received(:completed_trial).with(account)
      end

      @mail.should have_received(:deliver).twice

      @requires_notification.each { |account| account.reload.should be_notified_of_completed_trial }
    end

    it "notifies Airbrake if completed trial notifications fail" do
      @mail.stubs(:deliver).raises(RuntimeError).then.returns(true)
      Account.deliver_completed_trial_notifications
      Airbrake.should have_received(:notify).once()
    end

    it "delivers the rest of the emails even if one fails" do
      @mail.stubs(:deliver).raises(RuntimeError)
      Account.deliver_completed_trial_notifications
      @mail.should have_received(:deliver).twice()
    end
  end

  context "with unactivated accounts" do
    before do
      @unactivated = [Factory(:account, :created_at => 7.days.ago),
                      Factory(:account, :created_at => 8.days.ago)]
      fresh = Factory(:account, :created_at => 6.days.ago)
      activated = Factory(:account, :created_at => 9.days.ago, :activated => true)
      already_notified = Factory(:account, :created_at        => 9.days.ago,
                                           :asked_to_activate => true)

      @mail = stub('mail', :deliver => true)
      BillingMailer.stubs(:new_unactivated => @mail)
      Airbrake.stubs(:notify => true)
    end

    it "sends notifications after 7 days" do
      Account.deliver_new_unactivated_notifications

      @unactivated.each do |account|
        BillingMailer.should have_received(:new_unactivated).with(account)
      end

      @mail.should have_received(:deliver).twice

      @unactivated.each { |account| account.reload.should be_asked_to_activate }
    end

    it "notifies Airbrake if completed trial notifications fail" do
      @mail.stubs(:deliver).raises(RuntimeError).then.returns(true)
      Account.deliver_new_unactivated_notifications
      Airbrake.should have_received(:notify).once()
    end

    it "delivers the rest of the emails even if one fails" do
      @mail.stubs(:deliver).raises(RuntimeError)
      Account.deliver_new_unactivated_notifications
      @mail.should have_received(:deliver).twice()
    end
  end

  it "uses the unactivated_notice_on setting for new_unactivated" do
    begin
      unactivated = [Factory(:account, :created_at => 7.days.ago),
        Factory(:account, :created_at => 8.days.ago)]
      fresh = Factory(:account, :created_at => 6.days.ago)
      Account.new_unactivated.order(:created_at).should == unactivated.reverse
      Saucy::Configuration.unactivated_notice_on = 6
      Account.new_unactivated.order(:created_at).should == (unactivated.reverse + [fresh]).flatten
    ensure
      Saucy::Configuration.unactivated_notice_on = 7
    end
  end

  it "uses the expiring_notice_on for notifying expiring accounts" do
    begin
      trial   = Factory(:plan, :trial => true)
      forever = Factory(:plan, :trial => false)

      expiring   = Factory(:account, :plan => trial, :created_at => 1.day.ago)
      expiring.trial_expires_at = 7.days.from_now
      expiring.save!

      Account.trial_expiring.should == [expiring]

      Saucy::Configuration.expiring_notice_on = 8
      expiring.trial_expires_at = 8.days.from_now
      expiring.save!

      Account.trial_expiring.should == [expiring]
    ensure
      Saucy::Configuration.expiring_notice_on = 7
    end
  end

  context "with a trial length of 14 days" do
    before(:each) do
      Saucy::Configuration.trial_length = 14
    end

    after(:each) do
      Saucy::Configuration.trial_length = 30
    end

    it "is expired with a trial plan after 14 days" do
      trial = Factory(:plan, :trial => true)
      Factory(:account, :created_at => 14.days.ago, :plan => trial).should be_expired
    end

    it "isn't expired with a trial plan before 14 days" do
      trial = Factory(:plan, :trial => true)
      Factory(:account, :created_at => 13.days.ago, :plan => trial).should_not be_expired
    end
  end
  
  it "searches records for keyword and name" do
    name = Factory :account, :name => 'match'
    keyword = Factory :account, :keyword => 'match'
    nope = Factory :account
    
    Account.search('match').should == [name, keyword]
  end
  
  it "should return nothing for nil search" do
    Account.search(nil).should == []
  end
end

describe Account, "with coupon" do
  let(:free_months) { 3 }
  let(:coupon) { Factory(:coupon, :free_months => free_months) }

  subject { Factory(:account, :coupon => coupon) }

  it "sets the trial expires at based on coupon free months" do
    subject.reload.trial_expires_at.to_s.should == (free_months * 30).days.from_now.to_s
  end
end