elastic-site-search 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +58 -0
  3. data/.gitignore +10 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +201 -0
  7. data/NOTICE.txt +3 -0
  8. data/README.md +418 -0
  9. data/Rakefile +1 -0
  10. data/elastic-site-search.gemspec +26 -0
  11. data/lib/data/ca-bundle.crt +3554 -0
  12. data/lib/elastic/site-search.rb +9 -0
  13. data/lib/elastic/site-search/client.rb +527 -0
  14. data/lib/elastic/site-search/configuration.rb +67 -0
  15. data/lib/elastic/site-search/exceptions.rb +11 -0
  16. data/lib/elastic/site-search/ext/backport-uri.rb +33 -0
  17. data/lib/elastic/site-search/request.rb +156 -0
  18. data/lib/elastic/site-search/result_set.rb +84 -0
  19. data/lib/elastic/site-search/sso.rb +22 -0
  20. data/lib/elastic/site-search/version.rb +5 -0
  21. data/logo-site-search.png +0 -0
  22. data/spec/client_spec.rb +728 -0
  23. data/spec/configuration_spec.rb +37 -0
  24. data/spec/fixtures/vcr/analytics_autoselects.yml +57 -0
  25. data/spec/fixtures/vcr/analytics_autoselects_with_document_type.yml +57 -0
  26. data/spec/fixtures/vcr/analytics_autoselects_with_document_type_and_time_range.yml +57 -0
  27. data/spec/fixtures/vcr/analytics_autoselects_with_time_range.yml +57 -0
  28. data/spec/fixtures/vcr/analytics_clicks.yml +57 -0
  29. data/spec/fixtures/vcr/analytics_clicks_with_document_type.yml +57 -0
  30. data/spec/fixtures/vcr/analytics_clicks_with_document_type_and_time_range.yml +57 -0
  31. data/spec/fixtures/vcr/analytics_clicks_with_time_range.yml +57 -0
  32. data/spec/fixtures/vcr/analytics_searches.yml +57 -0
  33. data/spec/fixtures/vcr/analytics_searches_with_document_type_and_time_range.yml +57 -0
  34. data/spec/fixtures/vcr/analytics_searches_with_time_range.yml +57 -0
  35. data/spec/fixtures/vcr/analytics_searchs_with_document_type.yml +57 -0
  36. data/spec/fixtures/vcr/analytics_top_no_result_queries.yml +57 -0
  37. data/spec/fixtures/vcr/analytics_top_no_result_queries_paginated.yml +57 -0
  38. data/spec/fixtures/vcr/analytics_top_queries.yml +57 -0
  39. data/spec/fixtures/vcr/analytics_top_queries_paginated.yml +57 -0
  40. data/spec/fixtures/vcr/analytics_top_queries_too_large.yml +53 -0
  41. data/spec/fixtures/vcr/async_create_or_update_document_failure.yml +48 -0
  42. data/spec/fixtures/vcr/async_create_or_update_document_success.yml +52 -0
  43. data/spec/fixtures/vcr/bulk_create_documents.yml +52 -0
  44. data/spec/fixtures/vcr/bulk_create_or_update_documents_failure.yml +47 -0
  45. data/spec/fixtures/vcr/bulk_create_or_update_documents_success.yml +52 -0
  46. data/spec/fixtures/vcr/bulk_create_or_update_documents_verbose_failure.yml +47 -0
  47. data/spec/fixtures/vcr/bulk_create_or_update_documents_verbose_success.yml +52 -0
  48. data/spec/fixtures/vcr/bulk_destroy_documents.yml +47 -0
  49. data/spec/fixtures/vcr/crawl_url.yml +47 -0
  50. data/spec/fixtures/vcr/create_document.yml +52 -0
  51. data/spec/fixtures/vcr/create_document_type.yml +47 -0
  52. data/spec/fixtures/vcr/create_domain.yml +47 -0
  53. data/spec/fixtures/vcr/create_engine.yml +47 -0
  54. data/spec/fixtures/vcr/create_or_update_document_create.yml +51 -0
  55. data/spec/fixtures/vcr/create_or_update_document_update.yml +51 -0
  56. data/spec/fixtures/vcr/create_user.yml +47 -0
  57. data/spec/fixtures/vcr/destroy_document.yml +43 -0
  58. data/spec/fixtures/vcr/destroy_document_type.yml +43 -0
  59. data/spec/fixtures/vcr/destroy_domain.yml +43 -0
  60. data/spec/fixtures/vcr/destroy_engine.yml +43 -0
  61. data/spec/fixtures/vcr/destroy_non_existent_document_type.yml +45 -0
  62. data/spec/fixtures/vcr/document_receipts_multiple.yml +48 -0
  63. data/spec/fixtures/vcr/document_receipts_multiple_complete.yml +48 -0
  64. data/spec/fixtures/vcr/document_type_search.yml +63 -0
  65. data/spec/fixtures/vcr/document_type_search_pagination.yml +47 -0
  66. data/spec/fixtures/vcr/document_type_suggest.yml +52 -0
  67. data/spec/fixtures/vcr/document_type_suggest_pagination.yml +47 -0
  68. data/spec/fixtures/vcr/engine_search.yml +67 -0
  69. data/spec/fixtures/vcr/engine_search_facets.yml +164 -0
  70. data/spec/fixtures/vcr/engine_search_pagination.yml +47 -0
  71. data/spec/fixtures/vcr/engine_suggest.yml +55 -0
  72. data/spec/fixtures/vcr/engine_suggest_pagination.yml +47 -0
  73. data/spec/fixtures/vcr/find_document.yml +50 -0
  74. data/spec/fixtures/vcr/find_document_type.yml +47 -0
  75. data/spec/fixtures/vcr/find_domain.yml +47 -0
  76. data/spec/fixtures/vcr/find_domain_failure.yml +45 -0
  77. data/spec/fixtures/vcr/find_engine.yml +47 -0
  78. data/spec/fixtures/vcr/list_document_type.yml +47 -0
  79. data/spec/fixtures/vcr/list_documents.yml +71 -0
  80. data/spec/fixtures/vcr/list_documents_with_pagination.yml +71 -0
  81. data/spec/fixtures/vcr/list_domains.yml +47 -0
  82. data/spec/fixtures/vcr/list_engines.yml +48 -0
  83. data/spec/fixtures/vcr/list_users.yml +47 -0
  84. data/spec/fixtures/vcr/list_users_with_pagination.yml +47 -0
  85. data/spec/fixtures/vcr/log_clickthrough_failure.yml +45 -0
  86. data/spec/fixtures/vcr/log_clickthrough_success.yml +47 -0
  87. data/spec/fixtures/vcr/recrawl_domain_failure.yml +46 -0
  88. data/spec/fixtures/vcr/recrawl_domain_success.yml +47 -0
  89. data/spec/fixtures/vcr/show_user.yml +47 -0
  90. data/spec/fixtures/vcr/update_document.yml +50 -0
  91. data/spec/fixtures/vcr/update_document_unknown_field_failure.yml +48 -0
  92. data/spec/fixtures/vcr/update_documents_failure_non_existent_document.yml +47 -0
  93. data/spec/fixtures/vcr/update_documents_success.yml +47 -0
  94. data/spec/fixtures/vcr/users_client_secret_incorrect.yml +44 -0
  95. data/spec/fixtures/vcr/users_no_api_key.yml +44 -0
  96. data/spec/fixtures/vcr/users_no_client_id_or_secret.yml +45 -0
  97. data/spec/platform_spec.rb +95 -0
  98. data/spec/spec_helper.rb +27 -0
  99. data/spec/ssl_spec.rb +34 -0
  100. data/spec/sso_spec.rb +24 -0
  101. metadata +279 -0
@@ -0,0 +1,67 @@
1
+ require 'uri'
2
+ require 'elastic/site-search/version'
3
+
4
+ module Elastic
5
+ module SiteSearch
6
+ module Configuration
7
+ DEFAULT_ENDPOINT = "https://api.swiftype.com/api/v1/"
8
+
9
+ VALID_OPTIONS_KEYS = [
10
+ :api_key,
11
+ :user_agent,
12
+ :platform_client_id,
13
+ :platform_client_secret,
14
+ :endpoint
15
+ ]
16
+
17
+ attr_accessor *VALID_OPTIONS_KEYS
18
+
19
+ def self.extended(base)
20
+ base.reset
21
+ end
22
+
23
+ # Reset configuration to default values.
24
+ def reset
25
+ self.api_key = nil
26
+ self.endpoint = DEFAULT_ENDPOINT
27
+ self.user_agent = nil
28
+ self.platform_client_id = nil
29
+ self.platform_client_secret = nil
30
+ self
31
+ end
32
+
33
+ # Yields the Elastic::SiteSearch::Configuration module which can be used to set configuration options.
34
+ #
35
+ # @return self
36
+ def configure
37
+ yield self
38
+ self
39
+ end
40
+
41
+ # Return a hash of the configured options.
42
+ def options
43
+ options = {}
44
+ VALID_OPTIONS_KEYS.each{|k| options[k] = send(k)}
45
+ options
46
+ end
47
+
48
+ # Set api_key and endpoint based on a URL with HTTP authentication.
49
+ def authenticated_url=(url)
50
+ uri = URI(url)
51
+ self.api_key = uri.user
52
+ uri.user = nil
53
+ uri.password = nil
54
+ self.endpoint = uri.to_s
55
+ end
56
+
57
+ # setter for endpoint that ensures it always ends in '/'
58
+ def endpoint=(endpoint)
59
+ if endpoint.end_with?('/')
60
+ @endpoint = endpoint
61
+ else
62
+ @endpoint = "#{endpoint}/"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,11 @@
1
+ module Elastic
2
+ module SiteSearch
3
+ class ClientException < StandardError; end
4
+ class NonExistentRecord < ClientException; end
5
+ class RecordAlreadyExists < ClientException; end
6
+ class InvalidCredentials < ClientException; end
7
+ class BadRequest < ClientException; end
8
+ class Forbidden < ClientException; end
9
+ class UnexpectedHTTPException < ClientException; end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # :stopdoc:
2
+
3
+ # Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
4
+ #
5
+ # https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
6
+ #
7
+ #
8
+
9
+ module URI
10
+ def self.encode_www_form(enum)
11
+ enum.map do |k,v|
12
+ if v.nil?
13
+ encode_www_form_component(k)
14
+ elsif v.respond_to?(:to_ary)
15
+ v.to_ary.map do |w|
16
+ str = encode_www_form_component(k)
17
+ unless w.nil?
18
+ str << '='
19
+ str << encode_www_form_component(w)
20
+ end
21
+ end.join('&')
22
+ else
23
+ str = encode_www_form_component(k)
24
+ str << '='
25
+ str << encode_www_form_component(v)
26
+ end
27
+ end.join('&')
28
+ end
29
+
30
+ def self.encode_www_form_component(str)
31
+ str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/) { |chr| TBLENCWWWCOMP_[chr] }
32
+ end
33
+ end
@@ -0,0 +1,156 @@
1
+ require 'net/https'
2
+ if RUBY_VERSION < '1.9'
3
+ require 'elastic/site-search/ext/backport-uri'
4
+ else
5
+ require 'uri'
6
+ end
7
+ require 'json'
8
+ require 'elastic/site-search/exceptions'
9
+ require 'openssl'
10
+
11
+ module Elastic
12
+ module SiteSearch
13
+ CLIENT_NAME = 'elastic-site-search-ruby'
14
+ CLIENT_VERSION = Elastic::SiteSearch::VERSION
15
+
16
+ module Request
17
+ def get(path, params={})
18
+ request(:get, path, params)
19
+ end
20
+
21
+ def post(path, params={})
22
+ request(:post, path, params)
23
+ end
24
+
25
+ def put(path, params={})
26
+ request(:put, path, params)
27
+ end
28
+
29
+ def delete(path, params={})
30
+ request(:delete, path, params)
31
+ end
32
+
33
+ # Poll a block with backoff until a timeout is reached.
34
+ #
35
+ # @param [Hash] options optional arguments
36
+ # @option options [Numeric] :timeout (10) Number of seconds to wait before timing out
37
+ #
38
+ # @yieldreturn a truthy value to return from poll
39
+ # @yieldreturn [false] to continue polling.
40
+ #
41
+ # @return the truthy value returned from the block.
42
+ #
43
+ # @raise [Timeout::Error] when the timeout expires
44
+ def poll(options={})
45
+ timeout = options[:timeout] || 10
46
+ delay = 0.05
47
+ Timeout.timeout(timeout) do
48
+ while true
49
+ res = yield
50
+ return res if res
51
+ sleep delay *= 2
52
+ end
53
+ end
54
+ end
55
+
56
+ # Construct and send a request to the API.
57
+ #
58
+ # @raise [Timeout::Error] when the timeout expires
59
+ def request(method, path, params={})
60
+ Timeout.timeout(overall_timeout) do
61
+ uri = URI.parse("#{Elastic::SiteSearch.endpoint}#{path}")
62
+
63
+ request = build_request(method, uri, params)
64
+
65
+ if proxy
66
+ proxy_parts = URI.parse(proxy)
67
+ http = Net::HTTP.new(uri.host, uri.port, proxy_parts.host, proxy_parts.port, proxy_parts.user, proxy_parts.password)
68
+ else
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ end
71
+
72
+ http.open_timeout = open_timeout
73
+ http.read_timeout = overall_timeout
74
+
75
+ if uri.scheme == 'https'
76
+ http.use_ssl = true
77
+ # st_ssl_verify_none provides a means to disable SSL verification for debugging purposes. An example
78
+ # is Charles, which uses a self-signed certificate in order to inspect https traffic. This will
79
+ # not be part of this client's public API, this is more of a development enablement option
80
+ http.verify_mode = ENV['st_ssl_verify_none'] == 'true' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
81
+ http.ca_file = File.join(File.dirname(__FILE__), '../..', 'data', 'ca-bundle.crt')
82
+ http.ssl_timeout = open_timeout
83
+ end
84
+
85
+ response = http.request(request)
86
+ handle_errors(response)
87
+ JSON.parse(response.body) if response.body && response.body.strip != ''
88
+ end
89
+ end
90
+
91
+ private
92
+ def handle_errors(response)
93
+ case response
94
+ when Net::HTTPSuccess
95
+ response
96
+ when Net::HTTPUnauthorized
97
+ raise Elastic::SiteSearch::InvalidCredentials, error_message_from_response(response)
98
+ when Net::HTTPNotFound
99
+ raise Elastic::SiteSearch::NonExistentRecord, error_message_from_response(response)
100
+ when Net::HTTPConflict
101
+ raise Elastic::SiteSearch::RecordAlreadyExists, error_message_from_response(response)
102
+ when Net::HTTPBadRequest
103
+ raise Elastic::SiteSearch::BadRequest, error_message_from_response(response)
104
+ when Net::HTTPForbidden
105
+ raise Elastic::SiteSearch::Forbidden, error_message_from_response(response)
106
+ else
107
+ raise Elastic::SiteSearch::UnexpectedHTTPException, "#{response.code} #{response.body}"
108
+ end
109
+ end
110
+
111
+ def error_message_from_response(response)
112
+ body = response.body
113
+ json = JSON.parse(body) if body && body.strip != ''
114
+ return json['error'] if json && json.key?('error')
115
+ body
116
+ rescue JSON::ParserError
117
+ body
118
+ end
119
+
120
+ def build_request(method, uri, params)
121
+ klass = case method
122
+ when :get
123
+ Net::HTTP::Get
124
+ when :post
125
+ Net::HTTP::Post
126
+ when :put
127
+ Net::HTTP::Put
128
+ when :delete
129
+ Net::HTTP::Delete
130
+ end
131
+
132
+ case method
133
+ when :get, :delete
134
+ uri.query = URI.encode_www_form(params) if params && !params.empty?
135
+ req = klass.new(uri.request_uri)
136
+ when :post, :put
137
+ req = klass.new(uri.request_uri)
138
+ req.body = JSON.generate(params) unless params.length == 0
139
+ end
140
+
141
+ req['User-Agent'] = Elastic::SiteSearch.user_agent if Elastic::SiteSearch.user_agent
142
+ req['Content-Type'] = 'application/json'
143
+ req['X-Swiftype-Client'] = CLIENT_NAME
144
+ req['X-Swiftype-Client-Version'] = CLIENT_VERSION
145
+
146
+ if platform_access_token
147
+ req['Authorization'] = "Bearer #{platform_access_token}"
148
+ elsif api_key
149
+ req.basic_auth api_key, ''
150
+ end
151
+
152
+ req
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,84 @@
1
+ module Elastic
2
+ module SiteSearch
3
+ # The Elastic::SiteSearch::ResultSet class represents a {search}[https://swiftype.com/documentation/site-search/searching]
4
+ # or {suggest result}[https://swiftype.com/documentation/site-search/autocomplete] returned by the Elastic::SiteSearch API.
5
+ class ResultSet
6
+ # @attribute errors [r]
7
+ # a hash of errors for the search (for example filtering on a missing attribute) keyed by DocumentType slug
8
+ attr_reader :errors
9
+
10
+ # @attribute records [r]
11
+ # a hash of results for the search keyed by DocumentType slug. Use `[]` to access results more easily.
12
+ attr_reader :records
13
+
14
+ # @attribute info [r]
15
+ # a hash of extra query info (for example, facets and number of results) keyed by DocumentType slug.
16
+ # Use the convenience methods of this class for easier access.
17
+ attr_reader :info
18
+
19
+ # Create a Elastic::SiteSearch::ResultSet from deserialized JSON.
20
+ def initialize(results)
21
+ @records = results['records']
22
+ @info = results['info']
23
+ @errors = results['errors']
24
+ end
25
+
26
+ # Get results for the provided DocumentType
27
+ #
28
+ # @param [String] document_type the DocumentType slug to get results for
29
+ def [](document_type)
30
+ records[document_type]
31
+ end
32
+
33
+ # Return a list of DocumentType slugs represented in the ResultSet.
34
+ def document_types
35
+ records.keys
36
+ end
37
+
38
+ # Return the search facets for the provided DocumentType. Will be
39
+ # empty unless a facets parameter was provided when calling the
40
+ # search API.
41
+ #
42
+ # @param [String] document_type the DocumentType slug to get facets for
43
+ def facets(document_type)
44
+ info[document_type]['facets']
45
+ end
46
+
47
+ # Return the page of results for this ResultSet
48
+ def current_page
49
+ info[info.keys.first]['current_page']
50
+ end
51
+
52
+ # Return the number of results per page.
53
+ def per_page
54
+ info[info.keys.first]['per_page']
55
+ end
56
+
57
+ # Return the number of pages. Since a search can cover multiple
58
+ # DocumentTypes with different numbers of results, the number of
59
+ # pages can vary between DocumentTypes. With no argument, it
60
+ # returns the maximum num_pages for all DocumentTypes in this
61
+ # ResultSet. With a DocumentType slug, it returns the number of
62
+ # pages for that DocumentType.
63
+ #
64
+ # @param [String] document_type the DocumentType slug to return the number of pages for
65
+ def num_pages(document_type=nil)
66
+ if document_type
67
+ info[document_type]['num_pages']
68
+ else
69
+ info.values.map { |v| v['num_pages'] }.max
70
+ end
71
+ end
72
+
73
+ # Return the total number of results for the query
74
+ def total_result_count(document_type)
75
+ info[document_type]['total_result_count']
76
+ end
77
+
78
+ # Return the query used for this search
79
+ def query
80
+ info[info.keys.first]['query']
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,22 @@
1
+ require 'digest/sha1'
2
+
3
+ module Elastic
4
+ module SiteSearch
5
+ # Single sign-on for the Site Search Dashboard.
6
+ module SSO
7
+ BASE_URL = 'https://swiftype.com/sso'
8
+
9
+ # Generate a URL that a user can click on to be logged into the Site Search Dashboard.
10
+ # This requires the +platform_client_id+ and +platform_client_secret+ configuration options be set.
11
+ def self.url(user_id)
12
+ timestamp = Time.now.to_i
13
+
14
+ "#{BASE_URL}?user_id=#{user_id}&client_id=#{Elastic::SiteSearch.platform_client_id}&timestamp=#{timestamp}&token=#{token(user_id, timestamp)}"
15
+ end
16
+
17
+ def self.token(user_id, timestamp)
18
+ Digest::SHA1.hexdigest("#{user_id}:#{Elastic::SiteSearch.platform_client_secret}:#{timestamp}")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ module Elastic
2
+ module SiteSearch
3
+ VERSION = "2.0.0"
4
+ end
5
+ end
Binary file
@@ -0,0 +1,728 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elastic::SiteSearch::Client do
4
+ let(:engine_slug) { 'site-search-api-example' }
5
+ let(:client) { Elastic::SiteSearch::Client.new }
6
+
7
+ before :each do
8
+ Elastic::SiteSearch.api_key = 'hello'
9
+ end
10
+
11
+ context 'Search' do
12
+ context '#search' do
13
+ it 'searches all DocumentTypes in the engine' do
14
+ VCR.use_cassette(:engine_search) do
15
+ results = client.search(engine_slug, 'cat')
16
+ expect(results.document_types.size).to eq(2)
17
+ expect(results['videos'].size).to eq(2)
18
+ expect(results['channels'].size).to eq(1)
19
+ end
20
+ end
21
+
22
+ it 'searches the engine with options' do
23
+ VCR.use_cassette(:engine_search_pagination) do
24
+ results = client.search(engine_slug, 'cat', {:page => 2})
25
+ expect(results.document_types.size).to eq(2)
26
+ expect(results['videos'].size).to eq(0)
27
+ expect(results['channels'].size).to eq(0)
28
+ end
29
+ end
30
+
31
+ it 'includes facets when requested' do
32
+ VCR.use_cassette(:engine_search_facets) do
33
+ results = client.search(engine_slug, nil, {:facets => {:videos => ['category_id']}})
34
+ expect(results.document_types.size).to eq(2)
35
+ expect(results.facets('channels')).to be_empty
36
+ expect(results.facets('videos')['category_id']).to eq({
37
+ "23" => 4,
38
+ "28" => 2,
39
+ "15" => 2,
40
+ "25" => 1,
41
+ "22" => 1,
42
+ "2" => 1,
43
+ "10" => 1
44
+ })
45
+ end
46
+ end
47
+ end
48
+
49
+ context '#search_document_type' do
50
+ let(:document_type_slug) { 'videos' }
51
+
52
+ it 'searches only the provided DocumentType' do
53
+ VCR.use_cassette(:document_type_search) do
54
+ results = client.search_document_type(engine_slug, document_type_slug, 'cat')
55
+ expect(results.document_types).to eq(['videos'])
56
+ expect(results['videos'].size).to eq(2)
57
+ end
58
+ end
59
+
60
+ it 'searches the DocumentType with options' do
61
+ VCR.use_cassette(:document_type_search_pagination) do
62
+ results = client.search_document_type(engine_slug, document_type_slug, 'cat', {:page => 2})
63
+ expect(results.document_types).to eq(['videos'])
64
+ expect(results[document_type_slug].size).to eq(0)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'Options' do
71
+ let(:options_client) { Elastic::SiteSearch::Client.new(options) }
72
+
73
+ context '#request' do
74
+ context 'open_timeout' do
75
+ let(:options) { { :open_timeout => 3 } }
76
+ it 'respects the Net::HTTP open_timeout option' do
77
+ expect(options_client.open_timeout).to eq(3)
78
+ end
79
+ end
80
+
81
+ context 'overall_timeout' do
82
+ context 'with timeout specified' do
83
+ let(:options) { { :overall_timeout => 0.001 } }
84
+ it 'respects the overall timeout option' do
85
+ expect(options_client.overall_timeout).to eq(0.001)
86
+ allow_any_instance_of(Net::HTTP).to receive(:request) { sleep 3 }
87
+ expect {
88
+ options_client.search(engine_slug, 'cat')
89
+ }.to raise_error(Timeout::Error)
90
+ end
91
+ end
92
+
93
+ context 'without timeout specified' do
94
+ let(:options) { Hash.new }
95
+ it 'omits the option' do
96
+ expect(options_client.overall_timeout).to eq(Elastic::SiteSearch::Client::DEFAULT_TIMEOUT.to_f)
97
+ expect(Timeout).to receive(:timeout).with(Elastic::SiteSearch::Client::DEFAULT_TIMEOUT.to_f).and_call_original
98
+ VCR.use_cassette(:engine_search) do
99
+ options_client.search(engine_slug, 'cat')
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ context 'with proxy specified' do
106
+ let(:options) { { :proxy => 'http://username:password@localhost:8888' } }
107
+
108
+ it 'will set proxy' do
109
+ expect(options_client.proxy).to eq('http://username:password@localhost:8888')
110
+ end
111
+
112
+ # There doesn't seem to be an elgant way to test that a request actually uses a proxy, so the best
113
+ # we can do here is ensure that the behavior for methods operates normally
114
+ it 'will execute methods with proxy' do
115
+ VCR.use_cassette(:engine_search) do
116
+ results = options_client.search(engine_slug, 'cat')
117
+ expect(results.document_types.size).to eq(2)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ context 'Suggest' do
125
+ context '#suggest' do
126
+ it 'does prefix searches for all DocumentTypes in the engine' do
127
+ VCR.use_cassette(:engine_suggest) do
128
+ results = client.suggest(engine_slug, 'goo')
129
+ expect(results.document_types.size).to eq(2)
130
+ expect(results['videos'].size).to eq(1)
131
+ expect(results['channels'].size).to eq(1)
132
+ end
133
+ end
134
+
135
+ it 'suggests for an engine with options' do
136
+ VCR.use_cassette(:engine_suggest_pagination) do
137
+ results = client.suggest(engine_slug, 'goo', {:page => 2})
138
+ expect(results.document_types.size).to eq(2)
139
+ expect(results['videos'].size).to eq(0)
140
+ expect(results['channels'].size).to eq(0)
141
+ end
142
+ end
143
+ end
144
+
145
+ context '#suggest_document_type' do
146
+ let(:document_type_slug) { 'videos' }
147
+
148
+ it 'does a prefix search on the provided DocumentType' do
149
+ VCR.use_cassette(:document_type_suggest) do
150
+ results = client.suggest_document_type(engine_slug, document_type_slug, 'goo')
151
+ expect(results.document_types).to eq(['videos'])
152
+ expect(results['videos'].size).to eq(1)
153
+ end
154
+ end
155
+
156
+ it 'suggests for a document types with options' do
157
+ VCR.use_cassette(:document_type_suggest_pagination) do
158
+ results = client.suggest_document_type(engine_slug, document_type_slug, 'goo', {:page => 2})
159
+ expect(results.document_types).to eq(['videos'])
160
+ expect(results[document_type_slug].size).to eq(0)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+
167
+ context 'Engine' do
168
+ it 'gets all engines' do
169
+ VCR.use_cassette(:list_engines) do
170
+ engines = client.engines
171
+ expect(engines.size).to eq(6)
172
+ end
173
+ end
174
+
175
+ it 'gets an engine' do
176
+ VCR.use_cassette(:find_engine) do
177
+ engine = client.engine(engine_slug)
178
+ expect(engine['slug']).to eq(engine_slug)
179
+ end
180
+ end
181
+
182
+ it 'creates engines' do
183
+ VCR.use_cassette(:create_engine) do
184
+ engine = client.create_engine('new engine from spec')
185
+ expect(engine['slug']).to eq('new-engine-from-spec')
186
+ end
187
+ end
188
+
189
+ it 'destroys engines' do
190
+ VCR.use_cassette(:destroy_engine) do
191
+ response = client.destroy_engine('new-engine-from-spec')
192
+ expect(response).to be_nil
193
+ end
194
+ end
195
+ end
196
+
197
+ context 'DocumentType' do
198
+ let(:document_type_slug) { 'videos' }
199
+
200
+ it 'gets all document types' do
201
+ VCR.use_cassette(:list_document_type) do
202
+ document_types = client.document_types(engine_slug)
203
+ expect(document_types.size).to eq(2)
204
+ expect(document_types.map { |dt| dt['name'] }.sort).to eq(['channels', 'videos'])
205
+ end
206
+ end
207
+
208
+ it 'gets a document type' do
209
+ VCR.use_cassette(:find_document_type) do
210
+ document_type = client.document_type(engine_slug, document_type_slug)
211
+ expect(document_type['slug']).to eq(document_type_slug)
212
+ end
213
+ end
214
+
215
+ it 'creates a document type' do
216
+ VCR.use_cassette(:create_document_type) do
217
+ name = document_type_slug
218
+ document_type = client.create_document_type(engine_slug, 'new_doc_type')
219
+ expect(document_type['name']).to eq('new_doc_type')
220
+ expect(document_type['slug']).to eq('new-doc-type')
221
+ end
222
+ end
223
+
224
+ it 'destroys document types' do
225
+ VCR.use_cassette(:destroy_document_type) do
226
+ response = client.destroy_document_type(engine_slug, 'new-doc-type')
227
+ expect(response).to be_nil
228
+ end
229
+ end
230
+
231
+ it 'raises an error if deleting a non-existent DocumentType' do
232
+ VCR.use_cassette(:destroy_non_existent_document_type) do
233
+ expect do
234
+ response = client.destroy_document_type(engine_slug, 'not_there')
235
+ end.to raise_error
236
+ end
237
+ end
238
+ end
239
+
240
+ context 'Document' do
241
+
242
+ def check_async_response_format(response, options = {})
243
+ expect(response.keys).to match_array(["document_receipts", "batch_link"])
244
+ expect(response["document_receipts"]).to be_a_kind_of(Array)
245
+ expect(response["document_receipts"].first.keys).to match_array(["id", "external_id", "link", "status", "errors"])
246
+ expect(response["document_receipts"].first["external_id"]).to eq(options[:external_id]) if options[:external_id]
247
+ expect(response["document_receipts"].first["status"]).to eq(options[:status]) if options[:status]
248
+ expect(response["document_receipts"].first["errors"]).to eq(options[:errors]) if options[:errors]
249
+ end
250
+
251
+ let(:document_type_slug) { 'videos' }
252
+ let(:document_id) { 'FtX8nswnUKU'}
253
+ let(:documents) do
254
+ [{'external_id'=>'INscMGmhmX4',
255
+ 'fields' => [{'name'=>'url', 'value'=>'http://www.youtube.com/watch?v=v1uyQZNg2vE', 'type'=>'enum'},
256
+ {'name'=>'thumbnail_url', 'value'=>'https://i.ytimg.com/vi/INscMGmhmX4/mqdefault.jpg', 'type'=>'enum'},
257
+ {'name'=>'channel_id', 'value'=>'UCTzVrd9ExsI3Zgnlh3_btLg', 'type'=>'enum'},
258
+ {'name'=>'title', 'value'=>'The Original Grumpy Cat', 'type'=>'string'},
259
+ {'name'=>'category_name', 'value'=>'Pets &amp; Animals', 'type'=>'string'}]},
260
+ {'external_id'=>'XfY9Dsg_DZk',
261
+ 'fields' => [{'name'=>'url', 'value'=>'http://www.youtube.com/watch?v=XfY9Dsg_DZk', 'type'=>'enum'},
262
+ {'name'=>'thumbnail_url', 'value'=>'https://i.ytimg.com/vi/XfY9Dsg_DZk/mqdefault.jpg', 'type'=>'enum'},
263
+ {'name'=>'channel_id', 'value'=>'UC5VA5j05FjETg-iLekcyiBw', 'type'=>'enum'},
264
+ {'name'=>'title', 'value'=>'Corgi talks to cat', 'type'=>'string'},
265
+ {'name'=>'category_name', 'value'=>'Pets &amp; Animals', 'type'=>'string'}]}]
266
+ end
267
+
268
+ it 'lists documents in a document type' do
269
+ VCR.use_cassette(:list_documents) do
270
+ documents = client.documents(engine_slug, document_type_slug)
271
+ expect(documents.size).to eq(2)
272
+ end
273
+ end
274
+
275
+ it 'lists documents with pagination' do
276
+ VCR.use_cassette(:list_documents_with_pagination) do
277
+ documents = client.documents(engine_slug, document_type_slug, 2, 2)
278
+ expect(documents.size).to eq(2)
279
+ end
280
+ end
281
+
282
+ it 'find a document' do
283
+ VCR.use_cassette(:find_document) do
284
+ document = client.document(engine_slug, document_type_slug, document_id)
285
+ expect(document['external_id']).to eq(document_id)
286
+ end
287
+ end
288
+
289
+ it 'creates a document' do
290
+ VCR.use_cassette(:create_document) do
291
+ document = client.create_document(engine_slug, document_type_slug, documents.first)
292
+ expect(document['external_id']).to eq('INscMGmhmX4')
293
+ end
294
+ end
295
+
296
+ it 'bulk create multiple documents' do
297
+ VCR.use_cassette(:bulk_create_documents) do
298
+ response = client.create_documents(engine_slug, document_type_slug, documents)
299
+ expect(response).to eq([true, true])
300
+ end
301
+ end
302
+
303
+ it 'destroys a document' do
304
+ VCR.use_cassette(:destroy_document) do
305
+ response = client.destroy_document(engine_slug, document_type_slug, 'INscMGmhmX4')
306
+ expect(response).to be_nil
307
+ end
308
+ end
309
+
310
+ it 'destroys multiple documents' do
311
+ VCR.use_cassette(:bulk_destroy_documents) do
312
+ response = client.destroy_documents(engine_slug, document_type_slug, ['INscMGmhmX4', 'XfY9Dsg_DZk'])
313
+ expect(response).to eq([true, true])
314
+ end
315
+ end
316
+
317
+ context '#create_or_update_document' do
318
+ it 'creates a document' do
319
+ VCR.use_cassette(:create_or_update_document_create) do
320
+ response = client.create_or_update_document(engine_slug, document_type_slug, {:external_id => 'foobar', :fields => [{:type => :string, :name => 'title', :value => 'new document'}]})
321
+ expect(response['external_id']).to eq('foobar')
322
+ expect(response['title']).to eq('new document')
323
+ end
324
+ end
325
+
326
+ it 'updates an existing document' do
327
+ VCR.use_cassette(:create_or_update_document_update) do
328
+ response = client.create_or_update_document(engine_slug, document_type_slug, {:external_id => document_id, :fields => [{:type => :string, :name => 'title', :value => 'new title'}]})
329
+ expect(response['external_id']).to eq(document_id)
330
+ expect(response['title']).to eq('new title')
331
+ end
332
+ end
333
+ end
334
+
335
+ context '#bulk_create_or_update_documents' do
336
+ it 'returns true for all documents successfully created or updated' do
337
+ VCR.use_cassette(:bulk_create_or_update_documents_success) do
338
+ response = client.create_or_update_documents(engine_slug, document_type_slug, documents)
339
+ expect(response).to eq([true, true])
340
+ end
341
+ end
342
+
343
+ it 'returns false if a document cannot be created or updated due to an error' do
344
+ documents = [{:external_id => 'failed_doc', :fields => [{:type => :string, :name => :title}]}] # missing value
345
+
346
+ VCR.use_cassette(:bulk_create_or_update_documents_failure) do
347
+ response = client.create_or_update_documents(engine_slug, document_type_slug, documents)
348
+ expect(response).to eq([false])
349
+ end
350
+ end
351
+ end
352
+
353
+ context '#async_create_or_update_documents' do
354
+ it 'returns true for all documents successfully created or updated' do
355
+ VCR.use_cassette(:async_create_or_update_document_success) do
356
+ response = client.async_create_or_update_documents(engine_slug, document_type_slug, documents)
357
+ check_async_response_format(response, :external_id => documents.first["external_id"], :status => "pending")
358
+ end
359
+ end
360
+
361
+ it 'returns false if a document cannot be created or updated due to an error' do
362
+ documents = [{:external_id => 'failed_doc', :fields => [{:type => :string, :name => :title}]}] # missing value
363
+
364
+ VCR.use_cassette(:async_create_or_update_document_failure) do
365
+ response = client.async_create_or_update_documents(engine_slug, document_type_slug, documents)
366
+ check_async_response_format(response, :external_id => documents.first["external_id"], :status => "failed", :errors => ["Missing required parameter: value"])
367
+ end
368
+ end
369
+ end
370
+
371
+ context '#document_receipts' do
372
+ before :each do
373
+ def get_receipt_ids
374
+ receipt_ids = nil
375
+ VCR.use_cassette(:async_create_or_update_document_success) do
376
+ response = client.async_create_or_update_documents(engine_slug, document_type_slug, documents)
377
+ receipt_ids = response["document_receipts"].map { |r| r["id"] }
378
+ end
379
+ receipt_ids
380
+ end
381
+ end
382
+
383
+ it 'returns array of hashes one for each receipt' do
384
+ VCR.use_cassette(:document_receipts_multiple) do
385
+ receipt_ids = get_receipt_ids
386
+ response = client.document_receipts(receipt_ids)
387
+ expect(response).to eq([{"id" => receipt_ids[0], "status" => "pending"}, {"id" => receipt_ids[1], "status" => "pending"}])
388
+ end
389
+ end
390
+ end
391
+
392
+ context '#index_documents' do
393
+ it 'returns #async_create_or_update_documents format return when async has been passed as true' do
394
+ VCR.use_cassette(:async_create_or_update_document_success) do
395
+ response = client.index_documents(engine_slug, document_type_slug, documents, :async => true)
396
+ check_async_response_format(response, :external_id => documents.first["external_id"], :status => "pending")
397
+ end
398
+ end
399
+
400
+ it 'returns document_receipts when successfull' do
401
+ VCR.use_cassette(:async_create_or_update_document_success) do
402
+ VCR.use_cassette(:document_receipts_multiple_complete) do
403
+ response = client.index_documents(engine_slug, document_type_slug, documents)
404
+ # Sort keys for Ruby 1.8.7 compatibility
405
+ expect(response.map(&:keys).map(&:sort)).to eq([["id", "link", "status"], ["id", "link", "status"]])
406
+ expect(response.map { |a| a["status"] }).to eq(["complete", "complete"])
407
+ end
408
+ end
409
+ end
410
+
411
+ it 'should timeout if the process takes longer than the timeout option passed' do
412
+ allow(client).to receive(:document_receipts) { sleep 0.05 }
413
+
414
+ VCR.use_cassette(:async_create_or_update_document_success) do
415
+ expect do
416
+ client.index_documents(engine_slug, document_type_slug, documents, :timeout => 0.01)
417
+ end.to raise_error(Timeout::Error)
418
+ end
419
+ end
420
+ end
421
+
422
+ context '#bulk_create_or_update_documents_verbose' do
423
+ it 'returns true for all documents successfully created or updated' do
424
+ VCR.use_cassette(:bulk_create_or_update_documents_verbose_success) do
425
+ response = client.create_or_update_documents_verbose(engine_slug, document_type_slug, documents)
426
+ expect(response).to eq([true, true])
427
+ end
428
+ end
429
+
430
+ it 'returns a descriptive error message if a document cannot be created or updated due to an error' do
431
+ documents = [{:external_id => 'failed_doc', :fields => [{:type => :string, :name => :title}]}] # missing value
432
+
433
+ VCR.use_cassette(:bulk_create_or_update_documents_verbose_failure) do
434
+ response = client.create_or_update_documents_verbose(engine_slug, document_type_slug, documents)
435
+ expect(response.size).to eq(1)
436
+ expect(response.first).to match /^Invalid field definition/
437
+ end
438
+ end
439
+ end
440
+
441
+ context '#update_document' do
442
+ it 'updates a document given its id and fields to update' do
443
+ fields = {:title => 'awesome new title', :channel_id => 'UC5VA5j05FjETg-iLekcyiBw'}
444
+ VCR.use_cassette(:update_document) do
445
+ response = client.update_document(engine_slug, document_type_slug, document_id, fields)
446
+ expect(response['external_id']).to eq(document_id)
447
+ expect(response['title']).to eq('awesome new title')
448
+ expect(response['channel_id']).to eq('UC5VA5j05FjETg-iLekcyiBw')
449
+ end
450
+ end
451
+
452
+ it 'raises an error if a unknown field is included' do
453
+ fields = {:not_a_field => 'not a field'}
454
+
455
+ VCR.use_cassette(:update_document_unknown_field_failure) do
456
+ expect do
457
+ response = client.update_document(engine_slug, document_type_slug, document_id, fields)
458
+ end.to raise_error
459
+ end
460
+ end
461
+ end
462
+
463
+ context "#update_documents" do
464
+ it 'returns true for each document successfully updated' do
465
+ documents = [{:external_id => 'INscMGmhmX4', :fields => {:title => 'hi'}}, {:external_id => 'XfY9Dsg_DZk', :fields => {:title => 'bye'}}]
466
+
467
+ VCR.use_cassette(:update_documents_success) do
468
+ response = client.update_documents(engine_slug, document_type_slug, documents)
469
+ expect(response).to eq([true, true])
470
+ end
471
+ end
472
+
473
+ it 'returns false if document is not successfully updated' do
474
+ documents = [{:external_id => 'not_there', :fields => [{:name => :title, :value => 'hi', :type => :string}]}]
475
+
476
+ VCR.use_cassette(:update_documents_failure_non_existent_document) do
477
+ response = client.update_documents(engine_slug, document_type_slug, documents)
478
+ expect(response).to eq([false])
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ context 'Analytics' do
485
+ let(:engine_slug) { 'recursion' }
486
+
487
+ context '#analytics_searches' do
488
+ it 'returns search counts for the default time frame' do
489
+ VCR.use_cassette(:analytics_searches) do
490
+ searches = client.analytics_searches(engine_slug)
491
+ expect(searches.size).to eq(15) # FIXME: is this a bug in the API?
492
+ expect(searches.first).to eq(['2013-09-13', 0])
493
+ end
494
+ end
495
+
496
+ it 'returns search counts for a specified time range' do
497
+ VCR.use_cassette(:analytics_searches_with_time_range) do
498
+ searches = client.analytics_searches(engine_slug, :start_date => '2013-01-01', :end_date => '2013-01-07')
499
+ expect(searches.size).to eq(7)
500
+ expect(searches.first).to eq(['2013-01-07', 0])
501
+ end
502
+ end
503
+
504
+ it 'returns search counts for a specified DocumentType' do
505
+ VCR.use_cassette(:analytics_searchs_with_document_type) do
506
+ searches = client.analytics_searches(engine_slug, :document_type_id => 'page')
507
+ expect(searches.size).to eq(15)
508
+ expect(searches.first).to eq(['2013-09-16', 0])
509
+ end
510
+ end
511
+
512
+ it 'returns search counts for a specified DocumentType and time range' do
513
+ VCR.use_cassette(:analytics_searches_with_document_type_and_time_range) do
514
+ searches = client.analytics_autoselects(engine_slug, :document_type_id => 'page', :start_date => '2013-07-01', :end_date => '2013-07-07')
515
+ expect(searches.size).to eq(7)
516
+ expect(searches.first).to eq(['2013-07-07', 0])
517
+ end
518
+ end
519
+ end
520
+
521
+ context '#analytics_autoselects' do
522
+ it 'returns autoselect counts for the default time frame' do
523
+ VCR.use_cassette(:analytics_autoselects) do
524
+ autoselects = client.analytics_autoselects(engine_slug)
525
+ expect(autoselects.size).to eq(15)
526
+ expect(autoselects.first).to eq(['2013-09-13', 0])
527
+ end
528
+ end
529
+
530
+ it 'returns autoselects counts for a specified time range' do
531
+ VCR.use_cassette(:analytics_autoselects_with_time_range) do
532
+ autoselects = client.analytics_autoselects(engine_slug, :start_date => '2013-07-01', :end_date => '2013-07-07')
533
+ expect(autoselects.size).to eq(7)
534
+ end
535
+ end
536
+
537
+ it 'returns autoselect counts for a specified DocumentType' do
538
+ VCR.use_cassette(:analytics_autoselects_with_document_type) do
539
+ autoselects = client.analytics_autoselects(engine_slug, :document_type_id => 'page')
540
+ expect(autoselects.size).to eq(15)
541
+ end
542
+ end
543
+
544
+ it 'returns autoselect counts for a specified DocumentType and time range' do
545
+ VCR.use_cassette(:analytics_autoselects_with_document_type_and_time_range) do
546
+ autoselects = client.analytics_autoselects(engine_slug, :document_type_id => 'page', :start_date => '2013-07-01', :end_date => '2013-07-07')
547
+ expect(autoselects.size).to eq(7)
548
+ end
549
+ end
550
+ end
551
+
552
+ context '#analytics_clicks' do
553
+ it 'returns click counts for the default time frame' do
554
+ VCR.use_cassette(:analytics_clicks) do
555
+ clicks = client.analytics_clicks(engine_slug)
556
+ expect(clicks.size).to eq(15)
557
+ expect(clicks.first).to eq(['2013-09-17', 0])
558
+ end
559
+ end
560
+
561
+ it 'returns clicks counts for a specified time range' do
562
+ VCR.use_cassette(:analytics_clicks_with_time_range) do
563
+ clicks = client.analytics_clicks(engine_slug, :start_date => '2013-07-01', :end_date => '2013-07-07')
564
+ expect(clicks.size).to eq(7)
565
+ expect(clicks.first).to eq(['2013-07-07', 0])
566
+ end
567
+ end
568
+
569
+ it 'returns click counts for a specified DocumentType' do
570
+ VCR.use_cassette(:analytics_clicks_with_document_type) do
571
+ clicks = client.analytics_clicks(engine_slug, :document_type_id => 'page')
572
+ expect(clicks.size).to eq(15)
573
+ end
574
+ end
575
+
576
+ it 'returns click counts for a specified DocumentType and time range' do
577
+ VCR.use_cassette(:analytics_clicks_with_document_type_and_time_range) do
578
+ clicks = client.analytics_clicks(engine_slug, :document_type_id => 'page', :start_date => '2013-07-01', :end_date => '2013-07-07')
579
+ expect(clicks.size).to eq(7)
580
+ expect(clicks.first).to eq(['2013-07-07', 0])
581
+ end
582
+ end
583
+ end
584
+
585
+ context '#analytics_top_queries' do
586
+ it 'returns top queries' do
587
+ VCR.use_cassette(:analytics_top_queries) do
588
+ top_queries = client.analytics_top_queries(engine_slug)
589
+ expect(top_queries.size).to eq(3)
590
+ expect(top_queries.first).to eq(['"fire watch"', 1])
591
+ end
592
+ end
593
+
594
+ it 'returns top queries with pagination' do
595
+ VCR.use_cassette(:analytics_top_queries_paginated) do
596
+ top_queries = client.analytics_top_queries(engine_slug, :start_date => '2013-08-01', :end_date => '2013-08-30', :per_page => 5, :page => 2)
597
+ expect(top_queries.size).to eq(5)
598
+ expect(top_queries.first).to eq(['no simple victory', 1])
599
+ end
600
+ end
601
+
602
+ it 'raises an error if the timeframe is to large' do
603
+ VCR.use_cassette(:analytics_top_queries_too_large) do
604
+ expect do
605
+ top_queries = client.analytics_top_queries(engine_slug, :start_date => '2013-01-01', :end_date => '2013-05-01')
606
+ end.to raise_error(Elastic::SiteSearch::BadRequest)
607
+ end
608
+ end
609
+ end
610
+
611
+ context 'analytics_top_no_result_queries' do
612
+ it 'returns top queries with no results for the default time range' do
613
+ VCR.use_cassette(:analytics_top_no_result_queries) do
614
+ top_no_result_queries = client.analytics_top_no_result_queries(engine_slug)
615
+ expect(top_no_result_queries.size).to eq(2)
616
+ expect(top_no_result_queries.first).to eq(['no results', 10])
617
+ end
618
+ end
619
+
620
+ it 'has top no result queries in date ranges' do
621
+ VCR.use_cassette(:analytics_top_no_result_queries_paginated) do
622
+ top_no_result_queries = client.analytics_top_no_result_queries(engine_slug, :start_date => '2013-08-01', :end_date => '2013-08-30', :per_page => 5, :page => 2)
623
+ expect(top_no_result_queries.size).to eq(1)
624
+ expect(top_no_result_queries.first).to eq(['no result again', 2])
625
+ end
626
+ end
627
+ end
628
+ end
629
+
630
+ context 'Domain' do
631
+ let(:engine_slug) { 'crawler-demo-site' }
632
+ let(:domain_id) { '51534c6e2ed960cc79000001' }
633
+
634
+ it 'gets all domains' do
635
+ VCR.use_cassette(:list_domains) do
636
+ domains = client.domains(engine_slug)
637
+ expect(domains.size).to eq(1)
638
+ expect(domains.first['id']).to eq(domain_id)
639
+ end
640
+ end
641
+
642
+ context '#domain' do
643
+ it 'shows a domain if it exists' do
644
+ VCR.use_cassette(:find_domain) do
645
+ domain = client.domain(engine_slug, domain_id)
646
+ expect(domain['id']).to eq(domain_id)
647
+ end
648
+ end
649
+
650
+ it 'raises an error if the domain does not exist' do
651
+ VCR.use_cassette(:find_domain_failure) do
652
+ expect do
653
+ domain = client.domain(engine_slug, 'bogus')
654
+ end.to raise_error(Elastic::SiteSearch::NonExistentRecord)
655
+ end
656
+ end
657
+ end
658
+
659
+ context '#create_domain' do
660
+ it 'creates a domain' do
661
+ VCR.use_cassette(:create_domain) do
662
+ url = 'http://www.zombo.com/'
663
+ domain = client.create_domain(engine_slug, url)
664
+ expect(domain['submitted_url']).to eq(url)
665
+ end
666
+ end
667
+ end
668
+
669
+ it 'destroys a domain' do
670
+ VCR.use_cassette(:destroy_domain) do
671
+ response = client.destroy_domain(engine_slug, '52324b132ed960589800004a')
672
+ expect(response).to be_nil
673
+ end
674
+ end
675
+
676
+ context '#recrawl_domain' do
677
+ it 'enqueues a request to recrawl a domain' do
678
+ VCR.use_cassette(:recrawl_domain_success) do
679
+ domain = client.recrawl_domain(engine_slug, domain_id)
680
+ expect(domain['id']).to eq(domain_id)
681
+ end
682
+ end
683
+
684
+ it 'raises an exception if domain recrawl is not allowed' do
685
+ VCR.use_cassette(:recrawl_domain_failure) do
686
+ expect do
687
+ domain = client.recrawl_domain(engine_slug, domain_id)
688
+ end.to raise_error(Elastic::SiteSearch::Forbidden)
689
+ end
690
+ end
691
+ end
692
+
693
+ context '#crawl_url' do
694
+ it 'enqueues a request to crawl a URL on a domain' do
695
+ VCR.use_cassette(:crawl_url) do
696
+ url = 'http://crawler-demo-site.herokuapp.com/2012/01/01/first-post.html'
697
+ crawled_url = client.crawl_url(engine_slug, domain_id, url)
698
+ expect(crawled_url['url']).to eq(url)
699
+ end
700
+ end
701
+ end
702
+ end
703
+
704
+ context 'Clickthrough' do
705
+ let(:query) { 'foo' }
706
+ let(:document_type_slug) { 'videos' }
707
+ let(:external_id) { 'FtX8nswnUKU'}
708
+
709
+ context "#log_clickthough" do
710
+ # Not thrilled with this test, but since nothing is returned all we
711
+ # can reasonably check is that an error isn't raised
712
+ it 'returns nil' do
713
+ VCR.use_cassette(:log_clickthrough_success) do
714
+ response = client.log_clickthrough(engine_slug, document_type_slug, query, external_id)
715
+ expect(response).to eq(nil)
716
+ end
717
+ end
718
+
719
+ it 'raises an error when missing params' do
720
+ VCR.use_cassette(:log_clickthrough_failure) do
721
+ expect do
722
+ client.log_clickthrough(engine_slug, document_type_slug, nil, external_id)
723
+ end.to raise_error(Elastic::SiteSearch::BadRequest)
724
+ end
725
+ end
726
+ end
727
+ end
728
+ end