From 39f1eac300327411b1acb9f6d100f8bc0244c86b Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 21 May 2024 14:32:11 +1000 Subject: [PATCH 1/4] Move invoices specs to the invoices spec file The orders file is too big and causes a bottleneck for parallelising specs. Maybe they should be merged with the above specs, but I'm not familiar enough to know for sure. --- spec/system/admin/order_spec.rb | 161 -------------------- spec/system/admin/orders/invoices_spec.rb | 175 ++++++++++++++++++++++ 2 files changed, 175 insertions(+), 161 deletions(-) diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index b0b08badee..5a0ff97a0b 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -1140,165 +1140,4 @@ RSpec.describe ' end end end - - describe "Legal Invoices", feature: :invoices do - before do - login_as user - end - - describe "for order states" do - context "complete" do - let!(:order1) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'complete', - payment_state: 'balance_due', - customer_id: customer.id) - } - - context "editing the order" do - before do - visit spree.edit_admin_order_path(order1) - end - - it "displays the invoice tab" do - expect(page).to have_content "Complete".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "visiting the invoices tab" do - let!(:table_header) { - [ - "Date/Time", - "Invoice Number", - "Amount", - "Status", - "File", - ].join(" ").upcase - } - - let(:invoice_number){ "#{order.distributor_id}-1" } - let(:table_contents) { - [ - Invoice.first.created_at.strftime('%B %d, %Y').to_s, - invoice_number, - "0.0", - "Active", - "Download" - ].join(" ") - } - let(:download_href) { - "#{spree.print_admin_order_path(order1)}?invoice_id=#{Invoice.last.id}" - } - - before do - Spree::Config[:enterprise_number_required_on_invoices?] = false - visit spree.admin_order_invoices_path(order1) - end - - it "displays the invoices table" do - # with no invoices, only the table header is displayed - expect(page).to have_css "table.index" - expect(page).to have_content "#{customer.first_name} #{customer.last_name} -" - expect(page.find("table").text).to have_content(table_header) - - # the New invoice button + the warning should be visible - expect(page).to have_link "Create or Update Invoice" - expect(page).to have_content "The order has changed since the last invoice update." - click_link "Create or Update Invoice" - - # and disappear after clicking - expect(page).not_to have_link "Create or Update Invoice" - expect(page).not_to have_content "The order has changed since the last invoice update." - - # creating an invoice, displays a second row - expect(page.find("table").text).to have_content(table_contents) - - # with a valid invoice download link - expect(page).to have_link("Download", - href: download_href) - end - - context "the Create or Update Invoice button" do - context "when an ABN number is mandatory for invoices but not present" do - before do - Spree::Config[:enterprise_number_required_on_invoices?] = true - end - - it "displays a warning that an ABN is required when it's clicked" do - visit spree.admin_order_invoices_path(order1) - message = accept_prompt { click_link "Create or Update Invoice" } - distributor = order1.distributor - expect(message) - .to eq "#{distributor.name} must have a valid ABN before invoices can be used." - end - end - end - end - end - - context "resumed" do - let!(:order2) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'resumed', - payment_state: 'balance_due') - } - before do - visit spree.edit_admin_order_path(order2) - end - - it "displays the invoice tab" do - expect(page).to have_content "Resumed".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "canceled" do - let!(:order3) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'canceled', - payment_state: 'balance_due') - } - before do - visit spree.edit_admin_order_path(order3) - end - - it "displays the invoice tab" do - expect(page).to have_content "Cancelled".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "cart" do - let!(:order_empty) { - create(:order_with_line_items, user:, distributor:, order_cycle:, - line_items_count: 0) - } - before do - visit spree.edit_admin_order_path(order_empty) - end - - it "should not display the invoice tab" do - expect(page).to have_content "Cart".upcase - expect(page).not_to have_content "Invoices".upcase - end - end - - context "payment" do - let!(:order4) do - create(:order_ready_for_payment, user:, distributor:, - order_cycle:, - payment_state: 'balance_due') - end - before do - visit spree.edit_admin_order_path(order4) - end - - it "should not display the invoice tab" do - expect(page).to have_content "Payment".upcase - expect(page).not_to have_content "Invoices".upcase - end - end - end - end end diff --git a/spec/system/admin/orders/invoices_spec.rb b/spec/system/admin/orders/invoices_spec.rb index b5ade9edc9..b101a28a7a 100644 --- a/spec/system/admin/orders/invoices_spec.rb +++ b/spec/system/admin/orders/invoices_spec.rb @@ -211,3 +211,178 @@ RSpec.describe ' end end end + +RSpec.describe "Invoice order states", feature: :invoices do + let(:user) { create(:user) } + let(:product) { create(:simple_product) } + let(:distributor) { create(:distributor_enterprise, owner: user, charges_sales_tax: true) } + let(:order_cycle) do + create(:simple_order_cycle, name: 'One', distributors: [distributor], + variants: [product.variants.first]) + end + let(:order) do + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'complete', + payment_state: 'balance_due') + end + let(:customer) { order.customer } + + before do + order.finalize! + + login_as user + end + + context "complete" do + let!(:order1) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'complete', + payment_state: 'balance_due', + customer_id: customer.id) + } + + context "editing the order" do + before do + visit spree.edit_admin_order_path(order1) + end + + it "displays the invoice tab" do + expect(page).to have_content "Complete".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "visiting the invoices tab" do + let!(:table_header) { + [ + "Date/Time", + "Invoice Number", + "Amount", + "Status", + "File", + ].join(" ").upcase + } + + let(:invoice_number){ "#{order.distributor_id}-1" } + let(:table_contents) { + [ + Invoice.first.created_at.strftime('%B %d, %Y').to_s, + invoice_number, + "0.0", + "Active", + "Download" + ].join(" ") + } + let(:download_href) { + "#{spree.print_admin_order_path(order1)}?invoice_id=#{Invoice.last.id}" + } + + before do + Spree::Config[:enterprise_number_required_on_invoices?] = false + visit spree.admin_order_invoices_path(order1) + end + + it "displays the invoices table" do + # with no invoices, only the table header is displayed + expect(page).to have_css "table.index" + expect(page).to have_content "#{customer.first_name} #{customer.last_name} -" + expect(page.find("table").text).to have_content(table_header) + + # the New invoice button + the warning should be visible + expect(page).to have_link "Create or Update Invoice" + expect(page).to have_content "The order has changed since the last invoice update." + click_link "Create or Update Invoice" + + # and disappear after clicking + expect(page).not_to have_link "Create or Update Invoice" + expect(page).not_to have_content "The order has changed since the last invoice update." + + # creating an invoice, displays a second row + expect(page.find("table").text).to have_content(table_contents) + + # with a valid invoice download link + expect(page).to have_link("Download", + href: download_href) + end + + context "the Create or Update Invoice button" do + context "when an ABN number is mandatory for invoices but not present" do + before do + Spree::Config[:enterprise_number_required_on_invoices?] = true + end + + it "displays a warning that an ABN is required when it's clicked" do + visit spree.admin_order_invoices_path(order1) + message = accept_prompt { click_link "Create or Update Invoice" } + distributor = order1.distributor + expect(message) + .to eq "#{distributor.name} must have a valid ABN before invoices can be used." + end + end + end + end + end + + context "resumed" do + let!(:order2) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'resumed', + payment_state: 'balance_due') + } + before do + visit spree.edit_admin_order_path(order2) + end + + it "displays the invoice tab" do + expect(page).to have_content "Resumed".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "canceled" do + let!(:order3) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'canceled', + payment_state: 'balance_due') + } + before do + visit spree.edit_admin_order_path(order3) + end + + it "displays the invoice tab" do + expect(page).to have_content "Cancelled".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "cart" do + let!(:order_empty) { + create(:order_with_line_items, user:, distributor:, order_cycle:, + line_items_count: 0) + } + before do + visit spree.edit_admin_order_path(order_empty) + end + + it "should not display the invoice tab" do + expect(page).to have_content "Cart".upcase + expect(page).not_to have_content "Invoices".upcase + end + end + + context "payment" do + let!(:order4) do + create(:order_ready_for_payment, user:, distributor:, + order_cycle:, + payment_state: 'balance_due') + end + before do + visit spree.edit_admin_order_path(order4) + end + + it "should not display the invoice tab" do + expect(page).to have_content "Payment".upcase + expect(page).not_to have_content "Invoices".upcase + end + end +end From 1d11f8c85b08f96a2d7c58bf93c2453c35a63fe4 Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 21 May 2024 16:55:17 +1000 Subject: [PATCH 2/4] Move bulk order actions to separate spec file This was another large file, potentially causing a bottleneck. All the order setup is duplicated from the other file which is a bit of shame, but I think it makes sense. --- spec/system/admin/orders/bulk_actions_spec.rb | 473 ++++++++++++++++++ spec/system/admin/orders_spec.rb | 378 -------------- 2 files changed, 473 insertions(+), 378 deletions(-) create mode 100644 spec/system/admin/orders/bulk_actions_spec.rb diff --git a/spec/system/admin/orders/bulk_actions_spec.rb b/spec/system/admin/orders/bulk_actions_spec.rb new file mode 100644 index 0000000000..e93cfe4b5f --- /dev/null +++ b/spec/system/admin/orders/bulk_actions_spec.rb @@ -0,0 +1,473 @@ +# frozen_string_literal: true + +require "system_helper" + +RSpec.describe ' + As an administrator + I want to perform bulk order actions +' do + include AuthenticationHelper + include WebHelper + + let(:owner) { create(:user) } + let(:owner2) { create(:user) } + let(:customer) { create(:user) } + let(:customer2) { create(:user) } + let(:customer3) { create(:user) } + let(:customer4) { create(:user) } + let(:customer5) { create(:user) } + let(:billing_address) { create(:address, :randomized) } + let(:billing_address2) { create(:address, :randomized) } + let(:billing_address3) { create(:address, :randomized) } + let(:billing_address4) { create(:address, :randomized) } + let(:billing_address5) { create(:address, :randomized) } + let(:product) { create(:simple_product) } + let(:distributor) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor2) { create(:distributor_enterprise_with_tax, owner:) } + let(:distributor3) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor4) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor5) { create(:distributor_enterprise, owner: owner2, charges_sales_tax: true) } + let!(:shipping_method) { + create(:shipping_method_with, :pickup, name: "pick_up", + distributors: [distributor, distributor2, distributor3]) + } + let!(:shipping_method2) { + create(:shipping_method_with, :pickup, name: "delivery", + distributors: [distributor4, distributor5]) + } + let(:order_cycle) do + create(:simple_order_cycle, name: 'One', distributors: [distributor, distributor2, + distributor3, distributor4], + variants: [product.variants.first]) + end + + context "with a complete order" do + let(:order) do + create(:order_with_totals_and_distribution, user: customer, distributor:, + order_cycle:, + state: 'complete', payment_state: 'balance_due', + bill_address_id: billing_address.id) + end + + let!(:order_cycle2) { + create(:simple_order_cycle, name: 'Two', orders_close_at: 2.weeks.from_now) + } + let!(:order_cycle3) { + create(:simple_order_cycle, name: 'Three', orders_close_at: 3.weeks.from_now) + } + let!(:order_cycle4) { + create(:simple_order_cycle, name: 'Four', orders_close_at: 4.weeks.from_now) + } + let!(:order_cycle5) do + create(:simple_order_cycle, name: 'Five', coordinator: distributor5, + distributors: [distributor5], variants: [product.variants.first]) + end + + let!(:order2) { + create(:order_ready_to_ship, user: customer2, distributor: distributor2, + order_cycle: order_cycle2, completed_at: 2.days.ago, + bill_address_id: billing_address2.id) + } + let!(:order3) { + create(:order_with_credit_payment, user: customer3, distributor: distributor3, + order_cycle: order_cycle3, + bill_address_id: billing_address3.id) + } + let!(:order4) { + create(:order_with_credit_payment, user: customer4, distributor: distributor4, + order_cycle: order_cycle4, + bill_address_id: billing_address4.id) + } + let!(:order5) { + create(:order_ready_to_ship, user: customer5, distributor: distributor5, + order_cycle: order_cycle5, + bill_address_id: billing_address5.id) + } + + context "as a super admin" do + before do + login_as_admin + visit spree.admin_orders_path + end + + context "can bulk send invoices per email" do + before do + Spree::Config[:enable_invoices?] = true + Spree::Config[:enterprise_number_required_on_invoices?] = false + end + + context "with multiple orders with differents states" do + before do + order2.update(state: "complete") + order3.update(state: "resumed") + order4.update(state: "canceled") + order5.update(state: "payment") + end + + it "can bulk send invoices per email, but only for the 'complete' or 'resumed' ones" do + within "#listing_orders" do + page.find("input[name='bulk_ids[]'][value='#{order2.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order3.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order4.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order5.id}']").click + end + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Send Invoices").click + end + + expect(page).to have_content "This will email customer invoices " \ + "for all selected complete orders." + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content "Invoice emails sent for 2 orders." + end + end + + it "can bulk send confirmation email from 2 orders" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Resend Confirmation").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content "Confirmation emails sent for 2 orders." + end + end + + context "can bulk print invoices" do + let(:order4_selector){ "#order_#{order4.id} input[name='bulk_ids[]']" } + let(:order5_selector){ "#order_#{order5.id} input[name='bulk_ids[]']" } + + shared_examples "can bulk print invoices from 2 orders" do + it "bulk prints invoices in pdf format" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + # we don't run Sidekiq in test environment, so we need to manually run enqueued jobs + # to generate PDF files, and change the modal accordingly + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + + within ".modal-content" do + expect(page).to have_link(class: "button", text: "VIEW FILE", href: /invoices/) + + invoice_content = extract_pdf_content + + expect(invoice_content).to have_content("TAX INVOICE", count: 2) + expect(invoice_content).to have_content(order4.number.to_s) + expect(invoice_content).to have_content(order5.number.to_s) + expect(invoice_content).to have_content(distributor4.name.to_s) + expect(invoice_content).to have_content(distributor5.name.to_s) + expect(invoice_content).to have_content(order_cycle4.name.to_s) + expect(invoice_content).to have_content(order_cycle5.name.to_s) + end + end + end + + shared_examples "should ignore the non invoiceable order" do + it "bulk prints invoices in pdf format" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + + within ".modal-content" do + expect(page).to have_link(class: "button", text: "VIEW FILE", + href: /invoices/) + + invoice_content = extract_pdf_content + + expect(invoice_content).to have_content("TAX INVOICE", count: 1) + expect(invoice_content).not_to have_content(order4.number.to_s) + expect(invoice_content).to have_content(order5.number.to_s) + expect(invoice_content).not_to have_content(distributor4.name.to_s) + expect(invoice_content).to have_content(distributor5.name.to_s) + expect(invoice_content).not_to have_content(order_cycle4.name.to_s) + expect(invoice_content).to have_content(order_cycle5.name.to_s) + end + end + end + + context "ABN is not required" do + before do + allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) + .and_return false + end + + it_behaves_like "can bulk print invoices from 2 orders" + + context "with legal invoices feature", feature: :invoices do + it_behaves_like "can bulk print invoices from 2 orders" + end + + context "one of the two orders is not invoiceable" do + before do + order4.cancel! + end + + it_behaves_like "should ignore the non invoiceable order" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should ignore the non invoiceable order" + end + end + + context "ordering by customer name" do + context "ascending" do + let!(:surnames) { + [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), + order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort + } + it "orders by customer name ascending" do + page.find('a', text: "NAME").click # orders alphabetically (asc) + sleep(0.5) # waits for column sorting + + page.find("#selectAll").click + + print_all_invoices + + invoice_content = extract_pdf_content + + expect( + invoice_content.join + ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) + end + end + context "descending" do + let!(:surnames) { + [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), + order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort.reverse + } + it "order by customer name descending" do + page.find('a', text: "NAME").click # orders alphabetically (asc) + sleep(0.5) # waits for column sorting + page.find('a', text: "NAME").click # orders alphabetically (desc) + sleep(0.5) # waits for column sorting + + page.find("#selectAll").click + + print_all_invoices + + invoice_content = extract_pdf_content + + expect( + invoice_content.join + ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) + end + end + end + end + context "ABN is required" do + before do + allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) + .and_return true + end + context "All the distributors setup the ABN" do + before do + order4.distributor.update(abn: "123456789") + order5.distributor.update(abn: "987654321") + end + context "all the orders are invoiceable (completed/resumed)" do + it_behaves_like "can bulk print invoices from 2 orders" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "can bulk print invoices from 2 orders" + end + end + + context "one of the two orders is not invoiceable" do + before do + order4.cancel! + end + + it_behaves_like "should ignore the non invoiceable order" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should ignore the non invoiceable order" + end + end + end + context "the distributor of one of the order didn't set the ABN" do + before do + order4.distributor.update(abn: "123456789") + order5.distributor.update(abn: nil) + end + + shared_examples "should not print the invoice" do + it "should render a warning message" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.not_to enqueue_job(BulkInvoiceJob) + end + + expect(page).not_to have_content "Compiling Invoices" + expect(page).not_to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + expect(page).to have_content "#{ + order5.distributor.name + } must have a valid ABN before invoices can be used." + end + end + it_behaves_like "should not print the invoice" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should not print the invoice" + end + end + end + end + it "can bulk cancel 2 orders" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Cancel Orders").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + expect(page).to have_content "This will cancel the current order." + + within ".reveal-modal" do + uncheck "Send a cancellation email to the customer" + expect { + find_button("Cancel").click # Cancels the cancel action + }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Cancel Orders").click + end + + within ".reveal-modal" do + expect { + find_button("Confirm").click # Confirms the cancel action + }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content("CANCELLED", count: 2) + end + end + + context "for a hub manager" do + before do + login_as owner2 + visit spree.admin_orders_path + end + + it "displays the orders for the respective distributor" do + expect(page).to have_content order5.number # displays the only order for distributor5 + expect(page).not_to have_content order.number + expect(page).not_to have_content order2.number + expect(page).not_to have_content order3.number + expect(page).not_to have_content order4.number + end + + it "cannot send emails to orders if permission have been revoked in the meantime" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + # Find the clicked order + order = Spree::Order.find_by( + id: page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").value + ) + # Revoke permission for the current user on that specific order by changing its owners + order.update_attribute(:distributor, distributor) + order.update_attribute(:order_cycle, order_cycle) + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Resend Confirmation").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.not_to enqueue_job(ActionMailer::MailDeliveryJob) + end + end + end + end + + def extract_pdf_content + # Extract last part of invoice URL + link = page.find(class: "button", text: "VIEW FILE") + filename = link[:href].match %r{/invoices/.*} + + # Load invoice temp file directly instead of downloading + reader = PDF::Reader.new("tmp/#{filename}.pdf") + reader.pages.map(&:text) + end + + def print_all_invoices + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + end +end diff --git a/spec/system/admin/orders_spec.rb b/spec/system/admin/orders_spec.rb index a2ce6a82a0..deedf07e3f 100644 --- a/spec/system/admin/orders_spec.rb +++ b/spec/system/admin/orders_spec.rb @@ -504,358 +504,6 @@ RSpec.describe ' end end - context "bulk actions" do - context "as a super admin" do - before do - login_as_admin - visit spree.admin_orders_path - end - - context "can bulk send invoices per email" do - before do - Spree::Config[:enable_invoices?] = true - Spree::Config[:enterprise_number_required_on_invoices?] = false - end - - context "with multiple orders with differents states" do - before do - order2.update(state: "complete") - order3.update(state: "resumed") - order4.update(state: "canceled") - order5.update(state: "payment") - end - - it "can bulk send invoices per email, but only for the 'complete' or 'resumed' ones" do - within "#listing_orders" do - page.find("input[name='bulk_ids[]'][value='#{order2.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order3.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order4.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order5.id}']").click - end - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Send Invoices").click - end - - expect(page).to have_content "This will email customer invoices " \ - "for all selected complete orders." - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content "Invoice emails sent for 2 orders." - end - end - - it "can bulk send confirmation email from 2 orders" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Resend Confirmation").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content "Confirmation emails sent for 2 orders." - end - end - - context "can bulk print invoices" do - let(:order4_selector){ "#order_#{order4.id} input[name='bulk_ids[]']" } - let(:order5_selector){ "#order_#{order5.id} input[name='bulk_ids[]']" } - - shared_examples "can bulk print invoices from 2 orders" do - it "bulk prints invoices in pdf format" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - # we don't run Sidekiq in test environment, so we need to manually run enqueued jobs - # to generate PDF files, and change the modal accordingly - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - - within ".modal-content" do - expect(page).to have_link(class: "button", text: "VIEW FILE", href: /invoices/) - - invoice_content = extract_pdf_content - - expect(invoice_content).to have_content("TAX INVOICE", count: 2) - expect(invoice_content).to have_content(order4.number.to_s) - expect(invoice_content).to have_content(order5.number.to_s) - expect(invoice_content).to have_content(distributor4.name.to_s) - expect(invoice_content).to have_content(distributor5.name.to_s) - expect(invoice_content).to have_content(order_cycle4.name.to_s) - expect(invoice_content).to have_content(order_cycle5.name.to_s) - end - end - end - - shared_examples "should ignore the non invoiceable order" do - it "bulk prints invoices in pdf format" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - - within ".modal-content" do - expect(page).to have_link(class: "button", text: "VIEW FILE", - href: /invoices/) - - invoice_content = extract_pdf_content - - expect(invoice_content).to have_content("TAX INVOICE", count: 1) - expect(invoice_content).not_to have_content(order4.number.to_s) - expect(invoice_content).to have_content(order5.number.to_s) - expect(invoice_content).not_to have_content(distributor4.name.to_s) - expect(invoice_content).to have_content(distributor5.name.to_s) - expect(invoice_content).not_to have_content(order_cycle4.name.to_s) - expect(invoice_content).to have_content(order_cycle5.name.to_s) - end - end - end - - context "ABN is not required" do - before do - allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) - .and_return false - end - - it_behaves_like "can bulk print invoices from 2 orders" - - context "with legal invoices feature", feature: :invoices do - it_behaves_like "can bulk print invoices from 2 orders" - end - - context "one of the two orders is not invoiceable" do - before do - order4.cancel! - end - - it_behaves_like "should ignore the non invoiceable order" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should ignore the non invoiceable order" - end - end - - context "ordering by customer name" do - context "ascending" do - let!(:surnames) { - [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), - order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort - } - it "orders by customer name ascending" do - page.find('a', text: "NAME").click # orders alphabetically (asc) - sleep(0.5) # waits for column sorting - - page.find("#selectAll").click - - print_all_invoices - - invoice_content = extract_pdf_content - - expect( - invoice_content.join - ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) - end - end - context "descending" do - let!(:surnames) { - [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), - order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort.reverse - } - it "order by customer name descending" do - page.find('a', text: "NAME").click # orders alphabetically (asc) - sleep(0.5) # waits for column sorting - page.find('a', text: "NAME").click # orders alphabetically (desc) - sleep(0.5) # waits for column sorting - - page.find("#selectAll").click - - print_all_invoices - - invoice_content = extract_pdf_content - - expect( - invoice_content.join - ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) - end - end - end - end - context "ABN is required" do - before do - allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) - .and_return true - end - context "All the distributors setup the ABN" do - before do - order4.distributor.update(abn: "123456789") - order5.distributor.update(abn: "987654321") - end - context "all the orders are invoiceable (completed/resumed)" do - it_behaves_like "can bulk print invoices from 2 orders" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "can bulk print invoices from 2 orders" - end - end - - context "one of the two orders is not invoiceable" do - before do - order4.cancel! - end - - it_behaves_like "should ignore the non invoiceable order" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should ignore the non invoiceable order" - end - end - end - context "the distributor of one of the order didn't set the ABN" do - before do - order4.distributor.update(abn: "123456789") - order5.distributor.update(abn: nil) - end - - shared_examples "should not print the invoice" do - it "should render a warning message" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.not_to enqueue_job(BulkInvoiceJob) - end - - expect(page).not_to have_content "Compiling Invoices" - expect(page).not_to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - expect(page).to have_content "#{ - order5.distributor.name - } must have a valid ABN before invoices can be used." - end - end - it_behaves_like "should not print the invoice" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should not print the invoice" - end - end - end - end - it "can bulk cancel 2 orders" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Cancel Orders").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - expect(page).to have_content "This will cancel the current order." - - within ".reveal-modal" do - uncheck "Send a cancellation email to the customer" - expect { - find_button("Cancel").click # Cancels the cancel action - }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Cancel Orders").click - end - - within ".reveal-modal" do - expect { - find_button("Confirm").click # Confirms the cancel action - }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content("CANCELLED", count: 2) - end - end - - context "for a hub manager" do - before do - login_as owner2 - visit spree.admin_orders_path - end - - it "displays the orders for the respective distributor" do - expect(page).to have_content order5.number # displays the only order for distributor5 - expect(page).not_to have_content order.number - expect(page).not_to have_content order2.number - expect(page).not_to have_content order3.number - expect(page).not_to have_content order4.number - end - - it "cannot send emails to orders if permission have been revoked in the meantime" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - # Find the clicked order - order = Spree::Order.find_by( - id: page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").value - ) - # Revoke permission for the current user on that specific order by changing its owners - order.update_attribute(:distributor, distributor) - order.update_attribute(:order_cycle, order_cycle) - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Resend Confirmation").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.not_to enqueue_job(ActionMailer::MailDeliveryJob) - end - end - end - end - context "pagination" do before do login_as_admin @@ -1131,30 +779,4 @@ RSpec.describe ' expect(find("input.datepicker").value).to be_empty end end - def extract_pdf_content - # Extract last part of invoice URL - link = page.find(class: "button", text: "VIEW FILE") - filename = link[:href].match %r{/invoices/.*} - - # Load invoice temp file directly instead of downloading - reader = PDF::Reader.new("tmp/#{filename}.pdf") - reader.pages.map(&:text) - end - - def print_all_invoices - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - end end From 85165369a243774064a448ce52a147c76758a51e Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 21 May 2024 17:01:12 +1000 Subject: [PATCH 3/4] Move run runner over for system specs. --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b553c7b74..e49c391133 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,10 +106,10 @@ jobs: # [n] - where the n is a number of parallel jobs you want to run your tests on. # Use a higher number if you have slow tests to split them between more parallel jobs. # Remember to update the value of the `ci_node_index` below to (0..n-1). - ci_node_total: [5] + ci_node_total: [4] # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. - ci_node_index: [0, 1, 2, 3, 4] + ci_node_index: [0, 1, 2, 3] steps: - uses: actions/checkout@v3 @@ -165,10 +165,10 @@ jobs: # [n] - where the n is a number of parallel jobs you want to run your tests on. # Use a higher number if you have slow tests to split them between more parallel jobs. # Remember to update the value of the `ci_node_index` below to (0..n-1). - ci_node_total: [13] + ci_node_total: [14] # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. - ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] steps: - uses: actions/checkout@v3 From 5ff89a279cc63b003f2b8d4861676e9b6e22325e Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 21 May 2024 17:05:08 +1000 Subject: [PATCH 4/4] Use Spring to save on subsequent boot times Each time we run a rails command, it can take some time to load up (I think it was 20s). We run two commands (db setup, then rspec), so the second one should be faster now. --- .github/workflows/build.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e49c391133..66f7d5a3cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,7 +65,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -83,7 +83,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}" run: | git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3) - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec models: runs-on: ubuntu-22.04 @@ -125,7 +125,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -142,7 +142,7 @@ jobs: #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec system_admin: runs-on: ubuntu-22.04 @@ -191,7 +191,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -210,7 +210,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:queue:rspec + bin/rake knapsack_pro:queue:rspec - name: Archive failed tests screenshots if: failure() @@ -268,7 +268,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -287,7 +287,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:queue:rspec + bin/rake knapsack_pro:queue:rspec - name: Archive failed tests screenshots if: failure() @@ -346,7 +346,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -365,7 +365,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec - name: Archive failed tests screenshots if: failure() @@ -424,7 +424,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -441,7 +441,7 @@ jobs: #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec non_knapsack_jest_karma: runs-on: ubuntu-22.04 @@ -475,7 +475,7 @@ jobs: run: yarn install --frozen-lockfile - name: Run JS tests - run: bundle exec rake karma:run + run: bin/rake karma:run - name: Run jest tests run: yarn jest