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