dynamodb 0.0.2 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +2 -1
- data/README.md +41 -11
- data/dynamodb.gemspec +3 -2
- data/lib/dynamodb.rb +18 -17
- data/lib/dynamodb/connection.rb +34 -70
- data/lib/dynamodb/failure_response.rb +41 -0
- data/lib/dynamodb/http_handler.rb +74 -0
- data/lib/dynamodb/request.rb +107 -0
- data/lib/dynamodb/success_response.rb +51 -0
- data/lib/dynamodb/version.rb +3 -0
- data/lib/net/http/connection_pool.rb +226 -0
- data/lib/net/http/connection_pool/connection.rb +189 -0
- data/lib/net/http/connection_pool/session.rb +126 -0
- data/spec/dynamodb/connection_spec.rb +14 -117
- data/spec/dynamodb/failure_response_spec.rb +27 -0
- data/spec/dynamodb/http_handler_spec.rb +71 -0
- data/spec/dynamodb/request_spec.rb +31 -0
- data/spec/dynamodb/success_response_spec.rb +93 -0
- data/spec/dynamodb_spec.rb +24 -0
- metadata +18 -28
- data/lib/dynamodb/credentials.rb +0 -30
- data/lib/dynamodb/response.rb +0 -33
- data/lib/dynamodb/security_token_service.rb +0 -110
- data/lib/dynamodb/typhoeus/request.rb +0 -27
- data/spec/dynamodb/credentials_spec.rb +0 -22
- data/spec/dynamodb/response_spec.rb +0 -87
- data/spec/dynamodb/security_token_service_spec.rb +0 -97
@@ -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(:
|
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
|
-
|
54
|
-
|
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 "
|
82
|
-
stub_request(:post,
|
83
|
-
to_return(
|
84
|
-
|
85
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|