orchestrate 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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