elastomer-client 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +108 -0
  8. data/Rakefile +9 -0
  9. data/docs/notifications.md +71 -0
  10. data/elastomer-client.gemspec +30 -0
  11. data/lib/elastomer/client.rb +307 -0
  12. data/lib/elastomer/client/bulk.rb +257 -0
  13. data/lib/elastomer/client/cluster.rb +208 -0
  14. data/lib/elastomer/client/docs.rb +432 -0
  15. data/lib/elastomer/client/errors.rb +51 -0
  16. data/lib/elastomer/client/index.rb +407 -0
  17. data/lib/elastomer/client/multi_search.rb +115 -0
  18. data/lib/elastomer/client/nodes.rb +87 -0
  19. data/lib/elastomer/client/scan.rb +161 -0
  20. data/lib/elastomer/client/template.rb +85 -0
  21. data/lib/elastomer/client/warmer.rb +96 -0
  22. data/lib/elastomer/core_ext/time.rb +7 -0
  23. data/lib/elastomer/middleware/encode_json.rb +51 -0
  24. data/lib/elastomer/middleware/opaque_id.rb +69 -0
  25. data/lib/elastomer/middleware/parse_json.rb +39 -0
  26. data/lib/elastomer/notifications.rb +83 -0
  27. data/lib/elastomer/version.rb +7 -0
  28. data/script/bootstrap +16 -0
  29. data/script/cibuild +28 -0
  30. data/script/console +9 -0
  31. data/script/testsuite +10 -0
  32. data/test/assertions.rb +74 -0
  33. data/test/client/bulk_test.rb +226 -0
  34. data/test/client/cluster_test.rb +113 -0
  35. data/test/client/docs_test.rb +394 -0
  36. data/test/client/index_test.rb +244 -0
  37. data/test/client/multi_search_test.rb +129 -0
  38. data/test/client/nodes_test.rb +35 -0
  39. data/test/client/scan_test.rb +84 -0
  40. data/test/client/stubbed_client_tests.rb +40 -0
  41. data/test/client/template_test.rb +33 -0
  42. data/test/client/warmer_test.rb +56 -0
  43. data/test/client_test.rb +86 -0
  44. data/test/core_ext/time_test.rb +46 -0
  45. data/test/middleware/encode_json_test.rb +53 -0
  46. data/test/middleware/opaque_id_test.rb +39 -0
  47. data/test/middleware/parse_json_test.rb +54 -0
  48. data/test/test_helper.rb +94 -0
  49. metadata +210 -0
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe 'stubbed client tests' do
4
+ before do
5
+ @stubs = Faraday::Adapter.lookup_middleware(:test)::Stubs.new
6
+ @client = Elastomer::Client.new :adapter => [:test, @stubs]
7
+ end
8
+
9
+ describe Elastomer::Client::Cluster do
10
+ it 'reroutes shards' do
11
+ @stubs.post '/_cluster/reroute?dry_run=true' do |env|
12
+ assert_match %r/^\{"commands":\[\{"move":\{[^\{\}]+\}\}\]\}$/, env[:body]
13
+ [200, {'Content-Type' => 'application/json'}, '{"acknowledged" : true}']
14
+ end
15
+
16
+ commands = { :move => { :index => 'test', :shard => 0, :from_node => 'node1', :to_node => 'node2' }}
17
+ h = @client.cluster.reroute commands, :dry_run => true
18
+ assert_acknowledged h
19
+ end
20
+
21
+ it 'performs a shutdown of the cluster' do
22
+ @stubs.post('/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"cluster_name":"elasticsearch"}'] }
23
+ h = @client.cluster.shutdown
24
+ assert_equal "elasticsearch", h['cluster_name']
25
+ end
26
+ end
27
+
28
+ describe Elastomer::Client::Nodes do
29
+ it 'performs a shutdown of the node(s)' do
30
+ @stubs.post('/_cluster/nodes/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"nodes":{"1":{"name":"Node1"}}}'] }
31
+ @stubs.post('/_cluster/nodes/node2/_shutdown') { [200, {'Content-Type' => 'application/json'}, '{"nodes":{"2":{"name":"Node2"}}}'] }
32
+
33
+ h = @client.nodes.shutdown
34
+ assert_equal "Node1", h['nodes']['1']['name']
35
+
36
+ h = @client.nodes('node2').shutdown
37
+ assert_equal 'Node2', h['nodes']['2']['name']
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Cluster do
4
+
5
+ before do
6
+ @name = 'elastomer-template-test'
7
+ @template = $client.template @name
8
+ end
9
+
10
+ after do
11
+ @template.delete if @template.exists?
12
+ end
13
+
14
+ it 'creates a template' do
15
+ assert !@template.exists?, 'the template should not exist'
16
+
17
+ @template.create({
18
+ :template => 'test-elastomer*',
19
+ :settings => { :number_of_shards => 3 },
20
+ :mappings => {
21
+ :doco => { :_source => { :enabled => false }}
22
+ }
23
+ })
24
+
25
+ assert @template.exists?, ' we now have a cluster-test template'
26
+
27
+ template = @template.get
28
+ assert_equal [@name], template.keys
29
+ assert_equal 'test-elastomer*', template[@name]['template']
30
+ assert_equal '3', template[@name]['settings']['index.number_of_shards']
31
+ end
32
+
33
+ end
@@ -0,0 +1,56 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client::Warmer do
4
+ before do
5
+ @name = 'elastomer-warmer-test'
6
+ @index = $client.index(@name)
7
+
8
+ unless @index.exists?
9
+ @index.create(
10
+ :settings => { 'index.number_of_shards' => 1, 'index.number_of_replicas' => 0 },
11
+ :mappings => {
12
+ :tweet => {
13
+ :_source => { :enabled => true }, :_all => { :enabled => false },
14
+ :properties => {
15
+ :message => { :type => 'string', :analyzer => 'standard' },
16
+ :author => { :type => 'string', :index => 'not_analyzed' }
17
+ }
18
+ }
19
+ }
20
+ )
21
+ wait_for_index(@name)
22
+ end
23
+ end
24
+
25
+ after do
26
+ @index.delete if @index.exists?
27
+ end
28
+
29
+ it 'creates warmers' do
30
+ h = @index.warmer('test1').create(:query => { :match_all => {}})
31
+ assert_acknowledged h
32
+ end
33
+
34
+ it 'deletes warmers' do
35
+ @index.warmer('test1').create(:query => { :match_all => {}})
36
+
37
+ h = @index.warmer('test1').delete
38
+ assert_acknowledged h
39
+ end
40
+
41
+ it 'gets warmers' do
42
+ body = { "query" => {"match_all" => {}}}
43
+ @index.warmer('test1').create(body)
44
+
45
+ h = @index.warmer('test1').get
46
+ assert_equal body, h[@name]["warmers"]["test1"]["source"]
47
+ end
48
+
49
+ it 'knows when warmers exist' do
50
+ assert_equal false, @index.warmer('test1').exists?
51
+ assert_equal false, @index.warmer('test1').exist?
52
+
53
+ h = @index.warmer('test1').create(:query => { :match_all => {}})
54
+ assert_equal true, @index.warmer('test1').exists?
55
+ end
56
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Client do
4
+
5
+ it 'uses the adapter specified at creation' do
6
+ c = Elastomer::Client.new(:adapter => :test)
7
+ assert_includes c.connection.builder.handlers, Faraday::Adapter::Test
8
+ end
9
+
10
+ it "use Faraday's default adapter if none is specified" do
11
+ c = Elastomer::Client.new
12
+ adapter = Faraday::Adapter.lookup_middleware(Faraday.default_adapter)
13
+ assert_includes c.connection.builder.handlers, adapter
14
+ end
15
+
16
+ it 'uses the same connection for all requests' do
17
+ c = $client.connection
18
+ assert_same c, $client.connection
19
+ end
20
+
21
+ it 'raises an error for unknown HTTP request methods' do
22
+ assert_raises(ArgumentError) { $client.request :foo, '/', {} }
23
+ end
24
+
25
+ it 'raises an error on 4XX responses with an `error` field' do
26
+ begin
27
+ $client.get '/non-existent-index/_search?q=*:*'
28
+ assert false, 'exception was not raised when it should have been'
29
+ rescue Elastomer::Client::Error => err
30
+ assert_equal 404, err.status
31
+ assert_equal 'IndexMissingException[[non-existent-index] missing]', err.message
32
+ end
33
+ end
34
+
35
+ it 'handles path expansions' do
36
+ uri = $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => 'health'
37
+ assert_equal '/_cluster/health', uri
38
+
39
+ uri = $client.expand_path '{/foo}{/baz}{/bar}', :foo => '_cluster', :bar => 'state'
40
+ assert_equal '/_cluster/state', uri
41
+ end
42
+
43
+ it 'handles query parameters' do
44
+ uri = $client.expand_path '/_cluster/health', :level => 'shards'
45
+ assert_equal '/_cluster/health?level=shards', uri
46
+ end
47
+
48
+ it 'validates path expansions' do
49
+ assert_raises(ArgumentError) {
50
+ $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => nil
51
+ }
52
+
53
+ assert_raises(ArgumentError) {
54
+ $client.expand_path '/{foo}/{bar}', :foo => '_cluster', :bar => ''
55
+ }
56
+ end
57
+
58
+ describe 'when validating parameters' do
59
+ it 'rejects nil values' do
60
+ assert_raises(ArgumentError) { $client.assert_param_presence nil }
61
+ end
62
+
63
+ it 'rejects empty strings' do
64
+ assert_raises(ArgumentError) { $client.assert_param_presence "" }
65
+ assert_raises(ArgumentError) { $client.assert_param_presence " " }
66
+ assert_raises(ArgumentError) { $client.assert_param_presence " \t \r \n " }
67
+ end
68
+
69
+ it 'rejects empty strings and nil values found in arrays' do
70
+ assert_raises(ArgumentError) { $client.assert_param_presence ['foo', nil, 'bar'] }
71
+ assert_raises(ArgumentError) { $client.assert_param_presence ['baz', " \t \r \n "] }
72
+ end
73
+
74
+ it 'strips whitespace from strings' do
75
+ assert_equal 'foo', $client.assert_param_presence(" foo \t")
76
+ end
77
+
78
+ it 'joins array values into a string' do
79
+ assert_equal 'foo,bar', $client.assert_param_presence(%w[foo bar])
80
+ end
81
+
82
+ it 'flattens arrays' do
83
+ assert_equal 'foo,bar,baz,buz', $client.assert_param_presence([" foo \t", %w[bar baz buz]])
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,46 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+ require 'elastomer/core_ext/time'
3
+
4
+ describe 'JSON conversions for Time' do
5
+ before do
6
+ @name = 'elastomer-time-test'
7
+ @index = $client.index(@name)
8
+
9
+ unless @index.exists?
10
+ @index.create \
11
+ :settings => { 'index.number_of_shards' => 1, 'index.number_of_replicas' => 0 },
12
+ :mappings => {
13
+ :doc1 => {
14
+ :_source => { :enabled => true }, :_all => { :enabled => false },
15
+ :properties => {
16
+ :title => { :type => 'string', :index => 'not_analyzed' },
17
+ :created_at => { :type => 'date' }
18
+ }
19
+ }
20
+ }
21
+
22
+ wait_for_index(@name)
23
+ end
24
+
25
+ @docs = @index.docs
26
+ end
27
+
28
+ after do
29
+ @index.delete if @index.exists?
30
+ end
31
+
32
+ it 'generates ISO8601 formatted time strings' do
33
+ time = Time.utc(2013, 5, 3, 10, 1, 31)
34
+ assert_equal '"2013-05-03T10:01:31Z"', MultiJson.encode(time)
35
+ end
36
+
37
+ it 'indexes time fields' do
38
+ time = Time.utc(2013, 5, 3, 10, 1, 31)
39
+ h = @docs.index({:title => 'test document', :created_at => time}, :type => 'doc1')
40
+
41
+ assert_created(h)
42
+
43
+ doc = @docs.get(:type => 'doc1', :id => h['_id'])
44
+ assert_equal '2013-05-03T10:01:31Z', doc['_source']['created_at']
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::EncodeJson do
4
+ let(:middleware) { Elastomer::Middleware::EncodeJson.new(lambda {|env| env})}
5
+
6
+ def process(body, content_type = nil)
7
+ env = { :body => body, :request_headers => Faraday::Utils::Headers.new }
8
+ env[:request_headers]['content-type'] = content_type if content_type
9
+ middleware.call(env)
10
+ end
11
+
12
+ it 'handles no body' do
13
+ result = process(nil)
14
+ assert_nil result[:body]
15
+ assert_nil result[:request_headers]['content-type']
16
+ end
17
+
18
+ it 'handles empty body' do
19
+ result = process('')
20
+ assert_empty result[:body]
21
+ assert_nil result[:request_headers]['content-type']
22
+ end
23
+
24
+ it 'handles string body' do
25
+ result = process('{"a":1}')
26
+ assert_equal '{"a":1}', result[:body]
27
+ assert_equal 'application/json', result[:request_headers]['content-type']
28
+ end
29
+
30
+ it 'handles object body' do
31
+ result = process({:a => 1})
32
+ assert_equal '{"a":1}', result[:body]
33
+ assert_equal 'application/json', result[:request_headers]['content-type']
34
+ end
35
+
36
+ it 'handles empty object body' do
37
+ result = process({})
38
+ assert_equal '{}', result[:body]
39
+ assert_equal 'application/json', result[:request_headers]['content-type']
40
+ end
41
+
42
+ it 'handles object body with json type' do
43
+ result = process({:a => 1}, 'application/json; charset=utf-8')
44
+ assert_equal '{"a":1}', result[:body]
45
+ assert_equal 'application/json; charset=utf-8', result[:request_headers]['content-type']
46
+ end
47
+
48
+ it 'handles object body with incompatible type' do
49
+ result = process({:a => 1}, 'application/xml; charset=utf-8')
50
+ assert_equal({:a => 1}, result[:body])
51
+ assert_equal 'application/xml; charset=utf-8', result[:request_headers]['content-type']
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::OpaqueId do
4
+
5
+ before do
6
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
7
+ stub.get('/_cluster/health') { |env|
8
+ [ 200,
9
+
10
+ { 'X-Opaque-Id' => env[:request_headers]['X-Opaque-Id'],
11
+ 'Content-Type' => 'application/json; charset=UTF-8',
12
+ 'Content-Length' => '49' },
13
+
14
+ %q[{"cluster_name":"elasticsearch","status":"green"}]
15
+ ]
16
+ }
17
+
18
+ stub.get('/_cluster/state') { |env|
19
+ [ 200, {'X-Opaque-Id' => "00000000-0000-0000-0000-000000000000"}, %q[{"foo":"bar"}] ]
20
+ }
21
+ end
22
+
23
+ opts = $client_params.merge \
24
+ :opaque_id => true,
25
+ :adapter => [:test, stubs]
26
+
27
+ @client = Elastomer::Client.new opts
28
+ end
29
+
30
+ it 'generates an "X-Opaque-Id" header' do
31
+ health = @client.cluster.health
32
+ assert_equal({"cluster_name" => "elasticsearch", "status" => "green"}, health)
33
+ end
34
+
35
+ it 'raises an exception on conflicting headers' do
36
+ assert_raises(Elastomer::Client::OpaqueIdError) { @client.cluster.state }
37
+ end
38
+
39
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ describe Elastomer::Middleware::ParseJson do
4
+ let(:middleware) { Elastomer::Middleware::ParseJson.new(lambda {|env| Faraday::Response.new(env)})}
5
+ let(:headers) { Hash.new }
6
+
7
+ def process(body, content_type = nil)
8
+ env = { :body => body, :response_headers => Faraday::Utils::Headers.new(headers) }
9
+ env[:response_headers]['content-type'] = content_type if content_type
10
+ middleware.call(env)
11
+ end
12
+
13
+ it "doesn't change nil body" do
14
+ response = process(nil)
15
+ assert_nil response.body
16
+ end
17
+
18
+ it 'nullifies empty body' do
19
+ response = process("")
20
+ assert_nil response.body
21
+ end
22
+
23
+ it 'nullifies blank body' do
24
+ response = process(" ")
25
+ assert_nil response.body
26
+ end
27
+
28
+ it 'parses json body with empty type' do
29
+ response = process('{"a":1}')
30
+ assert_equal({'a' => 1}, response.body)
31
+ end
32
+
33
+ it 'parses json body of correct type' do
34
+ response = process('{"a":1}', 'application/json; charset=utf-8')
35
+ assert_equal({'a' => 1}, response.body)
36
+ end
37
+
38
+ it 'ignores json body if incorrect type' do
39
+ response = process('{"a":1}', 'application/xml; charset=utf-8')
40
+ assert_equal('{"a":1}', response.body)
41
+ end
42
+
43
+ it 'chokes on invalid json' do
44
+ assert_raises(Faraday::Error::ParsingError) { process '{!' }
45
+ assert_raises(Faraday::Error::ParsingError) { process 'invalid' }
46
+
47
+ # surprisingly these are all valid according to MultiJson
48
+ #
49
+ # assert_raises(Faraday::Error::ParsingError) { process '"a"' }
50
+ # assert_raises(Faraday::Error::ParsingError) { process 'true' }
51
+ # assert_raises(Faraday::Error::ParsingError) { process 'null' }
52
+ # assert_raises(Faraday::Error::ParsingError) { process '1' }
53
+ end
54
+ end
@@ -0,0 +1,94 @@
1
+ require 'rubygems' unless defined? Gem
2
+ require 'bundler'
3
+ Bundler.require(:default, :development)
4
+
5
+ if ENV['COVERAGE'] == 'true'
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter "/test/"
9
+ add_filter "/vendor/"
10
+ end
11
+ end
12
+
13
+ require 'minitest/spec'
14
+ require 'minitest/autorun'
15
+
16
+ # push the lib folder onto the load path
17
+ $LOAD_PATH.unshift 'lib'
18
+ require 'elastomer/client'
19
+
20
+ # we are going to use the same client instance everywhere!
21
+ # the client should always be stateless
22
+ $client_params = {
23
+ :port => ENV['BOXEN_ELASTICSEARCH_PORT'] || 9200,
24
+ :read_timeout => 2,
25
+ :open_timeout => 1,
26
+ :opaque_id => true
27
+ }
28
+ $client = Elastomer::Client.new $client_params
29
+
30
+ # ensure we have an ElasticSearch server to test with
31
+ raise "No server available at #{$client.url}" unless $client.available?
32
+
33
+ # remove any lingering test indices from the cluster
34
+ MiniTest::Unit.after_tests do
35
+ $client.cluster.indices.keys.each do |name|
36
+ next unless name =~ /^elastomer-/i
37
+ $client.index(name).delete
38
+ end
39
+
40
+ $client.cluster.templates.keys.each do |name|
41
+ next unless name =~ /^elastomer-/i
42
+ $client.template(name).delete
43
+ end
44
+ end
45
+
46
+ # add custom assertions
47
+ require File.expand_path('../assertions', __FILE__)
48
+
49
+ # require 'elastomer/notifications'
50
+ # require 'pp'
51
+
52
+ # ActiveSupport::Notifications.subscribe('request.client.elastomer') do |name, start_time, end_time, transaction_id, payload|
53
+ # $stdout.puts '-'*100
54
+ # #$stdout.puts "-- #{payload[:action].inspect}"
55
+ # pp payload #if payload[:action].nil?
56
+ # end
57
+
58
+ # Wait for an index to be created. Since index creation requests return
59
+ # before the index is actually ready to receive documents, one needs to wait
60
+ # until the cluster status recovers before proceeding.
61
+ #
62
+ # name - The index name to wait for
63
+ # status - The status to wait for. Defaults to yellow. Yellow is the
64
+ # preferred status for tests, because it waits for at least one
65
+ # shard to be active, but doesn't wait for all replicas. Single
66
+ # node clusters will never achieve green status with the default
67
+ # setting of 1 replica.
68
+ #
69
+ # Returns the cluster health response.
70
+ # Raises Elastomer::Client::TimeoutError if requested status is not achieved
71
+ # within 5 seconds.
72
+ def wait_for_index(name, status='yellow')
73
+ $client.cluster.health(
74
+ :index => name,
75
+ :wait_for_status => status,
76
+ :timeout => '5s'
77
+ )
78
+ end
79
+
80
+ # Elasticsearch 1.0 changed some request formats in a non-backward-compatible
81
+ # way. Some tests need to know what version is running to structure requests
82
+ # as expected.
83
+ #
84
+ # Returns true if Elasticsearch version is 1.x.
85
+ def es_version_1_x?
86
+ $client.semantic_version >= '1.0.0'
87
+ end
88
+
89
+ # Elasticsearch 1.2 removed support for gateway snapshots.
90
+ #
91
+ # Returns true if Elasticsearch version supports gateway snapshots.
92
+ def es_version_supports_gateway_snapshots?
93
+ $client.semantic_version <= '1.2.0'
94
+ end