gentle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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