orchestrate 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +84 -0
- data/lib/orchestrate/client.rb +42 -0
- data/lib/orchestrate/collection.rb +31 -0
- data/lib/orchestrate/key_value.rb +160 -3
- data/lib/orchestrate/version.rb +1 -1
- data/test/orchestrate/api/key_value_api_test.rb +71 -1
- data/test/orchestrate/collection_geo_queries_test.rb +89 -0
- data/test/orchestrate/key_value_persistence_test.rb +401 -16
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7d5c6bc09a49e08d464387e25b98ae9777bbf93
|
4
|
+
data.tar.gz: 72a68c6e456c4920c9875e7e3f7108b29bd59d12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8a2a5f7124cd557dc4a3ed3a4fc9d71064494e8241ae87409c8fcfd2fe40236eaebd0171e54c5c2ab22bcba03a1e33bb8a81171681a205edb4c153f747ef2a9
|
7
|
+
data.tar.gz: 753527cab83aac5537fd178ac976cf704f6c173c63b0b64a2f9c9fdd859c30d18e6d8cd34cb92bcc4b6bb2aa3643e66f47918193d561c65e88b2f591b82f802e
|
data/README.md
CHANGED
@@ -45,6 +45,24 @@ jill.value # { "name" => "Jill", "location" =>
|
|
45
45
|
jill.save # PUT-If-Match, updates ref
|
46
46
|
```
|
47
47
|
|
48
|
+
#### Manipulate KeyValues with PATCH
|
49
|
+
```ruby
|
50
|
+
jill.merge({location: "Under the Hill"}) # Updates jill by merging given partial value into existing value
|
51
|
+
|
52
|
+
# Patch Operations
|
53
|
+
jill.add('favorite_sibling', 'Jack').update # adds new field/value pair to jill
|
54
|
+
jill.remove('favorite_sibling').update # removes given field/value pair from jill
|
55
|
+
jill.replace('on_the_hill', false).update # replaces given field with given value
|
56
|
+
jill.move('name', 'first_name').update # moves given field's value to new field
|
57
|
+
jill.copy('first_name', 'full_name').update # copies given field's value to another field
|
58
|
+
jill.increment('age', 1).update # increments given field (must have numeric value) by provided amount
|
59
|
+
jill.decrement('years_to_live', 1).update # decrements given field (must have numeric value) by provided amount
|
60
|
+
jill.test('full_name', 'Jill').update # tests equality of existing field/value pair with given field/value
|
61
|
+
|
62
|
+
# Patch Operations can be chained together to perform multiple updates to a KeyValue item
|
63
|
+
jill.add('favorite_food', 'Pizza').remove('years_to_live').update()
|
64
|
+
```
|
65
|
+
|
48
66
|
#### Searching, Sorting for KeyValues
|
49
67
|
```ruby
|
50
68
|
users.search("name:Jill") # returns users with name "Jill"
|
@@ -63,6 +81,21 @@ users.search("location: Portland").order(:name) # returns users in ascending ord
|
|
63
81
|
users.search("location: Portland").order(:name, :asc, :rank, :desc, :created_at) # :created_at argument defaults to :asc
|
64
82
|
```
|
65
83
|
|
84
|
+
### Geo Queries
|
85
|
+
```ruby
|
86
|
+
# Create a Collection object
|
87
|
+
cafes = app[:cafes]
|
88
|
+
|
89
|
+
# Find cafes near a given geographic point
|
90
|
+
cafes.near(:location, 12.56, 19.443, 4, 'mi') # returns cafes in a 4 mile radius of given latitude, longitude
|
91
|
+
|
92
|
+
# Sort nearby cafes by distance
|
93
|
+
cafes.near(:location, 12.56, 19.443, 4, 'mi').order(:distance) # returns nearby cafes in ascending order (closest to farthest)
|
94
|
+
|
95
|
+
# Find cafes in a given area using a bounding box
|
96
|
+
cafes.in(:location, {north:12.5, east:57, south:12, west:56}) # returns all cafes within specified bounding box
|
97
|
+
```
|
98
|
+
|
66
99
|
### Method Client use
|
67
100
|
|
68
101
|
#### Create a Client
|
@@ -84,6 +117,27 @@ client.delete(:users, :jack, jack.ref) # DELETE-If-Match, returns API::Resp
|
|
84
117
|
client.list(:users) # LIST users, returns API::CollectionResposne
|
85
118
|
```
|
86
119
|
|
120
|
+
#### Manipulate KeyValues with PATCH
|
121
|
+
```ruby
|
122
|
+
# Give a set of operations to manipulate a given key
|
123
|
+
# Operations are executed in sequential order
|
124
|
+
ops = [
|
125
|
+
{ "op" => "add", "path" => "nimble", "value" => true }, # adds new field/value pair
|
126
|
+
{ "op" => "remove", "path" => "nimble" }, # removes given field/value pair
|
127
|
+
{ "op" => "replace", "path" => "quick", "value" => true }, # replaces given field with given value
|
128
|
+
{ "op" => "move", "from" => "name", "path" => "first_name" }, # moves given field's value to new field
|
129
|
+
{ "op" => "copy", "from" => "city", "path" => "home_town" }, # copies given field's value to another field
|
130
|
+
{ "op" => "inc", "path" => "age", "value" => 1 }, # increment a numeric value at a given path
|
131
|
+
{ "op" => "inc", "path" => "age", "value" => -1 }, # pass a negative number to decrement a numeric value
|
132
|
+
{ "op" => "test", "path" => "first_name", "value" => "Jack" }, # tests equality of existing field/value pair with given field/value
|
133
|
+
]
|
134
|
+
|
135
|
+
client.patch(:users, :jack, ops)
|
136
|
+
|
137
|
+
# Merge partial values into existing key
|
138
|
+
client.patch_merge(:users, :jack, { favorite_food: "Donuts" })
|
139
|
+
```
|
140
|
+
|
87
141
|
#### Search Collections
|
88
142
|
```ruby
|
89
143
|
client.search(:users, "location:Portland") # search 'users' collection for items with a location of 'Portland'
|
@@ -96,6 +150,29 @@ client.search(:users, "location:Portland", { sort: "value.name:asc" }) # returns
|
|
96
150
|
client.search(:users, "location:Portland", { sort: "value.name.last:asc,value.name.first:asc" }) # returns items sorted primarily by last name, but whenever two users have an identical last name, the results will be sorted by first name as well.
|
97
151
|
```
|
98
152
|
|
153
|
+
### Geo Queries
|
154
|
+
```ruby
|
155
|
+
# Find cafes near a given geographic point
|
156
|
+
|
157
|
+
coords = {
|
158
|
+
lat: 12.56,
|
159
|
+
lon: 19.443,
|
160
|
+
dist: '4mi' # Define size of search radius for NEAR query
|
161
|
+
}
|
162
|
+
query = "value.location:NEAR:{lat:#{coords.lat} lon:#{coords.lon} dist:#{coords.dist}}"
|
163
|
+
client.search(:cafes, query) # returns cafes in a 4 mile radius of given latitude, longitude
|
164
|
+
|
165
|
+
# Using the previous coords & query,
|
166
|
+
# sort results by distance
|
167
|
+
client.search(:cafes, query, {
|
168
|
+
sort: 'value.location:distance:asc'
|
169
|
+
})
|
170
|
+
|
171
|
+
# Find cafes in a given area using a bounding box
|
172
|
+
query = "value:IN:{ north:12.5 east:57 south:12 west:56 }"
|
173
|
+
client.search(:cafes, query)
|
174
|
+
```
|
175
|
+
|
99
176
|
### Examples and Documentation
|
100
177
|
|
101
178
|
There are more examples at [Orchestrate's API Documentation][apidoc] and documentation in the [rdoc][].
|
@@ -200,6 +277,13 @@ end
|
|
200
277
|
|
201
278
|
## Release Notes
|
202
279
|
|
280
|
+
### December 11, 2014: release 0.10.0
|
281
|
+
- **BACKWARDS-INCOMPATIBLE** Prior `KeyValue#update` & `KeyValue#update!` renamed to `KeyValue#set` & `KeyValue#set!`. `KeyValue#update` now used after `PATCH` operations to fire the request.
|
282
|
+
- Implement `Collection#near` & `Collection#in`, allowing `Collection` to perform geo queries.
|
283
|
+
- Implement `Client#patch`, `Client#patch_merge`, allowing `Client` to perform subdocument updates through `PATCH` requests.
|
284
|
+
- Implement `KeyValue::OperationSet`, allowing a set of `PATCH` operations to be built by `KeyValue` through `KeyValue#add`, `KeyValue#remove`, `KeyValue#replace`, `KeyValue#move`, `KeyValue#copy`, `KeyValue#increment`, `KeyValue#decrement`, & `KeyValue#test`. The `KeyValue::OperationSet` is fired by ending the chain with `KeyValue#update`.
|
285
|
+
- Implement `KeyValue#merge`, allowing `KeyValue` to merge partial values into existing keys through `PATCH` requests.
|
286
|
+
|
203
287
|
### November 19, 2014: release 0.9.2
|
204
288
|
- Implement `SearchResults#order`, allowing `Collection` object results to be sorted.
|
205
289
|
- Implement Data Center choice on `Orchestrate::Client` and `Orchestrate::Application`.
|
data/lib/orchestrate/client.rb
CHANGED
@@ -166,6 +166,46 @@ module Orchestrate
|
|
166
166
|
put collection, key, body, false
|
167
167
|
end
|
168
168
|
|
169
|
+
# Manipulate values associated with a key, without retrieving the key object.
|
170
|
+
# Array of operations passed as body, will execute operations on the key sequentially.
|
171
|
+
# [Patch](http://orchestrate.io/docs/apiref#keyvalue-patch).
|
172
|
+
# @param collection [#to_s] The name of the collection.
|
173
|
+
# @param key [#to_s] The name of the key.
|
174
|
+
# @param body [#to_json] The value for the key.
|
175
|
+
# @param condition [String, false, nil] Conditions for setting the value.
|
176
|
+
# If `String`, value used as `If-Match`, value will only be updated if key's current value's ref matches.
|
177
|
+
# If `nil` (default), value is set regardless.
|
178
|
+
# @return Orchestrate::API::ItemResponse
|
179
|
+
# @raise Orchestrate::API::BadRequest the body is not valid JSON.
|
180
|
+
# @raise Orchestrate::API::AlreadyPresent the `false` condition was given, but a value already exists for this collection/key combo.
|
181
|
+
def patch(collection, key, body, condition=nil)
|
182
|
+
headers = {'Content-Type' => 'application/json-patch+json'}
|
183
|
+
if condition.is_a?(String)
|
184
|
+
headers['If-Match'] = API::Helpers.format_ref(condition)
|
185
|
+
end
|
186
|
+
send_request :patch, [collection, key], { body: body, headers: headers, response: API::ItemResponse }
|
187
|
+
end
|
188
|
+
|
189
|
+
# Merge field/value pairs into existing key, without retrieving the key object.
|
190
|
+
# [Patch](http://orchestrate.io/docs/apiref#keyvalue-patch-merge).
|
191
|
+
# If a given field's value is nil, will remove field from existing key on merge.
|
192
|
+
# @param collection [#to_s] The name of the collection.
|
193
|
+
# @param key [#to_s] The name of the key.
|
194
|
+
# @param body [#to_json] The value for the key.
|
195
|
+
# @param condition [String, false, nil] Conditions for setting the value.
|
196
|
+
# If `String`, value used as `If-Match`, value will only be updated if key's current value's ref matches.
|
197
|
+
# If `nil` (default), value is set regardless.
|
198
|
+
# @return Orchestrate::API::ItemResponse
|
199
|
+
# @raise Orchestrate::API::BadRequest the body is not valid JSON.
|
200
|
+
# @raise Orchestrate::API::AlreadyPresent the `false` condition was given, but a value already exists for this collection/key combo.
|
201
|
+
def patch_merge(collection, key, body, condition=nil)
|
202
|
+
headers = {'Content-Type' => 'application/merge-patch+json'}
|
203
|
+
if condition.is_a?(String)
|
204
|
+
headers['If-Match'] = API::Helpers.format_ref(condition)
|
205
|
+
end
|
206
|
+
send_request :patch, [collection, key], { body: body, headers: headers, response: API::ItemResponse }
|
207
|
+
end
|
208
|
+
|
169
209
|
# [Sets the current value of a key to a null
|
170
210
|
# object](http://orchestrate.io/docs/api/#key/value/delete11).
|
171
211
|
# @param collection [#to_s] The name of the collection.
|
@@ -404,6 +444,8 @@ module Orchestrate
|
|
404
444
|
if [:put, :post].include?(method)
|
405
445
|
headers['Content-Type'] = 'application/json'
|
406
446
|
request.body = body.to_json
|
447
|
+
elsif [:patch].include?(method)
|
448
|
+
request.body = body.to_json
|
407
449
|
end
|
408
450
|
headers.each {|header, value| request[header] = value }
|
409
451
|
end
|
@@ -357,6 +357,37 @@ module Orchestrate
|
|
357
357
|
end
|
358
358
|
# @!endgroup
|
359
359
|
|
360
|
+
# @!group Geo Queries
|
361
|
+
# Performs a search for items near a specified geographic point in a collection.
|
362
|
+
# [Search for items near a geographic point](http://orchestrate.io/docs/apiref#geo-queries-near)
|
363
|
+
# @param field [#to_s] The field containing location data (latitude & longitude)
|
364
|
+
# @param latitude [Float] The number representing latitude of a geographic point
|
365
|
+
# @param longitude [Float] The number representing longitude of a geographic point
|
366
|
+
# @param distance [Integer] The number of distance units.
|
367
|
+
# @param units [#to_s] Unit of measurement for distance, default to kilometers (km),
|
368
|
+
# supported units: km, m, cm, mm, mi, yd, ft, in, nmi
|
369
|
+
# @return [SearchResults] A loaded SearchResults object ready to enumerate over.
|
370
|
+
def near(field, latitude, longitude, distance, units=nil)
|
371
|
+
units ||= 'km'
|
372
|
+
query = "#{field}:NEAR:{lat:#{latitude} lon:#{longitude} dist:#{distance}#{units}}"
|
373
|
+
SearchResults.new(self, query)
|
374
|
+
end
|
375
|
+
|
376
|
+
# Performs a search for items within a particular area,
|
377
|
+
# using a specified bounding box
|
378
|
+
# [Search for items in a geographic bounding box](https://orchestrate.io/docs/apiref#geo-queries-bounding)
|
379
|
+
# @param field [#to_s] The field containing location data (latitude & longitude)
|
380
|
+
# @param box [#to_json] The values to create the bounding box,
|
381
|
+
# @example
|
382
|
+
# collection.in(:field, {north: 12.5, south: 15, east: 14, west: 3})
|
383
|
+
# @return [SearchResults] A loaded SearchResults object ready to enumerate over.
|
384
|
+
def in(field, box={})
|
385
|
+
box = box.flatten.each_slice(2).map {|dir, val| "#{dir}:#{val}" }.join(" ")
|
386
|
+
query = "#{field}:IN:{#{box}}"
|
387
|
+
SearchResults.new(self, query)
|
388
|
+
end
|
389
|
+
# @!endgroup
|
390
|
+
|
360
391
|
# An enumerator with a query for searching an Orchestrate::Collection
|
361
392
|
class SearchResults
|
362
393
|
# @return [Collection] The collection this object will search.
|
@@ -208,9 +208,9 @@ module Orchestrate
|
|
208
208
|
# Merges a set of values into the item's existing value and saves.
|
209
209
|
# @param merge [#each_pair] The Hash-like to merge into #value. Keys will be stringified.
|
210
210
|
# @return [true, false]
|
211
|
-
def
|
211
|
+
def set(merge)
|
212
212
|
begin
|
213
|
-
|
213
|
+
set!(merge)
|
214
214
|
rescue API::RequestError, API::ServiceError
|
215
215
|
false
|
216
216
|
end
|
@@ -221,11 +221,168 @@ module Orchestrate
|
|
221
221
|
# @return [true]
|
222
222
|
# @raise [Orchestrate::API::VersionMismatch] If the KeyValue item has been updated with a new ref since this KeyValue was loaded.
|
223
223
|
# @raise [Orchestrate::API::RequestError, Orchestrate::API::ServiceError] If there was any other problem with saving.
|
224
|
-
def
|
224
|
+
def set!(merge)
|
225
225
|
merge.each_pair {|key, value| @value[key.to_s] = value }
|
226
226
|
save!
|
227
227
|
end
|
228
228
|
|
229
|
+
# @!group patch-merge
|
230
|
+
|
231
|
+
# Merges the given value into a KeyValue item using a patch request,
|
232
|
+
# enabling merges to be peformed without retrieving the KeyValue item.
|
233
|
+
# @param value [#to_json] Hash of partial values, to be converted to json,
|
234
|
+
# and merged into the KeyValue item.
|
235
|
+
# @param ref [String] optional condition, provide ref for 'If-Match' header
|
236
|
+
def merge(value, ref=nil)
|
237
|
+
self.perform(:patch_merge, value, ref)
|
238
|
+
end
|
239
|
+
|
240
|
+
# @!endgroup patch-merge
|
241
|
+
# @!group patch-operations
|
242
|
+
|
243
|
+
# Manipulate a KeyValue item with a set of operations.
|
244
|
+
# Chain a series of operation methods (#add, #remove, etc) to build the set,
|
245
|
+
# operations will be executed by Orchestrate in sequential order.
|
246
|
+
# @param ref [String] optional condition, provide ref for 'If-Match' header
|
247
|
+
# To perform singular operations or a set, add #update to the end of the method chain.
|
248
|
+
# @example
|
249
|
+
# users['jon-snow'].add(:beard, true).remove(:some_field).update()
|
250
|
+
# @raise Orchestrate::API::NotFound if key does not exist
|
251
|
+
def update(ref=nil)
|
252
|
+
OperationSet.new(self).update(ref)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Adds a new field/value pair to a KeyValue at the designated path (field name).
|
256
|
+
# @param path [#to_s] The location of the field to add.
|
257
|
+
# @param value [#to_json] The value to assign to the specified field.
|
258
|
+
# @return Orchestrate::KeyValue::OperationSet
|
259
|
+
def add(path, value)
|
260
|
+
OperationSet.new(self).add(path, value)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Removes a field/value pair from a KeyValue item
|
264
|
+
# @param path [#to_s] The field to remove.
|
265
|
+
# @raise Orchestrate::API::RequestError if field does not exist
|
266
|
+
def remove(path)
|
267
|
+
OperationSet.new(self).remove(path)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Replaces an existing field's value
|
271
|
+
# @param path [#to_s] The field whose value to replace.
|
272
|
+
# @param value [#to_json] The value to assign to the specified field.
|
273
|
+
# @raise Orchestrate::API::RequestError if field does not exist
|
274
|
+
# @return Orchestrate::KeyValue::OperationSet
|
275
|
+
def replace(path, value)
|
276
|
+
OperationSet.new(self).replace(path, value)
|
277
|
+
end
|
278
|
+
|
279
|
+
# Moves a KeyValue item's field/value pair to a new field (path)
|
280
|
+
# @param from_path [#to_s] The field to move.
|
281
|
+
# @param to_path [#to_s] The new location of the moved field.
|
282
|
+
# @raise Orchestrate::API::RequestError if field does not exist
|
283
|
+
# @return Orchestrate::KeyValue::OperationSet
|
284
|
+
def move(from_path, to_path)
|
285
|
+
OperationSet.new(self).move(from_path, to_path)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Copies a KeyValue item's field value to another field (path)
|
289
|
+
# @param from_path [#to_s] The field to copy.
|
290
|
+
# @param to_path [#to_s] The target location to copy field/value to.
|
291
|
+
# @raise Orchestrate::API::RequestError if field does not exist
|
292
|
+
# @return Orchestrate::KeyValue::OperationSet
|
293
|
+
def copy(from_path, to_path)
|
294
|
+
OperationSet.new(self).copy(from_path, to_path)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Increases a KeyValue item's field number value by given amount
|
298
|
+
# @param path [#to_s] The field (with a number value) to increment.
|
299
|
+
# @param amount [Integer] The amount to increment the field's value by.
|
300
|
+
# @raise Orchestrate::API::RequestError if field does not exist or
|
301
|
+
# if existing value is not a number type
|
302
|
+
# @return Orchestrate::KeyValue::OperationSet
|
303
|
+
def increment(path, amount)
|
304
|
+
OperationSet.new(self).increment(path, amount)
|
305
|
+
end
|
306
|
+
alias :inc :increment
|
307
|
+
|
308
|
+
# Decreases a KeyValue item's field number value by given amount
|
309
|
+
# @param path [#to_s] The field (with a number value) to decrement.
|
310
|
+
# @param amount [Integer] The amount to decrement the field's value by.
|
311
|
+
# @raise Orchestrate::API::RequestError if field does not exist or
|
312
|
+
# if existing value is not a number type
|
313
|
+
# @return Orchestrate::KeyValue::OperationSet
|
314
|
+
def decrement(path, amount)
|
315
|
+
OperationSet.new(self).decrement(path, amount)
|
316
|
+
end
|
317
|
+
alias :dec :decrement
|
318
|
+
|
319
|
+
# Tests equality of a KeyValue's existing field/value pair
|
320
|
+
# @param path [#to_s] The field to check equality of.
|
321
|
+
# @param value [#to_json] The expected value of the field.
|
322
|
+
# @raise Orchestrate::API::RequestError if field does not exist
|
323
|
+
# @raise Orchestrate::API::IndexingError if equality check fails
|
324
|
+
# @return Orchestrate::KeyValue::OperationSet
|
325
|
+
def test(path, value)
|
326
|
+
OperationSet.new(self).test(path, value)
|
327
|
+
end
|
328
|
+
|
329
|
+
# An object which builds a set of operations for manipulating an Orchestrate::KeyValue
|
330
|
+
class OperationSet
|
331
|
+
|
332
|
+
# @return [KeyValue] The keyvalue this object will manipulate.
|
333
|
+
attr_reader :key_value
|
334
|
+
|
335
|
+
# @return operations to be performed.
|
336
|
+
attr_reader :operations
|
337
|
+
|
338
|
+
# Initialize a new OperationSet object
|
339
|
+
# @param key_value [Orchestrate::KeyValue] The keyvalue to manipulate.
|
340
|
+
def initialize(key_value, operations=[])
|
341
|
+
@key_value = key_value
|
342
|
+
@operations = operations
|
343
|
+
end
|
344
|
+
|
345
|
+
def update(ref=nil)
|
346
|
+
key_value.perform(:patch, operations, ref)
|
347
|
+
end
|
348
|
+
|
349
|
+
def add(path, value)
|
350
|
+
self.class.new(key_value, operations.push({op: "add", path: "#{path}", value: value}))
|
351
|
+
end
|
352
|
+
|
353
|
+
def remove(path)
|
354
|
+
self.class.new(key_value, operations.push({op: "remove", path: "#{path}"}))
|
355
|
+
end
|
356
|
+
|
357
|
+
def replace(path, value)
|
358
|
+
self.class.new(key_value, operations.push({op: "replace", path: "#{path}", value: value}))
|
359
|
+
end
|
360
|
+
|
361
|
+
def move(from_path, to_path)
|
362
|
+
self.class.new(key_value, operations.push({op: "move", from: "#{from_path}", path: "#{to_path}"}))
|
363
|
+
end
|
364
|
+
|
365
|
+
def copy(from_path, to_path)
|
366
|
+
self.class.new(key_value, operations.push({op: "copy", from: "#{from_path}", path: "#{to_path}"}))
|
367
|
+
end
|
368
|
+
|
369
|
+
def increment(path, amount)
|
370
|
+
self.class.new(key_value, operations.push({op: "inc", path: "#{path}", value: amount}))
|
371
|
+
end
|
372
|
+
alias :inc :increment
|
373
|
+
|
374
|
+
def decrement(path, amount)
|
375
|
+
self.class.new(key_value, operations.push({op: "inc", path: "#{path}", value: -amount}))
|
376
|
+
end
|
377
|
+
alias :dec :decrement
|
378
|
+
|
379
|
+
def test(path, value)
|
380
|
+
self.class.new(key_value, operations.push({op: "test", path: "#{path}", value: value}))
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# @!endgroup patch-operations
|
385
|
+
|
229
386
|
# Deletes the KeyValue item from Orchestrate using 'If-Match' with the current ref.
|
230
387
|
# Returns false if the item failed to delete because a new ref had been created since this KeyValue was loaded.
|
231
388
|
# @return [true, false]
|
data/lib/orchestrate/version.rb
CHANGED
@@ -64,7 +64,7 @@ class KeyValueAPITest < MiniTest::Unit::TestCase
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def test_puts_key_value_without_ref
|
67
|
-
body={"foo" => "bar"}
|
67
|
+
body = {"foo" => "bar"}
|
68
68
|
ref = "12345"
|
69
69
|
base_location = "/v0/#{@collection}/#{@key}"
|
70
70
|
@stubs.put(base_location) do |env|
|
@@ -143,6 +143,76 @@ class KeyValueAPITest < MiniTest::Unit::TestCase
|
|
143
143
|
assert_equal 200, response.status
|
144
144
|
end
|
145
145
|
|
146
|
+
def test_patches_key_value_without_ref
|
147
|
+
body =[ {"op" => "add", "path" => "foo", "value" => "bar"} ]
|
148
|
+
ref = "12345"
|
149
|
+
base_location = "/v0/#{@collection}/#{@key}"
|
150
|
+
@stubs.patch(base_location) do |env|
|
151
|
+
assert_authorization @basic_auth, env
|
152
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
153
|
+
assert_equal body.to_json, env.body
|
154
|
+
headers = { "Location" => "#{base_location}/refs/#{ref}",
|
155
|
+
"Etag" => "\"#{ref}\"" }
|
156
|
+
[ 201, response_headers(headers), '' ]
|
157
|
+
end
|
158
|
+
|
159
|
+
response = @client.patch(@collection, @key, body)
|
160
|
+
assert_equal 201, response.status
|
161
|
+
assert_equal ref, response.ref
|
162
|
+
assert_equal "#{base_location}/refs/#{ref}", response.location
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_patches_key_value_with_specific_ref
|
166
|
+
body = [ {"op" => "add", "path" => "foo", "value" => "bar"} ]
|
167
|
+
ref = '123456'
|
168
|
+
|
169
|
+
@stubs.patch("/v0/#{@collection}/#{@key}") do |env|
|
170
|
+
assert_authorization @basic_auth, env
|
171
|
+
assert_header 'If-Match', "\"#{ref}\"", env
|
172
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
173
|
+
assert_equal body.to_json, env.body
|
174
|
+
[ 201, response_headers, '' ]
|
175
|
+
end
|
176
|
+
|
177
|
+
response = @client.patch(@collection, @key, body, ref)
|
178
|
+
assert_equal 201, response.status
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_patch_merges_key_value_without_ref
|
182
|
+
body = {"foo" => "bar"}
|
183
|
+
ref = "12345"
|
184
|
+
base_location = "/v0/#{@collection}/#{@key}"
|
185
|
+
@stubs.patch(base_location) do |env|
|
186
|
+
assert_authorization @basic_auth, env
|
187
|
+
assert_header 'Content-Type', 'application/merge-patch+json', env
|
188
|
+
assert_equal body.to_json, env.body
|
189
|
+
headers = { "Location" => "#{base_location}/refs/#{ref}",
|
190
|
+
"Etag" => "\"#{ref}\"" }
|
191
|
+
[ 201, response_headers(headers), '' ]
|
192
|
+
end
|
193
|
+
|
194
|
+
response = @client.patch_merge(@collection, @key, body)
|
195
|
+
assert_equal 201, response.status
|
196
|
+
assert_equal ref, response.ref
|
197
|
+
assert_equal "#{base_location}/refs/#{ref}", response.location
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_patch_merges_key_value_with_specific_ref
|
201
|
+
body = {"foo" => "bar"}
|
202
|
+
ref = '123456'
|
203
|
+
|
204
|
+
@stubs.patch("/v0/#{@collection}/#{@key}") do |env|
|
205
|
+
assert_authorization @basic_auth, env
|
206
|
+
assert_header 'If-Match', "\"#{ref}\"", env
|
207
|
+
assert_header 'Content-Type', 'application/merge-patch+json', env
|
208
|
+
assert_equal body.to_json, env.body
|
209
|
+
[ 201, response_headers, '' ]
|
210
|
+
end
|
211
|
+
|
212
|
+
response = @client.patch_merge(@collection, @key, body, ref)
|
213
|
+
assert_equal 201, response.status
|
214
|
+
end
|
215
|
+
|
146
216
|
def test_delete_key_value
|
147
217
|
@stubs.delete("/v0/#{@collection}/#{@key}") do |env|
|
148
218
|
assert_authorization @basic_auth, env
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CollectionGeoQueriesTest < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@app, @stubs = make_application({parallel:true})
|
6
|
+
@items = @app[:items]
|
7
|
+
|
8
|
+
@near_query = "location:NEAR:{lat:12 lon:56 dist:1km}"
|
9
|
+
@in_query = "location:IN:{north:12 east:57 south:12 west:56}"
|
10
|
+
@total = 110
|
11
|
+
|
12
|
+
@make_listing = lambda{|i| make_kv_listing(:items, key: "item-#{i}", reftime: nil, score: @total-i/@total*5.0) }
|
13
|
+
@handle_geo = lambda do |query|
|
14
|
+
case query
|
15
|
+
when "location:NEAR:{lat:12 lon:56 dist:1km}"
|
16
|
+
{ "results" => 10.times.map{|i| @make_listing.call(i)}, "count" => 10, "total_count" => 10 }
|
17
|
+
when "location:IN:{north:12 east:57 south:12 west:56}"
|
18
|
+
{ "results" => 12.times.map{|i| @make_listing.call(i)}, "count" => 12, "total_count" => 12 }
|
19
|
+
when "location:NEAR:{lat:12 lon:56 dist:1km}&sort=location:distance:asc"
|
20
|
+
{ "results" => 10.times.map{|i| @make_listing.call(i)}, "count" => 10, "total_count" => 10 }
|
21
|
+
else
|
22
|
+
raise ArgumentError.new("unexpected query: #{env.params['query']}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@called = false
|
27
|
+
@stubs.get("/v0/items") do |env|
|
28
|
+
@called = true
|
29
|
+
assert_equal @query, env.params['query']
|
30
|
+
body = @handle_geo.call(env.params['query'])
|
31
|
+
[ 200, response_headers, body.to_json ]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_near_search_without_units
|
36
|
+
@query = @near_query
|
37
|
+
results = @items.near("location", 12, 56, 1).map{|i| i }
|
38
|
+
assert_equal 10, results.length
|
39
|
+
results.each_with_index do |item, idx|
|
40
|
+
assert_in_delta (@total-idx/@total * 5.0), item[0], 0.005
|
41
|
+
assert_equal "item-#{idx}", item[1].key
|
42
|
+
assert_nil item[1].reftime
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_near_search_with_units
|
47
|
+
@query = @near_query
|
48
|
+
results = @items.near("location", 12, 56, 1, 'km').map{|i| i }
|
49
|
+
assert_equal 10, results.length
|
50
|
+
results.each_with_index do |item, idx|
|
51
|
+
assert_in_delta (@total-idx/@total * 5.0), item[0], 0.005
|
52
|
+
assert_equal "item-#{idx}", item[1].key
|
53
|
+
assert_nil item[1].reftime
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_near_search_with_sort
|
58
|
+
@query = @near_query
|
59
|
+
results = @items.near("location", 12, 56, 1, 'km').order(:location).map{|i| i }
|
60
|
+
assert_equal 10, results.length
|
61
|
+
results.each_with_index do |item, idx|
|
62
|
+
assert_in_delta (@total-idx/@total * 5.0), item[0], 0.005
|
63
|
+
assert_equal "item-#{idx}", item[1].key
|
64
|
+
assert_nil item[1].reftime
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_in_bounding_box
|
69
|
+
@query = @in_query
|
70
|
+
results = @items.in("location", {north:12, east:57, south:12, west:56}).map{|i| i }
|
71
|
+
assert_equal 12, results.length
|
72
|
+
results.each_with_index do |item, idx|
|
73
|
+
assert_in_delta (@total-idx/@total * 5.0), item[0], 0.005
|
74
|
+
assert_equal "item-#{idx}", item[1].key
|
75
|
+
assert_nil item[1].reftime
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_in_bounding_box_with_sort
|
80
|
+
@query = @in_query
|
81
|
+
results = @items.in("location", {north:12, east:57, south:12, west:56}).order(:location).map{|i| i }
|
82
|
+
assert_equal 12, results.length
|
83
|
+
results.each_with_index do |item, idx|
|
84
|
+
assert_in_delta (@total-idx/@total * 5.0), item[0], 0.005
|
85
|
+
assert_equal "item-#{idx}", item[1].key
|
86
|
+
assert_nil item[1].reftime
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -109,55 +109,55 @@ class KeyValuePersistenceTest < MiniTest::Unit::TestCase
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
def
|
113
|
-
|
112
|
+
def test_set_merges_values_and_saves
|
113
|
+
set = {hello: "there", 'two' => 2}
|
114
114
|
body = @kv.value
|
115
|
-
|
115
|
+
set.each_pair {|key, value| body[key.to_s] = value }
|
116
116
|
new_ref = make_ref
|
117
117
|
@stubs.put("/v0/items/#{@kv.key}") do |env|
|
118
118
|
assert_equal body, JSON.parse(env.body)
|
119
119
|
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
120
120
|
[201, response_headers({'Etag' => new_ref, "Location" => "/v0/items/#{@kv.key}/refs/#{new_ref}"}), '']
|
121
121
|
end
|
122
|
-
@kv.
|
122
|
+
@kv.set(set)
|
123
123
|
assert_equal body, @kv.value
|
124
124
|
assert_equal new_ref, @kv.ref
|
125
125
|
end
|
126
126
|
|
127
|
-
def
|
128
|
-
|
127
|
+
def test_set_merges_values_and_returns_false_on_error
|
128
|
+
set = {hello: "there", 'two' => 2}
|
129
129
|
body = @kv.value
|
130
|
-
|
130
|
+
set.each_pair {|key, value| body[key.to_s] = value }
|
131
131
|
old_ref = @kv.ref
|
132
132
|
@stubs.put("/v0/items/#{@kv.key}") do |env|
|
133
133
|
assert_equal body, JSON.parse(env.body)
|
134
134
|
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
135
135
|
error_response(:service_error)
|
136
136
|
end
|
137
|
-
refute @kv.
|
137
|
+
refute @kv.set(set)
|
138
138
|
assert_equal body, @kv.value
|
139
139
|
assert_equal old_ref, @kv.ref
|
140
140
|
end
|
141
141
|
|
142
|
-
def
|
143
|
-
|
142
|
+
def test_set_bang_merges_values_and_save_bangs
|
143
|
+
set = {hello: "there", 'two' => 2}
|
144
144
|
body = @kv.value
|
145
|
-
|
145
|
+
set.each_pair {|key, value| body[key.to_s] = value }
|
146
146
|
new_ref = make_ref
|
147
147
|
@stubs.put("/v0/items/#{@kv.key}") do |env|
|
148
148
|
assert_equal body, JSON.parse(env.body)
|
149
149
|
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
150
150
|
[201, response_headers({'Etag' => new_ref, "Location" => "/v0/items/#{@kv.key}/refs/#{new_ref}"}), '']
|
151
151
|
end
|
152
|
-
@kv.
|
152
|
+
@kv.set!(set)
|
153
153
|
assert_equal body, @kv.value
|
154
154
|
assert_equal new_ref, @kv.ref
|
155
155
|
end
|
156
156
|
|
157
|
-
def
|
158
|
-
|
157
|
+
def test_set_bang_merges_values_and_save_bangs_raises_on_error
|
158
|
+
set = {hello: "there", 'two' => 2}
|
159
159
|
body = @kv.value
|
160
|
-
|
160
|
+
set.each_pair {|key, value| body[key.to_s] = value }
|
161
161
|
old_ref = @kv.ref
|
162
162
|
@stubs.put("/v0/items/#{@kv.key}") do |env|
|
163
163
|
assert_equal body, JSON.parse(env.body)
|
@@ -165,12 +165,397 @@ class KeyValuePersistenceTest < MiniTest::Unit::TestCase
|
|
165
165
|
error_response(:service_error)
|
166
166
|
end
|
167
167
|
assert_raises Orchestrate::API::ServiceError do
|
168
|
-
@kv.
|
168
|
+
@kv.set!(set)
|
169
169
|
end
|
170
170
|
assert_equal body, @kv.value
|
171
171
|
assert_equal old_ref, @kv.ref
|
172
172
|
end
|
173
173
|
|
174
|
+
def test_merge_patch_merges_partial_value_into_existing_kv_without_ref
|
175
|
+
merge = {"foo" => "bar"}
|
176
|
+
body = @kv.value
|
177
|
+
body["foo"] = "bar"
|
178
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
179
|
+
assert_equal merge, JSON.parse(env.body)
|
180
|
+
assert_header 'Content-Type', 'application/merge-patch+json', env
|
181
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
182
|
+
end
|
183
|
+
@kv.merge(merge)
|
184
|
+
assert_equal body, @kv.value
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_merge_patch_merges_partial_value_into_existing_kv_with_ref
|
188
|
+
merge = {"foo" => "bar"}
|
189
|
+
body = @kv.value
|
190
|
+
body["foo"] = "bar"
|
191
|
+
ref = @kv.ref
|
192
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
193
|
+
assert_equal merge, JSON.parse(env.body)
|
194
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
195
|
+
assert_header 'Content-Type', 'application/merge-patch+json', env
|
196
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
197
|
+
end
|
198
|
+
@kv.merge(merge, ref)
|
199
|
+
assert_equal body, @kv.value
|
200
|
+
assert_equal ref, @kv.ref
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_update_add_operation_creates_new_field_and_value_without_ref
|
204
|
+
add = [{ "op" => "add", "path" => "aloha", "value" => "universe"}]
|
205
|
+
body = @kv.value
|
206
|
+
body["aloha"] = "universe"
|
207
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
208
|
+
assert_equal add, JSON.parse(env.body)
|
209
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
210
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
211
|
+
end
|
212
|
+
@kv.add(:aloha, "universe").update()
|
213
|
+
assert_equal body, @kv.value
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_update_add_operation_creates_new_field_and_value_with_ref
|
217
|
+
add = [{ "op" => "add", "path" => "aloha", "value" => "universe"}]
|
218
|
+
body = @kv.value
|
219
|
+
body["aloha"] = "universe"
|
220
|
+
ref = @kv.ref
|
221
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
222
|
+
assert_equal add, JSON.parse(env.body)
|
223
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
224
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
225
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
226
|
+
end
|
227
|
+
@kv.add(:aloha, "universe").update(ref)
|
228
|
+
assert_equal body, @kv.value
|
229
|
+
assert_equal ref, @kv.ref
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_update_remove_operation_removes_new_field_and_value_without_ref
|
233
|
+
remove = [{ "op" => "remove", "path" => "hello"}]
|
234
|
+
body = @kv.value
|
235
|
+
body.delete("hello")
|
236
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
237
|
+
assert_equal remove, JSON.parse(env.body)
|
238
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
239
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
240
|
+
end
|
241
|
+
@kv.remove(:hello).update()
|
242
|
+
assert_equal body, @kv.value
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_update_remove_operation_removes_new_field_and_value_with_ref
|
246
|
+
remove = [{ "op" => "remove", "path" => "hello"}]
|
247
|
+
body = @kv.value
|
248
|
+
body.delete("hello")
|
249
|
+
ref = @kv.ref
|
250
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
251
|
+
assert_equal remove, JSON.parse(env.body)
|
252
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
253
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
254
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
255
|
+
end
|
256
|
+
@kv.remove(:hello).update(ref)
|
257
|
+
assert_equal body, @kv.value
|
258
|
+
assert_equal ref, @kv.ref
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_update_remove_operation_raises_error_with_non_existing_field
|
262
|
+
remove = [{ "op" => "remove", "path" => "foo"}]
|
263
|
+
body = @kv.value
|
264
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
265
|
+
assert_equal remove, JSON.parse(env.body)
|
266
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
267
|
+
error_response(:malformed_ref)
|
268
|
+
end
|
269
|
+
assert_raises Orchestrate::API::MalformedRef do
|
270
|
+
@kv.remove(:foo).update()
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_update_replace_operation_replaces_field_with_new_value_without_ref
|
275
|
+
replace = [{ "op" => "replace", "path" => "hello", "value" => "universe"}]
|
276
|
+
body = @kv.value
|
277
|
+
body["hello"] = "universe"
|
278
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
279
|
+
assert_equal replace, JSON.parse(env.body)
|
280
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
281
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
282
|
+
end
|
283
|
+
@kv.replace(:hello, "universe").update()
|
284
|
+
assert_equal body, @kv.value
|
285
|
+
end
|
286
|
+
|
287
|
+
def test_update_replace_operation_replaces_field_with_new_value_with_ref
|
288
|
+
replace = [{ "op" => "replace", "path" => "hello", "value" => "universe"}]
|
289
|
+
body = @kv.value
|
290
|
+
body["hello"] = "universe"
|
291
|
+
ref = @kv.ref
|
292
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
293
|
+
assert_equal replace, JSON.parse(env.body)
|
294
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
295
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
296
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
297
|
+
end
|
298
|
+
@kv.replace(:hello, "universe").update(ref)
|
299
|
+
assert_equal body, @kv.value
|
300
|
+
assert_equal ref, @kv.ref
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_update_replace_operation_raises_error_with_non_existing_field
|
304
|
+
replace = [{ "op" => "replace", "path" => "foo", "value" => "bar"}]
|
305
|
+
body = @kv.value
|
306
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
307
|
+
assert_equal replace, JSON.parse(env.body)
|
308
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
309
|
+
error_response(:malformed_ref)
|
310
|
+
end
|
311
|
+
assert_raises Orchestrate::API::MalformedRef do
|
312
|
+
@kv.replace(:foo, "bar").update()
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def test_update_move_operation_moves_field_without_ref
|
317
|
+
move = [{ "op" => "move", "from" => "hello", "path" => "foo" }]
|
318
|
+
body = @kv.value
|
319
|
+
body.delete("hello")
|
320
|
+
body["foo"] = "world"
|
321
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
322
|
+
assert_equal move, JSON.parse(env.body)
|
323
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
324
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
325
|
+
end
|
326
|
+
@kv.move(:hello, :foo).update()
|
327
|
+
assert_equal body, @kv.value
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_update_move_operation_moves_field_with_ref
|
331
|
+
move = [{ "op" => "move", "from" => "hello", "path" => "foo" }]
|
332
|
+
body = @kv.value
|
333
|
+
body.delete("hello")
|
334
|
+
body["foo"] = "world"
|
335
|
+
ref = @kv.ref
|
336
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
337
|
+
assert_equal move, JSON.parse(env.body)
|
338
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
339
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
340
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
341
|
+
end
|
342
|
+
@kv.move(:hello, :foo).update(ref)
|
343
|
+
assert_equal body, @kv.value
|
344
|
+
assert_equal ref, @kv.ref
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_update_move_operation_raises_error_with_non_existing_field
|
348
|
+
move = [{ "op" => "move", "from" => "foo", "path" => "hello" }]
|
349
|
+
body = @kv.value
|
350
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
351
|
+
assert_equal move, JSON.parse(env.body)
|
352
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
353
|
+
error_response(:malformed_ref)
|
354
|
+
end
|
355
|
+
assert_raises Orchestrate::API::MalformedRef do
|
356
|
+
@kv.move(:foo, :hello).update()
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_update_copy_operation_copies_field_without_ref
|
361
|
+
copy = [{ "op" => "copy", "from" => "hello", "path" => "foo" }]
|
362
|
+
body = @kv.value
|
363
|
+
body["foo"] = "world"
|
364
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
365
|
+
assert_equal copy, JSON.parse(env.body)
|
366
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
367
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
368
|
+
end
|
369
|
+
@kv.copy(:hello, :foo).update()
|
370
|
+
assert_equal body, @kv.value
|
371
|
+
end
|
372
|
+
|
373
|
+
def test_update_copy_operation_copies_field_with_ref
|
374
|
+
copy = [{ "op" => "copy", "from" => "hello", "path" => "foo" }]
|
375
|
+
body = @kv.value
|
376
|
+
body["foo"] = "world"
|
377
|
+
ref = @kv.ref
|
378
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
379
|
+
assert_equal copy, JSON.parse(env.body)
|
380
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
381
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
382
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
383
|
+
end
|
384
|
+
@kv.copy(:hello, :foo).update(ref)
|
385
|
+
assert_equal body, @kv.value
|
386
|
+
assert_equal ref, @kv.ref
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_update_copy_operation_raises_error_with_non_existing_field
|
390
|
+
copy = [{ "op" => "copy", "from" => "foo", "path" => "hello" }]
|
391
|
+
body = @kv.value
|
392
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
393
|
+
assert_equal copy, JSON.parse(env.body)
|
394
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
395
|
+
error_response(:malformed_ref)
|
396
|
+
end
|
397
|
+
assert_raises Orchestrate::API::MalformedRef do
|
398
|
+
@kv.copy(:foo, :hello).update()
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def test_update_increment_operation_increases_field_number_value_without_ref
|
403
|
+
increment = [{ "op" => "inc", "path" => "count", "value" => 1 }]
|
404
|
+
body = @kv.value
|
405
|
+
old_count = 2
|
406
|
+
body["count"] = 3
|
407
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
408
|
+
assert_equal increment, JSON.parse(env.body)
|
409
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
410
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
411
|
+
end
|
412
|
+
@kv.increment(:count, 1).update()
|
413
|
+
assert_equal body, @kv.value
|
414
|
+
assert_equal old_count+1, @kv.value["count"]
|
415
|
+
end
|
416
|
+
|
417
|
+
def test_update_increment_operation_increases_field_number_value_with_ref
|
418
|
+
increment = [{ "op" => "inc", "path" => "count", "value" => 1 }]
|
419
|
+
body = @kv.value
|
420
|
+
old_count = 2
|
421
|
+
body["count"] = 3
|
422
|
+
ref = @kv.ref
|
423
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
424
|
+
assert_equal increment, JSON.parse(env.body)
|
425
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
426
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
427
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
428
|
+
end
|
429
|
+
@kv.increment(:count, 1).update(ref)
|
430
|
+
assert_equal body, @kv.value
|
431
|
+
assert_equal ref, @kv.ref
|
432
|
+
assert_equal old_count+1, @kv.value["count"]
|
433
|
+
end
|
434
|
+
|
435
|
+
def test_update_increment_operation_raises_error_with_non_number_field
|
436
|
+
increment = [{ "op" => "inc", "path" => "hello", "value" => 1 }]
|
437
|
+
body = @kv.value
|
438
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
439
|
+
assert_equal increment, JSON.parse(env.body)
|
440
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
441
|
+
error_response(:malformed_ref)
|
442
|
+
end
|
443
|
+
assert_raises Orchestrate::API::MalformedRef do
|
444
|
+
@kv.increment(:hello, 1).update()
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_update_increment_operation_raises_error_with_non_existing_field
|
449
|
+
increment = [{ "op" => "inc", "path" => "foo", "value" => 1 }]
|
450
|
+
body = @kv.value
|
451
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
452
|
+
assert_equal increment, JSON.parse(env.body)
|
453
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
454
|
+
error_response(:malformed_ref)
|
455
|
+
end
|
456
|
+
assert_raises Orchestrate::API::MalformedRef do
|
457
|
+
@kv.increment(:foo, 1).update()
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def test_update_decrement_operation_decreases_field_number_value_without_ref
|
462
|
+
decrement = [{ "op" => "inc", "path" => "count", "value" => -1 }]
|
463
|
+
body = @kv.value
|
464
|
+
old_count = 3
|
465
|
+
body["count"] = 2
|
466
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
467
|
+
assert_equal decrement, JSON.parse(env.body)
|
468
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
469
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
470
|
+
end
|
471
|
+
@kv.decrement(:count, 1).update()
|
472
|
+
assert_equal body, @kv.value
|
473
|
+
assert_equal old_count-1, @kv.value["count"]
|
474
|
+
end
|
475
|
+
|
476
|
+
def test_update_decrement_operation_decreases_field_number_value_with_ref
|
477
|
+
decrement = [{ "op" => "inc", "path" => "count", "value" => -1 }]
|
478
|
+
body = @kv.value
|
479
|
+
old_count = 3
|
480
|
+
body["count"] = 2
|
481
|
+
ref = @kv.ref
|
482
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
483
|
+
assert_equal decrement, JSON.parse(env.body)
|
484
|
+
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
485
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
486
|
+
[201, response_headers({'Etag' => ref, "Location" => "/v0/items/#{@kv.key}/refs/#{ref}"}), '']
|
487
|
+
end
|
488
|
+
@kv.decrement(:count, 1).update(ref)
|
489
|
+
assert_equal body, @kv.value
|
490
|
+
assert_equal ref, @kv.ref
|
491
|
+
assert_equal old_count-1, @kv.value["count"]
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_update_decrement_operation_raises_error_with_non_number_field
|
495
|
+
decrement = [{ "op" => "inc", "path" => "hello", "value" => -1 }]
|
496
|
+
body = @kv.value
|
497
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
498
|
+
assert_equal decrement, JSON.parse(env.body)
|
499
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
500
|
+
error_response(:malformed_ref)
|
501
|
+
end
|
502
|
+
assert_raises Orchestrate::API::MalformedRef do
|
503
|
+
@kv.decrement(:hello, 1).update()
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def test_update_decrement_operation_raises_error_with_non_existing_field
|
508
|
+
decrement = [{ "op" => "inc", "path" => "foo", "value" => -1 }]
|
509
|
+
body = @kv.value
|
510
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
511
|
+
assert_equal decrement, JSON.parse(env.body)
|
512
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
513
|
+
error_response(:malformed_ref)
|
514
|
+
end
|
515
|
+
assert_raises Orchestrate::API::MalformedRef do
|
516
|
+
@kv.decrement(:foo, 1).update()
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def test_update_test_operation_checks_equality_of_field_value_success
|
521
|
+
test = [{ "op" => "test", "path" => "count", "value" => 1 }]
|
522
|
+
body = @kv.value
|
523
|
+
body["count"] = 1
|
524
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
525
|
+
assert_equal test, JSON.parse(env.body)
|
526
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
527
|
+
[201, response_headers({'Etag' => @kv.ref, "Location" => "/v0/items/#{@kv.key}/refs/#{@kv.ref}"}), '']
|
528
|
+
end
|
529
|
+
@kv.test(:count, 1).update()
|
530
|
+
assert_equal body, @kv.value
|
531
|
+
end
|
532
|
+
|
533
|
+
def test_update_test_operation_checks_equality_of_field_value_failure
|
534
|
+
test = [{ "op" => "test", "path" => "count", "value" => 2 }]
|
535
|
+
body = @kv.value
|
536
|
+
body["count"] = 1
|
537
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
538
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
539
|
+
error_response(:indexing_conflict)
|
540
|
+
end
|
541
|
+
assert_raises Orchestrate::API::IndexingConflict do
|
542
|
+
@kv.test(:foo, 1).update()
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def test_update_test_operation_raises_error_with_non_existing_field
|
547
|
+
test = [{ "op" => "test", "path" => "foo", "value" => 1 }]
|
548
|
+
body = @kv.value
|
549
|
+
@stubs.patch("/v0/items/#{@kv.key}") do |env|
|
550
|
+
assert_equal test, JSON.parse(env.body)
|
551
|
+
assert_header 'Content-Type', 'application/json-patch+json', env
|
552
|
+
error_response(:malformed_ref)
|
553
|
+
end
|
554
|
+
assert_raises Orchestrate::API::MalformedRef do
|
555
|
+
@kv.test(:foo, 1).update()
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
174
559
|
def test_destroy_performs_delete_if_match_and_returns_true_on_success
|
175
560
|
@stubs.delete("/v0/items/#{@kv.key}") do |env|
|
176
561
|
assert_header 'If-Match', "\"#{@kv.ref}\"", env
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: orchestrate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Lyon
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-11
|
13
|
+
date: 2014-12-11 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: faraday
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- test/orchestrate/application_test.rb
|
151
151
|
- test/orchestrate/client_test.rb
|
152
152
|
- test/orchestrate/collection_enumeration_test.rb
|
153
|
+
- test/orchestrate/collection_geo_queries_test.rb
|
153
154
|
- test/orchestrate/collection_kv_accessors_test.rb
|
154
155
|
- test/orchestrate/collection_searching_test.rb
|
155
156
|
- test/orchestrate/collection_sorting_test.rb
|
@@ -195,6 +196,7 @@ test_files:
|
|
195
196
|
- test/orchestrate/application_test.rb
|
196
197
|
- test/orchestrate/client_test.rb
|
197
198
|
- test/orchestrate/collection_enumeration_test.rb
|
199
|
+
- test/orchestrate/collection_geo_queries_test.rb
|
198
200
|
- test/orchestrate/collection_kv_accessors_test.rb
|
199
201
|
- test/orchestrate/collection_searching_test.rb
|
200
202
|
- test/orchestrate/collection_sorting_test.rb
|