Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit 106e010592a5fbcb9bdf2d1ecc3ba64654aaaa58 @mikekelly committed Jul 28, 2012
Showing with 207 additions and 0 deletions.
  1. +14 −0 README.md
  2. +5 −0 Rakefile
  3. +46 −0 lib/checkout.rb
  4. +47 −0 spec/checkout_integration_spec.rb
  5. +32 −0 spec/checkout_spec.rb
  6. +47 −0 spec/order_spec.rb
  7. +16 −0 spec/product_spec.rb
@@ -0,0 +1,14 @@
+A Pricing Mechanism in Ruby
+===========================
+Goal: produce something with an API that works like this:
+
+```
+co = Checkout.new(pricing_rules)
+co.scan(item)
+co.scan(another_item)
+co.total
+```
+
+Running The Tests
+-----------------
+`rake test`
@@ -0,0 +1,5 @@
+require 'rake/testtask'
+
+Rake::TestTask.new do |t|
+ t.pattern = "spec/*_spec.rb"
+end
@@ -0,0 +1,46 @@
+require 'ostruct'
+
+class Checkout
+ def initialize(pricing_rules = Module.new, order = Order.new)
+ @pricing_rules = pricing_rules
+ @order = order
+ order.extend(@pricing_rules)
+ end
+
+ def scan(item)
+ @order << item.to_s
+ end
+
+ def total
+ @order.calculate!
+ end
+
+ class Order
+ def initialize(items = [], product_klass = Product)
+ @items = items
+ @product_klass = product_klass
+ end
+
+ def <<(item)
+ @items << @product_klass.find_by_code(item)
+ end
+
+ def calculate!
+ @items.reduce(0) { |total, item| total += item.price }
+ end
+ end
+
+ class Product
+ PRODUCTS = [
+ { code: 'FR1', name: 'Fruit tea', price: 3.11 },
+ { code: 'SR1', name: 'Strawberries', price: 5.00 },
+ { code: 'CF1', name: 'Coffee', price: 11.23 }
+ ]
+
+ def self.find_by_code(code)
+ product = PRODUCTS.find { |p| p[:code] == code } or
+ raise ArgumentError, "Invalid product code supplied"
+ OpenStruct.new(product)
+ end
+ end
+end
@@ -0,0 +1,47 @@
+require 'minitest/autorun'
+require_relative '../lib/checkout.rb'
+
+module ExamplePricingRules
+ def adjust_prices!
+ if hit_strawberry_threshold?
+ @items.each do |item|
+ if item.code == 'SR1'
+ item.price = 4.50
+ end
+ end
+ end
+ end
+
+ def calculate!
+ adjust_prices!
+ super
+ end
+
+ def hit_strawberry_threshold?
+ @items.select { |item| item.code == 'SR1' }.count > 2
+ end
+end
+
+describe Checkout do
+ before do
+ @pricing_rules = ExamplePricingRules
+ end
+
+ it "satisfies the first test data" do
+ co = Checkout.new(@pricing_rules)
+ [:FR1, :SR1, :FR1, :CF1].each { |item| co.scan(item) }
+ assert_equal 22.45, co.total
+ end
+
+ it "satisfies the second test data" do
+ co = Checkout.new(@pricing_rules)
+ [:FR1, :FR1].each { |item| co.scan(item) }
+ assert_equal 6.22, co.total
+ end
+
+ it "satisfies the third test data" do
+ co = Checkout.new(@pricing_rules)
+ [:SR1, :SR1, :FR1, :SR1].each { |item| co.scan(item) }
+ assert_equal 16.61, co.total
+ end
+end
@@ -0,0 +1,32 @@
+require 'minitest/autorun'
+require_relative '../lib/checkout.rb'
+
+module ExamplePricingRules; end
+
+describe Checkout do
+ it "can be instantiated" do
+ Checkout.new
+ end
+
+ it "can be instantiated with pricing rules" do
+ Checkout.new(ExamplePricingRules)
+ end
+
+ describe "#scan" do
+ before do
+ @order = MiniTest::Mock.new
+ @order.expect :extend, nil, [ExamplePricingRules]
+ @co = Checkout.new(ExamplePricingRules, @order)
+ end
+
+ it "creates and stores an Item" do
+ item = 'XYZ'
+ @order.expect :<<, nil, [item]
+ @co.scan(item)
+ end
+
+ after do
+ @order.verify
+ end
+ end
+end
@@ -0,0 +1,47 @@
+require 'minitest/autorun'
+require_relative '../lib/checkout.rb'
+
+describe Checkout::Order do
+ it "can be instantiated" do
+ Checkout::Order.new
+ end
+
+ describe "#<<" do
+ before do
+ @items = MiniTest::Mock.new
+ @product_klass = MiniTest::Mock.new
+ end
+
+ it "stores items pushed in with a code" do
+ @order = Checkout::Order.new(@items, @product_klass)
+
+ item_code = 'XYZ'
+ item = OpenStruct.new
+
+ @product_klass.expect :find_by_code, item, [item_code]
+ @items.expect :<<, nil, [item]
+
+ @order << item_code
+ end
+
+ after do
+ @product_klass.verify
+ @items.verify
+ end
+ end
+
+ describe "#calculate" do
+ before do
+ @items = []
+ 3.times do
+ @items << OpenStruct.new({ price: 10.00 })
+ end
+ @co = Checkout::Order.new(@items)
+ end
+
+ it "calculates a price" do
+ result = @co.calculate!
+ assert_equal 30, result
+ end
+ end
+end
@@ -0,0 +1,16 @@
+require 'minitest/autorun'
+require_relative '../lib/checkout.rb'
+
+describe Checkout::Product do
+ describe ".find_by_code" do
+ it "can find each of the three products" do
+ ['FR1', 'SR1', 'CF1'].each do |code|
+ Checkout::Product.find_by_code(code).wont_be_nil
+ end
+ end
+
+ it "throws an error if an invalid code is submitted" do
+ lambda { Checkout::Product.find_by_code 'bogus' }.must_raise(ArgumentError)
+ end
+ end
+end

0 comments on commit 106e010

Please sign in to comment.