Add service objects for summarizing outcomes of standing order processing jobs

This commit is contained in:
Rob Harrington
2017-11-23 18:20:05 +11:00
parent 0c0e12e165
commit cf30b7c883
4 changed files with 259 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
require 'open_food_network/standing_order_summary'
# Used by for StandingOrderPlacementJob and StandingOrderConfirmJob to summarize the
# result of automatic processing of standing orders for the relevant shop owners.
module OpenFoodNetwork
class StandingOrderSummarizer
def initialize
@summaries = {}
end
def record_order(order)
summary_for(order).record_order(order)
end
def record_success(order)
summary_for(order).record_success(order)
end
def record_issue(type, order, message)
summary_for(order).record_issue(type, order, message)
end
def record_failure(order)
line1 = "StandingOrderPlacementError: Cannot process order #{order.number} due to errors"
line2 = "Errors: #{order.errors.full_messages.join(', ')}"
Rails.logger.info("#{line1}\n#{line2}")
record_issue(:failure, order, line2)
end
def send_placement_summary_emails
@summaries.values.each do |summary|
StandingOrderMailer.placement_summary_email(summary).deliver
end
end
private
def summary_for(order)
shop_id = order.distributor_id
@summaries[shop_id] ||= StandingOrderSummary.new(shop_id)
end
end
end

View File

@@ -0,0 +1,37 @@
module OpenFoodNetwork
class StandingOrderSummary
attr_reader :shop_id, :order_count, :success_count, :issues
def initialize(shop_id)
@shop_id = shop_id
@order_ids = []
@success_ids = []
@issues = {}
end
def record_order(order)
@order_ids << order.id
end
def record_success(order)
@success_ids << order.id
end
def record_issue(type, order, message)
issues[type] ||= []
issues[type][order.id] = message
end
def order_count
@order_ids.count
end
def success_count
@success_ids.count
end
def issue_count
(@order_ids - @success_ids).count
end
end
end

View File

@@ -0,0 +1,96 @@
require 'open_food_network/standing_order_summarizer'
module OpenFoodNetwork
describe StandingOrderSummarizer do
let(:order) { create(:order) }
let(:summarizer) { OpenFoodNetwork::StandingOrderSummarizer.new }
describe "#summary_for" do
let(:order) { double(:order, distributor_id: 123) }
context "when a summary for the order's distributor doesn't already exist" do
it "initializes a new summary object, and returns it" do
expect(summarizer.instance_variable_get(:@summaries).count).to be 0
summary = summarizer.send(:summary_for, order)
expect(summary.shop_id).to be 123
expect(summarizer.instance_variable_get(:@summaries).count).to be 1
end
end
context "when a summary for the order's distributor already exists" do
let(:summary) { double(:summary) }
before do
summarizer.instance_variable_set(:@summaries, { 123 => summary })
end
it "returns the existing summary object" do
expect(summarizer.instance_variable_get(:@summaries).count).to be 1
expect(summarizer.send(:summary_for, order)).to eq summary
expect(summarizer.instance_variable_get(:@summaries).count).to be 1
end
end
end
describe "recording events" do
let(:order) { double(:order) }
let(:summary) { double(:summary) }
before { allow(summarizer).to receive(:summary_for).with(order) { summary } }
describe "#record_order" do
it "requests a summary for the order and calls #record_order on it" do
expect(summary).to receive(:record_order).with(order).once
summarizer.record_order(order)
end
end
describe "#record_success" do
it "requests a summary for the order and calls #record_success on it" do
expect(summary).to receive(:record_success).with(order).once
summarizer.record_success(order)
end
end
describe "#record_issue" do
it "requests a summary for the order and calls #record_issue on it" do
expect(summary).to receive(:record_issue).with(:type, order, "message").once
summarizer.record_issue(:type, order, "message")
end
end
describe "#record_failure" do
before do
allow(order).to receive(:number) { "123" }
allow(order).to receive(:errors) { double(:errors, full_messages: ["Some error"]) }
allow(summarizer).to receive(:record_issue)
end
it "sends error info to the rails logger" do
expect(Rails.logger).to receive(:info)
summarizer.record_failure(order)
end
it "calls #record_issue on itself" do
summarizer.record_failure(order)
expect(summarizer).to have_received(:record_issue)
end
end
end
describe "#send_placement_summary_emails" do
let(:summary1) { double(:summary) }
let(:summary2) { double(:summary) }
let(:summaries) { { 1 => summary1, 2 => summary2 } }
let(:mail_mock) { double(:mail, deliver: true) }
before do
summarizer.instance_variable_set(:@summaries, summaries)
end
it "sends a placement summary email for each summary" do
expect(StandingOrderMailer).to receive(:placement_summary_email).twice { mail_mock }
summarizer.send_placement_summary_emails
end
end
end
end

View File

@@ -0,0 +1,83 @@
require 'open_food_network/standing_order_summary'
module OpenFoodNetwork
describe StandingOrderSummary do
let(:summary) { OpenFoodNetwork::StandingOrderSummary.new(123) }
describe "#initialize" do
it "initializes instance variables: shop_id, order_count, success_count and issues" do
expect(summary.shop_id).to be 123
expect(summary.order_count).to be 0
expect(summary.success_count).to be 0
expect(summary.issues).to be_a Hash
end
end
describe "#record_order" do
let(:order) { double(:order, id: 37) }
it "adds the order id to the order_ids array" do
summary.record_order(order)
expect(summary.instance_variable_get(:@order_ids)).to eq [order.id]
end
end
describe "#record_success" do
let(:order) { double(:order, id: 37) }
it "adds the order id to the success_ids array" do
summary.record_success(order)
expect(summary.instance_variable_get(:@success_ids)).to eq [order.id]
end
end
describe "#record_issue" do
let(:order) { double(:order, id: 1) }
context "when no issues of the same type have been recorded yet" do
it "adds a new type to the issues hash, and stores a new issue against it" do
summary.record_issue(:some_type, order, "message")
expect(summary.issues.keys).to include :some_type
expect(summary.issues[:some_type][order.id]).to eq "message"
end
end
context "when an issue of the same type has already been recorded" do
let(:existing_issue) { double(:existing_issue) }
before { summary.issues[:some_type] = [existing_issue] }
it "stores a new issue against the existing type" do
summary.record_issue(:some_type, order, "message")
expect(summary.issues[:some_type]).to include existing_issue
expect(summary.issues[:some_type][order.id]).to eq "message"
end
end
end
describe "#order_count" do
let(:order_ids) { [1,2,3,4,5,6,7] }
it "counts the number of items in the order_ids instance_variable" do
summary.instance_variable_set(:@order_ids, order_ids)
expect(summary.order_count).to be 7
end
end
describe "#success_count" do
let(:success_ids) { [1,2,3,4,5,6,7] }
it "counts the number of items in the success_ids instance_variable" do
summary.instance_variable_set(:@success_ids, success_ids)
expect(summary.success_count).to be 7
end
end
describe "#lissue_count" do
let(:order_ids) { [1,3,5,7,9] }
let(:success_ids) { [1,2,3,4,5] }
it "counts the number of items in order_ids that are not in success_ids" do
summary.instance_variable_set(:@order_ids, order_ids)
summary.instance_variable_set(:@success_ids, success_ids)
expect(summary.issue_count).to be 2 # 7 & 9
end
end
end
end