Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions core/app/models/spree/price.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ class Price < Spree::Base

acts_as_paranoid

MAXIMUM_AMOUNT = BigDecimal('99_999_999.99')
AMOUNT_LIMIT = {
max: BigDecimal('99_999_999.99'),
min: 0
}.freeze

belongs_to :variant, class_name: 'Spree::Variant', inverse_of: :prices, touch: true

before_validation :ensure_currency

validates :amount, allow_nil: true, numericality: {
greater_than_or_equal_to: 0,
less_than_or_equal_to: MAXIMUM_AMOUNT
greater_than_or_equal_to: AMOUNT_LIMIT[:min],
less_than_or_equal_to: AMOUNT_LIMIT[:max]
}

extend DisplayMoney
Expand Down
218 changes: 159 additions & 59 deletions core/spec/models/spree/price_spec.rb
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
require 'spec_helper'

describe Spree::Price, :type => :model do
describe '#amount=' do
let(:price) { Spree::Price.new }
let(:amount) { '3,0A0' }

before do
price.amount = amount
end
it_behaves_like 'a Paranoid model'

it 'is expected to equal to localized number' do
expect(price.amount).to eq(Spree::LocalizedNumber.parse(amount))
describe 'Modules' do
describe 'Inclusions' do
it { expect(Spree::Price.include?(Spree::VatPriceCalculation)).to be true }
it { expect(Spree::Price.ancestors).to include Spree::DisplayMoney }
end
end

describe '#price' do
let(:price) { Spree::Price.new }
let(:amount) { 3000.00 }

context 'when amount is changed' do
before do
price.amount = amount
describe 'Constants' do
describe 'AMOUNT_LIMIT[:max]' do
it 'return 99999999.99' do
expect(Spree::Price::AMOUNT_LIMIT[:max]).to eq BigDecimal('99_999_999.99')
end
end

it 'is expected to equal to price' do
expect(price.amount).to eq(price.price)
describe 'AMOUNT_LIMIT[:min]' do
it 'return 0' do
expect(Spree::Price::AMOUNT_LIMIT[:min]).to eq 0
end
end
end

describe 'association' do
it do
is_expected.to belong_to(:variant).class_name('Spree::Variant').
inverse_of(:prices).touch(true)
end
end

describe 'validations' do
let(:variant) { stub_model Spree::Variant }
subject { Spree::Price.new variant: variant, amount: amount }
Expand All @@ -38,91 +41,188 @@
it { is_expected.to be_valid }
end

context 'when the amount is less than 0' do
context 'when the amount is less than #{Spree::Price::AMOUNT_LIMIT[:min]}' do
let(:amount) { -1 }

it 'has 1 error_on' do
expect(subject.error_on(:amount).size).to eq(1)
end
it 'populates errors' do
subject.valid?
expect(subject.errors.messages[:amount].first).to eq 'must be greater than or equal to 0'
expect(subject.errors.messages[:amount].first).
to eq "must be greater than or equal to #{Spree::Price::AMOUNT_LIMIT[:min]}"
end
end

context 'when the amount is greater than maximum amount' do
let(:amount) { Spree::Price::MAXIMUM_AMOUNT + 1 }
let(:amount) { Spree::Price::AMOUNT_LIMIT[:max] + 1 }

it 'has 1 error_on' do
expect(subject.error_on(:amount).size).to eq(1)
end
it 'populates errors' do
subject.valid?
expect(subject.errors.messages[:amount].first).to eq "must be less than or equal to #{Spree::Price::MAXIMUM_AMOUNT}"
expect(subject.errors.messages[:amount].first).
to eq "must be less than or equal to #{Spree::Price::AMOUNT_LIMIT[:max]}"
end
end

context 'when the amount is between 0 and the maximum amount' do
let(:amount) { Spree::Price::MAXIMUM_AMOUNT }
let(:amount) { Spree::Price::AMOUNT_LIMIT[:max] }
it { is_expected.to be_valid }
end
end

describe '#price_including_vat_for(zone)' do
let(:variant) { stub_model Spree::Variant }
let(:default_zone) { Spree::Zone.new }
let(:zone) { Spree::Zone.new }
let(:amount) { 10 }
let(:tax_category) { Spree::TaxCategory.new }
let(:price) { Spree::Price.new variant: variant, amount: amount }
let(:price_options) { { tax_zone: zone } }
describe 'Callbacks' do
it { is_expected.to callback(:ensure_currency).before(:validation) }
end

subject(:price_with_vat) { price.price_including_vat_for(price_options) }
describe 'whitelisted ransackable attributes' do
it 'returns amount attribute' do
expect(Spree::Price.whitelisted_ransackable_attributes).to eq(['amount'])
end
end

# Instance methods

describe 'Instance Methods' do
describe '#amount=' do
let(:price) { Spree::Price.new }
let(:amount) { '3,0A0' }

context 'when called with a non-default zone' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(default_zone)
allow(price).to receive(:apply_foreign_vat?).and_return(true)
allow(price).to receive(:included_tax_amount).with(tax_zone: default_zone, tax_category: tax_category) { 0.19 }
allow(price).to receive(:included_tax_amount).with(tax_zone: zone, tax_category: tax_category) { 0.25 }
price.amount = amount
end

it "returns the correct price including another VAT to two digits" do
expect(price_with_vat).to eq(10.50)
it 'is expected to equal to localized number' do
expect(price.amount).to eq(Spree::LocalizedNumber.parse(amount))
end
end

context 'when called from the default zone' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(zone)
describe '#price_including_vat_for(zone)' do
let(:variant) { stub_model Spree::Variant }
let(:default_zone) { Spree::Zone.new }
let(:zone) { Spree::Zone.new }
let(:amount) { 10 }
let(:tax_category) { Spree::TaxCategory.new }
let(:price) { Spree::Price.new variant: variant, amount: amount }
let(:price_options) { { tax_zone: zone } }

subject(:price_with_vat) { price.price_including_vat_for(price_options) }

context 'when called with a non-default zone' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(default_zone)
allow(price).to receive(:apply_foreign_vat?).and_return(true)
allow(price).to receive(:included_tax_amount).
with(tax_zone: default_zone, tax_category: tax_category) { 0.19 }
allow(price).to receive(:included_tax_amount).with(tax_zone: zone, tax_category: tax_category) { 0.25 }
end

it "returns the correct price including another VAT to two digits" do
expect(price_with_vat).to eq(10.50)
end
end

context 'when called from the default zone' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(zone)
end

it "returns the correct price" do
expect(price).to receive(:price).and_call_original
expect(price_with_vat).to eq(10.00)
end
end

context 'when no default zone is set' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(nil)
end

it "returns the correct price" do
expect(price).to receive(:price).and_call_original
expect(price.price_including_vat_for(tax_zone: zone)).to eq(10.00)
end
end
end

it "returns the correct price" do
expect(price).to receive(:price).and_call_original
expect(price_with_vat).to eq(10.00)
describe '#display_price_including_vat_for(zone)' do
subject { Spree::Price.new amount: 10 }
it 'calls #price_including_vat_for' do
expect(subject).to receive(:price_including_vat_for)
subject.display_price_including_vat_for(nil)
end
end

context 'when no default zone is set' do
before do
allow(variant).to receive(:tax_category).and_return(tax_category)
expect(price).to receive(:default_zone).at_least(:once).and_return(nil)
describe '#money' do
context 'when price has amount' do
let(:price) { build(:price) }
it 'reutrns amount with currency' do
expect(price.money.to_s).to eq('$19.99')
end
end

it "returns the correct price" do
expect(price).to receive(:price).and_call_original
expect(price.price_including_vat_for(tax_zone: zone)).to eq(10.00)
context 'when price does not has amount' do
let(:price) { build(:price) }
before { price.amount = nil }
it 'reutrns amount with currency' do
expect(price.money.to_s).to eq('$0.00')
end
end
end
end

describe '#display_price_including_vat_for(zone)' do
subject { Spree::Price.new amount: 10 }
it 'calls #price_including_vat_for' do
expect(subject).to receive(:price_including_vat_for)
subject.display_price_including_vat_for(nil)
describe '#price' do
let(:price) { build(:price) }
it 'returns amount' do
expect(price.price).to eq(price.amount)
end
end

describe '#price=' do
let(:price) { build(:price) }
context 'when price contains currency symbol' do
it 'removes currency symbol' do
expect(Spree::LocalizedNumber).to receive(:parse).with(price.price)
price.price = 19.99
end
end
end

describe '#variant' do
let(:price) { create(:price) }
subject(:variant) { price.variant }
before do
variant.deleted_at = Time.current
variant.save
end
it 'returns variants without adding the deleted_at where clause when unscoped' do
expect(price.reload.variant).to eq variant
end
end

describe '#ensure_currency' do
let(:price) { create(:price) }

context 'when currency is not already set' do
it 'sets currency to Spree::Config[:currency]' do
expect(price.currency).to eq Spree::Config[:currency]
end
end

context 'when currency is already set and configuration changes' do
subject(:currency) { 'RS' }
before do
Spree::Config[:currency] = currency
price.save
end
it 'does not sets currency to Spree::Config[:currency]' do
expect(price.currency).not_to eq currency
end
end
end
end
end
13 changes: 13 additions & 0 deletions core/spec/models/spree/shared/paranoia_examples.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
shared_examples_for 'a Paranoid model' do
it { is_expected.to have_db_column(:deleted_at) }

it { is_expected.to have_db_index(:deleted_at) }

it 'adds a deleted_at where clause' do
expect(described_class.all.to_sql).to include("deleted_at")
end

it 'skips adding the deleted_at where clause when unscoped' do
expect(described_class.unscoped.to_sql).not_to include("deleted_at")
end
end
1 change: 1 addition & 0 deletions core/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
require 'ffaker'

Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
Dir["./spec/models/spree/shared/**/*.rb"].sort.each { |f| require f }

if ENV["CHECK_TRANSLATIONS"]
require "spree/testing_support/i18n"
Expand Down