gentle 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +9 -0
- data/gentle.gemspec +29 -0
- data/lib/gentle.rb +8 -0
- data/lib/gentle/blackboard.rb +44 -0
- data/lib/gentle/client.rb +92 -0
- data/lib/gentle/documents/document.rb +15 -0
- data/lib/gentle/documents/request/hash_converter.rb +55 -0
- data/lib/gentle/documents/request/shipment_order.rb +167 -0
- data/lib/gentle/documents/response/shipment_order_result.rb +56 -0
- data/lib/gentle/error_message.rb +19 -0
- data/lib/gentle/message.rb +43 -0
- data/lib/gentle/phase_1_set.rb +53 -0
- data/lib/gentle/queue.rb +40 -0
- data/lib/gentle/request.rb +11 -0
- data/lib/gentle/response.rb +11 -0
- data/lib/gentle/version.rb +3 -0
- data/spec/fixtures/credentials.yml +12 -0
- data/spec/fixtures/documents/responses/shipment_order_result.xml +23 -0
- data/spec/fixtures/messages/error_message.xml +10 -0
- data/spec/fixtures/messages/purchase_order_message.xml +11 -0
- data/spec/remote/blackboard_spec.rb +43 -0
- data/spec/remote/queue_spec.rb +64 -0
- data/spec/spec_helper.rb +323 -0
- data/spec/unit/blackboard_spec.rb +102 -0
- data/spec/unit/client_spec.rb +97 -0
- data/spec/unit/documents/document_spec.rb +44 -0
- data/spec/unit/documents/request/hash_converter_spec.rb +42 -0
- data/spec/unit/documents/request/shipment_order_spec.rb +164 -0
- data/spec/unit/documents/response/shipment_order_result_spec.rb +26 -0
- data/spec/unit/error_message_spec.rb +31 -0
- data/spec/unit/message_spec.rb +71 -0
- data/spec/unit/phase_1_set_spec.rb +76 -0
- data/spec/unit/queue_spec.rb +57 -0
- 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
|