dynamodb 0.0.2 → 1.1.1

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.
@@ -0,0 +1,126 @@
1
+ # Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'net/http'
15
+ require 'net/https'
16
+ require 'openssl'
17
+
18
+ class Net::HTTP::ConnectionPool
19
+
20
+ # Used by Net::HTTP::ConnectionPool to wrap Net::HTTP::Session objects.
21
+ # Users should never need to interact with these session wrappers.
22
+ # @private
23
+ class Session
24
+
25
+ # @param [Net::HTTPSession] http_session
26
+ # @param [String] key
27
+ def initialize http_session, key
28
+ @http_session = http_session
29
+ @key = key
30
+ @created_at = Time.now
31
+ @last_used_at = nil
32
+ end
33
+
34
+ # @return [Net::HTTPSession]
35
+ attr_reader :http_session
36
+
37
+ # @return [String]
38
+ attr_reader :key
39
+
40
+ # @return [Time]
41
+ attr_reader :created_at
42
+
43
+ # @return [Time]
44
+ attr_reader :last_used_at
45
+
46
+ # @param [Integer] timeout Number of seconds before Net::HTTP should
47
+ # timeout while waiting to read a response.
48
+ def read_timeout= timeout
49
+ http_session.read_timeout = timeout
50
+ end
51
+
52
+ # Makes a HTTP request. See Net::HTTPSession#request documentation
53
+ # from the Ruby standard library for information about argments.
54
+ #
55
+ # connection.request(Net::HTTP::Get.new('/')) do |response|
56
+ # # Parse the response (status, headers and body) here.
57
+ # # You should be done with the response by the end of the block.
58
+ # end
59
+ #
60
+ # @yield [response]
61
+ # @yieldparam [Net::HTTPResponse] response
62
+ # @return [nil]
63
+ def request *args, &block
64
+ http_session.request(*args, &block)
65
+ @last_used_at = Time.now
66
+ nil
67
+ end
68
+
69
+ # Attempts to cleanly close the HTTP session.
70
+ # @return [nil]
71
+ def finish
72
+ begin
73
+ http_session.finish if http_session.started?
74
+ rescue IOError
75
+ end
76
+ nil
77
+ end
78
+
79
+ class << self
80
+
81
+ # Starts a new HTTP session and returns it wrapped in a {Session} object.
82
+ # @param [Connection] connection
83
+ # @param [Hash] options
84
+ # @option options [Integer] :open_timeout (15) The number of seconds to
85
+ # wait while trying to open the HTTP session before timeing out.
86
+ # @option options [Logger] :debug_logger HTTP wire traces are logged
87
+ # here when specified.
88
+ # @return [Session]
89
+ def start connection, options = {}
90
+
91
+ http_args = []
92
+ http_args << connection.host
93
+ http_args << connection.port
94
+ if connection.proxy?
95
+ http_args << connection.proxy_address
96
+ http_args << connection.proxy_port
97
+ http_args << connection.proxy_user
98
+ http_args << connection.proxy_password
99
+ end
100
+
101
+ http = Net::HTTP.new(*http_args)
102
+ http.set_debug_output(options[:debug_logger]) if options[:debug_logger]
103
+ http.open_timeout = options[:open_timeout] || 15
104
+
105
+ if connection.ssl?
106
+ http.use_ssl = true
107
+ if connection.ssl_verify_peer?
108
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
109
+ http.ca_file = connection.ssl_ca_file if connection.ssl_ca_file
110
+ http.ca_path = connection.ssl_ca_path if connection.ssl_ca_path
111
+ else
112
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
113
+ end
114
+ else
115
+ http.use_ssl = false
116
+ end
117
+
118
+ http.start
119
+
120
+ Session.new(http, connection.key)
121
+
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -1,131 +1,28 @@
1
1
  require 'spec_helper'
2
- require 'benchmark'
3
2
 
4
3
  describe DynamoDB::Connection do
5
- let(:token_service) {
6
- stub(:credentials =>
7
- DynamoDB::Credentials.new(
8
- "access_key_id",
9
- "secret_access_key",
10
- "session_token"
11
- )
12
- )
13
- }
14
-
15
- describe "#initialize" do
16
- it "can be initialized with a token service" do
17
- DynamoDB::Connection.new(:token_service => token_service)
18
- end
19
-
20
- it "can be initialized with an access key" do
21
- DynamoDB::Connection.new(
22
- :access_key_id => "id",
23
- :secret_access_key => "secret"
24
- )
25
- end
26
-
27
- context "no token service was provided" do
28
- it "requires an access_key_id and secret_access_key" do
29
- lambda {
30
- DynamoDB::Connection.new
31
- }.should raise_error(ArgumentError)
32
- end
33
- end
34
- end
35
-
36
4
  describe "#post" do
37
- let(:connection) { DynamoDB::Connection.new(:token_service => token_service) }
38
-
39
- before do
40
- Time.stub(:now => Time.at(1332635893)) # Sat Mar 24 20:38:13 -0400 2012
41
-
42
- @url = "https://dynamodb.us-east-1.amazonaws.com/"
43
- @headers = {
44
- 'Content-Type' => 'application/x-amz-json-1.0',
45
- 'Host' => 'dynamodb.us-east-1.amazonaws.com',
46
- 'X-Amz-Date' => 'Sun, 25 Mar 2012 00:38:13 GMT',
47
- 'X-Amz-Security-Token' => 'session_token',
48
- 'X-Amzn-Authorization' => 'AWS3 AWSAccessKeyId=access_key_id,Algorithm=HmacSHA256,Signature=2xa6v0WW+980q8Hgt+ym3/7C0D1DlkueGMugi1NWE+o='
49
- }
50
- end
5
+ let(:connection) { DynamoDB::Connection.new(:access_key_id => "id", :secret_access_key => "secret") }
51
6
 
52
7
  it "signs and posts a request" do
53
- @headers['X-Amz-Target'] = 'DynamoDB_20111205.ListTables'
54
- stub_request(:post, @url).
55
- with(
56
- :body => "{}",
57
- :headers => @headers
58
- ).
59
- to_return(
60
- :status => 200,
61
- :body => '{"TableNames":["example"]}',
62
- :headers => {}
63
- )
64
-
8
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
9
+ to_return(status: 200, body: MultiJson.encode({"TableNames" => ["example"]}))
65
10
  result = connection.post :ListTables
66
- result.should == {"TableNames" => ["example"]}
67
- end
68
-
69
- it "type casts response when Query" do
70
- stub_request(:post, @url).
71
- to_return(
72
- :status => 200,
73
- :body => "{}",
74
- :headers => {}
75
- )
76
-
77
- response = connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
78
- response.should be_a_kind_of(DynamoDB::Response)
11
+ result.data.should == {"TableNames" => ["example"]}
79
12
  end
80
13
 
81
- it "type casts response when GetItem" do
82
- stub_request(:post, @url).
83
- to_return(
84
- :status => 200,
85
- :body => "{}",
86
- :headers => {}
87
- )
88
-
89
- response = connection.post :GetItem, :TableName => "people", :Key => {:HashKeyElement => {:N => "1"}, :RangeKeyElement => {:N => 2}}
90
- response.should be_a_kind_of(DynamoDB::Response)
91
- end
92
-
93
- context "when a failure occurs" do
94
- it "raises an error with the response attached" do
95
- stub_request(:post, @url).to_return(:status => 500, :body => "Failed for some reason.")
96
- error = nil
97
- begin
98
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
99
- rescue => e
100
- error = e
101
- end
102
- error.should be_an_instance_of(DynamoDB::ServerError)
103
- error.response.code.should == 500
104
- error.message.should == "500: Failed for some reason."
105
- end
106
- end
107
-
108
- context "when the connection fails" do
109
- it "raises a server error" do
110
- stub_request(:post, @url).to_return(:status => 0, :body => "")
111
- error = nil
112
- begin
113
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
114
- rescue => e
115
- error = e
116
- end
117
- error.should be_an_instance_of(DynamoDB::ServerError)
118
- error.response.code.should == 0
119
- end
14
+ it "creates a SuccessResponse when 200" do
15
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
16
+ to_return(status: 200, body: "")
17
+ result = connection.post :ListTables
18
+ result.should be_a_kind_of(DynamoDB::SuccessResponse)
120
19
  end
121
20
 
122
- context "when the connection times out" do
123
- it "raises a DynamoDB::TimeoutError" do
124
- stub_request(:post, @url).to_timeout
125
- expect {
126
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
127
- }.to raise_error(DynamoDB::TimeoutError)
128
- end
21
+ it "creates a FailureResponse when 400" do
22
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
23
+ to_return(status: 400, body: "")
24
+ result = connection.post :ListTables
25
+ result.should be_a_kind_of(DynamoDB::FailureResponse)
129
26
  end
130
27
  end
131
28
  end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::FailureResponse do
4
+ describe "#error" do
5
+ it "returns a ClientError if the HTTP response code is between 400 and 499" do
6
+ http_response = stub("Response", code: "401", message: "Not authorized", body: "Error details")
7
+ response = DynamoDB::FailureResponse.new(http_response)
8
+ response.error.should be_an_instance_of(DynamoDB::ClientError)
9
+ response.error.message.should == "401: Not authorized"
10
+ response.body.should == "Error details"
11
+ end
12
+
13
+ it "returns a ServerError if the HTTP response code is between 500 and 599" do
14
+ http_response = stub("Response", code: "500", message: "Internal server error", body: "")
15
+ response = DynamoDB::FailureResponse.new(http_response)
16
+ response.error.should be_an_instance_of(DynamoDB::ServerError)
17
+ response.error.message.should == "500: Internal server error"
18
+ end
19
+
20
+ it "returns a ServerError otherwise" do
21
+ http_response = stub("Response", code: "0", message: "")
22
+ response = DynamoDB::FailureResponse.new(http_response)
23
+ response.error.should be_an_instance_of(DynamoDB::ServerError)
24
+ response.error.message.should == "0: "
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::HttpHandler do
4
+ describe "#handle" do
5
+ let(:http_handler) { DynamoDB::HttpHandler.new }
6
+ let(:request) {
7
+ stub(DynamoDB::Request,
8
+ uri: URI("https://dynamo.local/"),
9
+ headers: {},
10
+ body: "{}"
11
+ )
12
+ }
13
+
14
+ it "performs an HTTP request given a DynamoDB::Request" do
15
+ http_request = stub_request(:post, "https://dynamo.local/").to_return(status: 200)
16
+ http_handler.handle(request)
17
+ http_request.should have_been_requested
18
+ end
19
+
20
+ it "returns a DynamoDB::SuccessResponse for successes" do
21
+ stub_request(:post, "https://dynamo.local/").to_return(status: 200)
22
+
23
+ response = http_handler.handle(request)
24
+ response.should be_an_instance_of(DynamoDB::SuccessResponse)
25
+ response.should be_success
26
+ end
27
+
28
+ it "returns a DynamoDB::Response for failures" do
29
+ stub_request(:post, "https://dynamo.local/").to_return(status: 500, body: "Server errors")
30
+
31
+ response = http_handler.handle(request)
32
+ response.should be_an_instance_of(DynamoDB::FailureResponse)
33
+ response.should_not be_success
34
+ end
35
+
36
+ it "returns a DynamoDB::Response for network errors" do
37
+ error = Errno::ECONNRESET.new
38
+ stub_request(:post, "https://dynamo.local/").to_raise(error)
39
+
40
+ response = http_handler.handle(request)
41
+ response.should be_an_instance_of(DynamoDB::FailureResponse)
42
+ response.should_not be_success
43
+ response.error.should == error
44
+ response.body.should be_nil
45
+ end
46
+
47
+ it "respects a custom timeout option (set on initialize)" do
48
+ stub_request(:post, "https://dynamo.local/").to_return(status: 200)
49
+ connection = Net::HTTP::ConnectionPool::Connection.any_instance
50
+ connection.should_receive(:read_timeout=).with(5)
51
+ http_handler.handle(request)
52
+ end
53
+ end
54
+
55
+ describe "#build_http_request" do
56
+ it "converts a DynamoDB::Request into a Net::HTTP::Post" do
57
+ request = stub(DynamoDB::Request,
58
+ uri: URI("https://dynamo.local/"),
59
+ headers: {"content-type" => "application/json"},
60
+ body: "POST body"
61
+ )
62
+
63
+ http_handler = DynamoDB::HttpHandler.new
64
+ http_request = http_handler.build_http_request(request)
65
+ http_request.should be_an_instance_of(Net::HTTP::Post)
66
+ http_request.path.should == "https://dynamo.local/"
67
+ http_request["content-type"].should == "application/json"
68
+ http_request.body.should == "POST body"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::Request do
4
+ let(:uri) { URI("https://dynamodb.us-east-1.amazonaws.com/") }
5
+ let(:credentials) { DynamoDB::Credentials.new("access_key_id", "secret_access_key") }
6
+ let(:data) { {} }
7
+ let(:request) {
8
+ DynamoDB::Request.new(
9
+ uri: uri,
10
+ api_version: "DynamoDB_20111205",
11
+ credentials: credentials,
12
+ data: data,
13
+ operation: "ListTables",
14
+ )
15
+ }
16
+
17
+ it "signs the request" do
18
+ Time.stub(now: Time.parse("20130508T201304Z"))
19
+
20
+ request.headers.should == {
21
+ "content-type"=>"application/x-amz-json-1.0",
22
+ "x-amz-target"=>"DynamoDB_20111205.ListTables",
23
+ "content-length"=>2,
24
+ "user-agent"=>"groupme/dynamodb",
25
+ "host"=>"dynamodb.us-east-1.amazonaws.com",
26
+ "x-amz-date"=>"20130508T201304Z",
27
+ "x-amz-content-sha256"=>"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
28
+ "authorization"=>"AWS4-HMAC-SHA256 Credential=access_key_id/20130508/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=52fab5c720b35ce247f8388c5c8f66b300074ae88b666985ee26ca70d923be1d"
29
+ }
30
+ end
31
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe DynamoDB::SuccessResponse do
4
+ describe "#hash_key_element" do
5
+ it "returns the typecast value of HashKeyElement" do
6
+ body = {
7
+ "LastEvaluatedKey" => {
8
+ "HashKeyElement" => {"N" => "1"},
9
+ "RangeKeyElement" => {"N" => "1501"}
10
+ }
11
+ }
12
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
13
+ response = DynamoDB::SuccessResponse.new(http_response)
14
+ response.hash_key_element.should == 1
15
+ end
16
+ end
17
+
18
+ describe "#range_key_element" do
19
+ it "returns the typecast value of RangeKeyElement" do
20
+ body = {
21
+ "LastEvaluatedKey" => {
22
+ "HashKeyElement" => {"N" => "1"},
23
+ "RangeKeyElement" => {"N" => "1501"}
24
+ }
25
+ }
26
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
27
+ response = DynamoDB::SuccessResponse.new(http_response)
28
+ response.range_key_element.should == 1501
29
+ end
30
+ end
31
+
32
+ describe "#items" do
33
+ it "returns type-casted entries from the 'Items' key" do
34
+ body = {
35
+ "Items" => [
36
+ {
37
+ "name" => {"S" => "John Smith"},
38
+ "created_at" => {"N" => "1321564309.99428"},
39
+ "disabled" => {"N" => "0"},
40
+ "group_id" => {"N" => "1"},
41
+ "person_id" => {"N" => "1500"}
42
+ },
43
+ {
44
+ "name" => {"S" => "Jane Smith"},
45
+ "created_at" => {"N" => "1321564309.99428"},
46
+ "disabled" => {"N" => "1"},
47
+ "group_id" => {"N" => "1"},
48
+ "person_id" => {"N" => "1501"}
49
+ }
50
+ ],
51
+ "Count" => 1,
52
+ "ConsumedCapacityUnits" => 0.5
53
+ }
54
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
55
+
56
+ response = DynamoDB::SuccessResponse.new(http_response)
57
+ response.items[0]["name"].should == "John Smith"
58
+ response.items[0]["created_at"].should == 1321564309.99428
59
+ response.items[0]["disabled"].should == 0
60
+ response.items[0]["group_id"].should == 1
61
+ response.items[0]["person_id"].should == 1500
62
+
63
+ response.items[1]["name"].should == "Jane Smith"
64
+ response.items[1]["created_at"].should == 1321564309.99428
65
+ response.items[1]["disabled"].should == 1
66
+ response.items[1]["group_id"].should == 1
67
+ response.items[1]["person_id"].should == 1501
68
+ end
69
+ end
70
+
71
+ describe "#item" do
72
+ it "returns the type-casted 'Item' key" do
73
+ body = {
74
+ "Item"=> {
75
+ "name" => {"S" => "John Smith"},
76
+ "created_at" => {"N" => "1321564309.99428"},
77
+ "disabled" => {"N" => "0"},
78
+ "group_id" => {"N" => "1"},
79
+ "person_id" => {"N" => "1500"}
80
+ },
81
+ "ConsumedCapacityUnits" => 0.5
82
+ }
83
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
84
+
85
+ response = DynamoDB::SuccessResponse.new(http_response)
86
+ response.item["name"].should == "John Smith"
87
+ response.item["created_at"].should == 1321564309.99428
88
+ response.item["disabled"].should == 0
89
+ response.item["group_id"].should == 1
90
+ response.item["person_id"].should == 1500
91
+ end
92
+ end
93
+ end