gentle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +64 -0
  6. data/Rakefile +9 -0
  7. data/gentle.gemspec +29 -0
  8. data/lib/gentle.rb +8 -0
  9. data/lib/gentle/blackboard.rb +44 -0
  10. data/lib/gentle/client.rb +92 -0
  11. data/lib/gentle/documents/document.rb +15 -0
  12. data/lib/gentle/documents/request/hash_converter.rb +55 -0
  13. data/lib/gentle/documents/request/shipment_order.rb +167 -0
  14. data/lib/gentle/documents/response/shipment_order_result.rb +56 -0
  15. data/lib/gentle/error_message.rb +19 -0
  16. data/lib/gentle/message.rb +43 -0
  17. data/lib/gentle/phase_1_set.rb +53 -0
  18. data/lib/gentle/queue.rb +40 -0
  19. data/lib/gentle/request.rb +11 -0
  20. data/lib/gentle/response.rb +11 -0
  21. data/lib/gentle/version.rb +3 -0
  22. data/spec/fixtures/credentials.yml +12 -0
  23. data/spec/fixtures/documents/responses/shipment_order_result.xml +23 -0
  24. data/spec/fixtures/messages/error_message.xml +10 -0
  25. data/spec/fixtures/messages/purchase_order_message.xml +11 -0
  26. data/spec/remote/blackboard_spec.rb +43 -0
  27. data/spec/remote/queue_spec.rb +64 -0
  28. data/spec/spec_helper.rb +323 -0
  29. data/spec/unit/blackboard_spec.rb +102 -0
  30. data/spec/unit/client_spec.rb +97 -0
  31. data/spec/unit/documents/document_spec.rb +44 -0
  32. data/spec/unit/documents/request/hash_converter_spec.rb +42 -0
  33. data/spec/unit/documents/request/shipment_order_spec.rb +164 -0
  34. data/spec/unit/documents/response/shipment_order_result_spec.rb +26 -0
  35. data/spec/unit/error_message_spec.rb +31 -0
  36. data/spec/unit/message_spec.rb +71 -0
  37. data/spec/unit/phase_1_set_spec.rb +76 -0
  38. data/spec/unit/queue_spec.rb +57 -0
  39. metadata +196 -0
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+ require 'gentle/blackboard'
3
+
4
+ module Gentle
5
+ module Documents
6
+ module Response
7
+ class ThingerResponse
8
+ attr_reader :contents
9
+ def initialize(contents)
10
+ @contents = contents
11
+ end
12
+ end
13
+ end
14
+
15
+ module Request
16
+ class ThingerRequest
17
+ attr_reader :contents
18
+ def initialize(contents)
19
+ @contents = contents
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ module Gentle
27
+ describe "Blackboard" do
28
+ before do
29
+ @bucket = mock()
30
+
31
+ @client = mock()
32
+ @client.stubs(:to_quiet_bucket).returns(@bucket)
33
+ @client.stubs(:from_quiet_bucket).returns(@bucket)
34
+
35
+ @io = StringIO.new
36
+
37
+ @document = mock()
38
+ @document.stubs(:to_xml).returns("actually doesn't matter")
39
+ @document.stubs(:filename).returns("abracadabra_1234_xyz.xml")
40
+
41
+ @message = mock()
42
+ @message.stubs(:document_type).returns('ShipmentOrderResult')
43
+ @message.stubs(:document_name).returns('abracadabra_4321_zyx.xml')
44
+
45
+ @blackboard = Blackboard.new(@client)
46
+ end
47
+
48
+ it "should be possible to post a document to the blackboard" do
49
+ @bucket.expects(:objects).returns({@document.filename => @io})
50
+ @blackboard.post(@document)
51
+ assert_io(@document.to_xml, @io)
52
+ end
53
+
54
+ it "should be possible to build a document for a response type" do
55
+ Response.expects(:valid_type?).returns(true)
56
+ response = @blackboard.build_document('ThingerResponse', 'thinger')
57
+ assert_equal 'thinger', response.contents[:io]
58
+ end
59
+
60
+ it "should be possible to build a document for a request type" do
61
+ Request.expects(:valid_type?).returns(true)
62
+ response = @blackboard.build_document('ThingerRequest', 'thinger')
63
+ assert_equal 'thinger', response.contents[:io]
64
+ end
65
+
66
+ it "should return nil if the type was not valid" do
67
+ assert_equal nil, @blackboard.build_document('ThingerRequest', 'thinger')
68
+ end
69
+
70
+ it "should be possible to fetch a document from the blackboard" do
71
+ @io.write('fancy noodles')
72
+ @io.rewind
73
+ @bucket.expects(:objects).returns({@message.document_name => @io})
74
+ Response.expects(:valid_type?).returns(true)
75
+ @message.stubs(:document_type).returns('ThingerResponse')
76
+ assert_equal 'fancy noodles', @blackboard.fetch(@message).contents[:io]
77
+ end
78
+
79
+ it "should be possible to remove a document from the blackboard" do
80
+ s3object = mock()
81
+ s3object.expects(:delete)
82
+ s3object.expects(:exists?).returns(true)
83
+
84
+ @bucket.expects(:objects).returns({@message.document_name => s3object})
85
+ assert_equal true, @blackboard.remove(@message)
86
+ end
87
+
88
+ it "should not raise an exception if the document for removal couldn't be found" do
89
+ s3object = mock()
90
+ s3object.expects(:delete).never
91
+ s3object.expects(:exists?).returns(false)
92
+
93
+ @bucket.expects(:objects).returns({@message.document_name => s3object})
94
+ assert_equal false, @blackboard.remove(@message)
95
+ end
96
+
97
+ def assert_io(expectation, io)
98
+ io.rewind
99
+ assert_equal expectation, io.read
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'gentle/client'
3
+ require 'active_support/core_ext/hash/except'
4
+ require 'active_support/core_ext/hash/keys'
5
+
6
+ module Gentle
7
+ describe "Client" do
8
+ before do
9
+ AWS.config(:stub_requests => true)
10
+ @options = {
11
+ access_key_id: 'abracadabra',
12
+ secret_access_key: 'alakazam',
13
+ client_id: 'Gentle',
14
+ business_unit: 'Gentle',
15
+ warehouse: 'SPACE',
16
+ buckets: {
17
+ to: 'gentle-to-quiet',
18
+ from: 'gentle-from-quiet'
19
+ },
20
+ queues: {
21
+ to: 'http://queue.amazonaws.com/123456789/gentle_to_quiet',
22
+ from: 'http://queue.amazonaws.com/123456789/gentle_from_quiet',
23
+ inventory: 'http://queue.amazonaws.com/123456789/gentle_inventory'
24
+ }
25
+ }
26
+ @client = Client.new(@options)
27
+ end
28
+
29
+ after do
30
+ AWS.config(:stub_requests => false)
31
+ end
32
+
33
+ it "should be able to handle configuration that is passed in with keys as strings" do
34
+ client = Client.new(@options.stringify_keys)
35
+ assert_equal 'Gentle', client.client_id
36
+ assert_equal 'Gentle', client.business_unit
37
+ end
38
+
39
+ it "should not be possible to initialize a client without credentials" do
40
+ assert_raises Client::InitializationError do
41
+ Client.new(@options.except(:access_key_id, :secret_access_key))
42
+ end
43
+ end
44
+
45
+ it "should not be possible to initialize a client without a client_id" do
46
+ assert_raises Client::InitializationError do
47
+ Client.new(@options.except(:client_id))
48
+ end
49
+ end
50
+
51
+ it "should not be possible to initialize a client with a business_unit" do
52
+ assert_raises Client::InitializationError do
53
+ Client.new(@options.except(:business_unit))
54
+ end
55
+ end
56
+
57
+ it "should not be possible to initialize a client with missing bucket information" do
58
+ assert_raises Client::InitializationError do
59
+ Client.new(@options.except(:buckets))
60
+ end
61
+ end
62
+
63
+ it "should not be possible to initialize a client with partial bucket information" do
64
+ assert_raises Client::InitializationError do
65
+ buckets = {to: 'neato'}
66
+ Client.new(@options.merge(buckets: buckets))
67
+ end
68
+ end
69
+
70
+ it "should not be possible to initialize a client with missing queue information" do
71
+ assert_raises Client::InitializationError do
72
+ Client.new(@options.except(:queues))
73
+ end
74
+ end
75
+
76
+ it "should not be possible to initialize a client with partial queue information" do
77
+ assert_raises Client::InitializationError do
78
+ queues = {inventory: 'neato'}
79
+ Client.new(@options.merge(queues: queues))
80
+ end
81
+ end
82
+
83
+ it "should raise an error if the bucket names are invalid" do
84
+ AWS::S3::Bucket.any_instance.expects(:exists?).returns(false)
85
+ assert_raises Client::InvalidBucketError do
86
+ @client.to_quiet_bucket
87
+ end
88
+ end
89
+
90
+ it "should raise an error if the queue names are invalid" do
91
+ AWS::SQS::Queue.any_instance.expects(:exists?).returns(false)
92
+ assert_raises Client::InvalidQueueError do
93
+ @client.to_quiet_queue
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,44 @@
1
+ module Gentle
2
+ module Documents
3
+ module DocumentInterfaceTestcases
4
+
5
+ def test_it_should_be_able_to_generate_a_filename
6
+ assert bn = @object.business_unit
7
+ assert type = @object.type
8
+ assert number = @object.document_number
9
+ assert date = @object.date.strftime("%Y%m%d_%H%M%S")
10
+ expected_filename = "#{bn}_#{type}_#{number}_#{date}.xml"
11
+ assert_equal expected_filename, @object.filename
12
+ end
13
+
14
+ def test_it_should_respond_to_warehouse
15
+ assert @object.respond_to?(:warehouse), "#{@object.class} does not respond to #warehouse"
16
+ end
17
+
18
+ def test_it_should_be_initializable_with_a_hash
19
+ begin
20
+ @object.class.new(:thinger => 123)
21
+ rescue TypeError
22
+ flunk "It should be possible to initialize #{@object.class} with an args hash"
23
+ rescue StandardError
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ describe "DocumentDouble" do
31
+ include Documents::DocumentInterfaceTestcases
32
+
33
+ before do
34
+ @object = DocumentDouble.new(
35
+ :message_id => '1234567',
36
+ :date => Time.new(2013, 04, 05, 12, 30, 15).utc,
37
+ :client => @client,
38
+ :type => 'Thinger',
39
+ :business_unit => 'Gentle',
40
+ :document_number => '123456'
41
+ )
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'gentle/documents/request/shipment_order'
3
+ require 'gentle/documents/request/hash_converter'
4
+
5
+ module Gentle
6
+ module Documents
7
+ module Request
8
+ describe "ShipmentOrder" do
9
+ include Gentle::Documents::Request::HashConverter
10
+
11
+ it "serializes the line item parts into a hash" do
12
+ part = PartLineItemDouble.example(
13
+ variant: VariantDouble.example(id: 1214, sku: "SKU42", price: 25.95),
14
+ line_item: LineItemDouble.example(quantity: 3)
15
+ )
16
+
17
+ hash = part_line_item_hash(part)
18
+
19
+ assert_equal "SKU42", hash['ItemNumber']
20
+ assert_instance_of Fixnum, hash['Line']
21
+ assert_equal 3, hash['QuantityOrdered']
22
+ assert_equal 3, hash['QuantityToShip']
23
+ assert_equal "EA", hash['UOM']
24
+ assert_equal 25.95, hash['Price']
25
+ end
26
+
27
+ it "serializes the line item into a hash" do
28
+ item = LineItemDouble.example
29
+
30
+ hash = line_item_hash(item)
31
+
32
+ assert_equal item.sku, hash['ItemNumber']
33
+ assert_instance_of Fixnum, hash['Line']
34
+ assert_equal item.quantity, hash['QuantityOrdered']
35
+ assert_equal item.quantity, hash['QuantityToShip']
36
+ assert_equal "EA", hash['UOM']
37
+ assert_equal item.price, hash['Price']
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+ require 'gentle/documents/request/shipment_order'
3
+
4
+ module Gentle
5
+ module Documents
6
+ module Request
7
+ describe "ShipmentOrder" do
8
+ include Gentle::Documents::DocumentInterfaceTestcases
9
+
10
+ before do
11
+ @shipment = ShipmentDouble.example
12
+ @order = @shipment.order
13
+ @client = ClientDouble.new(:client_id => 'Gentle', :business_unit => 'Gentle', :warehouse => 'SPACE')
14
+ @object = @shipment_order = ShipmentOrder.new(shipment: @shipment, client: @client)
15
+ end
16
+
17
+ it "should raise an error if an shipment wasn't passed in" do
18
+ assert_raises KeyError do
19
+ ShipmentOrder.new(client: @client)
20
+ end
21
+ end
22
+
23
+ it "should be able to generate an XML document" do
24
+ message = ShipmentOrder.new(:shipment => @shipment, :client => @client)
25
+ document = Nokogiri::XML::Document.parse(message.to_xml)
26
+
27
+ expected_namespaces = {'xmlns' => ShipmentOrder::NAMESPACE}
28
+ assert_equal expected_namespaces, document.collect_namespaces()
29
+
30
+ assert_equal 1, document.css('ShipOrderDocument').length
31
+
32
+ assert_equal @client.client_id, document.css('ClientID').first.text
33
+ assert_equal @client.business_unit, document.css('BusinessUnit').first.text
34
+
35
+ assert_header(document.css('OrderHeader').first)
36
+ assert_equal @order.note, document.css('Comments').first.text
37
+
38
+ assert_shipping(document.css('ShipMode').first)
39
+
40
+ assert_address(@order.email, @order.shipping_address, document.css('ShipTo').first)
41
+ assert_address(@order.email, @order.billing_address, document.css('BillTo').first)
42
+
43
+ order_details = document.css('OrderDetails')
44
+ assert_equal 1, order_details.length
45
+ assert_line_item(@order.line_items.first, order_details.first)
46
+ end
47
+
48
+ it "uses a sequence as the Line attribute of the OrderDetails" do
49
+ order = OrderDouble.example(line_items: [
50
+ LineItemDouble.example(sku: "SKU-1"),
51
+ LineItemDouble.example(sku: "SKU-2"),
52
+ LineItemDouble.example(sku: "SKU-3")
53
+ ])
54
+ shipment = ShipmentDouble.example(order: order)
55
+
56
+ message = ShipmentOrder.new(:shipment => shipment, :client => @client)
57
+
58
+ document = Nokogiri::XML::Document.parse(message.to_xml)
59
+ order_details = document.css('OrderDetails')
60
+ assert_equal "1", order_details[0]['Line']
61
+ assert_equal "2", order_details[1]['Line']
62
+ assert_equal "3", order_details[2]['Line']
63
+ end
64
+
65
+ it "explodes phase 1 items into individual skus" do
66
+ phase_1_set = LineItemDouble.example(sku: "GPS1-5")
67
+ order = OrderDouble.example(line_items: [
68
+ phase_1_set,
69
+ LineItemDouble.example
70
+ ])
71
+ shipment = ShipmentDouble.example(order: order)
72
+
73
+ message = ShipmentOrder.new(:shipment => shipment, :client => @client)
74
+
75
+ document = Nokogiri::XML::Document.parse(message.to_xml)
76
+ order_details = document.css('OrderDetails')
77
+ assert_equal 5, order_details.count
78
+ assert_equal "GMJC100", order_details[0]['ItemNumber']
79
+ assert_equal "GPM100", order_details[1]['ItemNumber']
80
+ assert_equal "GBD100-3", order_details[2]['ItemNumber']
81
+ assert_equal "GPST100 - 2", order_details[3]['ItemNumber']
82
+ end
83
+
84
+ it "explodes the line items into parts when applicable" do
85
+ line_item_with_parts = LineItemDouble.example(part_line_items: [
86
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GBB200")),
87
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GSC300")),
88
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GML100"))
89
+ ])
90
+ order = OrderDouble.example(line_items: [
91
+ line_item_with_parts,
92
+ LineItemDouble.example
93
+ ])
94
+ shipment = ShipmentDouble.example(order: order)
95
+
96
+ message = ShipmentOrder.new(:shipment => shipment, :client => @client)
97
+
98
+ document = Nokogiri::XML::Document.parse(message.to_xml)
99
+ order_details = document.css('OrderDetails')
100
+ assert_equal 4, order_details.count
101
+ assert_equal "GBB200", order_details[0]['ItemNumber']
102
+ assert_equal "GSC300", order_details[1]['ItemNumber']
103
+ assert_equal "GML100", order_details[2]['ItemNumber']
104
+ end
105
+
106
+ it "groups duplicate line items together" do
107
+ phase_2 = LineItemDouble.example(part_line_items: [
108
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GBB200")),
109
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GSC300")),
110
+ PartLineItemDouble.example(variant: VariantDouble.example(sku: "GML100"))
111
+ ], quantity: 2)
112
+ order = OrderDouble.example(line_items: [
113
+ phase_2,
114
+ LineItemDouble.example(sku: "GBB200", quantity: 3)
115
+ ])
116
+ shipment = ShipmentDouble.example(order: order)
117
+
118
+ message = ShipmentOrder.new(:shipment => shipment, :client => @client)
119
+
120
+ document = Nokogiri::XML::Document.parse(message.to_xml)
121
+ order_details = document.css('OrderDetails')
122
+ assert_equal 3, order_details.count
123
+ assert_equal "GBB200", order_details[0]['ItemNumber']
124
+ assert_equal "5", order_details[0]['QuantityOrdered']
125
+ assert_equal "5", order_details[0]['QuantityToShip']
126
+ end
127
+
128
+ private
129
+
130
+ def assert_header(header)
131
+ assert_equal "#{@shipment.number}", header['OrderNumber']
132
+ assert_equal @shipment.created_at.utc.iso8601.to_s, header['OrderDate']
133
+ assert_equal @order.type, header['OrderType']
134
+ end
135
+
136
+ def assert_shipping(shipping)
137
+ assert_equal 'FEDEX', shipping['Carrier']
138
+ assert_equal 'GROUND', shipping['ServiceLevel']
139
+ end
140
+
141
+ def assert_line_item(expected_line_item, line_item)
142
+ assert_equal expected_line_item.sku.to_s, line_item['ItemNumber']
143
+ assert_equal "1", line_item['Line']
144
+ assert_equal expected_line_item.quantity.to_s, line_item['QuantityOrdered']
145
+ assert_equal expected_line_item.quantity.to_s, line_item['QuantityToShip']
146
+ assert_equal "EA", line_item['UOM']
147
+ assert_equal expected_line_item.price, line_item['Price']
148
+ end
149
+
150
+ def assert_address(email, address, node)
151
+ assert_equal address.company, node['Company']
152
+ assert_equal address.name, node['Contact']
153
+ assert_equal address.address1, node['Address1']
154
+ assert_equal address.address2, node['Address2']
155
+ assert_equal address.city, node['City']
156
+ assert_equal address.state.name, node['State']
157
+ assert_equal address.zipcode, node['PostalCode']
158
+ assert_equal address.country.name, node['Country']
159
+ end
160
+
161
+ end
162
+ end
163
+ end
164
+ end