Files
openfoodnetwork/spec/controllers/spree/orders_controller_spec.rb
Matt-Yorkley 9f49a84e7f Clarify use of access tokens used for viewing order details as a guest user
There are 4 or 5 different places in the app where we reference a :token and params[:token] for completely different purposes (they're not even vaguely the *same* token).

This is an attempt to clarify the places in the app where we use params[:token] in relation to *orders*, for allowing guest users (who are not logged in) to view details of an order they have placed (like after checkout completion), and differentiate it from the various other places where params[:token] can actually be used for something entirely different!
2021-12-16 13:35:55 +00:00

607 lines
21 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
describe Spree::OrdersController, type: :controller do
include OpenFoodNetwork::EmailHelper
include CheckoutHelper
include StripeStubs
let(:distributor) { double(:distributor) }
let(:order) { create(:order) }
let(:order_cycle) { create(:simple_order_cycle) }
describe "viewing an order" do
let(:customer) { create(:customer) }
let(:order) {
create(:order_with_credit_payment, customer: customer, distributor: customer.enterprise)
}
before do
allow(controller).to receive(:spree_current_user) { current_user }
end
context "after checking out as an anonymous guest" do
let(:customer) { create(:customer, user: nil) }
let(:current_user) { nil }
it "loads page" do
get :show, params: { id: order.number, order_token: order.token }
expect(response.status).to eq 200
end
it "stores order token in session as 'access_token'" do
get :show, params: { id: order.number, order_token: order.token }
expect(session[:access_token]).to eq(order.token)
end
end
context "when returning to order page after checking out as an anonymous guest" do
let(:customer) { create(:customer, user: nil) }
let(:current_user) { nil }
before do
session[:access_token] = order.token
end
it "loads page" do
get :show, params: { id: order.number }
expect(response.status).to eq 200
end
end
context "when logged in as the customer" do
let(:current_user) { order.user }
it "loads page" do
get :show, params: { id: order.number }
expect(response.status).to eq 200
end
end
context "when logged in as another customer" do
let(:current_user) { create(:user) }
it "redirects to unauthorized" do
get :show, params: { id: order.number }
expect(response).to redirect_to unauthorized_path
end
end
context "when neither checked out as an anonymous guest nor logged in" do
let(:current_user) { nil }
before do
request.env["PATH_INFO"] = order_path(order)
end
it "redirects to unauthorized" do
get :show, params: { id: order.number }
expect(response).to redirect_to(root_path(anchor: "login?after_login=#{order_path(order)}"))
expect(flash[:error]).to eq("Please log in to view your order.")
end
end
end
describe "confirming a payment intent" do
let(:customer) { create(:customer) }
let(:order) {
create(:order_with_totals, customer: customer, distributor: customer.enterprise,
state: "payment")
}
let(:payment_method) { create(:stripe_sca_payment_method) }
let!(:payment) {
create(
:payment,
payment_method: payment_method,
cvv_response_message: "https://stripe.com/redirect",
response_code: "pi_123",
order: order,
state: "requires_authorization"
)
}
before do
allow(controller).to receive(:spree_current_user) { current_user }
end
context "after returning from Stripe to authorize a payment" do
let(:current_user) { order.user }
context "with a valid payment intent" do
let(:payment_intent) { "pi_123" }
let(:payment_intent_response) { double(id: "pi_123", status: "requires_capture") }
before do
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call)
.and_return(payment_intent_response)
allow(Spree::Order).to receive(:find_by!) { order }
end
context "when the order is in payment state" do
it "completes the payment" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :show, params: { id: order.number, payment_intent: payment_intent }
expect(response.status).to eq 200
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
context "when the order is already completed" do
before do
order.update_columns(state: "complete")
end
it "should still process the payment" do
expect(order).to receive(:process_payments!) do
payment.complete!
end
get :show, params: { id: order.number, payment_intent: payment_intent }
expect(response.status).to eq 200
payment.reload
expect(payment.state).to eq("completed")
expect(payment.cvv_response_message).to be nil
end
end
end
context "when the payment intent response has errors" do
let(:payment_intent) { "pi_123" }
before do
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call)
.and_raise(Stripe::StripeError, "error message")
end
it "does not complete the payment" do
get :show, params: { id: order.number, payment_intent: payment_intent }
expect(response.status).to eq 200
expect(flash[:error]).to eq("#{I18n.t('payment_could_not_process')}. error message")
payment.reload
expect(payment.cvv_response_message).to be nil
expect(payment.state).to eq("failed")
end
end
context "with an invalid last payment" do
let(:payment_intent) { "valid" }
let(:finder) { instance_double(OrderPaymentFinder, last_payment: payment) }
before do
allow(payment).to receive(:response_code).and_return("invalid")
allow(OrderPaymentFinder).to receive(:new).with(order).and_return(finder)
allow(Stripe::PaymentIntentValidator)
.to receive_message_chain(:new, :call)
.and_return(payment_intent)
stub_payment_intent_get_request(payment_intent_id: "valid")
end
it "does not complete the payment" do
get :show, params: { id: order.number, payment_intent: payment_intent }
expect(response.status).to eq 200
expect(flash[:error]).to eq("#{I18n.t('payment_could_not_process')}. ")
payment.reload
expect(payment.cvv_response_message).to eq("https://stripe.com/redirect")
expect(payment.state).to eq("requires_authorization")
end
end
end
end
describe "viewing cart" do
it "redirects home when no distributor is selected" do
get :edit
expect(response).to redirect_to root_path
end
it "redirects to shop when order is empty" do
allow(controller).to receive(:current_distributor).and_return(distributor)
allow(controller).to receive(:current_order_cycle).and_return(order_cycle)
allow(controller).to receive(:current_order).and_return order
allow(order).to receive_message_chain(:line_items, :empty?).and_return true
allow(order).to receive(:insufficient_stock_lines).and_return []
allow(order).to receive(:line_item_variants).and_return []
allow(order_cycle).to receive(:variants_distributed_by).and_return []
session[:access_token] = order.token
get :edit
expect(response).to redirect_to shop_path
end
it "redirects to the shop when no order cycle is selected" do
allow(controller).to receive(:current_distributor).and_return(distributor)
get :edit
expect(response).to redirect_to shop_path
end
it "redirects home with message if hub is not ready for checkout" do
allow(VariantOverride).to receive(:indexed).and_return({})
order = subject.current_order(true)
allow(distributor).to receive(:ready_for_checkout?) { false }
allow(order).to receive_messages(distributor: distributor, order_cycle: order_cycle)
expect(order).to receive(:empty!)
expect(order).to receive(:set_distribution!).with(nil, nil)
get :edit
expect(response).to redirect_to root_url
expect(flash[:info]).to eq(I18n.t('order_cycles_closed_for_hub'))
end
describe "when an item is in the cart" do
let(:order) { subject.current_order(true) }
let(:oc) { create(:simple_order_cycle, distributors: [d], variants: [variant]) }
let(:d) {
create(:distributor_enterprise, shipping_methods: [create(:shipping_method)],
payment_methods: [create(:payment_method)])
}
let(:variant) { create(:variant, on_demand: false, on_hand: 5) }
let(:line_item) { order.line_items.last }
before do
order.set_distribution! d, oc
order.contents.add(variant, 5)
end
describe "the page" do
render_views
it "shows the right registration link" do
# We fixed our view by hardcoding the link.
spree_registration_path = '/signup'
ofn_registration_path = '/register'
get :edit
expect(response.body).to_not match spree_registration_path
expect(response.body).to match ofn_registration_path
end
end
describe "when an item has insufficient stock" do
before do
variant.update! on_hand: 3
end
it "displays a flash message when we view the cart" do
get :edit
expect(response.status).to eq 200
expect(flash[:error]).to eq I18n.t('spree.orders.error_flash_for_unavailable_items')
end
end
describe "when an item is unavailable" do
before do
order.order_cycle = create(:simple_order_cycle, distributors: [d], variants: [])
end
it "displays a flash message when we view the cart" do
get :edit
expect(response.status).to eq 200
expect(flash[:error]).to eq I18n.t('spree.orders.error_flash_for_unavailable_items')
end
end
end
end
describe "removing line items from cart" do
describe "when I pass params that includes a line item no longer in our cart" do
it "should silently ignore the missing line item" do
order = subject.current_order(true)
li = order.contents.add(create(:simple_product, on_hand: 110).variants.first)
get :update, params: { order: { line_items_attributes: {
"0" => { quantity: "0", id: "9999" },
"1" => { quantity: "99", id: li.id }
} } }
expect(response.status).to eq(302)
expect(li.reload.quantity).to eq(99)
end
end
it "filters line items that are missing from params" do
order = subject.current_order(true)
li = order.contents.add(create(:simple_product).variants.first)
attrs = {
"0" => { quantity: "0", id: "9999" },
"1" => { quantity: "99", id: li.id }
}
expect(controller.__send__(:remove_missing_line_items, attrs)).to eq(
"1" => { quantity: "99", id: li.id }
)
end
it "keeps the adjustments' previous state" do
order = subject.current_order(true)
line_item = order.contents.add(create(:simple_product, on_hand: 110).variants.first)
adjustment = create(:adjustment, adjustable: order)
get :update, params: { order: { line_items_attributes: {
"1" => { quantity: "99", id: line_item.id }
} } }
expect(adjustment.state).to eq('open')
end
end
describe "removing items from a completed order" do
context "with shipping and transaction fees" do
let(:distributor) {
create(:distributor_enterprise, charges_sales_tax: true, allow_order_changes: true)
}
let(:shipping_tax_rate) {
create(:tax_rate, amount: 0.25, included_in_price: true, zone: create(:zone_with_member))
}
let(:shipping_tax_category) { create(:tax_category, tax_rates: [shipping_tax_rate]) }
let(:order) {
create(:completed_order_with_fees, distributor: distributor, shipping_fee: shipping_fee,
payment_fee: payment_fee, shipping_tax_category: shipping_tax_category)
}
let(:line_item1) { order.line_items.first }
let(:line_item2) { order.line_items.second }
let(:shipping_fee) { 3 }
let(:payment_fee) { 5 }
let(:item_num) { order.line_items.length }
let(:expected_fees) { item_num * (shipping_fee + payment_fee) }
before do
allow(order).to receive(:tax_zone) { shipping_tax_rate.zone }
order.reload
order.create_tax_charge!
# Sanity check the fees
expect(order.all_adjustments.length).to eq 3
expect(item_num).to eq 2
expect(order.adjustment_total).to eq expected_fees
expect(order.shipment.adjustments.tax.first.amount).to eq 1.2
expect(order.shipment.included_tax_total).to eq 1.2
allow(subject).to receive(:spree_current_user) { order.user }
allow(subject).to receive(:order_to_update) { order }
end
it "updates the shipping and payment fees" do
spree_post :update,
order: { line_items_attributes: {
"0" => { id: line_item1.id, quantity: 1 },
"1" => { id: line_item2.id, quantity: 0 }
} }
expect(order.reload.line_items.count).to eq 1
expect(order.adjustment_total).to eq(1 * (shipping_fee + payment_fee))
expect(order.shipment.adjustments.tax.first.amount).to eq 0.6
expect(order.shipment.included_tax_total).to eq 0.6
end
end
context "with enterprise fees" do
let(:user) { create(:user) }
let(:variant1) { create(:variant) }
let(:variant2) { create(:variant) }
let(:distributor) { create(:distributor_enterprise, allow_order_changes: true) }
let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
let(:enterprise_fee) { create(:enterprise_fee, calculator: build(:calculator_per_item) ) }
let!(:exchange) {
create(:exchange, incoming: true, sender: variant1.product.supplier,
receiver: order_cycle.coordinator, variants: [variant1, variant2], enterprise_fees: [enterprise_fee])
}
let!(:order) do
order = create(:completed_order_with_totals, line_items_count: 2, user: user,
distributor: distributor, order_cycle: order_cycle)
order.reload.line_items.first.update(variant_id: variant1.id)
order.reload.line_items.last.update(variant_id: variant2.id)
break unless order.next! while !order.completed?
order.recreate_all_fees!
order
end
let(:params) {
{ order: { line_items_attributes: {
"0" => { id: order.line_items.first.id, quantity: 2 }
} } }
}
before do
allow(subject).to receive(:spree_current_user) { order.user }
allow(subject).to receive(:order_to_update) { order }
end
it "updates the fees" do
expect(order.total).to eq order.item_total + (enterprise_fee.calculator.preferred_amount * 2)
expect(order.adjustment_total).to eq enterprise_fee.calculator.preferred_amount * 2
allow(controller).to receive_messages spree_current_user: user
spree_post :update, params
expect(order.total).to eq order.item_total + (enterprise_fee.calculator.preferred_amount * 3)
expect(order.adjustment_total).to eq enterprise_fee.calculator.preferred_amount * 3
end
context "when a line item is removed" do
let(:params) {
{ order: { line_items_attributes: {
"0" => { id: order.line_items.first.id, quantity: 0 },
"1" => { id: order.line_items.last.id, quantity: 1 }
} } }
}
it "updates the fees" do
expect(order.total).to eq order.item_total + (enterprise_fee.calculator.preferred_amount * 2)
expect(order.adjustment_total).to eq enterprise_fee.calculator.preferred_amount * 2
allow(controller).to receive_messages spree_current_user: user
spree_post :update, params
expect(order.total).to eq order.item_total + (enterprise_fee.calculator.preferred_amount * 1)
expect(order.adjustment_total).to eq enterprise_fee.calculator.preferred_amount * 1
end
end
end
end
describe "request to remove items from a completed order" do
let(:order) { create(:completed_order_with_totals, line_items_count: 2) }
let(:params) {
{ order: { line_items_attributes: {
"0" => { id: order.line_items.first.id, quantity: 1 },
"1" => { id: order.line_items.second.id, quantity: 0 }
} } }
}
before { allow(subject).to receive(:order_to_update) { order } }
context "one item would remain in the order" do
it "removes the items" do
spree_post :update, params
expect(flash[:error]).to be nil
expect(response).to redirect_to order_path(order)
expect(order.reload.line_items.count).to eq 1
end
end
context "no item would remain in the order" do
before { params[:order][:line_items_attributes]["0"][:quantity] = 0 }
it "does not remove items, flash suggests cancellation" do
spree_post :update, params
expect(flash[:error]).to eq I18n.t(:orders_cannot_remove_the_final_item)
expect(response).to redirect_to order_path(order)
expect(order.reload.line_items.count).to eq 2
end
end
end
describe "#order_to_update" do
let!(:current_order) { double(:current_order) }
let(:params) { {} }
before do
allow(controller).to receive(:current_order) { current_order }
allow(controller).to receive(:params) { params }
end
context "when no order id is given in params" do
it "returns the current_order" do
expect(controller.send(:order_to_update)).to eq current_order
end
end
context "when an order_id is given in params" do
before do
params.merge!(id: order.number)
end
context "and the order is not complete" do
let!(:order) { create(:order) }
it "returns nil" do
expect(controller.send(:order_to_update)).to eq nil
end
end
context "and the order is complete" do
let!(:order) { create(:completed_order_with_totals) }
context "and the user doesn't have permisson to 'update' the order" do
before { allow(controller).to receive(:can?).with(:update, order) { false } }
it "returns nil" do
expect(controller.send(:order_to_update)).to eq nil
end
end
context "and the user has permission to 'update' the order" do
before { allow(controller).to receive(:can?).with(:update, order) { true } }
context "and the order is not editable" do
it "returns nil" do
expect(controller.send(:order_to_update)).to eq nil
end
end
context "and the order is editable" do
let(:distributor) { create(:enterprise, allow_order_changes: true) }
let(:order_cycle) do
create(
:simple_order_cycle,
distributors: [distributor],
variants: order.line_item_variants
)
end
before do
order.update!(order_cycle_id: order_cycle.id, distributor_id: distributor.id)
end
it "returns the order" do
expect(controller.send(:order_to_update)).to eq order
end
end
end
end
end
end
describe "cancelling an order" do
let(:user) { create(:user) }
let(:order) { create(:order, user: user) }
let(:params) { { id: order.number } }
context "when the user does not have permission to cancel the order" do
before { allow(controller).to receive(:spree_current_user) { create(:user) } }
it "responds with unauthorized" do
spree_put :cancel, params
expect(response).to redirect_to unauthorized_path
end
end
context "when the user has permission to cancel the order" do
before { allow(controller).to receive(:spree_current_user) { user } }
context "when the order is not yet complete" do
it "responds with forbidden" do
spree_put :cancel, params
expect(response.status).to redirect_to order_path(order)
expect(flash[:error]).to eq I18n.t(:orders_could_not_cancel)
end
end
context "when the order is complete" do
let(:order) {
create(:completed_order_with_totals, user: user,
distributor: create(:distributor_enterprise))
}
before do
setup_email
end
it "responds with success" do
spree_put :cancel, params
expect(response.status).to redirect_to order_path(order)
expect(flash[:success]).to eq I18n.t(:orders_your_order_has_been_cancelled)
end
end
end
end
private
def num_items_in_cart
Spree::Order.last&.line_items&.count || 0
end
end