orchestrate 0.6.3 → 0.7.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.
@@ -0,0 +1,63 @@
1
+ require "test_helper"
2
+
3
+ class CollectionTest < MiniTest::Unit::TestCase
4
+
5
+ def test_instantiates_with_app_and_name
6
+ app, stubs = make_application
7
+ users = Orchestrate::Collection.new(app, :users)
8
+ assert_equal app, users.app
9
+ assert_equal 'users', users.name
10
+ end
11
+
12
+ def test_instantiates_with_client_and_name
13
+ client, stubs = make_client_and_artifacts
14
+ stubs.head("/v0") { [200, response_headers, ''] }
15
+ users = Orchestrate::Collection.new(client, :users)
16
+ assert_equal client, users.app.client
17
+ assert_equal 'users', users.name
18
+ end
19
+
20
+ def test_destroy
21
+ app, stubs = make_application
22
+ users = Orchestrate::Collection.new(app, :users)
23
+ stubs.delete("/v0/users") do |env|
24
+ assert_equal "true", env.params["force"]
25
+ [204, response_headers, '']
26
+ end
27
+ assert true, users.destroy!
28
+ end
29
+
30
+ def test_equality
31
+ app, stubs = make_application
32
+ app2, stubs = make_application
33
+ items1 = app[:items]
34
+ items2 = app[:items]
35
+ other = app[:other]
36
+ assert_equal items1, items2
37
+ refute_equal items1, other
38
+ refute_equal items1, OpenStruct.new({name: 'items'})
39
+ refute_equal items1, 3
40
+ refute_equal items1, app2[:items]
41
+
42
+ assert items1.eql?(items2)
43
+ assert items2.eql?(items1)
44
+ refute items1.eql?(other)
45
+ refute other.eql?(items1)
46
+ refute items1.eql?(app2[:items])
47
+
48
+ # equal? is for object identity only
49
+ refute items1.equal?(items2)
50
+ refute other.equal?(items1)
51
+ end
52
+
53
+ def test_sorting
54
+ app, stubs = make_application
55
+ app2, stubs = make_application
56
+ assert_equal(-1, app[:users] <=> app[:items])
57
+ assert_equal 0, app[:items] <=> app[:items]
58
+ assert_equal 1, app[:items] <=> app[:users]
59
+ assert_nil 2 <=> app[:items]
60
+ assert_nil app2[:items] <=> app[:items]
61
+ end
62
+ end
63
+
@@ -0,0 +1,161 @@
1
+ require "test_helper"
2
+
3
+ class KeyValuePersistenceTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @app, @stubs = make_application
6
+ @items = @app[:items]
7
+ @kv = make_kv_item(@items, @stubs, :loaded => Time.now - 60)
8
+ end
9
+
10
+ def test_save_performs_put_if_match_and_returns_true_on_success
11
+ @kv[:foo] = "bar"
12
+ new_ref = nil
13
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
14
+ assert_equal @kv.value, JSON.parse(env.body)
15
+ assert_header 'If-Match', "\"#{@kv.ref}\"", env
16
+ new_ref = make_ref
17
+ [ 201, response_headers({'Etag' => new_ref, "Location" => "/v0/items/#{@kv.key}/refs/#{new_ref}"}), '' ]
18
+ end
19
+ assert_equal true, @kv.save
20
+ assert_equal new_ref, @kv.ref
21
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
22
+ assert_equal "bar", @kv[:foo]
23
+ end
24
+
25
+ def test_save_performs_put_if_match_and_returns_false_on_version_mismatch
26
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
27
+ assert_header 'If-Match', "\"#{@kv.ref}\"", env
28
+ error_response(:version_mismatch)
29
+ end
30
+ assert_equal false, @kv.save
31
+ end
32
+
33
+ def test_save_performs_put_if_match_and_returns_true_on_indexing_conflict
34
+ ref = @kv.ref
35
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
36
+ ref = make_ref
37
+ assert_header 'If-Match', "\"#{@kv.ref}\"", env
38
+ error_response(:indexing_conflict, {
39
+ headers: {"Location" => "/v0/items/#{@kv.key}/refs/#{ref}"},
40
+ conflicts_uri: "/v0/items/#{@kv.key}/refs/#{ref}/conflicts"
41
+ })
42
+ end
43
+ assert_equal true, @kv.save
44
+ assert_equal ref, @kv.ref
45
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
46
+ end
47
+
48
+ def test_save_returns_false_on_etc_errors
49
+ @stubs.put("/v0/items/#{@kv.key}") { error_response(:bad_request) }
50
+ assert_equal false, @kv.save
51
+
52
+ @stubs.put("/v0/items/#{@kv.key}") { error_response(:service_error) }
53
+ assert_equal false, @kv.save
54
+ end
55
+
56
+ def test_save_bang_performs_put_if_match_doesnt_raise_on_success
57
+ @kv[:foo] = "bar"
58
+ ref = @kv.ref
59
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
60
+ assert_equal @kv.value, JSON.parse(env.body)
61
+ assert_header 'If-Match', %|"#{ref}"|, env
62
+ ref = make_ref
63
+ [201, response_headers({'Etag'=>%|"#{ref}"|, 'Loation'=>"/v0/items/#{@kv.key}/refs/#{ref}"}), '']
64
+ end
65
+ @kv.save!
66
+ assert_equal ref, @kv.ref
67
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
68
+ end
69
+
70
+ def test_save_bang_performs_put_if_match_raises_on_version_mismatch
71
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
72
+ assert_header "If-Match", %|"#{@kv.ref}"|, env
73
+ error_response(:version_mismatch)
74
+ end
75
+ assert_raises Orchestrate::API::VersionMismatch do
76
+ @kv.save!
77
+ end
78
+ end
79
+
80
+ def test_save_bang_performs_put_if_match_doesnt_raise_on_indexing_conflict
81
+ ref = @kv.ref
82
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
83
+ ref = make_ref
84
+ assert_header "If-Match", %|"#{@kv.ref}"|, env
85
+ error_response(:indexing_conflict, {
86
+ headers: {"Location" => "/v0/items/#{@kv.key}/refs/#{ref}"},
87
+ conflicts_uri: "/v0/items/#{@kv.key}/refs/#{ref}/conflicts"
88
+ })
89
+ end
90
+ @kv.save!
91
+ assert_equal ref, @kv.ref
92
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
93
+ end
94
+
95
+ def test_save_bang_performs_put_if_match_raises_on_request_error
96
+ @stubs.put("/v0/items/#{@kv.key}") do |env|
97
+ assert_header "If-Match", %|"#{@kv.ref}"|, env
98
+ error_response(:bad_request)
99
+ end
100
+ assert_raises Orchestrate::API::BadRequest do
101
+ @kv.save!
102
+ end
103
+ end
104
+
105
+ def test_save_bang_performs_put_if_match_raises_on_service_error
106
+ @stubs.put("/v0/items/#{@kv.key}") { error_response(:service_error) }
107
+ assert_raises Orchestrate::API::ServiceError do
108
+ @kv.save!
109
+ end
110
+ end
111
+
112
+ def test_destroy_performs_delete_if_match_and_returns_true_on_success
113
+ @stubs.delete("/v0/items/#{@kv.key}") do |env|
114
+ assert_header 'If-Match', "\"#{@kv.ref}\"", env
115
+ [204, response_headers, '']
116
+ end
117
+ assert_equal true, @kv.destroy
118
+ assert_nil @kv.ref
119
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
120
+ end
121
+
122
+ def test_destroy_performs_delete_if_match_and_returns_false_on_error
123
+ @stubs.delete("/v0/items/#{@kv.key}") { error_response(:version_mismatch) }
124
+ assert_equal false, @kv.destroy
125
+ end
126
+
127
+ def test_destroy_bang_performs_delete_raises_on_error
128
+ @stubs.delete("/v0/items/#{@kv.key}") { error_response(:version_mismatch) }
129
+ assert_raises Orchestrate::API::VersionMismatch do
130
+ @kv.destroy!
131
+ end
132
+ end
133
+
134
+ def test_purge_performs_purge_if_match_and_returns_true_on_success
135
+ @stubs.delete("/v0/items/#{@kv.key}") do |env|
136
+ assert_header 'If-Match', %|"#{@kv.ref}"|, env
137
+ assert_equal "true", env.params['purge']
138
+ [ 204, response_headers, '' ]
139
+ end
140
+ assert_equal true, @kv.purge
141
+ assert_nil @kv.ref
142
+ assert_in_delta Time.now.to_f, @kv.last_request_time.to_f, 1.1
143
+ end
144
+
145
+ def test_purge_performs_purge_if_match_and_returns_false_on_error
146
+ @stubs.delete("/v0/items/#{@kv.key}") { error_response(:version_mismatch) }
147
+ assert_equal false, @kv.purge
148
+ end
149
+
150
+ def test_purge_bang_performs_purge_if_match_and_raises_on_error
151
+ @stubs.delete("/v0/items/#{@kv.key}") do |env|
152
+ assert_header 'If-Match', %|"#{@kv.ref}"|, env
153
+ assert_equal "true", env.params['purge']
154
+ error_response(:version_mismatch)
155
+ end
156
+ assert_raises Orchestrate::API::VersionMismatch do
157
+ @kv.purge!
158
+ end
159
+ end
160
+
161
+ end
@@ -0,0 +1,116 @@
1
+ require "test_helper"
2
+
3
+ class KeyValueTest < MiniTest::Unit::TestCase
4
+
5
+ def test_load_with_collection_and_key
6
+ app, stubs = make_application
7
+ items = app[:items]
8
+ ref = "123456"
9
+ body = {"hello" => "world"}
10
+ kv = make_kv_item(items, stubs, { key: "hello", ref: ref, body: body })
11
+ assert_equal "hello", kv.key
12
+ assert_equal ref, kv.ref
13
+ assert_equal "items/hello", kv.id
14
+ assert_equal body, kv.value
15
+ assert kv.loaded?
16
+ assert_in_delta Time.now.to_f, kv.last_request_time.to_f, 1.1
17
+ end
18
+
19
+ def test_value_accessors
20
+ app, stubs = make_application
21
+ items = app[:items]
22
+ body = {"hello" => "world", "one" => "two"}
23
+ kv = make_kv_item(items, stubs, {body: body})
24
+ assert_equal body["hello"], kv["hello"]
25
+ assert_equal body["hello"], kv[:hello]
26
+ assert_equal body["one"], kv["one"]
27
+ assert_equal body["one"], kv[:one]
28
+
29
+ kv[:one] = 1
30
+ assert_equal 1, kv["one"]
31
+ kv[:two] = "Two"
32
+ assert_equal "Two", kv[:two]
33
+ kv["three"] = "Tres"
34
+ assert_equal "Tres", kv[:three]
35
+ kv["four"] = "IV"
36
+ assert_equal "IV", kv["four"]
37
+ end
38
+
39
+ def test_instantiates_from_parallel_request
40
+ app, stubs = make_application({parallel: true})
41
+ items = app[:items]
42
+ body = {"hello" => "foo"}
43
+ ref = make_ref
44
+ stubs.get("/v0/items/foo") { [200, response_headers(ref_headers(:items, :foo, ref)), body.to_json] }
45
+ stubs.put("/v0/items/bar") { [201, response_headers(ref_headers(:items, :bar, make_ref)), ''] }
46
+ results = app.in_parallel do |r|
47
+ r[:foo] = items[:foo]
48
+ r[:bar] = items.create(:bar, {})
49
+ end
50
+ assert_equal 'foo', results[:foo].key
51
+ assert_equal ref, results[:foo].ref
52
+ assert_equal body, results[:foo].value
53
+ assert_equal 'bar', results[:bar].key
54
+ end
55
+
56
+ def test_instantiates_without_loading
57
+ app, stubs = make_application
58
+ items = app[:items]
59
+ kv = Orchestrate::KeyValue.new(items, 'keyname')
60
+ assert_equal 'keyname', kv.key
61
+ assert_equal items, kv.collection
62
+ assert ! kv.loaded?
63
+ end
64
+
65
+ def test_instantiates_from_collection_and_listing
66
+ app, stubs = make_application
67
+ listing = make_kv_listing('items', {key: "foo"})
68
+ kv = Orchestrate::KeyValue.new(app[:items], listing, Time.now)
69
+ assert_equal 'items', kv.collection_name
70
+ assert_equal "foo", kv.key
71
+ assert_equal listing['path']['ref'], kv.ref
72
+ assert_equal listing['value'], kv.value
73
+ assert_in_delta Time.at(listing['reftime'] / 1000), kv.reftime, 1.1
74
+ assert_in_delta Time.now.to_f, kv.last_request_time.to_f, 1.1
75
+ end
76
+
77
+ def test_equality
78
+ app, stubs = make_application
79
+ app2, stubs = make_application
80
+ items = app[:items]
81
+
82
+ foo = Orchestrate::KeyValue.new(items, :foo)
83
+
84
+ assert_equal foo, Orchestrate::KeyValue.new(items, :foo)
85
+ refute_equal foo, Orchestrate::KeyValue.new(items, :bar)
86
+ refute_equal foo, Orchestrate::KeyValue.new(app[:users], :foo)
87
+ refute_equal foo, Orchestrate::KeyValue.new(app2[:items], :foo)
88
+ refute_equal foo, :foo
89
+
90
+ assert foo.eql?(Orchestrate::KeyValue.new(items, :foo))
91
+ refute foo.eql?(Orchestrate::KeyValue.new(items, :bar))
92
+ refute foo.eql?(Orchestrate::KeyValue.new(app[:users], :foo))
93
+ refute foo.eql?(Orchestrate::KeyValue.new(app2[:items], :foo))
94
+ refute foo.eql?(:foo)
95
+
96
+ # equal? is for Object equality
97
+ assert foo.equal?(foo)
98
+ refute foo.equal?(Orchestrate::KeyValue.new(items, :foo))
99
+ end
100
+
101
+ def test_sorting
102
+ app, stubs = make_application
103
+ app2, stubs = make_application
104
+
105
+ foo = Orchestrate::KeyValue.new(app[:items], :foo)
106
+
107
+ assert_equal(-1, foo <=> Orchestrate::KeyValue.new(app[:items], :bar))
108
+ assert_equal 0, foo <=> Orchestrate::KeyValue.new(app[:items], :foo)
109
+ assert_equal 1, foo <=> Orchestrate::KeyValue.new(app[:items], :zoo)
110
+ assert_nil foo <=> Orchestrate::KeyValue.new(app[:users], :foo)
111
+ assert_nil foo <=> Orchestrate::KeyValue.new(app2[:items], :foo)
112
+ assert_nil foo <=> :foo
113
+ end
114
+
115
+ end
116
+
data/test/test_helper.rb CHANGED
@@ -7,6 +7,37 @@ require "securerandom"
7
7
  require "time"
8
8
  require "logger"
9
9
 
10
+ class ParallelTest < Faraday::Adapter::Test
11
+ self.supports_parallel = true
12
+ extend Faraday::Adapter::Parallelism
13
+
14
+ class Manager
15
+ def initialize
16
+ @queue = []
17
+ end
18
+
19
+ def queue(env)
20
+ @queue.push(env)
21
+ end
22
+
23
+ def run
24
+ @queue.each {|env| env[:response].finish(env) unless env[:response].finished? }
25
+ end
26
+ end
27
+
28
+ def self.setup_parallel_manager(options={})
29
+ @mgr ||= Manager.new
30
+ end
31
+
32
+ def call(env)
33
+ super(env)
34
+ env[:parallel_manager].queue(env) if env[:parallel_manager]
35
+ env[:response]
36
+ end
37
+ end
38
+
39
+ Faraday::Adapter.register_middleware :parallel_test => :ParallelTest
40
+
10
41
  # Test Helpers ---------------------------------------------------------------
11
42
 
12
43
  def output_message(name, msg = nil)
@@ -15,17 +46,59 @@ end
15
46
 
16
47
  # TODO this is a bit messy for now at least but there's a bunch of
17
48
  # intermediate state we'd have to deal with in a bunch of other places
18
- def make_client_and_artifacts
49
+ def make_client_and_artifacts(parallel=false)
19
50
  api_key = SecureRandom.hex(24)
20
51
  basic_auth = "Basic #{Base64.encode64("#{api_key}:").gsub(/\n/,'')}"
21
52
  stubs = Faraday::Adapter::Test::Stubs.new
22
53
  client = Orchestrate::Client.new(api_key) do |f|
23
- f.adapter :test, stubs
54
+ if parallel
55
+ f.adapter :parallel_test, stubs
56
+ else
57
+ f.adapter :test, stubs
58
+ end
24
59
  f.response :logger, Logger.new(File.join(File.dirname(__FILE__), "test.log"))
25
60
  end
26
61
  [client, stubs, basic_auth]
27
62
  end
28
63
 
64
+ def ref_headers(coll, key, ref)
65
+ {'Etag' => %|"#{ref}"|, 'Location' => "/v0/#{coll}/#{key}/refs/#{ref}"}
66
+ end
67
+
68
+ def make_application(opts={})
69
+ client, stubs = make_client_and_artifacts(opts[:parallel])
70
+ stubs.head("/v0") { [200, response_headers, ''] }
71
+ app = Orchestrate::Application.new(client)
72
+ [app, stubs]
73
+ end
74
+
75
+ def make_ref
76
+ SecureRandom.hex(16)
77
+ end
78
+
79
+ def make_kv_item(collection, stubs, opts={})
80
+ key = opts[:key] || 'hello'
81
+ ref = opts[:ref] || "12345"
82
+ body = opts[:body] || {"hello" => "world"}
83
+ res_headers = response_headers({
84
+ 'Etag' => "\"#{ref}\"",
85
+ 'Content-Location' => "/v0/#{collection.name}/#{key}/refs/#{ref}"
86
+ })
87
+ stubs.get("/v0/items/#{key}") { [200, res_headers, body.to_json] }
88
+ kv = Orchestrate::KeyValue.load(collection, key)
89
+ kv.instance_variable_set(:@last_request_time, opts[:loaded]) if opts[:loaded]
90
+ kv
91
+ end
92
+
93
+ def make_kv_listing(collection, opts={})
94
+ key = opts[:key] || "item-#{rand(1_000_000)}"
95
+ ref = opts[:ref] || make_ref
96
+ reftime = opts[:reftime] || Time.now.to_f - (rand(24) * 3600_000)
97
+ body = opts[:body] || {"key" => key}
98
+ { "path" => { "collection" => collection, "key" => key, "ref" => ref },
99
+ "reftime" => reftime, "value" => body }
100
+ end
101
+
29
102
  def capture_warnings
30
103
  old, $stderr = $stderr, StringIO.new
31
104
  begin
@@ -50,12 +123,65 @@ def chunked_encoding_header
50
123
  end
51
124
 
52
125
  def response_not_found(items)
53
- { "message" => "The requested items could not be found.",
54
- "details" => {
55
- "items" => [ items ]
56
- },
57
- "code" => "items_not_found"
58
- }.to_json
126
+ { "message" => "The requested items could not be found.",
127
+ "details" => {
128
+ "items" => [ items ]
129
+ },
130
+ "code" => "items_not_found"
131
+ }.to_json
132
+ end
133
+
134
+ def error_response(error, etc={})
135
+ headers = response_headers(etc.fetch(:headers, {}))
136
+ case error
137
+ when :bad_request
138
+ [400, headers, {message: "The API request is malformed.", code: "api_bad_request"}.to_json ]
139
+ when :search_query_malformed
140
+ [ 400, headers, {
141
+ message: "The search query provided is invalid.",
142
+ code: "search_query_malformed"
143
+ }.to_json ]
144
+ when :invalid_search_param
145
+ [ 400, headers, {
146
+ message: "A provided search query param is invalid.",
147
+ details: { query: "Query is empty." },
148
+ code: "search_param_invalid"
149
+ }.to_json ]
150
+ when :malformed_ref
151
+ [ 400, headers, {
152
+ message: "The provided Item Ref is malformed.",
153
+ details: { ref: "blerg" },
154
+ code: "item_ref_malformed"
155
+ }.to_json ]
156
+ when :unauthorized
157
+ [ 401, headers, {
158
+ "message" => "Valid credentials are required.",
159
+ "code" => "security_unauthorized"
160
+ }.to_json ]
161
+ when :indexing_conflict
162
+ [409, headers, {
163
+ message: "The item has been stored but conflicts were detected when indexing. Conflicting fields have not been indexed.",
164
+ details: {
165
+ conflicts: { name: { type: "string", expected: "long" } },
166
+ conflicts_uri: etc[:conflicts_uri]
167
+ },
168
+ code: "indexing_conflict"
169
+ }.to_json ]
170
+ when :version_mismatch
171
+ [412, headers, {
172
+ message: "The version of the item does not match.",
173
+ code: "item_version_mismatch"
174
+ }.to_json]
175
+ when :already_present
176
+ [ 412, headers, {
177
+ message: "The item is already present.",
178
+ code: "item_already_present"
179
+ }.to_json ]
180
+ when :service_error
181
+ headers.delete("Content-Type")
182
+ [ 500, headers, '' ]
183
+ else raise ArgumentError.new("unknown error #{error}")
184
+ end
59
185
  end
60
186
 
61
187
  # Assertion Helpers