engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb
# frozen_string_literal: true
require "spec_helper"
module OrderManagement
module Subscriptions
describe Validator do
let(:owner) { create(:user) }
let(:shop) { create(:enterprise, name: "Shop", owner:) }
describe "delegation" do
let(:subscription) { create(:subscription, shop:) }
let(:validator) { Validator.new(subscription) }
it "delegates to subscription" do
expect(validator.shop).to eq subscription.shop
expect(validator.customer).to eq subscription.customer
expect(validator.schedule).to eq subscription.schedule
expect(validator.shipping_method).to eq subscription.shipping_method
expect(validator.payment_method).to eq subscription.payment_method
expect(validator.bill_address).to eq subscription.bill_address
expect(validator.ship_address).to eq subscription.ship_address
expect(validator.begins_at).to eq subscription.begins_at
expect(validator.ends_at).to eq subscription.ends_at
end
end
describe "validations" do
let(:subscription_stubs) do
{
shop:,
customer: true,
schedule: true,
shipping_method: true,
payment_method: true,
bill_address: true,
ship_address: true,
begins_at: true,
ends_at: true,
}
end
let(:validation_stubs) do
{
shipping_method_allowed?: true,
payment_method_allowed?: true,
payment_method_type_allowed?: true,
ends_at_after_begins_at?: true,
customer_allowed?: true,
schedule_allowed?: true,
credit_card_ok?: true,
subscription_line_items_present?: true,
requested_variants_available?: true
}
end
let(:subscription) { instance_double(Subscription, subscription_stubs) }
let(:validator) { OrderManagement::Subscriptions::Validator.new(subscription) }
def stub_validations(validator, methods)
methods.each do |name, value|
allow(validator).to receive(name) { value }
end
end
describe "shipping method validation" do
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:shipping_method))
}
before { stub_validations(validator, validation_stubs.except(:shipping_method_allowed?)) }
context "when no shipping method is present" do
before { expect(subscription).to receive(:shipping_method).at_least(:once) { nil } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:shipping_method]).not_to be_empty
end
end
context "when a shipping method is present" do
let(:shipping_method) { instance_double(Spree::ShippingMethod, distributors: [shop]) }
before {
expect(subscription).to receive(:shipping_method).at_least(:once) { shipping_method }
}
context "and the shipping method is not associated with the shop" do
before { allow(shipping_method).to receive(:distributors) { [double(:enterprise)] } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:shipping_method]).not_to be_empty
end
end
context "and the shipping method is associated with the shop" do
before { allow(shipping_method).to receive(:distributors) { [shop] } }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:shipping_method]).to be_empty
end
end
end
end
describe "payment method validation" do
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:payment_method))
}
before { stub_validations(validator, validation_stubs.except(:payment_method_allowed?)) }
context "when no payment method is present" do
before { expect(subscription).to receive(:payment_method).at_least(:once) { nil } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "when a payment method is present" do
let(:payment_method) { instance_double(Spree::PaymentMethod, distributors: [shop]) }
before {
expect(subscription).to receive(:payment_method).at_least(:once) { payment_method }
}
context "and the payment method is not associated with the shop" do
before { allow(payment_method).to receive(:distributors) { [double(:enterprise)] } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "and the payment method is associated with the shop" do
before { allow(payment_method).to receive(:distributors) { [shop] } }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:payment_method]).to be_empty
end
end
end
end
describe "payment method type validation" do
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:payment_method))
}
before {
stub_validations(validator, validation_stubs.except(:payment_method_type_allowed?))
}
context "when a payment method is present" do
let(:payment_method) { instance_double(Spree::PaymentMethod, distributors: [shop]) }
before {
expect(subscription).to receive(:payment_method).at_least(:once) { payment_method }
}
context "and the payment method type is not in the approved list" do
before { allow(payment_method).to receive(:type) { "Blah" } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "and the payment method is in the approved list" do
let(:approved_type) { Subscription::ALLOWED_PAYMENT_METHOD_TYPES.first }
before { allow(payment_method).to receive(:type) { approved_type } }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:payment_method]).to be_empty
end
end
end
end
describe "dates" do
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:begins_at, :ends_at))
}
before { stub_validations(validator, validation_stubs.except(:ends_at_after_begins_at?)) }
before { expect(subscription).to receive(:begins_at).at_least(:once) { begins_at } }
context "when no begins_at is present" do
let(:begins_at) { nil }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:begins_at]).not_to be_empty
end
end
context "when a start date is present" do
let(:begins_at) { Time.zone.today }
before { expect(subscription).to receive(:ends_at).at_least(:once) { ends_at } }
context "when no ends_at is present" do
let(:ends_at) { nil }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:ends_at]).to be_empty
end
end
context "when ends_at is equal to begins_at" do
let(:ends_at) { Time.zone.today }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:ends_at]).not_to be_empty
end
end
context "when ends_at is before begins_at" do
let(:ends_at) { Time.zone.today - 1.day }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:ends_at]).not_to be_empty
end
end
context "when ends_at is after begins_at" do
let(:ends_at) { Time.zone.today + 1.day }
it "adds an error and returns false" do
expect(validator.valid?).to be true
expect(validator.errors[:ends_at]).to be_empty
end
end
end
end
describe "addresses" do
before { stub_validations(validator, validation_stubs) }
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:bill_address, :ship_address))
}
before { expect(subscription).to receive(:bill_address).at_least(:once) { bill_address } }
before { expect(subscription).to receive(:ship_address).at_least(:once) { ship_address } }
context "when bill_address and ship_address are not present" do
let(:bill_address) { nil }
let(:ship_address) { nil }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:bill_address]).not_to be_empty
expect(validator.errors[:ship_address]).not_to be_empty
end
end
context "when bill_address and ship_address are present" do
let(:bill_address) { instance_double(Spree::Address) }
let(:ship_address) { instance_double(Spree::Address) }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:bill_address]).to be_empty
expect(validator.errors[:ship_address]).to be_empty
end
end
end
describe "customer" do
let(:subscription) { instance_double(Subscription, subscription_stubs.except(:customer)) }
before { stub_validations(validator, validation_stubs.except(:customer_allowed?)) }
before { expect(subscription).to receive(:customer).at_least(:once) { customer } }
context "when no customer is present" do
let(:customer) { nil }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:customer]).not_to be_empty
end
end
context "when a customer is present" do
let(:customer) { instance_double(Customer) }
context "and the customer is not associated with the shop" do
before { allow(customer).to receive(:enterprise) { double(:enterprise) } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:customer]).not_to be_empty
end
end
context "and the customer is associated with the shop" do
before { allow(customer).to receive(:enterprise) { shop } }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:customer]).to be_empty
end
end
end
end
describe "schedule" do
let(:subscription) { instance_double(Subscription, subscription_stubs.except(:schedule)) }
before { stub_validations(validator, validation_stubs.except(:schedule_allowed?)) }
before { expect(subscription).to receive(:schedule).at_least(:once) { schedule } }
context "when no schedule is present" do
let(:schedule) { nil }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:schedule]).not_to be_empty
end
end
context "when a schedule is present" do
let(:schedule) { instance_double(Schedule) }
context "and the schedule is not associated with the shop" do
before { allow(schedule).to receive(:coordinators) { [double(:enterprise)] } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:schedule]).not_to be_empty
end
end
context "and the schedule is associated with the shop" do
before { allow(schedule).to receive(:coordinators) { [shop] } }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:schedule]).to be_empty
end
end
end
end
describe "credit card" do
let(:subscription) {
instance_double(Subscription, subscription_stubs.except(:payment_method))
}
before { stub_validations(validator, validation_stubs.except(:credit_card_ok?)) }
before {
expect(subscription).to receive(:payment_method).at_least(:once) { payment_method }
}
context "when using a Check payment method" do
let(:payment_method) {
instance_double(Spree::PaymentMethod, type: "Spree::PaymentMethod::Check")
}
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:subscription_line_items]).to be_empty
end
end
context "when using the StripeSCA payment gateway" do
let(:payment_method) {
instance_double(Spree::PaymentMethod, type: "Spree::Gateway::StripeSCA")
}
before { expect(subscription).to receive(:customer).at_least(:once) { customer } }
context "when the customer does not allow charges" do
let(:customer) { instance_double(Customer, allow_charges: false) }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "when the customer allows charges" do
let(:customer) { instance_double(Customer, allow_charges: true) }
context "and the customer is not associated with a user" do
before { allow(customer).to receive(:user) { nil } }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "and the customer is associated with a user" do
before { expect(customer).to receive(:user).once { user } }
context "and the user has no default card set" do
let(:user) { instance_double(Spree::User, default_card: nil) }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:payment_method]).not_to be_empty
end
end
context "and the user has a default card set" do
let(:user) { instance_double(Spree::User, default_card: 'some card') }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:payment_method]).to be_empty
end
end
end
end
end
end
describe "subscription line items" do
let(:subscription) { instance_double(Subscription, subscription_stubs) }
before {
stub_validations(validator, validation_stubs.except(:subscription_line_items_present?))
}
before {
expect(subscription).to receive(:subscription_line_items).at_least(:once) {
subscription_line_items
}
}
context "when no subscription line items exist" do
let(:subscription_line_items) { [] }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:subscription_line_items]).not_to be_empty
end
end
context "when some subscription line items exist" do
context "and they have some quantity but all are marked for destruction" do
let(:subscription_line_item1) {
instance_double(SubscriptionLineItem, marked_for_destruction?: true, quantity: 1)
}
let(:subscription_line_items) { [subscription_line_item1] }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:subscription_line_items]).not_to be_empty
end
end
context "and at least one has some quantity and is not marked for destruction" do
let(:subscription_line_item1) {
instance_double(SubscriptionLineItem, marked_for_destruction?: true, quantity: 1)
}
let(:subscription_line_item2) {
instance_double(SubscriptionLineItem, marked_for_destruction?: false, quantity: 1)
}
let(:subscription_line_items) { [subscription_line_item1, subscription_line_item2] }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:subscription_line_items]).to be_empty
end
end
context "and they are not marked for destruction but all have no quantity" do
let(:subscription_line_item1) {
instance_double(SubscriptionLineItem, marked_for_destruction?: false, quantity: 0)
}
let(:subscription_line_items) { [subscription_line_item1] }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:subscription_line_items]).not_to be_empty
end
end
context "but all have no quantity and are marked for destruction" do
let(:subscription_line_item1) {
instance_double(SubscriptionLineItem, marked_for_destruction?: true, quantity: 0)
}
let(:subscription_line_items) { [subscription_line_item1] }
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:subscription_line_items]).not_to be_empty
end
end
end
end
describe "variant availability" do
let(:subscription) { instance_double(Subscription, subscription_stubs) }
before {
stub_validations(validator, validation_stubs.except(:requested_variants_available?))
}
before {
expect(subscription).to receive(:subscription_line_items).at_least(:once) {
subscription_line_items
}
}
context "when no subscription line items exist" do
let(:subscription_line_items) { [] }
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:subscription_line_items]).to be_empty
end
end
context "when subscription line items exist" do
let(:variant1) { instance_double(Spree::Variant, id: 1, deleted_at: nil) }
let(:variant2) { instance_double(Spree::Variant, id: 2) }
let(:variant3) { instance_double(Spree::Variant, id: 1, deleted_at: Time.zone.now ) }
let(:subscription_line_item1) {
instance_double(SubscriptionLineItem,
variant: variant1,
marked_for_destruction?: false)
}
let(:subscription_line_item2) {
instance_double(SubscriptionLineItem,
variant: variant2,
marked_for_destruction?: false)
}
let(:subscription_line_item3) {
instance_double(SubscriptionLineItem,
variant: variant3,
marked_for_destruction?: true)
}
let(:subscription_line_items) { [subscription_line_item1, subscription_line_item3] }
context "but some variants are unavailable" do
let(:product) { instance_double(Spree::Product, name: "some_name") }
before do
allow(validator).to receive(:available_variant_ids) { [variant2.id] }
allow(variant1).to receive(:product) { product }
allow(variant1).to receive(:full_name) { "some name" }
end
it "adds an error and returns false" do
expect(validator.valid?).to be false
expect(validator.errors[:subscription_line_items]).not_to be_empty
end
end
context "and all requested variants are available" do
before do
allow(validator).to receive(:available_variant_ids) { [variant1.id, variant2.id] }
end
it "returns true" do
expect(validator.valid?).to be true
expect(validator.errors[:subscription_line_items]).to be_empty
end
end
end
end
end
end
end
end