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 validate_uniqueness_of(:name) }
  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_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 "has a member with a membership" do
    membership = Factory(:membership, :account => subject)
    should have_member(membership.user)
  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 "manifests braintree processor_declined errors as errors on number and doesn't save" do
    FakeBraintree.failures["4111111111111112"] = { "message" => "Do Not Honor", "code" => "2000", "status" => "processor_declined" }
    account = Factory.build(:account, 
                            :cardholder_name => "Ralph Robot", 
                            :billing_email => "ralph@example.com", 
                            :card_number => "4111111111111112", 
                            :expiration_month => 5, 
                            :expiration_year => 2012,
                            :plan => Factory(:paid_plan))
    account.save.should_not be
    FakeBraintree.customers.should be_empty
    account.persisted?.should_not be
    account.errors[:card_number].any? { |e| e =~ /denied/ }.should be
  end

  it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
    FakeBraintree.failures["4111111111111112"] = { "message" => "Gateway Rejected: cvv", "code" => "N", "status" => "gateway_rejected" }
    account = Factory.build(:account, 
                            :cardholder_name => "Ralph Robot", 
                            :billing_email => "ralph@example.com", 
                            :card_number => "4111111111111112", 
                            :expiration_month => 5, 
                            :expiration_year => 2012,
                            :verification_code => 200,
                            :plan => Factory(:paid_plan))
    account.save.should_not be
    FakeBraintree.customers.should be_empty
    account.persisted?.should_not be
    account.errors[:verification_code].any? { |e| e =~ /did not match/ }.should be
   end

  it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
    FakeBraintree.failures["4111111111111111"] = { "message" => "Credit card number is invalid.", "errors" => { "customer" => { "errors" => [], "credit-card" => { "errors" => [{ "message" => "Credit card number is invalid.", "code" => 81715, "attribute" => :number }] }}}}
    account = Factory.build(:account, 
                            :cardholder_name => "Ralph Robot", 
                            :billing_email => "ralph@example.com", 
                            :card_number => "4111111111111111", 
                            :expiration_month => 5, 
                            :expiration_year => 2012,
                            :verification_code => 123,
                            :plan => Factory(:paid_plan))
    account.save.should_not be
    FakeBraintree.customers.should be_empty
    account.persisted?.should_not be
    account.errors[:card_number].any? { |e| e =~ /is invalid/ }.should be
   end
end

describe Account, "with a paid plan" do
  subject do
    Factory(:account, 
            :cardholder_name => "Ralph Robot", 
            :billing_email => "ralph@example.com", 
            :card_number => "4111111111111111", 
            :verification_code => "123",
            :expiration_month => 5, 
            :expiration_year => 2012,
            :plan => Factory(:paid_plan))
  end

  it "has a customer_token" do
    subject.customer_token.should_not be_nil
  end

  it "has a subscription_token" do
    subject.subscription_token.should_not be_nil
  end

  it "has a customer" do
    subject.customer.should_not be_nil
  end

  it "has a credit card" do
    subject.credit_card.should_not be_nil
  end

  it "has a subscription" do
    subject.subscription.should_not be_nil
  end

  it "has a next_billing_date" do
    subject.next_billing_date.should_not be_nil
  end

  it "has an active subscription status" do
    subject.subscription_status.should == Braintree::Subscription::Status::Active
  end

  it "is not past due" do
    subject.past_due?.should_not be
  end

  it "creates a braintree customer, credit card, and subscription" do
    FakeBraintree.customers[subject.customer_token].should_not be_nil
    FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
    FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
  end

  it "changes the subscription when the plan is changed" do
    new_plan = Factory(:paid_plan, :name => "New Plan")
    subject.save_braintree!(:plan_id => new_plan.id)
    FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
  end

  it "updates the customer and credit card information when changed" do
    subject.save_braintree!(:billing_email => "jrobot@example.com",
                            :cardholder_name => "Jim Robot", 
                            :card_number => "4111111111111115",
                            :verification_code => "123",
                            :expiration_month => 5, 
                            :expiration_year => 2013)
    subject.customer.email.should == "jrobot@example.com"
    subject.credit_card.cardholder_name.should == "Jim Robot"
  end

  it "deletes the customer when deleted" do
    subject.destroy
    FakeBraintree.customers[subject.customer_token].should be_nil
  end
end

describe Account, "with a free plan" do
  subject do
    Factory(:account, :plan => Factory(:plan))
  end

  it "has a customer_token" do
    subject.customer_token.should_not be_nil
  end

  it "has a customer" do
    subject.customer.should_not be_nil
  end

  it "doesn't have a credit_card" do
    subject.credit_card.should be_nil
  end

  it "doesn't have a subscription_token" do
    subject.subscription_token.should be_nil
  end

  it "doesn't have a subscription" do
    subject.subscription.should be_nil
  end

  it "creates a braintree customer" do
    FakeBraintree.customers[subject.customer_token].should_not be_nil
  end

  it "doesn't create a credit card, and subscription" do
    FakeBraintree.customers[subject.customer_token]["credit_cards"].should be_nil
    FakeBraintree.subscriptions[subject.subscription_token].should be_nil
  end

  it "creates a credit card, and subscription when the plan is changed to a paid plan and the billing info is supplied" do
    new_plan = Factory(:paid_plan, :name => "New Plan")
    subject.save_braintree!(:plan_id => new_plan.id,
                            :cardholder_name => "Ralph Robot", 
                            :billing_email => "ralph@example.com", 
                            :card_number => "4111111111111111", 
                            :verification_code => "123",
                            :expiration_month => 5, 
                            :expiration_year => 2012)

    FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
    FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
    FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
    subject.credit_card.should_not be_nil
    subject.subscription.should_not be_nil
  end

  it "doesn't create a credit card, and subscription when the plan is changed to a different free plan" do
    new_plan = Factory(:plan, :name => "New Plan")
    subject.save_braintree!(:plan_id => new_plan.id)

    subject.credit_card.should be_nil
    subject.subscription.should be_nil
  end
end

describe Account, "with a plan and limits, and other plans" do
  subject { Factory(:account) }

  before do
    Factory(:limit, :name => "users", :value => 1, :plan => subject.plan)
    Factory(:limit, :name => "projects", :value => 1, :plan => subject.plan)
    Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => subject.plan)
    @can_switch = Factory(:plan)
    Factory(:limit, :name => "users", :value => 1, :plan => @can_switch)
    Factory(:limit, :name => "projects", :value => 1, :plan => @can_switch)
    Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean, :plan => @can_switch)
    @cannot_switch = Factory(:plan)
    Factory(:limit, :name => "users", :value => 0, :plan => @cannot_switch)
    Factory(:limit, :name => "projects", :value => 0, :plan => @cannot_switch)
    Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => @cannot_switch)

    Factory(:membership, :account => subject)
    Factory(:project, :account => subject)
  end

  it "indicates whether the account can switch to another plan" do
    subject.can_change_plan_to?(@can_switch).should be
    subject.can_change_plan_to?(@cannot_switch).should_not be
  end
end

describe Account, "with a paid subscription" do
  subject do
    Factory(:account, 
            :cardholder_name => "Ralph Robot", 
            :billing_email => "ralph@example.com", 
            :card_number => "4111111111111111", 
            :verification_code => "123",
            :expiration_month => 5, 
            :expiration_year => 2012,
            :plan => Factory(:paid_plan))
  end

  it "gets marked as past due and updates its next_billing_date when subscriptions are updated and it has been rejected by the gateway" do
    subscription = FakeBraintree.subscriptions[subject.subscription_token]
    subscription["status"] = Braintree::Subscription::Status::PastDue
    subscription["next_billing_date"] = 2.months.from_now

    Timecop.travel(subject.next_billing_date + 1.day) do
      Account.update_subscriptions!
      subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
      subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
      subject.past_due?.should be
    end
  end

  it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
    subscription = FakeBraintree.subscriptions[subject.subscription_token]
    subscription["status"] = Braintree::Subscription::Status::Active
    subscription["next_billing_date"] = 2.months.from_now
    FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
                                  :subscription_id => subject.subscription_token }
    subscription["transactions"] = [FakeBraintree.generated_transaction]

    Timecop.travel(subject.next_billing_date + 1.day) do
      Account.update_subscriptions!
      subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
      subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
    end
  end

  it "receives a receipt email at it's billing email with transaction details" do
    subscription = FakeBraintree.subscriptions[subject.subscription_token]
    subscription["status"] = Braintree::Subscription::Status::Active
    subscription["next_billing_date"] = 2.months.from_now
    FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
                                  :subscription_id => subject.subscription_token }
    subscription["transactions"] = [FakeBraintree.generated_transaction]

    Timecop.travel(subject.next_billing_date + 1.day) do
      ActionMailer::Base.deliveries.clear

      Account.update_subscriptions!

      ActionMailer::Base.deliveries.any? do |email|
        email.to == [subject.billing_email] &&
        email.subject =~ /receipt/i
      end.should be
    end
  end

  it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
    subscription = FakeBraintree.subscriptions[subject.subscription_token]
    subscription["status"] = Braintree::Subscription::Status::PastDue
    subscription["next_billing_date"] = 2.months.from_now
    FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
                                  :subscription_id => subject.subscription_token }
    subscription["transactions"] = [FakeBraintree.generated_transaction]

    Timecop.travel(subject.next_billing_date + 1.day) do
      ActionMailer::Base.deliveries.clear

      Account.update_subscriptions!

      ActionMailer::Base.deliveries.any? do |email|
        email.to == [subject.billing_email] &&
        email.subject =~ /problem/i
      end.should be
    end
  end
end