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.
- 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
|