fake_dynamo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +4 -0
- data/Rakefile +2 -0
- data/bin/fake_dynamo +17 -0
- data/fake_dynamo.gemspec +18 -0
- data/lib/fake_dynamo.rb +20 -0
- data/lib/fake_dynamo/api.yml +734 -0
- data/lib/fake_dynamo/attribute.rb +54 -0
- data/lib/fake_dynamo/db.rb +113 -0
- data/lib/fake_dynamo/exceptions.rb +57 -0
- data/lib/fake_dynamo/filter.rb +110 -0
- data/lib/fake_dynamo/item.rb +102 -0
- data/lib/fake_dynamo/key.rb +71 -0
- data/lib/fake_dynamo/key_schema.rb +26 -0
- data/lib/fake_dynamo/server.rb +43 -0
- data/lib/fake_dynamo/storage.rb +71 -0
- data/lib/fake_dynamo/table.rb +362 -0
- data/lib/fake_dynamo/validation.rb +155 -0
- data/lib/fake_dynamo/version.rb +3 -0
- data/spec/fake_dynamo/db_spec.rb +257 -0
- data/spec/fake_dynamo/filter_spec.rb +122 -0
- data/spec/fake_dynamo/item_spec.rb +97 -0
- data/spec/fake_dynamo/server_spec.rb +47 -0
- data/spec/fake_dynamo/table_spec.rb +435 -0
- data/spec/fake_dynamo/validation_spec.rb +63 -0
- data/spec/spec_helper.rb +28 -0
- metadata +105 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FakeDynamo
|
4
|
+
describe Item do
|
5
|
+
subject do
|
6
|
+
item = Item.new
|
7
|
+
key = Key.new
|
8
|
+
key.primary = Attribute.new('id', 'ananth', 'S')
|
9
|
+
item.key = key
|
10
|
+
item.attributes = {}
|
11
|
+
item
|
12
|
+
end
|
13
|
+
|
14
|
+
context "#update" do
|
15
|
+
it "should not allow to update primary key" do
|
16
|
+
expect { subject.update('id', nil) }.to raise_error(ValidationException, /part of the key/)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should handle unknown action" do
|
20
|
+
expect { subject.update('xyz', {'Action' => 'XYZ'}) }.to raise_error(ValidationException, /unknown action/i)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not allow empty value for action other than delete" do
|
24
|
+
expect { subject.update('xyz', {'Action' => 'PUT'})}.to raise_error(ValidationException, /only delete/i)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "#delete" do
|
29
|
+
it "should not fail when the attribute is not present" do
|
30
|
+
subject.delete('friends', nil)
|
31
|
+
subject.attributes['friends'].should be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should delete the attribute" do
|
35
|
+
subject.attributes['friends'] = Attribute.new('friends', ["1", "2"], "NS")
|
36
|
+
subject.delete('friends', nil)
|
37
|
+
subject.attributes['friends'].should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should handle value type" do
|
41
|
+
subject.attributes['friends'] = Attribute.new('friends', ["1", "2"], "NS")
|
42
|
+
expect { subject.delete('friends', { "S" => "XYZ" }) }.to raise_error(ValidationException, /type mismatch/i)
|
43
|
+
|
44
|
+
subject.attributes['age'] = Attribute.new('age', "5", "N")
|
45
|
+
expect { subject.delete('age', { "N" => "10" }) }.to raise_error(ValidationException, /not supported/i)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should delete values" do
|
49
|
+
subject.attributes['friends'] = Attribute.new('friends', ["1", "2"], "NS")
|
50
|
+
subject.delete('friends', { "NS" => ["2", "4"]})
|
51
|
+
subject.attributes['friends'].value.should == ["1"]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "#put" do
|
56
|
+
it "should update the attribute" do
|
57
|
+
old_name = Attribute.new('name', 'xxx', 'S')
|
58
|
+
subject.attributes['name'] = old_name
|
59
|
+
subject.put('name', { 'S' => 'ananth'});
|
60
|
+
subject.attributes['name'].should eq(Attribute.new('name', 'ananth', 'S'))
|
61
|
+
|
62
|
+
subject.attributes['xxx'].should be_nil
|
63
|
+
subject.put('xxx', { 'S' => 'new'} )
|
64
|
+
subject.attributes['xxx'].should eq(Attribute.new('xxx', 'new', 'S'))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "#add" do
|
69
|
+
it "should fail on string type" do
|
70
|
+
expect { subject.add('new', { 'S' => 'ananth'}) }.to raise_error(ValidationException, /not supported/)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should increment numbers" do
|
74
|
+
subject.attributes['number'] = Attribute.new('number', '5', 'N')
|
75
|
+
subject.add('number', { 'N' => '3'})
|
76
|
+
subject.attributes['number'].value.should eq('8')
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should handle sets" do
|
80
|
+
subject.attributes['set'] = Attribute.new('set', ['1', '2'], 'SS')
|
81
|
+
subject.add('set', { 'SS' => ['3']})
|
82
|
+
subject.attributes['set'].value.should eq(['1', '2', '3'])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should handle type mismatch" do
|
86
|
+
subject.attributes['xxx'] = Attribute.new('xxx', ['1', '2'], 'NS')
|
87
|
+
expect { subject.add('xxx', {'SS' => ['3']}) }.to raise_error(ValidationException, /type mismatch/i)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should add the item if attribute is not found" do
|
91
|
+
subject.attributes['unknown'].should be_nil
|
92
|
+
subject.add('unknown', {'SS' => ['1']})
|
93
|
+
subject.attributes['unknown'].should eq(Attribute.new('unknown', ['1'], 'SS'))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FakeDynamo
|
4
|
+
describe Server do
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def get_server(app)
|
8
|
+
s = app.instance_variable_get :@app
|
9
|
+
if s.instance_of? Server
|
10
|
+
s
|
11
|
+
else
|
12
|
+
get_server(s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:data) do
|
17
|
+
{
|
18
|
+
"TableName" => "Table1",
|
19
|
+
"KeySchema" =>
|
20
|
+
{"HashKeyElement" => {"AttributeName" => "AttributeName1","AttributeType" => "S"},
|
21
|
+
"RangeKeyElement" => {"AttributeName" => "AttributeName2","AttributeType" => "N"}},
|
22
|
+
"ProvisionedThroughput" => {"ReadCapacityUnits" => 5,"WriteCapacityUnits" => 10}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
let(:app) { Server.new }
|
26
|
+
let(:server) { get_server(app) }
|
27
|
+
|
28
|
+
it "should extract_operation" do
|
29
|
+
server.extract_operation('HTTP_X_AMZ_TARGET' => 'DynamoDB_20111205.CreateTable').should eq('CreateTable')
|
30
|
+
expect {
|
31
|
+
server.extract_operation('HTTP_X_AMZ_TARGET' => 'FakeDB_20111205.CreateTable')
|
32
|
+
}.to raise_error(UnknownOperationException)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should send operation to db" do
|
36
|
+
post '/', data.to_json, 'HTTP_X_AMZ_TARGET' => 'DynamoDB_20111205.CreateTable'
|
37
|
+
last_response.should be_ok
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should handle error properly" do
|
41
|
+
post '/', {'x' => 'y'}.to_json, 'HTTP_X_AMZ_TARGET' => 'DynamoDB_20111205.CreateTable'
|
42
|
+
last_response.should_not be_ok
|
43
|
+
last_response.status.should eq(400)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,435 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FakeDynamo
|
4
|
+
describe Table do
|
5
|
+
|
6
|
+
let(:data) do
|
7
|
+
{
|
8
|
+
"TableName" => "Table1",
|
9
|
+
"KeySchema" =>
|
10
|
+
{"HashKeyElement" => {"AttributeName" => "AttributeName1","AttributeType" => "S"},
|
11
|
+
"RangeKeyElement" => {"AttributeName" => "AttributeName2","AttributeType" => "N"}},
|
12
|
+
"ProvisionedThroughput" => {"ReadCapacityUnits" => 5,"WriteCapacityUnits" => 10}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:item) do
|
17
|
+
{ 'TableName' => 'Table1',
|
18
|
+
'Item' => {
|
19
|
+
'AttributeName1' => { 'S' => "test" },
|
20
|
+
'AttributeName2' => { 'N' => '11' },
|
21
|
+
'AttributeName3' => { 'S' => "another" }
|
22
|
+
}}
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:key) do
|
26
|
+
{'TableName' => 'Table1',
|
27
|
+
'Key' => {
|
28
|
+
'HashKeyElement' => { 'S' => 'test' },
|
29
|
+
'RangeKeyElement' => { 'N' => '11' }
|
30
|
+
}}
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:consumed_capacity) { { 'ConsumedCapacityUnits' => 1 } }
|
34
|
+
|
35
|
+
subject { Table.new(data) }
|
36
|
+
|
37
|
+
its(:status) { should == 'CREATING' }
|
38
|
+
its(:creation_date_time) { should_not be_nil }
|
39
|
+
|
40
|
+
context '#update' do
|
41
|
+
subject do
|
42
|
+
table = Table.new(data)
|
43
|
+
table.update(10, 15)
|
44
|
+
table
|
45
|
+
end
|
46
|
+
|
47
|
+
its(:read_capacity_units) { should == 10 }
|
48
|
+
its(:write_capacity_units) { should == 15 }
|
49
|
+
its(:last_increased_time) { should be_a_kind_of(Fixnum) }
|
50
|
+
its(:last_decreased_time) { should be_nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
context '#put_item' do
|
54
|
+
it 'should fail if hash key is not present' do
|
55
|
+
expect do
|
56
|
+
subject.put_item({ 'TableName' => 'Table1',
|
57
|
+
'Item' => {
|
58
|
+
'AttributeName2' => { 'S' => "test" }
|
59
|
+
}})
|
60
|
+
end.to raise_error(ValidationException, /missing.*item/i)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should fail if range key is not present' do
|
64
|
+
expect do
|
65
|
+
subject.put_item({ 'TableName' => 'Table1',
|
66
|
+
'Item' => {
|
67
|
+
'AttributeName1' => { 'S' => "test" }
|
68
|
+
}})
|
69
|
+
end.to raise_error(ValidationException, /missing.*item/i)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should fail on type mismatch' do
|
73
|
+
expect do
|
74
|
+
subject.put_item({ 'TableName' => 'Table1',
|
75
|
+
'Item' => {
|
76
|
+
'AttributeName1' => { 'N' => "test" },
|
77
|
+
'AttributeName2' => { 'N' => '11' }
|
78
|
+
}})
|
79
|
+
end.to raise_error(ValidationException, /mismatch/i)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should putitem in the table' do
|
83
|
+
subject.put_item(item)
|
84
|
+
subject.items.size.should == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'Expected & ReturnValues' do
|
88
|
+
subject do
|
89
|
+
table = Table.new(data)
|
90
|
+
table.put_item(item)
|
91
|
+
table
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should check condition' do
|
95
|
+
[[{}, /set to null/],
|
96
|
+
[{'Exists' => true}, /set to true/],
|
97
|
+
[{'Exists' => false}],
|
98
|
+
[{'Value' => { 'S' => 'xxx' } }],
|
99
|
+
[{'Value' => { 'S' => 'xxx' }, 'Exists' => true}],
|
100
|
+
[{'Value' => { 'S' => 'xxx' }, 'Exists' => false}, /cannot expect/i]].each do |value, message|
|
101
|
+
|
102
|
+
op = lambda {
|
103
|
+
subject.put_item(item.merge({'Expected' => { 'AttributeName3' => value }}))
|
104
|
+
}
|
105
|
+
|
106
|
+
if message
|
107
|
+
expect(&op).to raise_error(ValidationException, message)
|
108
|
+
else
|
109
|
+
expect(&op).to raise_error(ConditionalCheckFailedException)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should give default response' do
|
115
|
+
item['Item']['AttributeName3'] = { 'S' => "new" }
|
116
|
+
subject.put_item(item).should include(consumed_capacity)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should send old item' do
|
120
|
+
old_item = Utils.deep_copy(item)
|
121
|
+
new_item = Utils.deep_copy(item)
|
122
|
+
new_item['Item']['AttributeName3'] = { 'S' => "new" }
|
123
|
+
new_item.merge!({'ReturnValues' => 'ALL_OLD'})
|
124
|
+
subject.put_item(new_item)['Attributes'].should == old_item['Item']
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context '#get_item' do
|
130
|
+
subject do
|
131
|
+
table = Table.new(data)
|
132
|
+
table.put_item(item)
|
133
|
+
table
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should return empty when the key is not found' do
|
137
|
+
response = subject.get_item({'TableName' => 'Table1',
|
138
|
+
'Key' => {
|
139
|
+
'HashKeyElement' => { 'S' => 'xxx' },
|
140
|
+
'RangeKeyElement' => { 'N' => '11' }
|
141
|
+
}
|
142
|
+
})
|
143
|
+
response.should eq(consumed_capacity)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should filter attributes' do
|
147
|
+
response = subject.get_item({'TableName' => 'Table1',
|
148
|
+
'Key' => {
|
149
|
+
'HashKeyElement' => { 'S' => 'test' },
|
150
|
+
'RangeKeyElement' => { 'N' => '11' }
|
151
|
+
},
|
152
|
+
'AttributesToGet' => ['AttributeName3', 'xxx']
|
153
|
+
})
|
154
|
+
response.should eq({ 'Item' => { 'AttributeName3' => { 'S' => 'another'}},
|
155
|
+
'ConsumedCapacityUnits' => 1})
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context '#delete_item' do
|
160
|
+
subject do
|
161
|
+
table = Table.new(data)
|
162
|
+
table.put_item(item)
|
163
|
+
table
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should delete item' do
|
167
|
+
response = subject.delete_item(key)
|
168
|
+
response.should eq(consumed_capacity)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should be idempotent' do
|
172
|
+
response_1 = subject.delete_item(key)
|
173
|
+
response_2 = subject.delete_item(key)
|
174
|
+
|
175
|
+
response_1.should == response_2
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should check conditions' do
|
179
|
+
expect do
|
180
|
+
subject.delete_item(key.merge({'Expected' =>
|
181
|
+
{'AttributeName3' => { 'Exists' => false }}}))
|
182
|
+
end.to raise_error(ConditionalCheckFailedException)
|
183
|
+
|
184
|
+
response = subject.delete_item(key.merge({'Expected' =>
|
185
|
+
{'AttributeName3' =>
|
186
|
+
{'Value' => { 'S' => 'another'}}}}))
|
187
|
+
response.should eq(consumed_capacity)
|
188
|
+
|
189
|
+
expect do
|
190
|
+
subject.delete_item(key.merge({'Expected' =>
|
191
|
+
{'AttributeName3' =>
|
192
|
+
{'Value' => { 'S' => 'another'}}}}))
|
193
|
+
end.to raise_error(ConditionalCheckFailedException)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should return old value' do
|
197
|
+
response = subject.delete_item(key.merge('ReturnValues' => 'ALL_OLD'))
|
198
|
+
response.should eq(consumed_capacity.merge({'Attributes' => item['Item']}))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context '#update_item' do
|
203
|
+
subject do
|
204
|
+
table = Table.new(data)
|
205
|
+
table.put_item(item)
|
206
|
+
table
|
207
|
+
end
|
208
|
+
|
209
|
+
let(:put) do
|
210
|
+
{'AttributeUpdates' => {'AttributeName3' => { 'Value' => { 'S' => 'updated' },
|
211
|
+
'Action' => 'PUT'}}}
|
212
|
+
end
|
213
|
+
|
214
|
+
let(:delete) do
|
215
|
+
{'AttributeUpdates' => {'AttributeName3' => {'Action' => 'DELETE'}}}
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should check conditions" do
|
219
|
+
expect do
|
220
|
+
subject.update_item(key.merge({'Expected' =>
|
221
|
+
{'AttributeName3' => { 'Exists' => false }}}))
|
222
|
+
end.to raise_error(ConditionalCheckFailedException)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should create new item if the key doesn't exist" do
|
226
|
+
key['Key']['HashKeyElement']['S'] = 'new'
|
227
|
+
subject.update_item(key.merge(put))
|
228
|
+
subject.get_item(key).should include( "Item"=>
|
229
|
+
{"AttributeName1"=>{"S"=>"new"},
|
230
|
+
"AttributeName2"=>{"N"=>"11"},
|
231
|
+
"AttributeName3"=>{"S"=>"updated"}})
|
232
|
+
end
|
233
|
+
|
234
|
+
it "shouldn't create a new item if key doesn't exist and action is delete" do
|
235
|
+
key['Key']['HashKeyElement']['S'] = 'new'
|
236
|
+
subject.update_item(key.merge(delete))
|
237
|
+
subject.get_item(key).should eq(consumed_capacity)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should handle return values" do
|
241
|
+
data = key.merge(put).merge({'ReturnValues' => 'UPDATED_NEW'})
|
242
|
+
subject.update_item(data).should include({'Attributes' => { 'AttributeName3' => { 'S' => 'updated'}}})
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context '#return_values' do
|
247
|
+
let(:put) do
|
248
|
+
{'AttributeUpdates' => {'AttributeName3' => { 'Value' => { 'S' => 'updated' },
|
249
|
+
'Action' => 'PUT'}}}
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should return values" do
|
253
|
+
[['ALL_OLD', {'x' => 'y'}, nil, {"Attributes" => {'x' => 'y'}}],
|
254
|
+
['ALL_NEW', nil, {'x' => 'y'}, {"Attributes" => {'x' => 'y'}}],
|
255
|
+
['NONE', nil, nil, {}]].each do |return_value, old_item, new_item, response|
|
256
|
+
data = {'ReturnValues' => return_value }
|
257
|
+
subject.return_values(data, old_item, new_item).should eq(response)
|
258
|
+
end
|
259
|
+
expect { subject.return_values({'ReturnValues' => 'asdf'}, nil, nil) }.to raise_error(/unknown/)
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should return update old value" do
|
263
|
+
subject.put_item(item)
|
264
|
+
data = key.merge(put).merge({'ReturnValues' => 'UPDATED_OLD'})
|
265
|
+
subject.update_item(data).should include({'Attributes' => { 'AttributeName3' => { 'S' => 'another'}}})
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should return update new value" do
|
269
|
+
subject.put_item(item)
|
270
|
+
data = key.merge(put).merge({'ReturnValues' => 'UPDATED_NEW'})
|
271
|
+
subject.update_item(data).should include({'Attributes' => { 'AttributeName3' => { 'S' => 'updated'}}})
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context '#query' do
|
276
|
+
subject do
|
277
|
+
t = Table.new(data)
|
278
|
+
t.put_item(item)
|
279
|
+
(1..3).each do |i|
|
280
|
+
(1..10).each do |j|
|
281
|
+
item['Item']['AttributeName1']['S'] = "att#{i}"
|
282
|
+
item['Item']['AttributeName2']['N'] = j.to_s
|
283
|
+
t.put_item(item)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
t
|
287
|
+
end
|
288
|
+
|
289
|
+
let(:query) do
|
290
|
+
{
|
291
|
+
'TableName' => 'Table1',
|
292
|
+
'Limit' => 5,
|
293
|
+
'HashKeyValue' => {'S' => 'att1'},
|
294
|
+
'RangeKeyCondition' => {
|
295
|
+
'AttributeValueList' => [{'N' => '1'}],
|
296
|
+
'ComparisonOperator' => 'GT'
|
297
|
+
},
|
298
|
+
'ScanIndexForward' => true
|
299
|
+
}
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should not allow count and attributes_to_get simutaneously' do
|
303
|
+
expect {
|
304
|
+
subject.query({'Count' => 0, 'AttributesToGet' => ['xx']})
|
305
|
+
}.to raise_error(ValidationException, /count/i)
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'should not allow to query on a table without rangekey' do
|
309
|
+
data['KeySchema'].delete('RangeKeyElement')
|
310
|
+
t = Table.new(data)
|
311
|
+
expect {
|
312
|
+
t.query(query)
|
313
|
+
}.to raise_error(ValidationException, /key schema/)
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'should only allow limit greater than zero' do
|
317
|
+
expect {
|
318
|
+
subject.query(query.merge('Limit' => 0))
|
319
|
+
}.to raise_error(ValidationException, /limit/i)
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should handle basic query' do
|
323
|
+
result = subject.query(query)
|
324
|
+
result['Count'].should eq(5)
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'should handle scanindexforward' do
|
328
|
+
result = subject.query(query)
|
329
|
+
result['Items'].first['AttributeName2'].should eq({'N' => '2'})
|
330
|
+
result = subject.query(query.merge({'ScanIndexForward' => false}))
|
331
|
+
result['Items'].first['AttributeName2'].should eq({'N' => '10'})
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'should return lastevaluated key' do
|
335
|
+
result = subject.query(query)
|
336
|
+
result['LastEvaluatedKey'].should == {"HashKeyElement"=>{"S"=>"att1"}, "RangeKeyElement"=>{"N"=>"6"}}
|
337
|
+
result = subject.query(query.merge('Limit' => 100))
|
338
|
+
result['LastEvaluatedKey'].should be_nil
|
339
|
+
|
340
|
+
query.delete('Limit')
|
341
|
+
result = subject.query(query)
|
342
|
+
result['LastEvaluatedKey'].should be_nil
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'should handle exclusive start key' do
|
346
|
+
result = subject.query(query.merge({'ExclusiveStartKey' => {"HashKeyElement"=>{"S"=>"att1"}, "RangeKeyElement"=>{"N"=>"6"}}}))
|
347
|
+
result['Count'].should eq(4)
|
348
|
+
result['Items'].first['AttributeName2'].should eq({'N' => '7'})
|
349
|
+
result = subject.query(query.merge({'ExclusiveStartKey' => {"HashKeyElement"=>{"S"=>"att1"}, "RangeKeyElement"=>{"N"=>"88"}}}))
|
350
|
+
result['Count'].should eq(0)
|
351
|
+
result['Items'].should be_empty
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'should return all elements if not rangekeycondition is given' do
|
355
|
+
query.delete('RangeKeyCondition')
|
356
|
+
result = subject.query(query)
|
357
|
+
result['Count'].should eq(5)
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'should handle between operator' do
|
361
|
+
query['RangeKeyCondition'] = {
|
362
|
+
'AttributeValueList' => [{'N' => '1'}, {'N' => '4'}],
|
363
|
+
'ComparisonOperator' => 'BETWEEN'
|
364
|
+
}
|
365
|
+
result = subject.query(query)
|
366
|
+
result['Count'].should eq(4)
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'should handle attributes_to_get' do
|
370
|
+
query['AttributesToGet'] = ['AttributeName1', "AttributeName2"]
|
371
|
+
result = subject.query(query)
|
372
|
+
result['Items'].first.should eq('AttributeName1' => { 'S' => 'att1'},
|
373
|
+
'AttributeName2' => { 'N' => '2' })
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context '#scan' do
|
378
|
+
subject do
|
379
|
+
t = Table.new(data)
|
380
|
+
(1..3).each do |i|
|
381
|
+
(1..10).each do |j|
|
382
|
+
item['Item']['AttributeName1']['S'] = "att#{i}"
|
383
|
+
item['Item']['AttributeName2']['N'] = j.to_s
|
384
|
+
t.put_item(item)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
t
|
388
|
+
end
|
389
|
+
|
390
|
+
let(:scan) do
|
391
|
+
{
|
392
|
+
'TableName' => 'Table1',
|
393
|
+
'ScanFilter' => {
|
394
|
+
'AttributeName2' => {
|
395
|
+
'AttributeValueList' => [{'N' => '1'}],
|
396
|
+
'ComparisonOperator' => 'GE'
|
397
|
+
}
|
398
|
+
}
|
399
|
+
}
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'should not allow count and attributes_to_get simutaneously' do
|
403
|
+
expect {
|
404
|
+
subject.scan({'Count' => 0, 'AttributesToGet' => ['xx']})
|
405
|
+
}.to raise_error(ValidationException, /count/i)
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'should only allow limit greater than zero' do
|
409
|
+
expect {
|
410
|
+
subject.scan(scan.merge('Limit' => 0))
|
411
|
+
}.to raise_error(ValidationException, /limit/i)
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'should handle basic scan' do
|
415
|
+
result = subject.scan(scan)
|
416
|
+
result['Count'].should eq(30)
|
417
|
+
|
418
|
+
scan['ScanFilter']['AttributeName2']['ComparisonOperator'] = 'EQ'
|
419
|
+
subject.scan(scan)['Count'].should eq(3)
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'should return lastevaluated key' do
|
423
|
+
scan['Limit'] = 5
|
424
|
+
result = subject.scan(scan)
|
425
|
+
result['LastEvaluatedKey'].should == {"HashKeyElement"=>{"S"=>"att1"}, "RangeKeyElement"=>{"N"=>"5"}}
|
426
|
+
result = subject.scan(scan.merge('Limit' => 100))
|
427
|
+
result['LastEvaluatedKey'].should be_nil
|
428
|
+
|
429
|
+
scan.delete('Limit')
|
430
|
+
result = subject.scan(scan)
|
431
|
+
result['LastEvaluatedKey'].should be_nil
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|