Files
openfoodnetwork/spec/models/spree/tax_rate_spec.rb
2025-06-17 15:38:25 +02:00

406 lines
14 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Spree::TaxRate do
describe ".match" do
let!(:zone) { create(:zone_with_member) }
let!(:order) { create(:order, distributor: hub, bill_address: create(:address)) }
let!(:tax_rate) {
create(:tax_rate, included_in_price: true,
calculator: Calculator::FlatRate.new(preferred_amount: 0.1), zone:)
}
describe "when the order's hub charges sales tax" do
let(:hub) { create(:distributor_enterprise, charges_sales_tax: true) }
it "selects all tax rates" do
expect(described_class.match(order)).to eq([tax_rate])
end
end
describe "when the order's hub does not charge sales tax" do
let(:hub) { create(:distributor_enterprise, charges_sales_tax: false) }
it "selects no tax rates" do
expect(described_class.match(order)).to be_empty
end
end
describe "when the order does not have a hub" do
let!(:order) { create(:order, distributor: nil, bill_address: create(:address)) }
it "selects all tax rates" do
expect(described_class.match(order)).to eq([tax_rate])
end
end
end
context "original Spree::TaxRate specs" do
context "match" do
let(:order) { create(:order) }
let(:country) { create(:country) }
let(:tax_category) { create(:tax_category) }
let(:calculator) { Calculator::FlatRate.new }
it "returns an empty array when tax_zone is nil" do
allow(order).to receive(:tax_zone) { nil }
expect(described_class.match(order)).to eq []
end
context "when no rate zones match the tax zone" do
before do
described_class.create(amount: 1, zone: create(:zone))
end
context "when there is no default tax zone" do
let(:zone) { create( :zone, name: "Country Zone", default_tax: false, member: country) }
it "returns an empty array" do
allow(order).to receive(:tax_zone).and_return(zone)
expect(described_class.match(order)).to eq []
end
it "returns the rate that matches the rate zone" do
rate = described_class.create(
amount: 1,
zone:,
tax_category:,
calculator:
)
allow(order).to receive(:tax_zone).and_return(zone)
expect(described_class.match(order)).to eq [rate]
end
it "returns all rates that match the rate zone" do
rate1 = described_class.create(
amount: 1,
zone:,
tax_category:,
calculator:
)
rate2 = described_class.create(
amount: 2,
zone:,
tax_category:,
calculator: Calculator::FlatRate.new
)
allow(order).to receive(:tax_zone).and_return(zone)
expect(described_class.match(order)).to eq [rate1, rate2]
end
context "when the tax_zone is contained within a rate zone" do
let(:sub_zone) { create(:zone, name: "State Zone", member: create(:state, country:)) }
it "returns the rate zone" do
allow(order).to receive(:tax_zone).and_return(sub_zone)
rate = described_class.create(
amount: 1,
zone:,
tax_category:,
calculator:
)
expect(described_class.match(order)).to eq [rate]
end
end
end
context "when there is a default tax zone" do
let(:zone) { create(:zone, name: "Country Zone", default_tax: true, member: country) }
let(:included_in_price) { false }
let!(:rate) do
described_class.create(amount: 1,
zone:,
tax_category:,
calculator:,
included_in_price:)
end
subject { described_class.match(order) }
context "when the order has the same tax zone" do
before do
allow(order).to receive(:tax_zone) { zone }
allow(order).to receive(:billing_address) { tax_address }
end
let(:tax_address) { build_stubbed(:address) }
context "when the tax is not a VAT" do
it { is_expected.to eq [rate] }
end
context "when the tax is a VAT" do
let(:included_in_price) { true }
it { is_expected.to eq [rate] }
end
end
context "when the order has a different tax zone" do
let(:other_zone) { create(:zone, name: "Other Zone", default_tax: false) }
before do
allow(order).to receive(:tax_zone) { other_zone }
allow(order).to receive(:billing_address) { tax_address }
end
context "when the order has a tax_address" do
let(:tax_address) { build_stubbed(:address) }
context "when the tax is a VAT" do
let(:included_in_price) { true }
# The rate should match in this instance because:
# 1) It's the default rate (and as such, a negative adjustment should apply)
it { is_expected.to eq [rate] }
end
context "when the tax is not VAT" do
it "returns no tax rate" do
expect(subject).to be_empty
end
end
end
context "when the order does not have a tax_address" do
let(:tax_address) { nil }
context "when the tax is a VAT" do
let(:included_in_price) { true }
# The rate should match in this instance because:
# 1) The order has no tax address by this stage
# 2) With no tax address, it has no tax zone
# 3) Therefore, we assume the default tax zone
# 4) This default zone has a default tax rate.
it { is_expected.to eq [rate] }
end
context "when the tax is not a VAT" do
it { is_expected.to be_empty }
end
end
end
end
end
end
describe ".default" do
let(:tax_category) { create(:tax_category) }
let(:country) { create(:country) }
let(:calculator) { Calculator::FlatRate.new }
context "when there is no default tax_category" do
before { tax_category.is_default = false }
it "returns 0" do
expect(described_class.default).to eq 0
end
end
context "when there is a default tax_category" do
before { tax_category.update_column :is_default, true }
context "when the default category has tax rates in the default tax zone" do
before(:each) do
allow(DefaultCountry).to receive(:id) { country.id }
zone = create(:zone, name: "Country Zone", default_tax: true, member: country)
rate = described_class.create(
amount: 1,
zone:,
tax_category:,
calculator:
)
end
it "returns the correct tax_rate" do
expect(described_class.default.to_f).to eq 1.0
end
end
context "when the default category has no tax rates in the default tax zone" do
it "returns 0" do
expect(described_class.default).to eq 0
end
end
end
end
describe ".adjust" do
let!(:country) { create(:country, name: "Default Country") }
let!(:state) { create(:state, name: "Default State", country:) }
let!(:zone) { create(:zone_with_member, default_tax: true, member: country ) }
let!(:category) { create(:tax_category, name: "Taxable Foo") }
let!(:category2) { create(:tax_category, name: "Non Taxable") }
let!(:rate1) {
create(:tax_rate, amount: 0.10, zone:, tax_category: category)
}
let!(:rate2) {
create(:tax_rate, amount: 0.05, zone:, tax_category: category)
}
let(:hub) { create(:distributor_enterprise, charges_sales_tax: true) }
let(:address) { create(:address, state: country.states.first, country:) }
let!(:order) {
create(:order_with_line_items, line_items_count: 2, distributor: hub,
ship_address: address)
}
let!(:taxable) { order.line_items.first.variant }
let!(:nontaxable) { order.line_items.last.variant }
before do
taxable.update(tax_category: category)
nontaxable.update(tax_category: category2)
order.line_items.delete_all
end
context "not taxable line item " do
let!(:line_item) { order.contents.add(nontaxable, 1) }
it "does not create a tax adjustment" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.tax.charge.count).to eq 0
end
it "does not create a refund" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.credit.count).to eq 0
end
end
context "taxable line item" do
let!(:line_item) { order.contents.add(taxable, 1) }
before do
rate1.update_column(:included_in_price, true)
rate2.update_column(:included_in_price, true)
end
it "applies adjustments for the matching tax rates to the order" do
line_item = build_stubbed(:line_item, tax_category: category)
rate3 = create(:tax_rate, amount: 0.05, zone:)
allow(described_class).to receive(:match) { [rate1, rate3] }
expect(rate1).to receive(:adjust)
expect(rate3).not_to receive(:adjust)
described_class.adjust(order, [line_item])
end
context "when price includes tax" do
context "when zone is contained by default tax zone" do
it "creates two adjustments, one for each tax rate" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.count).to eq 2
end
it "does not create a tax refund" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.credit.count).to eq 0
end
end
context "when order's zone is neither the default zone, or included " \
"in the default zone, but matches the rate's zone" do
before do
# Create a new default zone, so the order's zone won't match this new one
create(:zone_with_member, default_tax: true)
end
it "creates an adjustment" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.charge.count).to eq 2
end
it "does not create a tax refund for each tax rate" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.credit.count).to eq 0
end
end
context "when order's zone does not match default zone, is not included in the " \
"default zone, AND does not match the rate's zone" do
let!(:other_country) { create(:country, name: "Other Country") }
let!(:other_state) { create(:state, name: "Other State", country: other_country) }
let!(:other_address) { create(:address, state: other_state, country: other_country) }
let!(:other_zone) {
create(:zone_with_member, name: "Other Zone", default_tax: false,
member: other_country)
}
before do
order.update(ship_address: other_address)
order.all_adjustments.delete_all
end
it "does not create positive adjustments" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.charge.count).to eq 0
end
it "creates a tax refund for each tax rate" do
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.credit.count).to eq 2
end
end
end
context "when price does not include tax" do
before do
allow(order).to receive(:tax_zone) { zone }
[rate1, rate2].each do |rate|
rate.update(included_in_price: false, zone:)
end
described_class.adjust(order, order.line_items)
end
it "does not delete adjustments for complete order when taxrate is deleted" do
rate1.destroy!
rate2.destroy!
expect(line_item.adjustments.count).to eq 2
end
it "creates adjustments" do
expect(line_item.adjustments.count).to eq 2
end
it "does not create a tax refund" do
expect(line_item.adjustments.credit.count).to eq 0
end
it "removes adjustments when tax_zone is removed" do
expect(line_item.adjustments.count).to eq 2
allow(order).to receive(:tax_zone) { nil }
described_class.adjust(order, order.line_items)
expect(line_item.adjustments.count).to eq 0
end
end
end
context "with shipments" do
let(:shipment) { build_stubbed(:shipment, order:) }
it "applies adjustments for two tax rates to the order" do
rate3 = create(:tax_rate, amount: 0.05, zone:)
allow(shipment).to receive(:tax_category) { category }
allow(described_class).to receive(:match) { [rate1, rate3] }
expect(rate1).to receive(:adjust)
expect(rate3).not_to receive(:adjust)
described_class.adjust(order, [shipment])
end
end
end
end
end