vacuum 0.1.3 → 0.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.md +69 -27
  2. data/lib/vacuum/endpoint/base.rb +58 -0
  3. data/lib/vacuum/endpoint/mws.rb +59 -0
  4. data/lib/vacuum/endpoint/product_advertising.rb +34 -0
  5. data/lib/vacuum/mws.rb +8 -0
  6. data/lib/vacuum/product_advertising.rb +7 -0
  7. data/lib/vacuum/request/base.rb +96 -0
  8. data/lib/vacuum/request/mws.rb +24 -0
  9. data/lib/vacuum/request/product_advertising.rb +90 -0
  10. data/lib/vacuum/request/signature/authentication.rb +32 -0
  11. data/lib/vacuum/request/signature/builder.rb +70 -0
  12. data/lib/vacuum/request/utils.rb +24 -0
  13. data/lib/vacuum/response/base.rb +57 -0
  14. data/lib/vacuum/response/mws.rb +7 -0
  15. data/lib/vacuum/response/product_advertising.rb +19 -0
  16. data/lib/vacuum/response/utils.rb +48 -0
  17. data/lib/vacuum/version.rb +1 -1
  18. data/lib/vacuum.rb +31 -11
  19. data/spec/fixtures/{http_response → product_advertising} +0 -0
  20. data/spec/spec_helper.rb +5 -4
  21. data/spec/support/shared_examples/endpoint.rb +43 -0
  22. data/spec/support/shared_examples/request.rb +95 -0
  23. data/spec/support/shared_examples/response.rb +51 -0
  24. data/spec/vacuum/endpoint/base_spec.rb +19 -0
  25. data/spec/vacuum/endpoint/mws_spec.rb +47 -0
  26. data/spec/vacuum/endpoint/product_advertising_spec.rb +25 -0
  27. data/spec/vacuum/request/base_spec.rb +22 -0
  28. data/spec/vacuum/request/mws_spec.rb +26 -0
  29. data/spec/vacuum/request/product_advertising_spec.rb +104 -0
  30. data/spec/vacuum/request/signature/authentication_spec.rb +30 -0
  31. data/spec/vacuum/request/signature/builder_spec.rb +76 -0
  32. data/spec/vacuum/request/utils_spec.rb +23 -0
  33. data/spec/vacuum/response/base_spec.rb +38 -0
  34. data/spec/vacuum/response/product_advertising_spec.rb +57 -0
  35. data/spec/vacuum/response/utils_spec.rb +42 -0
  36. metadata +107 -21
  37. data/lib/vacuum/em.rb +0 -21
  38. data/lib/vacuum/request.rb +0 -119
  39. data/lib/vacuum/response.rb +0 -63
  40. data/spec/vacuum/request_spec.rb +0 -130
  41. data/spec/vacuum/response_spec.rb +0 -80
@@ -0,0 +1,19 @@
1
+ module Vacuum
2
+ module Response
3
+ # A Product Advertising API response.
4
+ class ProductAdvertising < Base
5
+ # Returns an Array of errors.
6
+ def errors
7
+ find 'Error'
8
+ end
9
+
10
+ # Returns whether the response has errors.
11
+ #
12
+ # Note that a response with errors is still technically valid, i.e. the
13
+ # response code is 200.
14
+ def has_errors?
15
+ errors.count > 0
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ module Vacuum
2
+ module Response
3
+ module Utils
4
+ # Builds a Hash from an XML document.
5
+ #
6
+ # xml - a Nokogiri::XML::Document or Nokogiri::XML::Element.
7
+ #
8
+ # Returns a Hash representation of the XML document.
9
+ def self.xml_to_hash(xml)
10
+ case xml
11
+ when Nokogiri::XML::Document
12
+ xml_to_hash xml.root
13
+ when Nokogiri::XML::Element
14
+ hsh = {}
15
+
16
+ xml.attributes.each_pair do |key, attribute|
17
+ hsh[key] = attribute.value
18
+ end
19
+
20
+ xml.children.each do |child|
21
+ result = xml_to_hash child
22
+
23
+ if child.name == 'text'
24
+ if hsh.empty?
25
+ return result
26
+ else
27
+ hsh['__content__'] = result
28
+ end
29
+ elsif hsh[child.name]
30
+ case hsh[child.name]
31
+ when Array
32
+ hsh[child.name] << result
33
+ else
34
+ hsh[child.name] = [hsh[child.name]] << result
35
+ end
36
+ else
37
+ hsh[child.name] = result
38
+ end
39
+ end
40
+
41
+ hsh
42
+ else
43
+ xml.content.to_s
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Vacuum
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0.pre'
3
3
  end
data/lib/vacuum.rb CHANGED
@@ -1,25 +1,45 @@
1
+ # Standard library dependencies.
2
+ require 'base64'
1
3
  require 'forwardable'
2
- require 'net/http'
3
4
  require 'openssl'
4
5
  require 'time'
5
6
 
6
- require 'knack'
7
-
8
- %w{request response}.each do |f|
9
- require "vacuum/#{f}"
10
- end
7
+ # External dependencies.
8
+ require 'addressable/uri'
9
+ require 'faraday'
10
+ require 'nokogiri'
11
11
 
12
+ # Internal dependencies.
13
+ require 'vacuum/endpoint/base'
14
+ require 'vacuum/request/base'
15
+ require 'vacuum/request/signature/authentication'
16
+ require 'vacuum/request/signature/builder'
17
+ require 'vacuum/request/utils'
18
+ require 'vacuum/response/base'
19
+ require 'vacuum/response/utils'
20
+ require 'vacuum/version'
12
21
 
13
- # Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
22
+ # Vacuum is a Ruby wrapper to various Amazon Web Services (AWS) APIs.
14
23
  module Vacuum
15
24
  class BadLocale < ArgumentError; end
16
25
  class MissingKey < ArgumentError; end
17
26
  class MissingSecret < ArgumentError; end
18
- class MissingTag < ArgumentError; end
19
27
 
20
28
  class << self
21
- extend Forwardable
22
-
23
- def_delegators Request, :new
29
+ def new(api, &blk)
30
+ case api
31
+ when /^mws/
32
+ require 'vacuum/mws'
33
+ Request::MWS.new do |config|
34
+ config.api = api.slice(4, api.size).to_sym
35
+ blk.call config
36
+ end
37
+ when :product_advertising
38
+ require 'vacuum/product_advertising'
39
+ Request::ProductAdvertising.new &blk
40
+ else
41
+ raise NotImplementedError
42
+ end
43
+ end
24
44
  end
25
45
  end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,15 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
1
  require 'rspec'
4
-
5
2
  begin
6
3
  require 'pry'
7
4
  rescue LoadError
8
5
  end
9
6
 
10
- require File.expand_path('../../lib/vacuum', __FILE__)
7
+ require 'vacuum'
8
+ require 'vacuum/mws'
9
+ require 'vacuum/product_advertising'
11
10
 
12
11
  RSpec.configure do |c|
13
12
  c.treat_symbols_as_metadata_keys_with_true_values = true
14
13
  end
14
+
15
+ Dir['./spec/support/**/*.rb'].each { |f| require f }
@@ -0,0 +1,43 @@
1
+ shared_examples 'an endpoint' do
2
+ describe '#key' do
3
+ it 'requires key to have been set' do
4
+ expect { endpoint.key }.to raise_error Vacuum::MissingKey
5
+ end
6
+ end
7
+
8
+ describe '#locale' do
9
+ it 'defaults to the US' do
10
+ endpoint.locale.should eql 'US'
11
+ end
12
+
13
+ context 'if not valid' do
14
+ before do
15
+ endpoint.locale = 'foo'
16
+ end
17
+
18
+ it 'raises an error' do
19
+ expect { endpoint.locale }.to raise_error Vacuum::BadLocale
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#secret' do
25
+ it 'requires secret to have been set' do
26
+ expect { endpoint.secret }.to raise_error Vacuum::MissingSecret
27
+ end
28
+ end
29
+
30
+ describe '#user_agent' do
31
+ it 'includes the library version number' do
32
+ endpoint.user_agent.should match /Vacuum\/[\d\w.]+\s/
33
+ end
34
+
35
+ it 'describes the Ruby interpreter' do
36
+ endpoint.user_agent.should match /Language=(?:j?ruby|rbx)/
37
+ end
38
+
39
+ it 'describes the host' do
40
+ endpoint.user_agent.should match /Host=[\w\d]+/
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,95 @@
1
+ shared_examples 'a request' do
2
+ describe '#connection' do
3
+ let(:middleware) do
4
+ request.connection.builder.handlers
5
+ end
6
+
7
+ it 'returns a Faraday Connection' do
8
+ request.connection.should be_a Faraday::Connection
9
+ end
10
+
11
+ it 'adds Signature Authentication to middleware stack' do
12
+ middleware.should include Vacuum::Request::Signature::Authentication
13
+ end
14
+
15
+ it 'yields a builder' do
16
+ klass = Class.new Faraday::Middleware
17
+ request.connection do |builder|
18
+ builder.use klass
19
+ end
20
+ middleware.should include klass
21
+ end
22
+ end
23
+
24
+ describe '#endpoint' do
25
+ it 'returns an AWS API endpoint' do
26
+ request.endpoint.should be_a Vacuum::Endpoint::Base
27
+ end
28
+ end
29
+
30
+ describe '#build' do
31
+ it 'merges passed key and value pairs into the parameters' do
32
+ request.build 'Key' => 'value'
33
+ request.parameters['Key'].should eql 'value'
34
+ end
35
+
36
+ it 'casts Symbol keys to camel-cased String' do
37
+ request.build :foo_bar => 'value'
38
+ request.parameters.should have_key 'FooBar'
39
+ end
40
+
41
+ it 'does not modify String keys' do
42
+ request.build 'foo_bar' => 'value'
43
+ request.parameters.should have_key 'foo_bar'
44
+ end
45
+
46
+ it 'casts values to String' do
47
+ request.build 'Key' => 1
48
+ request.parameters['Key'].should eql '1'
49
+ end
50
+
51
+ it 'concatenates Array values with a comma' do
52
+ request.build 'Key' => ['foo', 'bar']
53
+ request.parameters['Key'].should eql 'foo,bar'
54
+ end
55
+
56
+ it 'returns self' do
57
+ request.build({}).should eql request
58
+ end
59
+ end
60
+
61
+ describe '#build!' do
62
+ it 'clears existing parameters' do
63
+ request.build 'Key' => 'value'
64
+ request.build!.parameters.should_not have_key 'Key'
65
+ end
66
+ end
67
+
68
+ describe '#configure' do
69
+ it 'configures the AWS API endpoint' do
70
+ request.configure do |config|
71
+ config.locale = 'JP'
72
+ end
73
+ request.endpoint.locale.should eql 'JP'
74
+ end
75
+ end
76
+
77
+ describe '#get' do
78
+ context 'given an implemented url' do
79
+ before do
80
+ request.stub!(:url).and_return Addressable::URI.parse 'http://example.com'
81
+ end
82
+
83
+ it 'returns a Response' do
84
+ request.get.should be_a Vacuum::Response::Base
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#parameters' do
90
+ it 'includes default parameters' do
91
+ request.parameters.should have_key 'AWSAccessKeyId'
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,51 @@
1
+ shared_examples 'a response' do
2
+ let(:child_name) do
3
+ response.xml.children.first.name
4
+ end
5
+
6
+ describe '#find' do
7
+ it 'returns an Array of matches' do
8
+ response.find(child_name).should_not be_empty
9
+ end
10
+
11
+ it 'yields matches to a block' do
12
+ yielded = false
13
+ response.find(child_name) { yielded = true }
14
+ yielded.should be_true
15
+ end
16
+
17
+ it 'is aliased to []' do
18
+ response.find(child_name).should eql response[child_name]
19
+ end
20
+ end
21
+
22
+ describe '#to_hash' do
23
+ it 'casts response to a hash' do
24
+ response.to_hash.should be_a Hash
25
+ end
26
+ end
27
+
28
+ describe '#valid?' do
29
+ context 'when HTTP status is OK' do
30
+ it 'returns true' do
31
+ response.should be_valid
32
+ end
33
+ end
34
+
35
+ context 'when HTTP status is not OK' do
36
+ before do
37
+ response.code = 403
38
+ end
39
+
40
+ it 'returns false' do
41
+ response.should_not be_valid
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#xml' do
47
+ it 'returns a Nokogiri document' do
48
+ response.xml.should be_an_instance_of Nokogiri::XML::Document
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Endpoint
5
+ describe Base do
6
+ let(:endpoint) do
7
+ described_class.new
8
+ end
9
+
10
+ it_behaves_like 'an endpoint'
11
+
12
+ describe '#host' do
13
+ it 'is not implemented' do
14
+ expect { endpoint.host }.to raise_error NotImplementedError
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Endpoint
5
+ describe MWS do
6
+ let(:endpoint) do
7
+ described_class.new
8
+ end
9
+
10
+ describe '#host' do
11
+ it 'returns a host' do
12
+ endpoint.host.should match /amazon/
13
+ end
14
+ end
15
+
16
+ describe '#marketplace' do
17
+ it 'requires marketplace ID to have been set' do
18
+ expect { endpoint.marketplace }.to raise_error MissingMarketplace
19
+ end
20
+ end
21
+
22
+ describe '#seller' do
23
+ it 'requires seller ID to have been set' do
24
+ expect { endpoint.seller }.to raise_error MissingSeller
25
+ end
26
+ end
27
+
28
+ describe '#path' do
29
+ context 'when API type is set to Products' do
30
+ before do
31
+ endpoint.api = :products
32
+ end
33
+
34
+ it 'returns a URL path' do
35
+ endpoint.path.should match %r{/\w+/\w+}
36
+ end
37
+ end
38
+
39
+ context 'when API type is not implemented' do
40
+ it 'raises a Not Implemented Error' do
41
+ expect { endpoint.path }.to raise_error NotImplementedError
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Endpoint
5
+ describe ProductAdvertising do
6
+ let(:endpoint) do
7
+ described_class.new
8
+ end
9
+
10
+ it_behaves_like 'an endpoint'
11
+
12
+ describe '#host' do
13
+ it 'returns a host' do
14
+ endpoint.host.should match /amazon/
15
+ end
16
+ end
17
+
18
+ describe '#tag' do
19
+ it 'requires tag to have been set' do
20
+ expect { endpoint.tag }.to raise_error MissingTag
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Request
5
+ describe Base do
6
+ let(:request) do
7
+ described_class.new do |config|
8
+ config.key = 'key'
9
+ config.secret = 'secret'
10
+ end
11
+ end
12
+
13
+ it_behaves_like 'a request'
14
+
15
+ describe '#url' do
16
+ it 'is not implemented' do
17
+ expect { request.url }.to raise_error NotImplementedError
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Request
5
+ describe MWS do
6
+ let(:request) do
7
+ described_class.new do |config|
8
+ config.key = 'key'
9
+ config.marketplace = 'marketplace'
10
+ config.secret = 'secret'
11
+ config.seller = 'seller'
12
+ end
13
+ end
14
+
15
+ context 'when API type is set to Products' do
16
+ before do
17
+ request.configure do |config|
18
+ config.api = :products
19
+ end
20
+ end
21
+
22
+ it_behaves_like 'a request'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Request
5
+ describe ProductAdvertising do
6
+ let(:request) do
7
+ described_class.new do |config|
8
+ config.key = 'key'
9
+ config.secret = 'secret'
10
+ config.tag = 'tag'
11
+ end
12
+ end
13
+
14
+ it_behaves_like 'a request'
15
+
16
+ describe '#find' do
17
+ before do
18
+ request.stub! :get
19
+ end
20
+
21
+ let(:parameters) do
22
+ request.parameters
23
+ end
24
+
25
+ context 'given no items' do
26
+ it 'raises an error' do
27
+ expect { request.find }.to raise_error ArgumentError
28
+ end
29
+ end
30
+
31
+ context 'given up to 10 items' do
32
+ before do
33
+ request.find *(1..10), :foo => 'bar'
34
+ end
35
+
36
+ it 'builds a single-batch query' do
37
+ parameters['ItemId'].split(',').should =~ (1..10).map(&:to_s)
38
+ end
39
+
40
+ it 'takes parameters' do
41
+ parameters['Foo'].should eql 'bar'
42
+ end
43
+ end
44
+
45
+ context 'given 11 to to 20 items' do
46
+ before do
47
+ request.find *(1..20), :foo => 'bar'
48
+ end
49
+
50
+ it 'builds a multi-batch query' do
51
+ first_batch = parameters['ItemId.1.ItemId'].split(',')
52
+ first_batch.should =~ (1..10).map(&:to_s)
53
+ second_batch = parameters['ItemId.2.ItemId'].split(',')
54
+ second_batch.should =~ (11..20).map(&:to_s)
55
+ end
56
+
57
+ it 'takes parameters' do
58
+ parameters['ItemLookup.Shared.Foo'].should eql 'bar'
59
+ end
60
+ end
61
+
62
+ context 'given over 20 items' do
63
+ it 'raises an error' do
64
+ expect { request.find *(1..21) }.to raise_error ArgumentError
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#search' do
70
+ let(:parameters) do
71
+ request.parameters
72
+ end
73
+
74
+ context 'when given a search index and a keyword' do
75
+ before do
76
+ request.search :foo, 'bar'
77
+ end
78
+
79
+ it 'builds a keyword search' do
80
+ parameters['Keywords'].should eql 'bar'
81
+ end
82
+
83
+ it 'sets the search index' do
84
+ parameters['SearchIndex'].should eql 'Foo'
85
+ end
86
+ end
87
+
88
+ context 'when given a search index and parameters' do
89
+ before do
90
+ request.search(:foo, :bar => 'baz')
91
+ end
92
+
93
+ it 'sets the parameters' do
94
+ parameters['Bar'].should eql 'baz'
95
+ end
96
+
97
+ it 'sets the search index' do
98
+ parameters['SearchIndex'].should eql 'Foo'
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module Vacuum
4
+ module Request
5
+ module Signature
6
+ describe Authentication do
7
+ let(:middleware) do
8
+ described_class.new lambda { |env| env }, 'secret'
9
+ end
10
+
11
+ def result
12
+ env = { :url => 'http:://example.com/foo?Baz=2&Bar=1' }
13
+ middleware.call env
14
+ end
15
+
16
+ it 'sorts the query values' do
17
+ result[:url].query.should match /^Bar/
18
+ end
19
+
20
+ it 'timestamps the request' do
21
+ result[:url].query.should include 'Timestamp'
22
+ end
23
+
24
+ it 'signs the request' do
25
+ result[:url].query.should include 'Signature'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end