# frozen_string_literal: true RSpec.describe Admin::OrderCyclesController do let!(:distributor_owner) { create(:user) } let(:datetime_confirmation_attrs) { { confirm_datetime_change: nil, error_class: Admin::OrderCyclesController::DateTimeChangeError } } before do allow(controller).to receive_messages spree_current_user: distributor_owner end describe "#index" do describe "when the user manages a coordinator" do let!(:coordinator) { create(:distributor_enterprise, owner: distributor_owner) } let!(:oc1) { create(:simple_order_cycle, orders_open_at: 70.days.ago, orders_close_at: 60.days.ago ) } let!(:oc2) { create(:simple_order_cycle, orders_open_at: 70.days.ago, orders_close_at: 40.days.ago ) } let!(:oc3) { create(:simple_order_cycle, orders_open_at: 70.days.ago, orders_close_at: 20.days.ago ) } let!(:oc4) { create(:simple_order_cycle, orders_open_at: 70.days.ago, orders_close_at: nil ) } context "html" do it "doesn't load any data" do get :index, as: :html expect(assigns(:collection)).to be_empty end end context "json" do context "where ransack conditions are specified" do it "loads order cycles closed within past month, and orders w/o a close_at date" do get :index, as: :json expect(assigns(:collection)).not_to include oc1, oc2 expect(assigns(:collection)).to include oc3, oc4 end end context "where q[orders_close_at_gt] is set" do let(:q) { { orders_close_at_gt: 45.days.ago } } it "loads order cycles closed after specified date, and orders w/o a close_at date" do get :index, as: :json, params: { q: } expect(assigns(:collection)).not_to include oc1 expect(assigns(:collection)).to include oc2, oc3, oc4 end context "and other conditions are specified" do before { q.merge!(id_not_in: [oc2.id, oc4.id]) } it "loads order cycles that meet all conditions" do get :index, format: :json, params: { q: } expect(assigns(:collection)).not_to include oc1, oc2, oc4 expect(assigns(:collection)).to include oc3 end end end end end end describe "new" do describe "when the user manages a single distributor enterprise suitable for coordinator" do let!(:distributor) { create(:distributor_enterprise, owner: distributor_owner) } it "renders the new template" do get :new expect(response).to render_template :new end end describe "when a user manages multiple enterprises suitable for coordinator" do let!(:distributor1) { create(:distributor_enterprise, owner: distributor_owner) } let!(:distributor2) { create(:distributor_enterprise, owner: distributor_owner) } let!(:distributor3) { create(:distributor_enterprise) } it "renders the set_coordinator template" do get :new expect(response).to render_template :set_coordinator end describe "and a coordinator_id is submitted as part of the request" do describe "when the user manages the enterprise" do it "renders the new template" do get :new, params: { coordinator_id: distributor1.id } expect(response).to render_template :new end end describe "when the user does not manage the enterprise" do it "renders the set_coordinator template and sets a flash error" do get :new, params: { coordinator_id: distributor3.id } expect(response).to render_template :set_coordinator expect(flash[:error]) .to eq "You don't have permission to create an order cycle " \ "coordinated by that enterprise" end end end end end describe "show" do context 'a distributor manages an order cycle' do let(:distributor) { create(:distributor_enterprise, owner: distributor_owner) } let(:oc) { create(:simple_order_cycle, coordinator: distributor) } context "distributor navigates to order cycle show page" do it 'redirects to edit page' do get :show, params: { id: oc.id } expect(response).to redirect_to edit_admin_order_cycle_path(oc.id) end end end describe "queries" do context "as manager, when order cycle has multiple exchanges" do let!(:distributor) { create(:distributor_enterprise) } let(:order_cycle) { create(:simple_order_cycle, coordinator: distributor) } before do order_cycle.exchanges.create! sender: distributor, receiver: distributor, incoming: true, receival_instructions: 'A', tag_list: "A" order_cycle.exchanges.create! sender: distributor, receiver: distributor, incoming: false, pickup_instructions: 'B', tag_list: "B" controller_login_as_enterprise_user([distributor]) end it do expect { get :show, params: { id: order_cycle.id }, as: :json }.to query_database( { select: { enterprise_fees: 3, enterprise_groups: 1, enterprises: 19, exchanges: 7, order_cycles: 6, proxy_orders: 1, schedules: 1, spree_variants: 8, tags: 1 }, update: { spree_users: 1 } } ) end end end end describe "create" do let(:shop) { create(:distributor_enterprise) } context "as a manager of a shop" do let(:form_mock) { instance_double(OrderCycles::FormService) } let(:params) { { as: :json, order_cycle: {} } } before do controller_login_as_enterprise_user([shop]) allow(OrderCycles::FormService).to receive(:new) { form_mock } end context "when creation is successful" do before { allow(form_mock).to receive(:save) { true } } # mock build_resource so that we can control the edit_path Admin::OrderCyclesController.class_eval do def build_resource order_cycle = OrderCycle.new order_cycle.id = 1 order_cycle end end it "returns success: true and a valid edit path" do spree_post :create, params json_response = response.parsed_body expect(json_response['success']).to be true expect(json_response['edit_path']).to eq "/admin/order_cycles/1/incoming" end end context "when an error occurs" do before { allow(form_mock).to receive(:save) { false } } it "returns an errors hash" do spree_post :create, params json_response = response.parsed_body expect(json_response['errors']).to be end end end end describe "update" do let(:order_cycle) { create(:simple_order_cycle) } let(:coordinator) { order_cycle.coordinator } let(:form_mock) { instance_double(OrderCycles::FormService) } before do allow(OrderCycles::FormService).to receive(:new) { form_mock } end context "as a manager of the coordinator" do before { controller_login_as_enterprise_user([coordinator]) } let(:params) { { format: :json, id: order_cycle.id, order_cycle: {} } } context "when order cycle has subscriptions" do let(:coordinator) { order_cycle.coordinator } let(:producer) { create(:supplier_enterprise) } let!(:schedule) { create(:schedule, order_cycles: [order_cycle]) } let(:v) { create(:variant) } let!(:incoming_exchange) { create(:exchange, order_cycle:, sender: producer, receiver: coordinator, incoming: true, variants: [v]) } let!(:outgoing_exchange) { create(:exchange, order_cycle:, sender: coordinator, receiver: coordinator, incoming: false, variants: [v]) } let!(:subscription) { create(:subscription, shop: coordinator, schedule:) } let!(:subscription_line_item) { create(:subscription_line_item, subscription:, variant: v) } before do allow(form_mock).to receive(:save) { true } v.destroy end it "can update order cycle even if the variant has been deleted" do spree_put :update, { format: :json, id: order_cycle.id, order_cycle: {} } expect(response).to have_http_status :ok end end context "when updating succeeds" do before { allow(form_mock).to receive(:save) { true } } context "when the page is reloading" do before { params[:reloading] = '1' } it "sets flash message" do spree_put :update, params expect(flash[:success]).to eq('Your order cycle has been updated.') end end context "when the page is not reloading" do it "does not set flash message" do spree_put :update, params expect(flash[:success]).to be nil end end end context "when a validation error occurs" do before { allow(form_mock).to receive(:save) { false } } it "returns an error message" do spree_put :update, params json_response = response.parsed_body expect(json_response['errors']).to be end end it "can update preference product_selection_from_coordinator_inventory_only" do expect(OrderCycles::FormService).to receive(:new). with(order_cycle, { "preferred_product_selection_from_coordinator_inventory_only" => true, **datetime_confirmation_attrs }, anything) { form_mock } allow(form_mock).to receive(:save) { true } spree_put :update, params. merge(order_cycle: { preferred_product_selection_from_coordinator_inventory_only: true }) end it "can update preference automatic_notifications" do expect(OrderCycles::FormService).to receive(:new). with(order_cycle, { "automatic_notifications" => true, **datetime_confirmation_attrs }, anything) { form_mock } allow(form_mock).to receive(:save) { true } spree_put :update, params. merge(order_cycle: { automatic_notifications: true }) end end end describe "limiting update scope" do let(:order_cycle) { create(:simple_order_cycle) } let(:producer) { create(:supplier_enterprise) } let(:coordinator) { order_cycle.coordinator } let(:hub) { create(:distributor_enterprise) } let(:v) { create(:variant) } let!(:incoming_exchange) { create(:exchange, order_cycle:, sender: producer, receiver: coordinator, incoming: true, variants: [v]) } let!(:outgoing_exchange) { create(:exchange, order_cycle:, sender: coordinator, receiver: hub, incoming: false, variants: [v]) } let(:allowed) { { incoming_exchanges: [], outgoing_exchanges: [] } } let(:restricted) { { name: 'some name', orders_open_at: 1.day.from_now.to_s, orders_close_at: 1.day.ago.to_s } } let(:params) { { format: :json, id: order_cycle.id, order_cycle: allowed.merge(restricted) } } let(:form_mock) { instance_double(OrderCycles::FormService, save: true) } before { allow(controller).to receive(:spree_current_user) { user } } context "as a manager of the coordinator" do let(:user) { coordinator.owner } let(:expected) { [order_cycle, allowed.merge(restricted).merge(datetime_confirmation_attrs), user] } it "allows me to update exchange information for exchanges, name and dates" do expect(OrderCycles::FormService).to receive(:new).with(*expected) { form_mock } spree_put :update, params end end context "as a producer supplying to an order cycle" do let(:user) { producer.owner } let(:expected) { [order_cycle, allowed.merge(datetime_confirmation_attrs), user] } it "allows me to update exchange information for exchanges, but not name or dates" do expect(OrderCycles::FormService).to receive(:new).with(*expected) { form_mock } spree_put :update, params end end end describe "bulk_update" do let(:oc) { create(:simple_order_cycle) } let!(:coordinator) { oc.coordinator } context "when I manage the coordinator of an order cycle" do let(:params) do { format: :json, order_cycle_set: { collection_attributes: { '0' => { id: oc.id, name: "Updated Order Cycle", orders_open_at: Date.current - 21.days, orders_close_at: Date.current + 21.days, } } } } end before { create(:enterprise_role, user: distributor_owner, enterprise: coordinator) } it "updates order cycle properties" do spree_put :bulk_update, params oc.reload expect(oc.name).to eq "Updated Order Cycle" expect(oc.orders_open_at.to_date).to eq Date.current - 21.days expect(oc.orders_close_at.to_date).to eq Date.current + 21.days end it "does nothing when no data is supplied" do expect do spree_put :bulk_update, format: :json end.to change { oc.orders_open_at }.by(0) json_response = response.parsed_body expect(json_response['errors']) .to eq 'Hm, something went wrong. No order cycle data found.' end context "when a validation error occurs" do let(:params) do { format: :json, order_cycle_set: { collection_attributes: { '0' => { id: oc.id, name: "Updated Order Cycle", orders_open_at: Date.current + 25.days, orders_close_at: Date.current + 21.days, } } } } end it "returns an error message" do spree_put :bulk_update, params json_response = response.parsed_body expect(json_response['errors']).to be_present end end end context "when I do not manage the coordinator of an order cycle" do # I need to manage a hub in order to access the bulk_update action let!(:another_distributor) { create(:distributor_enterprise, users: [distributor_owner]) } it "doesn't update order cycle properties" do spree_put :bulk_update, format: :json, order_cycle_set: { collection_attributes: { '0' => { id: oc.id, name: "Updated Order Cycle", orders_open_at: Date.current - 21.days, orders_close_at: Date.current + 21.days, } } } oc.reload expect(oc.name).not_to eq "Updated Order Cycle" expect(oc.orders_open_at.to_date).not_to eq Date.current - 21.days expect(oc.orders_close_at.to_date).not_to eq Date.current + 21.days end end end describe "notifying producers" do let(:user) { create(:user) } let(:admin_user) { create(:admin_user) } let(:order_cycle) { create(:simple_order_cycle) } before do allow(controller).to receive_messages spree_current_user: admin_user end it "enqueues a job" do expect do spree_post :notify_producers, id: order_cycle.id end.to enqueue_job OrderCycleNotificationJob end it "redirects back to the order cycles path with a success message" do spree_post :notify_producers, id: order_cycle.id expect(response).to redirect_to admin_order_cycles_path expect(flash[:success]).to eq( 'Emails to be sent to producers have been queued for sending.' ) end end describe "destroy" do let(:distributor) { create(:distributor_enterprise, owner: distributor_owner) } let(:oc) { create(:simple_order_cycle, coordinator: distributor) } describe "when an order cycle is deleteable" do it "allows the order_cycle to be destroyed" do get :destroy, params: { id: oc.id } expect(OrderCycle.find_by(id: oc.id)).to be nil end end describe "when an order cycle becomes non-deletable due to the presence of an order" do let!(:order) { create(:order, order_cycle: oc) } it "displays an error message when we attempt to delete it" do get :destroy, params: { id: oc.id } expect(response).to redirect_to admin_order_cycles_path expect(flash[:error]) .to eq 'That order cycle has been selected by a customer and cannot be deleted. ' \ 'To prevent customers from accessing it, please close it instead.' end end describe "when an order cycle becomes non-deletable because it is linked to a schedule" do let!(:schedule) { create(:schedule, order_cycles: [oc]) } it "displays an error message when we attempt to delete it" do get :destroy, params: { id: oc.id } expect(response).to redirect_to admin_order_cycles_path expect(flash[:error]) .to eq 'That order cycle is linked to a schedule and cannot be deleted. ' \ 'Please unlink or delete the schedule first.' end end describe "when an order cycle has any coordinator_fees" do let(:enterprise_fee1) { create(:enterprise_fee) } before do oc.coordinator_fees << enterprise_fee1 end it "actually delete the order cycle" do get :destroy, params: { id: oc.id } expect(OrderCycle.find_by(id: oc.id)).to be nil expect(response).to redirect_to admin_order_cycles_path end describe "when the order_cycle was previously cloned" do let(:cloned) { oc.clone! } it "actually delete the order cycle" do get :destroy, params: { id: cloned.id } expect(OrderCycle.find_by(id: cloned.id)).to be nil expect(OrderCycle.find_by(id: oc.id)).not_to be nil expect(EnterpriseFee.find_by(id: enterprise_fee1.id)).not_to be nil expect(response).to redirect_to admin_order_cycles_path end end end end end