orchestrate 0.9.2 → 0.10.0
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.
- 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
|