diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index c9ca6719b5..497205b751 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -161,7 +161,6 @@ Metrics/ModuleLength:
- 'app/models/spree/order/checkout.rb'
- 'app/models/spree/payment/processing.rb'
- 'lib/open_food_network/column_preference_defaults.rb'
- - 'spec/models/spree/product_spec.rb'
- 'spec/models/spree/shipping_method_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb'
- 'spec/services/permissions/order_spec.rb'
diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb
index 7967fd37c2..175d2f58f0 100644
--- a/spec/models/spree/product_spec.rb
+++ b/spec/models/spree/product_spec.rb
@@ -3,171 +3,206 @@
require 'spec_helper'
require 'spree/core/product_duplicator'
-module Spree
- RSpec.describe Product do
- context 'product instance' do
- let(:product) { create(:product) }
+RSpec.describe Spree::Product do
+ context 'product instance' do
+ let(:product) { create(:product) }
- context '#duplicate' do
- it 'duplicates product' do
- clone = product.duplicate
+ context '#duplicate' do
+ it 'duplicates product' do
+ clone = product.duplicate
- expect(clone).to be_persisted
- expect(clone.name).to eq "COPY OF #{product.name}"
- expect(clone.sku).to eq ""
- expect(clone.image).to eq product.image
- end
-
- it 'fails to duplicate invalid product' do
- # cloned product will be invalid
- product.update_columns(name: "l" * 254)
-
- expect{ product.duplicate }.to raise_error(ActiveRecord::ActiveRecordError)
- end
+ expect(clone).to be_persisted
+ expect(clone.name).to eq "COPY OF #{product.name}"
+ expect(clone.sku).to eq ""
+ expect(clone.image).to eq product.image
end
- context "product has variants" do
- before do
- product.reload.variants << create(:variant, product:)
- end
+ it 'fails to duplicate invalid product' do
+ # cloned product will be invalid
+ product.update_columns(name: "l" * 254)
- context "#destroy" do
- it "should set deleted_at value" do
- product.destroy
- expect(product.deleted_at).not_to be_nil
- expect(product.variants.all? { |v| !v.deleted_at.nil? }).to be_truthy
- end
- end
+ expect{ product.duplicate }.to raise_error(ActiveRecord::ActiveRecordError)
+ end
+ end
+
+ context "product has variants" do
+ before do
+ product.reload.variants << create(:variant, product:)
end
- describe 'Variants sorting' do
- it 'sorts variants by id' do
- expect(product.variants.to_sql).to match(/ORDER BY spree_variants.id ASC/)
+ context "#destroy" do
+ it "should set deleted_at value" do
+ product.destroy
+ expect(product.deleted_at).not_to be_nil
+ expect(product.variants.all? { |v| !v.deleted_at.nil? }).to be_truthy
end
end
end
- context "properties" do
- let(:product) { create(:product) }
-
- it "should properly assign properties" do
- expect {
- product.set_property('the_prop', 'value1')
- product.save
- product.reload
- }.to change { product.properties.length }.by(1)
- expect(product.property('the_prop')).to eq 'value1'
-
- product.set_property('the_prop', 'value2')
- expect(product.property('the_prop')).to eq 'value2'
+ describe 'Variants sorting' do
+ it 'sorts variants by id' do
+ expect(product.variants.to_sql).to match(/ORDER BY spree_variants.id ASC/)
end
+ end
+ end
- it "should not create duplicate properties when set_property is called" do
+ context "properties" do
+ let(:product) { create(:product) }
+
+ it "should properly assign properties" do
+ expect {
+ product.set_property('the_prop', 'value1')
+ product.save
+ product.reload
+ }.to change { product.properties.length }.by(1)
+ expect(product.property('the_prop')).to eq 'value1'
+
+ product.set_property('the_prop', 'value2')
+ expect(product.property('the_prop')).to eq 'value2'
+ end
+
+ it "should not create duplicate properties when set_property is called" do
+ product.set_property('the_prop', 'value2')
+ product.save
+
+ expect {
product.set_property('the_prop', 'value2')
product.save
-
- expect {
- product.set_property('the_prop', 'value2')
- product.save
- product.reload
- }.not_to change { product.properties.length }
- end
-
- # Regression test for #2455
- it "should not overwrite properties' presentation names" do
- Spree::Property.where(name: 'foo').first_or_create!(presentation: "Foo's Presentation Name")
- product.set_property('foo', 'value1')
- product.set_property('bar', 'value2')
- expect(Spree::Property.where(name: 'foo').first.presentation)
- .to eq "Foo's Presentation Name"
- expect(Spree::Property.where(name: 'bar').first.presentation).to eq "bar"
- end
+ product.reload
+ }.not_to change { product.properties.length }
end
- describe "associations" do
- it { is_expected.to have_one(:image) }
+ # Regression test for #2455
+ it "should not overwrite properties' presentation names" do
+ Spree::Property.where(name: 'foo').first_or_create!(presentation: "Foo's Presentation Name")
+ product.set_property('foo', 'value1')
+ product.set_property('bar', 'value2')
+ expect(Spree::Property.where(name: 'foo').first.presentation)
+ .to eq "Foo's Presentation Name"
+ expect(Spree::Property.where(name: 'bar').first.presentation).to eq "bar"
+ end
+ end
- it { is_expected.to have_many(:product_properties) }
- it { is_expected.to have_many(:properties).through(:product_properties) }
- it { is_expected.to have_many(:variants) }
- it { is_expected.to have_many(:prices).through(:variants) }
- it { is_expected.to have_many(:stock_items).through(:variants) }
- it { is_expected.to have_many(:variant_images).through(:variants) }
+ describe "associations" do
+ it { is_expected.to have_one(:image) }
+
+ it { is_expected.to have_many(:product_properties) }
+ it { is_expected.to have_many(:properties).through(:product_properties) }
+ it { is_expected.to have_many(:variants) }
+ it { is_expected.to have_many(:prices).through(:variants) }
+ it { is_expected.to have_many(:stock_items).through(:variants) }
+ it { is_expected.to have_many(:variant_images).through(:variants) }
+ end
+
+ describe "validations and defaults" do
+ it "is valid when built from factory" do
+ expect(build(:product)).to be_valid
end
- describe "validations and defaults" do
- it "is valid when built from factory" do
- expect(build(:product)).to be_valid
+ it { is_expected.to validate_presence_of :name }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ it { is_expected.to validate_length_of(:sku).is_at_most(255) }
+
+ context "when the product has variants" do
+ let(:product) do
+ product = create(:simple_product)
+ create(:variant, product:)
+ product.reload
end
- it { is_expected.to validate_presence_of :name }
- it { is_expected.to validate_length_of(:name).is_at_most(255) }
- it { is_expected.to validate_length_of(:sku).is_at_most(255) }
+ it { is_expected.to validate_numericality_of(:price).is_greater_than_or_equal_to(0) }
- context "when the product has variants" do
- let(:product) do
- product = create(:simple_product)
- create(:variant, product:)
- product.reload
+ context "saving a new product" do
+ let!(:product){ described_class.new }
+ let!(:shipping_category){ create(:shipping_category) }
+ let!(:taxon){ create(:taxon) }
+ let(:supplier){ create(:enterprise) }
+
+ it "copies properties to the first standard variant" do
+ product.primary_taxon_id = taxon.id
+ product.name = "Product1"
+ product.variant_unit = "weight"
+ product.variant_unit_scale = 1000
+ product.unit_value = 1
+ product.unit_description = "some product"
+ product.price = 4.27
+ product.shipping_category_id = shipping_category.id
+ product.supplier_id = supplier.id
+ product.save(context: :create_and_create_standard_variant)
+
+ expect(product.variants.reload.length).to eq 1
+ standard_variant = product.variants.reload.first
+
+ expect(standard_variant).to be_valid
+ expect(standard_variant.variant_unit).to eq("weight")
+ expect(standard_variant.variant_unit_scale).to eq(1000)
+ expect(standard_variant.unit_value).to eq(1)
+ expect(standard_variant.unit_description).to eq("some product")
+ expect(standard_variant.price).to eq 4.27
+ expect(standard_variant.shipping_category).to eq shipping_category
+ expect(standard_variant.primary_taxon).to eq taxon
+ expect(standard_variant.supplier).to eq supplier
end
- it { is_expected.to validate_numericality_of(:price).is_greater_than_or_equal_to(0) }
+ context "with variant attributes" do
+ it {
+ is_expected.to validate_presence_of(:variant_unit)
+ .on(:create_and_create_standard_variant)
+ }
+ it {
+ is_expected.to validate_presence_of(:supplier_id)
+ .on(:create_and_create_standard_variant)
+ }
+ it {
+ is_expected.to validate_presence_of(:primary_taxon_id)
+ .on(:create_and_create_standard_variant)
+ }
- context "saving a new product" do
- let!(:product){ Spree::Product.new }
- let!(:shipping_category){ create(:shipping_category) }
- let!(:taxon){ create(:taxon) }
- let(:supplier){ create(:enterprise) }
+ describe "unit_value" do
+ subject { build(:simple_product, variant_unit: "items") }
- it "copies properties to the first standard variant" do
- product.primary_taxon_id = taxon.id
- product.name = "Product1"
- product.variant_unit = "weight"
- product.variant_unit_scale = 1000
- product.unit_value = 1
- product.unit_description = "some product"
- product.price = 4.27
- product.shipping_category_id = shipping_category.id
- product.supplier_id = supplier.id
- product.save(context: :create_and_create_standard_variant)
-
- expect(product.variants.reload.length).to eq 1
- standard_variant = product.variants.reload.first
-
- expect(standard_variant).to be_valid
- expect(standard_variant.variant_unit).to eq("weight")
- expect(standard_variant.variant_unit_scale).to eq(1000)
- expect(standard_variant.unit_value).to eq(1)
- expect(standard_variant.unit_description).to eq("some product")
- expect(standard_variant.price).to eq 4.27
- expect(standard_variant.shipping_category).to eq shipping_category
- expect(standard_variant.primary_taxon).to eq taxon
- expect(standard_variant.supplier).to eq supplier
- end
-
- context "with variant attributes" do
it {
- is_expected.to validate_presence_of(:variant_unit)
+ is_expected.to validate_numericality_of(:unit_value).is_greater_than(0)
.on(:create_and_create_standard_variant)
}
it {
- is_expected.to validate_presence_of(:supplier_id)
- .on(:create_and_create_standard_variant)
- }
- it {
- is_expected.to validate_presence_of(:primary_taxon_id)
+ is_expected.not_to validate_presence_of(:unit_value)
.on(:create_and_create_standard_variant)
}
- describe "unit_value" do
- subject { build(:simple_product, variant_unit: "items") }
+ ["weight", "volume"].each do |variant_unit|
+ context "when variant_unit is #{variant_unit}" do
+ subject { build(:simple_product, variant_unit:) }
+ it {
+ is_expected.to validate_presence_of(:unit_value)
+ .on(:create_and_create_standard_variant)
+ }
+ end
+ end
+
+ describe "unit_description" do
it {
- is_expected.to validate_numericality_of(:unit_value).is_greater_than(0)
+ is_expected.not_to validate_presence_of(:unit_description)
.on(:create_and_create_standard_variant)
}
+
+ context "when variant_unit is et and unit_value is nil" do
+ subject {
+ build(:simple_product, variant_unit: "items", unit_value: nil,
+ unit_description: "box")
+ }
+
+ it {
+ is_expected.to validate_presence_of(:unit_description)
+ .on(:create_and_create_standard_variant)
+ }
+ end
+ end
+
+ describe "variant_unit_scale" do
it {
- is_expected.not_to validate_presence_of(:unit_value)
+ is_expected.not_to validate_presence_of(:variant_unit_scale)
.on(:create_and_create_standard_variant)
}
@@ -176,563 +211,528 @@ module Spree
subject { build(:simple_product, variant_unit:) }
it {
- is_expected.to validate_presence_of(:unit_value)
- .on(:create_and_create_standard_variant)
- }
- end
- end
-
- describe "unit_description" do
- it {
- is_expected.not_to validate_presence_of(:unit_description)
- .on(:create_and_create_standard_variant)
- }
-
- context "when variant_unit is et and unit_value is nil" do
- subject {
- build(:simple_product, variant_unit: "items", unit_value: nil,
- unit_description: "box")
- }
-
- it {
- is_expected.to validate_presence_of(:unit_description)
- .on(:create_and_create_standard_variant)
- }
- end
- end
-
- describe "variant_unit_scale" do
- it {
- is_expected.not_to validate_presence_of(:variant_unit_scale)
- .on(:create_and_create_standard_variant)
- }
-
- ["weight", "volume"].each do |variant_unit|
- context "when variant_unit is #{variant_unit}" do
- subject { build(:simple_product, variant_unit:) }
-
- it {
- is_expected.to validate_presence_of(:variant_unit_scale)
- .on(:create_and_create_standard_variant)
- }
- end
- end
- end
-
- describe "variant_unit_name" do
- subject { build(:simple_product, variant_unit: "volume") }
-
- it {
- is_expected.not_to validate_presence_of(:variant_unit_name)
- .on(:create_and_create_standard_variant)
- }
-
- context "when variant_unit is items" do
- subject { build(:simple_product, variant_unit: "items") }
-
- it {
- is_expected.to validate_presence_of(:variant_unit_name)
+ is_expected.to validate_presence_of(:variant_unit_scale)
.on(:create_and_create_standard_variant)
}
end
end
end
- end
- end
- end
- describe "#validate_image" do
- subject(:product) { create(:product_with_image) }
+ describe "variant_unit_name" do
+ subject { build(:simple_product, variant_unit: "volume") }
- context "when the image is invalid" do
- before { allow(product.image).to receive(:valid?).and_return(false) }
+ it {
+ is_expected.not_to validate_presence_of(:variant_unit_name)
+ .on(:create_and_create_standard_variant)
+ }
- context "and has been changed" do
- before { expect(product.image).to receive(:changed?).and_return(true) }
+ context "when variant_unit is items" do
+ subject { build(:simple_product, variant_unit: "items") }
- it "adds an error message to the base object" do
- expect(product).not_to be_valid
- expect(product.errors[:base]).to include('Image attachment is not a valid image.')
+ it {
+ is_expected.to validate_presence_of(:variant_unit_name)
+ .on(:create_and_create_standard_variant)
+ }
+ end
end
end
-
- it "ignores if unchanged" do
- expect(product).to be_valid
- end
- end
-
- context "when image is valid" do
- it { is_expected.to be_valid }
- end
-
- context "when image is blank" do
- subject { create(:product) }
-
- it { is_expected.to be_valid }
end
end
end
- describe "callbacks" do
- let(:product) { create(:simple_product) }
+ describe "#validate_image" do
+ subject(:product) { create(:product_with_image) }
- describe "destroy product" do
- let(:product) { create(:simple_product, supplier_id: distributor.id) }
- let(:distributor) { create(:distributor_enterprise) }
- let!(:oc) {
- create(:simple_order_cycle, distributors: [distributor],
- variants: [product.variants.first])
- }
+ context "when the image is invalid" do
+ before { allow(product.image).to receive(:valid?).and_return(false) }
- it "removes variants from order cycles" do
- expect { product.destroy }.to change { ExchangeVariant.count }
+ context "and has been changed" do
+ before { expect(product.image).to receive(:changed?).and_return(true) }
+
+ it "adds an error message to the base object" do
+ expect(product).not_to be_valid
+ expect(product.errors[:base]).to include('Image attachment is not a valid image.')
+ end
+ end
+
+ it "ignores if unchanged" do
+ expect(product).to be_valid
end
end
- describe "after updating primary taxon" do
- let(:product) { create(:simple_product, supplier_id: supplier.id) }
- let(:supplier) { create(:supplier_enterprise) }
- let(:new_taxon) { create(:taxon) }
-
- it "touches the supplier" do
- expect { product.update(primary_taxon_id: new_taxon.id) }
- .to change { supplier.reload.updated_at }
- end
-
- context "when product has no variant" do
- it "doesn't blow up" do
- product.variants = []
- product.save!
-
- expect { product.update(primary_taxon_id: new_taxon.id) }.not_to raise_error
- end
- end
+ context "when image is valid" do
+ it { is_expected.to be_valid }
end
- describe "after touching the product" do
- let(:product) { create(:simple_product, supplier_id: supplier.id) }
- let(:supplier) { create(:supplier_enterprise) }
+ context "when image is blank" do
+ subject { create(:product) }
- it "touches the supplier" do
- expect { product.touch }
- .to change { supplier.reload.updated_at }
- end
+ it { is_expected.to be_valid }
+ end
+ end
+ end
- context "when the first variant is missing supplier" do
- it "doesn't blow up" do
- product.variants.first.update_attribute(:supplier_id, nil)
+ describe "callbacks" do
+ let(:product) { create(:simple_product) }
- expect { product.touch }.not_to raise_error
- end
+ describe "destroy product" do
+ let(:product) { create(:simple_product, supplier_id: distributor.id) }
+ let(:distributor) { create(:distributor_enterprise) }
+ let!(:oc) {
+ create(:simple_order_cycle, distributors: [distributor],
+ variants: [product.variants.first])
+ }
+
+ it "removes variants from order cycles" do
+ expect { product.destroy }.to change { ExchangeVariant.count }
+ end
+ end
+
+ describe "after updating primary taxon" do
+ let(:product) { create(:simple_product, supplier_id: supplier.id) }
+ let(:supplier) { create(:supplier_enterprise) }
+ let(:new_taxon) { create(:taxon) }
+
+ it "touches the supplier" do
+ expect { product.update(primary_taxon_id: new_taxon.id) }
+ .to change { supplier.reload.updated_at }
+ end
+
+ context "when product has no variant" do
+ it "doesn't blow up" do
+ product.variants = []
+ product.save!
+
+ expect { product.update(primary_taxon_id: new_taxon.id) }.not_to raise_error
end
end
end
- describe "scopes" do
- describe ".with_properties" do
- let!(:product_with_wanted_property) { create(:product, properties: [wanted_property]) }
- let!(:product_without_wanted_property_property) {
- create(:product, properties: [unwanted_property])
- }
- let!(:product_ignoring_property) {
- create(:product, inherits_properties: false)
- }
- let(:wanted_property) { create(:property, presentation: 'Certified Organic') }
- let(:unwanted_property) { create(:property, presentation: 'Latest Hype') }
-
- it "returns no products without a property id" do
- expect(Spree::Product.with_properties([])).to eq []
- end
-
- it "returns only products with the wanted property set both on supplier & product itself" do
- expect(
- Spree::Product.with_properties([wanted_property.id, 99_999])
- ).to match_array [product_with_wanted_property]
- end
- end
-
- describe "in_supplier" do
- it "shows products in supplier" do
- s1 = create(:supplier_enterprise)
- p1 = create(:product, supplier_id: s1.id)
- # We create two variants to let us test we don't get duplicated product
- create(:variant, product: p1, supplier: s1)
- create(:variant, product: p1, supplier: s1)
- s2 = create(:supplier_enterprise)
- p2 = create(:product, supplier_id: s2.id)
- create(:variant, product: p2, supplier: s2)
-
- expect(Product.in_supplier(s1)).to eq([p1])
- end
- end
-
- describe "in_distributor" do
- it "shows products in order cycle distribution" do
- s = create(:supplier_enterprise)
- d1 = create(:distributor_enterprise)
- d2 = create(:distributor_enterprise)
- p1 = create(:product)
- p2 = create(:product)
- create(:simple_order_cycle, suppliers: [s], distributors: [d1],
- variants: [p1.variants.first])
- create(:simple_order_cycle, suppliers: [s], distributors: [d2],
- variants: [p2.variants.first])
- expect(Product.in_distributor(d1)).to eq([p1])
- end
-
- it "shows products in order cycle distribution by variant" do
- s = create(:supplier_enterprise)
- d1 = create(:distributor_enterprise)
- d2 = create(:distributor_enterprise)
- p1 = create(:product)
- v1 = create(:variant, product: p1)
- p2 = create(:product)
- v2 = create(:variant, product: p2)
- create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [v1])
- create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [v2])
- expect(Product.in_distributor(d1)).to eq([p1])
- end
-
- it "doesn't show products listed in the incoming exchange only" do
- s = create(:supplier_enterprise)
- c = create(:distributor_enterprise)
- d = create(:distributor_enterprise)
- p = create(:product)
- oc = create(:simple_order_cycle, coordinator: c, suppliers: [s], distributors: [d])
- ex = oc.exchanges.incoming.first
- ex.variants << p.variants.first
-
- expect(Product.in_distributor(d)).to be_empty
- end
- end
-
- describe "in_distributors" do
- let!(:distributor1) { create(:distributor_enterprise) }
- let!(:distributor2) { create(:distributor_enterprise) }
- let!(:product1) { create(:product) }
- let!(:product2) { create(:product) }
- let!(:product3) { create(:product) }
- let!(:product4) { create(:product) }
- let!(:order_cycle1) {
- create(:order_cycle, distributors: [distributor1],
- variants: [product1.variants.first, product2.variants.first])
- }
- let!(:order_cycle2) {
- create(:order_cycle, distributors: [distributor2],
- variants: [product3.variants.first])
- }
-
- it "returns distributed products for a given Enterprise AR relation" do
- distributors = Enterprise.where(id: [distributor1.id, distributor2.id]).to_a
-
- expect(Product.in_distributors(distributors)).to include product1, product2, product3
- expect(Product.in_distributors(distributors)).not_to include product4
- end
-
- it "returns distributed products for a given array of enterprise ids" do
- distributors_ids = [distributor1.id, distributor2.id]
-
- expect(Product.in_distributors(distributors_ids)).to include product1, product2, product3
- expect(Product.in_distributors(distributors_ids)).not_to include product4
- end
- end
-
- describe "in_order_cycle" do
- it "shows products in order cycle distribution" do
- s = create(:supplier_enterprise)
- d1 = create(:distributor_enterprise)
- d2 = create(:distributor_enterprise)
- p1 = create(:product)
- p2 = create(:product)
- oc1 = create(:simple_order_cycle, suppliers: [s], distributors: [d1],
- variants: [p1.variants.first])
- oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
- variants: [p2.variants.first])
- expect(Product.in_order_cycle(oc1)).to eq([p1])
- end
- end
-
- describe "in_an_active_order_cycle" do
- it "shows products in order cycle distribution" do
- s = create(:supplier_enterprise)
- d2 = create(:distributor_enterprise)
- d3 = create(:distributor_enterprise)
- p1 = create(:product)
- p2 = create(:product)
- p3 = create(:product)
- oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
- variants: [p2.variants.first],
- orders_open_at: 8.days.ago, orders_close_at: 1.day.ago)
- oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d3],
- variants: [p3.variants.first],
- orders_close_at: Date.tomorrow)
- expect(Product.in_an_active_order_cycle).to eq([p3])
- end
- end
-
- describe "by_producer" do
- it "orders by producer name" do
- producer_z = create(:enterprise, name: "z_cooperative")
- producer_a = create(:enterprise, name: "a_cooperative")
- producer_g = create(:enterprise, name: "g_cooperative")
-
- product1 = create(:product, supplier_id: producer_z.id)
- product2 = create(:product, supplier_id: producer_a.id)
- product3 = create(:product, supplier_id: producer_g.id)
-
- expect(Product.by_producer).to eq([product2, product3, product1])
- end
- end
-
- describe "managed_by" do
- let!(:e1) { create(:enterprise) }
- let!(:e2) { create(:enterprise) }
- let!(:p1) { create(:product) }
- let!(:p2) { create(:product) }
-
- before(:each) do
- create(:variant, product: p1, supplier: e1)
- create(:variant, product: p1, supplier: e2)
- end
-
- it "shows only products for given user" do
- user = create(:user)
- e1.enterprise_roles.build(user:).save
-
- product = Product.managed_by user
-
- expect(product.count).to eq(1)
- expect(product).to include p1
- end
-
- it "shows all products for admin user" do
- user = create(:admin_user)
-
- product = Product.managed_by user
-
- expect(product.count).to eq(2)
- expect(product).to include p1
- expect(product).to include p2
- end
- end
-
- describe "visible_for" do
- let(:enterprise) { create(:distributor_enterprise) }
- let!(:new_variant) { create(:variant) }
- let!(:hidden_variant) { create(:variant) }
-
- let!(:product) { create(:product) }
- let!(:visible_variant1) { create(:variant, product:) }
- let!(:visible_variant2) { create(:variant, product:) }
-
- let!(:hidden_inventory_item) {
- create(:inventory_item, enterprise:, variant: hidden_variant, visible: false )
- }
- let!(:visible_inventory_item1) {
- create(:inventory_item, enterprise:, variant: visible_variant1, visible: true )
- }
- let!(:visible_inventory_item2) {
- create(:inventory_item, enterprise:, variant: visible_variant2, visible: true )
- }
-
- let!(:products) { Spree::Product.visible_for(enterprise) }
-
- it "lists any products with variants that are listed as visible=true" do
- expect(products.length).to eq(1)
- expect(products).to include product
- expect(products).not_to include new_variant.product, hidden_variant.product
- end
- end
-
- describe "imported_on" do
- let!(:v1) { create(:variant, import_date: 1.day.ago) }
- let!(:v2) { create(:variant, import_date: 2.days.ago) }
- let!(:v3) { create(:variant, import_date: 1.day.ago) }
-
- it "returns products imported on given day" do
- imported_products = Spree::Product.imported_on(1.day.ago.to_date)
- expect(imported_products).to include v1.product, v3.product
- end
- end
- end
-
- describe "#properties_including_inherited" do
- let(:product) { create(:simple_product) }
+ describe "after touching the product" do
+ let(:product) { create(:simple_product, supplier_id: supplier.id) }
let(:supplier) { create(:supplier_enterprise) }
- before do
- product.variants = []
- product.variants << create(:variant, product:, supplier:)
+ it "touches the supplier" do
+ expect { product.touch }
+ .to change { supplier.reload.updated_at }
end
- it "returns product properties as a hash" do
- product.set_property 'Organic Certified', 'NASAA 12345'
- property = product.properties.last
+ context "when the first variant is missing supplier" do
+ it "doesn't blow up" do
+ product.variants.first.update_attribute(:supplier_id, nil)
- expect(product.properties_including_inherited)
- .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 12345' }])
+ expect { product.touch }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe "scopes" do
+ describe ".with_properties" do
+ let!(:product_with_wanted_property) { create(:product, properties: [wanted_property]) }
+ let!(:product_without_wanted_property_property) {
+ create(:product, properties: [unwanted_property])
+ }
+ let!(:product_ignoring_property) {
+ create(:product, inherits_properties: false)
+ }
+ let(:wanted_property) { create(:property, presentation: 'Certified Organic') }
+ let(:unwanted_property) { create(:property, presentation: 'Latest Hype') }
+
+ it "returns no products without a property id" do
+ expect(described_class.with_properties([])).to eq []
end
- it "returns producer properties as a hash" do
+ it "returns only products with the wanted property set both on supplier & product itself" do
+ expect(
+ described_class.with_properties([wanted_property.id, 99_999])
+ ).to match_array [product_with_wanted_property]
+ end
+ end
+
+ describe "in_supplier" do
+ it "shows products in supplier" do
+ s1 = create(:supplier_enterprise)
+ p1 = create(:product, supplier_id: s1.id)
+ # We create two variants to let us test we don't get duplicated product
+ create(:variant, product: p1, supplier: s1)
+ create(:variant, product: p1, supplier: s1)
+ s2 = create(:supplier_enterprise)
+ p2 = create(:product, supplier_id: s2.id)
+ create(:variant, product: p2, supplier: s2)
+
+ expect(described_class.in_supplier(s1)).to eq([p1])
+ end
+ end
+
+ describe "in_distributor" do
+ it "shows products in order cycle distribution" do
+ s = create(:supplier_enterprise)
+ d1 = create(:distributor_enterprise)
+ d2 = create(:distributor_enterprise)
+ p1 = create(:product)
+ p2 = create(:product)
+ create(:simple_order_cycle, suppliers: [s], distributors: [d1],
+ variants: [p1.variants.first])
+ create(:simple_order_cycle, suppliers: [s], distributors: [d2],
+ variants: [p2.variants.first])
+ expect(described_class.in_distributor(d1)).to eq([p1])
+ end
+
+ it "shows products in order cycle distribution by variant" do
+ s = create(:supplier_enterprise)
+ d1 = create(:distributor_enterprise)
+ d2 = create(:distributor_enterprise)
+ p1 = create(:product)
+ v1 = create(:variant, product: p1)
+ p2 = create(:product)
+ v2 = create(:variant, product: p2)
+ create(:simple_order_cycle, suppliers: [s], distributors: [d1], variants: [v1])
+ create(:simple_order_cycle, suppliers: [s], distributors: [d2], variants: [v2])
+ expect(described_class.in_distributor(d1)).to eq([p1])
+ end
+
+ it "doesn't show products listed in the incoming exchange only" do
+ s = create(:supplier_enterprise)
+ c = create(:distributor_enterprise)
+ d = create(:distributor_enterprise)
+ p = create(:product)
+ oc = create(:simple_order_cycle, coordinator: c, suppliers: [s], distributors: [d])
+ ex = oc.exchanges.incoming.first
+ ex.variants << p.variants.first
+
+ expect(described_class.in_distributor(d)).to be_empty
+ end
+ end
+
+ describe "in_distributors" do
+ let!(:distributor1) { create(:distributor_enterprise) }
+ let!(:distributor2) { create(:distributor_enterprise) }
+ let!(:product1) { create(:product) }
+ let!(:product2) { create(:product) }
+ let!(:product3) { create(:product) }
+ let!(:product4) { create(:product) }
+ let!(:order_cycle1) {
+ create(:order_cycle, distributors: [distributor1],
+ variants: [product1.variants.first, product2.variants.first])
+ }
+ let!(:order_cycle2) {
+ create(:order_cycle, distributors: [distributor2],
+ variants: [product3.variants.first])
+ }
+
+ it "returns distributed products for a given Enterprise AR relation" do
+ distributors = Enterprise.where(id: [distributor1.id, distributor2.id]).to_a
+
+ expect(described_class.in_distributors(distributors)).
+ to include product1, product2, product3
+ expect(described_class.in_distributors(distributors)).not_to include product4
+ end
+
+ it "returns distributed products for a given array of enterprise ids" do
+ distributors_ids = [distributor1.id, distributor2.id]
+
+ expect(described_class.in_distributors(distributors_ids)).
+ to include product1, product2, product3
+ expect(described_class.in_distributors(distributors_ids)).not_to include product4
+ end
+ end
+
+ describe "in_order_cycle" do
+ it "shows products in order cycle distribution" do
+ s = create(:supplier_enterprise)
+ d1 = create(:distributor_enterprise)
+ d2 = create(:distributor_enterprise)
+ p1 = create(:product)
+ p2 = create(:product)
+ oc1 = create(:simple_order_cycle, suppliers: [s], distributors: [d1],
+ variants: [p1.variants.first])
+ oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
+ variants: [p2.variants.first])
+ expect(described_class.in_order_cycle(oc1)).to eq([p1])
+ end
+ end
+
+ describe "in_an_active_order_cycle" do
+ it "shows products in order cycle distribution" do
+ s = create(:supplier_enterprise)
+ d2 = create(:distributor_enterprise)
+ d3 = create(:distributor_enterprise)
+ p1 = create(:product)
+ p2 = create(:product)
+ p3 = create(:product)
+ oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d2],
+ variants: [p2.variants.first],
+ orders_open_at: 8.days.ago, orders_close_at: 1.day.ago)
+ oc2 = create(:simple_order_cycle, suppliers: [s], distributors: [d3],
+ variants: [p3.variants.first],
+ orders_close_at: Date.tomorrow)
+ expect(described_class.in_an_active_order_cycle).to eq([p3])
+ end
+ end
+
+ describe "by_producer" do
+ it "orders by producer name" do
+ producer_z = create(:enterprise, name: "z_cooperative")
+ producer_a = create(:enterprise, name: "a_cooperative")
+ producer_g = create(:enterprise, name: "g_cooperative")
+
+ product1 = create(:product, supplier_id: producer_z.id)
+ product2 = create(:product, supplier_id: producer_a.id)
+ product3 = create(:product, supplier_id: producer_g.id)
+
+ expect(described_class.by_producer).to eq([product2, product3, product1])
+ end
+ end
+
+ describe "managed_by" do
+ let!(:e1) { create(:enterprise) }
+ let!(:e2) { create(:enterprise) }
+ let!(:p1) { create(:product) }
+ let!(:p2) { create(:product) }
+
+ before(:each) do
+ create(:variant, product: p1, supplier: e1)
+ create(:variant, product: p1, supplier: e2)
+ end
+
+ it "shows only products for given user" do
+ user = create(:user)
+ e1.enterprise_roles.build(user:).save
+
+ product = described_class.managed_by user
+
+ expect(product.count).to eq(1)
+ expect(product).to include p1
+ end
+
+ it "shows all products for admin user" do
+ user = create(:admin_user)
+
+ product = described_class.managed_by user
+
+ expect(product.count).to eq(2)
+ expect(product).to include p1
+ expect(product).to include p2
+ end
+ end
+
+ describe "visible_for" do
+ let(:enterprise) { create(:distributor_enterprise) }
+ let!(:new_variant) { create(:variant) }
+ let!(:hidden_variant) { create(:variant) }
+
+ let!(:product) { create(:product) }
+ let!(:visible_variant1) { create(:variant, product:) }
+ let!(:visible_variant2) { create(:variant, product:) }
+
+ let!(:hidden_inventory_item) {
+ create(:inventory_item, enterprise:, variant: hidden_variant, visible: false )
+ }
+ let!(:visible_inventory_item1) {
+ create(:inventory_item, enterprise:, variant: visible_variant1, visible: true )
+ }
+ let!(:visible_inventory_item2) {
+ create(:inventory_item, enterprise:, variant: visible_variant2, visible: true )
+ }
+
+ let!(:products) { described_class.visible_for(enterprise) }
+
+ it "lists any products with variants that are listed as visible=true" do
+ expect(products.length).to eq(1)
+ expect(products).to include product
+ expect(products).not_to include new_variant.product, hidden_variant.product
+ end
+ end
+
+ describe "imported_on" do
+ let!(:v1) { create(:variant, import_date: 1.day.ago) }
+ let!(:v2) { create(:variant, import_date: 2.days.ago) }
+ let!(:v3) { create(:variant, import_date: 1.day.ago) }
+
+ it "returns products imported on given day" do
+ imported_products = described_class.imported_on(1.day.ago.to_date)
+ expect(imported_products).to include v1.product, v3.product
+ end
+ end
+ end
+
+ describe "#properties_including_inherited" do
+ let(:product) { create(:simple_product) }
+ let(:supplier) { create(:supplier_enterprise) }
+
+ before do
+ product.variants = []
+ product.variants << create(:variant, product:, supplier:)
+ end
+
+ it "returns product properties as a hash" do
+ product.set_property 'Organic Certified', 'NASAA 12345'
+ property = product.properties.last
+
+ expect(product.properties_including_inherited)
+ .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 12345' }])
+ end
+
+ it "returns producer properties as a hash" do
+ supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
+ property = supplier.properties.last
+
+ expect(product.properties_including_inherited)
+ .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 54321' }])
+ end
+
+ it "overrides producer properties with product properties" do
+ product.set_property 'Organic Certified', 'NASAA 12345'
+ supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
+ property = product.properties.last
+
+ expect(product.properties_including_inherited)
+ .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 12345' }])
+ end
+
+ context "when product has an inherit_properties value set to true" do
+ let(:product) { create(:simple_product, inherits_properties: true) }
+
+ it "inherits producer properties" do
supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
property = supplier.properties.last
expect(product.properties_including_inherited)
.to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 54321' }])
end
+ end
- it "overrides producer properties with product properties" do
- product.set_property 'Organic Certified', 'NASAA 12345'
+ context "when product has an inherit_properties value set to false" do
+ let(:product) { create(:simple_product, inherits_properties: false) }
+
+ it "does not inherit producer properties" do
supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
- property = product.properties.last
- expect(product.properties_including_inherited)
- .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 12345' }])
- end
-
- context "when product has an inherit_properties value set to true" do
- let(:product) { create(:simple_product, inherits_properties: true) }
-
- it "inherits producer properties" do
- supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
- property = supplier.properties.last
-
- expect(product.properties_including_inherited)
- .to eq([{ id: property.id, name: "Organic Certified", value: 'NASAA 54321' }])
- end
- end
-
- context "when product has an inherit_properties value set to false" do
- let(:product) { create(:simple_product, inherits_properties: false) }
-
- it "does not inherit producer properties" do
- supplier.set_producer_property 'Organic Certified', 'NASAA 54321'
-
- expect(product.properties_including_inherited).to eq([])
- end
- end
-
- it "sorts by position" do
- pa = Spree::Property.create! name: 'A', presentation: 'A'
- pb = Spree::Property.create! name: 'B', presentation: 'B'
- pc = Spree::Property.create! name: 'C', presentation: 'C'
-
- product.product_properties.create!({ property_id: pa.id, value: '1', position: 1 })
- product.product_properties.create!({ property_id: pc.id, value: '3', position: 3 })
- supplier.producer_properties.create!({ property_id: pb.id, value: '2', position: 2 })
-
- expect(product.properties_including_inherited).to eq(
- [{ id: pa.id, name: "A", value: '1' },
- { id: pb.id, name: "B", value: '2' },
- { id: pc.id, name: "C", value: '3' }]
- )
+ expect(product.properties_including_inherited).to eq([])
end
end
- describe "membership" do
- it "queries its membership of a particular order cycle distribution" do
- d1 = create(:distributor_enterprise)
- d2 = create(:distributor_enterprise)
- p1 = create(:product)
- p2 = create(:product)
- oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
- oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
+ it "sorts by position" do
+ pa = Spree::Property.create! name: 'A', presentation: 'A'
+ pb = Spree::Property.create! name: 'B', presentation: 'B'
+ pc = Spree::Property.create! name: 'C', presentation: 'C'
- expect(p1).to be_in_distributor d1
- expect(p1).not_to be_in_distributor d2
- end
+ product.product_properties.create!({ property_id: pa.id, value: '1', position: 1 })
+ product.product_properties.create!({ property_id: pc.id, value: '3', position: 3 })
+ supplier.producer_properties.create!({ property_id: pb.id, value: '2', position: 2 })
- it "queries its membership of a particular order cycle" do
- d1 = create(:distributor_enterprise)
- d2 = create(:distributor_enterprise)
- p1 = create(:product)
- p2 = create(:product)
- oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
- oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
-
- expect(p1).to be_in_order_cycle oc1
- expect(p1).not_to be_in_order_cycle oc2
- end
- end
-
- describe "deletion" do
- let(:product) { create(:simple_product) }
- let(:variant) { create(:variant, product:) }
- let(:order_cycle) { create(:simple_order_cycle) }
- let(:supplier) { create(:supplier_enterprise) }
- let(:exchange) {
- create(
- :exchange,
- order_cycle:,
- incoming: true,
- sender: supplier,
- receiver: order_cycle.coordinator
- )
- }
-
- it "removes all variants from order cycles" do
- exchange.variants << variant
-
- product.destroy
- expect(exchange.variants.reload).to be_empty
- end
- end
-
- describe "serialisation" do
- it "sanitises HTML in description" do
- subject.description = "Hello dearest monster."
- expect(subject.description).to eq "Hello alert dearest monster."
- end
+ expect(product.properties_including_inherited).to eq(
+ [{ id: pa.id, name: "A", value: '1' },
+ { id: pb.id, name: "B", value: '2' },
+ { id: pc.id, name: "C", value: '3' }]
+ )
end
end
- RSpec.describe "product import" do
- describe "finding the most recent import date of the variants" do
- let!(:product) { create(:product) }
+ describe "membership" do
+ it "queries its membership of a particular order cycle distribution" do
+ d1 = create(:distributor_enterprise)
+ d2 = create(:distributor_enterprise)
+ p1 = create(:product)
+ p2 = create(:product)
+ oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
+ oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
- let(:reference_time) { Time.zone.now.beginning_of_day }
+ expect(p1).to be_in_distributor d1
+ expect(p1).not_to be_in_distributor d2
+ end
- before do
- product.reload
+ it "queries its membership of a particular order cycle" do
+ d1 = create(:distributor_enterprise)
+ d2 = create(:distributor_enterprise)
+ p1 = create(:product)
+ p2 = create(:product)
+ oc1 = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
+ oc2 = create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first])
+
+ expect(p1).to be_in_order_cycle oc1
+ expect(p1).not_to be_in_order_cycle oc2
+ end
+ end
+
+ describe "deletion" do
+ let(:product) { create(:simple_product) }
+ let(:variant) { create(:variant, product:) }
+ let(:order_cycle) { create(:simple_order_cycle) }
+ let(:supplier) { create(:supplier_enterprise) }
+ let(:exchange) {
+ create(
+ :exchange,
+ order_cycle:,
+ incoming: true,
+ sender: supplier,
+ receiver: order_cycle.coordinator
+ )
+ }
+
+ it "removes all variants from order cycles" do
+ exchange.variants << variant
+
+ product.destroy
+ expect(exchange.variants.reload).to be_empty
+ end
+ end
+
+ describe "serialisation" do
+ it "sanitises HTML in description" do
+ subject.description = "Hello dearest monster."
+ expect(subject.description).to eq "Hello alert dearest monster."
+ end
+ end
+end
+
+RSpec.describe "product import" do
+ describe "finding the most recent import date of the variants" do
+ let!(:product) { create(:product) }
+
+ let(:reference_time) { Time.zone.now.beginning_of_day }
+
+ before do
+ product.reload
+ end
+
+ context "when the variants do not have an import date" do
+ let!(:variant_a) { create(:variant, product:, import_date: nil) }
+ let!(:variant_b) { create(:variant, product:, import_date: nil) }
+
+ it "returns nil" do
+ expect(product.import_date).to be_nil
end
+ end
- context "when the variants do not have an import date" do
- let!(:variant_a) { create(:variant, product:, import_date: nil) }
- let!(:variant_b) { create(:variant, product:, import_date: nil) }
+ context "when some variants have import date and some do not" do
+ let!(:variant_a) { create(:variant, product:, import_date: nil) }
+ let!(:variant_b) {
+ create(:variant, product:, import_date: reference_time - 1.hour)
+ }
+ let!(:variant_c) {
+ create(:variant, product:, import_date: reference_time - 2.hours)
+ }
- it "returns nil" do
- expect(product.import_date).to be_nil
- end
+ it "returns the most recent import date" do
+ expect(product.import_date).to eq(variant_b.import_date)
end
+ end
- context "when some variants have import date and some do not" do
- let!(:variant_a) { create(:variant, product:, import_date: nil) }
- let!(:variant_b) {
- create(:variant, product:, import_date: reference_time - 1.hour)
- }
- let!(:variant_c) {
- create(:variant, product:, import_date: reference_time - 2.hours)
- }
+ context "when all variants have import date" do
+ let!(:variant_a) {
+ create(:variant, product:, import_date: reference_time - 2.hours)
+ }
+ let!(:variant_b) {
+ create(:variant, product:, import_date: reference_time - 1.hour)
+ }
+ let!(:variant_c) {
+ create(:variant, product:, import_date: reference_time - 3.hours)
+ }
- it "returns the most recent import date" do
- expect(product.import_date).to eq(variant_b.import_date)
- end
- end
-
- context "when all variants have import date" do
- let!(:variant_a) {
- create(:variant, product:, import_date: reference_time - 2.hours)
- }
- let!(:variant_b) {
- create(:variant, product:, import_date: reference_time - 1.hour)
- }
- let!(:variant_c) {
- create(:variant, product:, import_date: reference_time - 3.hours)
- }
-
- it "returns the most recent import date" do
- expect(product.import_date).to eq(variant_b.import_date)
- end
+ it "returns the most recent import date" do
+ expect(product.import_date).to eq(variant_b.import_date)
end
end
end