require 'spec_helper'

describe 'building prices' do
  describe 'simply' do
    before do
      Stripe.price :lite do |price|
        price.name = 'Acme as a service LITE'
        price.unit_amount = 699
        price.recurring = {
          interval: 'month',
          interval_count: 3,
          usage_type: 'metered',
          aggregate_usage: 'sum'
        }
        price.metadata = {:number_of_awesome_things => 5}
        price.statement_descriptor = 'Acme Lite'
        price.active = true
        price.nickname = 'lite'
        price.billing_scheme = 'per_unit'
        price.tiers_mode = 'graduated'
      end
    end

    after { Stripe::Prices.send(:remove_const, :LITE) }

    it 'is accessible via id' do
      _(Stripe::Prices::LITE).wont_be_nil
    end

    it 'is accessible via collection' do
      _(Stripe::Prices.all).must_include Stripe::Prices::LITE
    end

    it 'is accessible via hash lookup (symbol/string agnostic)' do
      _(Stripe::Prices[:lite]).must_equal Stripe::Prices::LITE
      _(Stripe::Prices['lite']).must_equal Stripe::Prices::LITE
    end

    it 'sets the lookup key' do
      _(Stripe::Prices::LITE.lookup_key).must_equal 'lite'
    end

    it 'accepts a billing interval of a day' do
      Stripe.price :daily do |price|
        price.name = 'Acme as a service daily'
        price.unit_amount = 100
        price.recurring = {
          interval: 'day'
        }
      end

      _(Stripe::Prices::DAILY).wont_be_nil
    end

    it 'denies a billing interval of a day and excessive intervals' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service daily'
          price.unit_amount = 100
          price.recurring = {
            interval: 'day',
            interval_count: 366
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'accepts a billing interval of a week' do
      Stripe.price :weekly do |price|
        price.name = 'Acme as a service weekly'
        price.unit_amount = 100
        price.recurring = {
          interval: 'week'
        }
      end

      _(Stripe::Prices::WEEKLY).wont_be_nil
    end

    it 'denies a billing interval of a week and excessive intervals' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service weekly'
          price.unit_amount = 100
          price.recurring = {
            interval: 'week',
            interval_count: 53
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'accepts a billing interval of a month' do
      Stripe.price :monthly do |price|
        price.name = 'Acme as a service monthly'
        price.unit_amount = 400
        price.recurring = {
          interval: 'month'
        }
      end

      _(Stripe::Prices::MONTHLY).wont_be_nil
    end

    it 'denies a billing interval of a month and excessive intervals' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service monthly'
          price.unit_amount = 400
          price.recurring = {
            interval: 'month',
            interval_count: 13
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'accepts a billing interval of a year' do
      Stripe.price :yearly do |price|
        price.name = 'Acme as a service yearly'
        price.unit_amount = 4800
        price.recurring = {
          interval: 'year'
        }
      end

      _(Stripe::Prices::YEARLY).wont_be_nil
    end

    it 'denies a billing interval of a year and excessive intervals' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service yearly'
          price.unit_amount = 4800
          price.recurring = {
            interval: 'year',
            interval_count: 2
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies arbitrary billing intervals' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service BROKEN'
          price.unit_amount = 999
          price.recurring = {
            interval: 'anything'
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'accepts empty recurring options' do
      Stripe.price :singular do |price|
        price.name = 'Acme as a service one time'
        price.unit_amount = 888
      end

      _(Stripe::Prices::SINGULAR).wont_be_nil
    end

    it 'accepts a statement descriptor' do
      Stripe.price :described do |price|
        price.name = 'Acme as a service'
        price.unit_amount = 999
        price.recurring = {
          interval: 'month'
        }
        price.statement_descriptor = 'ACME Monthly'
      end

      _(Stripe::Prices::DESCRIBED).wont_be_nil
    end

    it 'denies statement descriptors that are too long' do
      _(lambda {
        Stripe.price :described do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month'
          }
          price.statement_descriptor = 'ACME as a Service Monthly'
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies invalid values for active' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month'
          }
          price.active = 'whatever'
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies invalid values for usage_type' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month',
            usage_type: 'whatever'
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies invalid values for aggregate_usage' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month',
            aggregate_usage: 'whatever'
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies aggregate_usage if usage type is licensed' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month',
            usage_type: 'licensed',
            aggregate_usage: 'sum'
          }
        end
      }).must_raise Stripe::InvalidConfigurationError
    end


    it 'denies invalid values for billing_scheme' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month'
          }
          price.billing_scheme = 'whatever'
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    it 'denies invalid values for tiers_mode' do
      _(lambda {
        Stripe.price :broken do |price|
          price.name = 'Acme as a service'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month'
          }
          price.tiers_mode = 'whatever'
        end
      }).must_raise Stripe::InvalidConfigurationError
    end

    describe 'name and product id validation' do
      it 'should be valid when using just the product id' do
        Stripe.price :prodded do |price|
          price.product_id = 'acme'
          price.unit_amount = 999
          price.recurring = {
            interval: 'month'
          }
        end
        _(Stripe::Prices::PRODDED).wont_be_nil
      end

      it 'should be invalid when using both name and product id' do
        _(lambda {
          Stripe.price :broken do |price|
            price.name = 'Acme as a service'
            price.product_id = 'acme'
            price.unit_amount = 999
            price.recurring = {
              interval: 'month'
            }
          end
        }).must_raise Stripe::InvalidConfigurationError
      end
    end

    describe 'uploading' do
      include FixtureLoader

      describe 'when none exists on stripe.com' do
        before do
          Stripe::Price.stubs(:list).returns(Stripe::Price.construct_from(data: []))

          stub_request(:get, "https://api.stripe.com/v1/prices").
            with(headers: { 'Authorization'=>'Bearer XYZ',}).
            to_return(status: 200, body: load_request_fixture('stripe_prices.json'))
        end

        it 'creates the price online' do
          Stripe::Price.expects(:create).with(
            :lookup_key => 'gold',
            :nickname => 'gold',
            :currency => 'usd',
            :product_data => {
              :name => 'Solid Gold',
              :statement_descriptor => nil
            },
            :unit_amount => 699,
            :recurring => {
              :interval => 'month'
            }
          )
          Stripe::Prices::GOLD.put!
        end

        it 'creates a price with an alternative currency' do
          Stripe::Price.expects(:create).with(
            :lookup_key => 'alternative_currency',
            :nickname => 'alternative_currency',
            :currency => 'cad',
            :product_data => {
              :name => 'Alternative Currency',
              :statement_descriptor => nil
            },
            :unit_amount => 699,
            :recurring => {
              :interval => 'month'
            }
          )
          Stripe::Prices::ALTERNATIVE_CURRENCY.put!
        end

        it 'creates a metered price' do
          Stripe::Price.expects(:create).with(
            :lookup_key => 'metered',
            :nickname => 'metered',
            :currency => 'usd',
            :product_data => {
              :name => 'Metered',
              :statement_descriptor => nil,
            },
            :unit_amount => 699,
            :recurring => {
              :interval => 'month',
              :usage_type => 'metered',
              :aggregate_usage => 'max',
            },
            :billing_scheme => 'per_unit'
          )
          Stripe::Prices::METERED.put!
        end

        it 'creates a tiered price' do
          Stripe::Price.expects(:create).with(
            :lookup_key => 'tiered',
            :nickname => 'tiered',
            :currency => 'usd',
            :product_data => {
              :name => 'Tiered',
              :statement_descriptor => nil,
            },
            :recurring => {
              :interval => 'month',
              :interval_count => 2,
              :usage_type => 'metered',
              :aggregate_usage => 'max'
            },
            :billing_scheme => 'tiered',
            :tiers => [
              {
                :unit_amount => 1500,
                :up_to => 10
              },
              {
                :unit_amount => 1000,
                :up_to => 'inf'
              }
            ],
            :tiers_mode => 'graduated'
          )
          Stripe::Prices::TIERED.put!
        end

        describe 'when passed invalid arguments for tiered pricing' do
          it 'raises a Stripe::InvalidConfigurationError when billing tiers are invalid' do
            lambda {
              Stripe.price "Bad Tiers".to_sym do |price|
                price.name = 'Acme as a service BAD TIERS'
                price.constant_name = 'BAD_TIERS'
                price.recurring = {
                  interval: 'month',
                  interval_count: 1,
                  usage_type: 'metered',
                  aggregate_usage: 'sum'
                }
                price.tiers_mode = 'graduated'
                price.billing_scheme = 'per_unit'
                price.tiers = [
                  {
                    unit_amount: 1500,
                    up_to: 10
                  },
                  {
                    unit_amount: 1000,
                  }
                ]
              end
            }.must_raise Stripe::InvalidConfigurationError
          end

          it 'raises a Stripe::InvalidConfigurationError when billing tiers is not an array' do
            lambda {
              Stripe.price "Bad Tiers".to_sym do |price|
                price.name = 'Acme as a service BAD TIERS'
                price.constant_name = 'BAD_TIERS'
                price.recurring = {
                  interval: 'month',
                  interval_count: 1,
                  usage_type: 'metered',
                  aggregate_usage: 'sum'
                }
                price.tiers_mode = 'graduated'
                price.billing_scheme = 'per_unit'
                price.tiers = {
                  unit_amount: 1500,
                  up_to: 10
                }
              end
            }.must_raise Stripe::InvalidConfigurationError
          end
        end

        describe 'when using a product id' do
          before do
            Stripe::Prices::GOLD.product_id = 'prod_XXXXXXXXXXXXXX'
            Stripe::Prices::GOLD.name = nil
          end
          after do
            Stripe::Prices::GOLD.product_id = nil
            Stripe::Prices::GOLD.name = 'Solid Gold'
          end

          it 'creates the price online with the product id' do
            Stripe::Price.expects(:create).with(
              :lookup_key => 'gold',
              :nickname => 'gold',
              :currency => 'usd',
              :product => 'prod_XXXXXXXXXXXXXX',
              :unit_amount => 699,
              :recurring => {
                :interval => 'month'
              }
            )
            Stripe::Prices::GOLD.put!
          end
        end
      end

      describe 'when it is already present on stripe.com' do
        before do
          Stripe::Prices::GOLD.product_id = nil
          Stripe::Price.stubs(:list).returns(Stripe::Price.construct_from(
            data: [{
              :lookup_key => 'gold',
              :product => 'prod_XXXXXXXXXXXXXX'
          }]))
        end
        after do
          Stripe::Prices::GOLD.product_id = nil
        end


        it 'is a no-op on put!' do
          Stripe::Price.expects(:create).never
          Stripe::Prices::GOLD.put!
        end

        it 'transfers lookup key on reset!' do
          Stripe::Price.expects(:create).with(
            :lookup_key => 'gold',
            :transfer_lookup_key => true,
            :nickname => 'gold',
            :currency => 'usd',
            :product => 'prod_XXXXXXXXXXXXXX',
            :unit_amount => 699,
            :recurring => {
              :interval => 'month'
            }
          )

          Stripe::Prices::GOLD.reset!
        end
      end
    end
  end

  describe 'with missing mandatory values' do
    it 'raises an exception after configuring it' do
      _(-> { Stripe.price(:bad) {} }).must_raise Stripe::InvalidConfigurationError
    end
  end

  describe 'with custom constant name' do
    before do
      Stripe.price "Lite price".to_sym do |price|
        price.name = 'Acme as a service LITE'
        price.constant_name = 'LITE_PRICE'
        price.unit_amount = 699
        price.recurring = {
          interval: 'month',
          interval_count: 3,
          usage_type: 'metered',
          aggregate_usage: 'sum'
        }
        price.metadata = {:number_of_awesome_things => 5}
        price.statement_descriptor = 'Acme Lite'
        price.active = true
        price.nickname = 'lite'
        price.billing_scheme = 'per_unit'
        price.tiers_mode = 'graduated'
      end
    end

    after { Stripe::Prices.send(:remove_const, :LITE_PRICE) }

    it 'is accessible via upcased constant_name' do
      _(Stripe::Prices::LITE_PRICE).wont_be_nil
    end

    it 'is accessible via collection' do
      _(Stripe::Prices.all).must_include Stripe::Prices::LITE_PRICE
    end

    it 'is accessible via hash lookup (symbol/string agnostic)' do
      _(Stripe::Prices[:lite_price]).must_equal Stripe::Prices::LITE_PRICE
      _(Stripe::Prices['lite_price']).must_equal Stripe::Prices::LITE_PRICE
    end

    describe 'constant name validation' do
      it 'should be invalid when providing a constant name that can not be used for Ruby constant' do
        _(lambda {
          Stripe.price "Lite price".to_sym do |price|
            price.name = 'Acme as a service LITE'
            price.constant_name = 'LITE PRICE'
            price.unit_amount = 999
            price.recurring = {
              interval: 'month'
            }
          end
        }).must_raise Stripe::InvalidConfigurationError
      end
    end

    describe 'uploading' do
      include FixtureLoader

      describe 'when none exists on stripe.com' do
        before do
          Stripe::Price.stubs(:list).returns(Stripe::Price.construct_from(data: []))

          stub_request(:get, "https://api.stripe.com/v1/prices").
            with(headers: { 'Authorization'=>'Bearer XYZ',}).
            to_return(status: 200, body: load_request_fixture('stripe_prices.json'))
        end

        it 'creates the price online' do
          Stripe::Price.expects(:create).with(
            :lookup_key => "Solid Gold",
            :nickname => "Solid Gold",
            :currency => 'usd',
            :product_data => {
              :name => 'Solid Gold',
              :statement_descriptor => nil
            },
            :unit_amount => 699,
            :recurring => {
              :interval => 'month'
            }
          )
          Stripe::Prices::SOLID_GOLD.put!
        end
      end
    end
  end
end