cloudsearchable 0.0.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.
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +11 -0
- data/cloudsearchable.gemspec +50 -0
- data/lib/cloudsearchable.rb +206 -0
- data/lib/cloudsearchable/cloud_search.rb +41 -0
- data/lib/cloudsearchable/domain.rb +159 -0
- data/lib/cloudsearchable/field.rb +56 -0
- data/lib/cloudsearchable/query_chain.rb +218 -0
- data/lib/cloudsearchable/version.rb +3 -0
- data/spec/cloudsearchable/cloud_search_spec.rb +45 -0
- data/spec/cloudsearchable/cloudsearchable_spec.rb +71 -0
- data/spec/cloudsearchable/domain_spec.rb +158 -0
- data/spec/cloudsearchable/field_spec.rb +30 -0
- data/spec/cloudsearchable/query_chain_spec.rb +305 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_classes/cloud_searchable_test_class.rb +42 -0
- metadata +153 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable do
|
5
|
+
let(:clazz){ CloudSearchableSampleClassFactory.call }
|
6
|
+
|
7
|
+
it 'can describe an index that returns ids for the class type' do
|
8
|
+
test_index = clazz.cloudsearch_index(:test_index)
|
9
|
+
test_index.should be_kind_of(Cloudsearchable::Domain)
|
10
|
+
test_index.fields.should have(4).items #3 explicit + 1 for the id of the object
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a default index' do
|
14
|
+
clazz.cloudsearch_index.should be_kind_of(Cloudsearchable::Domain)
|
15
|
+
clazz.cloudsearch_index(:test_index).should_not be(clazz.cloudsearch_index)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'names domains consistent with CloudSearch limitations' do
|
19
|
+
clazz.cloudsearch_index(:test_index).name.should =~ /^[a-z][a-z0-9\-]+$/
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'an ordinary object' do
|
23
|
+
#An instance of the searchable class
|
24
|
+
let(:inst) do
|
25
|
+
inst = clazz.new
|
26
|
+
#arbitrary but plausible values for the core fields
|
27
|
+
inst.destroyed = false
|
28
|
+
inst.id = 42
|
29
|
+
inst.lock_version = 18
|
30
|
+
inst.customer = OpenStruct.new :id => 123
|
31
|
+
inst.name = "My Name"
|
32
|
+
inst
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'supplies the right values to the fields' do
|
36
|
+
test_index = clazz.cloudsearch_index(:test_index)
|
37
|
+
test_index.fields[:test_class_id].value_for(inst).should be(inst.id)
|
38
|
+
test_index.fields[:customer_id].value_for(inst).should be(inst.customer)
|
39
|
+
test_index.fields[:test_name].value_for(inst).should be(inst.name)
|
40
|
+
test_index.fields[:helpfulness].value_for(inst).should be(1234)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'reindexes when told to' do
|
44
|
+
clazz.cloudsearch_index( ).should_receive(:post_record).with(inst, inst.id, inst.lock_version)
|
45
|
+
clazz.cloudsearch_index(:test_index).should_receive(:post_record).with(inst, inst.id, inst.lock_version)
|
46
|
+
inst.update_indexes
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'generates a sensible addition sdf document' do
|
50
|
+
sdf = clazz.cloudsearch_index(:test_index).send :addition_sdf, inst, inst.id, inst.lock_version
|
51
|
+
sdf[:fields][:helpfulness].should be(1234)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'a destroyed object' do
|
56
|
+
#An instance of the searchable class
|
57
|
+
let(:inst) do
|
58
|
+
inst = clazz.new
|
59
|
+
#arbitrary but plausible values for the core fields
|
60
|
+
inst.destroyed = true
|
61
|
+
inst
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'reindexes when told to' do
|
65
|
+
clazz.cloudsearch_index( ).should_receive(:delete_record).with(inst.id, inst.lock_version)
|
66
|
+
clazz.cloudsearch_index(:test_index).should_receive(:delete_record).with(inst.id, inst.lock_version)
|
67
|
+
inst.update_indexes
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Domain do
|
5
|
+
before(:each) do
|
6
|
+
fake_client = double('client')
|
7
|
+
CloudSearch.stub(:client).and_return(fake_client)
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# First call to describe_domains returns it needs reindexing,
|
12
|
+
# Second call returns that it is processing,
|
13
|
+
# Third call returns that it is done processing
|
14
|
+
#
|
15
|
+
let(:needs_rebuild_domain) do
|
16
|
+
described_class.new('nrb-index').tap do |dom|
|
17
|
+
resp = describe_domain_response(dom.name)
|
18
|
+
CloudSearch.client.stub(:describe_domains).with(:domain_names => [dom.name]).
|
19
|
+
and_return(
|
20
|
+
describe_domain_response(dom.name, :required_index_documents => true),
|
21
|
+
describe_domain_response(dom.name, :processing => true),
|
22
|
+
describe_domain_response(dom.name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# A domain name named 'my-index'
|
28
|
+
let(:domain) do
|
29
|
+
described_class.new('my-index').tap do |dom|
|
30
|
+
CloudSearch.client.stub(:describe_domains).and_return(describe_domain_response(dom.name))
|
31
|
+
CloudSearch.client.stub(:describe_domains).with(:domain_names => [dom.name]).and_return(describe_domain_response(dom.name))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:empty_domain) do
|
36
|
+
described_class.new('my-index').tap do |dom|
|
37
|
+
CloudSearch.client.stub(:describe_domains).and_return({})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can be instantiated' do
|
42
|
+
index = domain
|
43
|
+
index.name.should end_with('my-index')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can haz a literal field' do
|
47
|
+
index = domain
|
48
|
+
index.add_field(:literary, :literal) { nil }
|
49
|
+
index.fields[:literary].type.should eq(:literal)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can be initialized with a nested class' do
|
53
|
+
class OuterClassForCloudSearch
|
54
|
+
class InnerClass
|
55
|
+
include Cloudsearchable
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
OuterClassForCloudSearch::InnerClass.cloudsearch_prefix.should match(/^[A-Za-z0-9_-]+$/)
|
60
|
+
Object.instance_eval { remove_const :OuterClassForCloudSearch }
|
61
|
+
end
|
62
|
+
|
63
|
+
context "SDF documents" do
|
64
|
+
let(:object) { OpenStruct.new(:field_with_nil_value => nil, :field_with_present_value => 42) }
|
65
|
+
subject do
|
66
|
+
described_class.new('my-index').tap do |d|
|
67
|
+
d.add_field(:field_with_nil_value, :literal)
|
68
|
+
d.add_field(:field_with_present_value, :literal)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should generate present fields" do
|
73
|
+
subject.addition_sdf(object, "id", 1)[:fields][:field_with_present_value].should == 42
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not generate nil fields" do
|
77
|
+
subject.addition_sdf(object, "id", 1)[:fields][:field_with_nil_value].should be_nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'raises if the domain cannot be found' do
|
82
|
+
expect { empty_domain.send(:search_endpoint) }.to raise_error( Cloudsearchable::Domain::DomainNotFound,
|
83
|
+
/Cloudsearchable could not find the domain/)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# A test for the accidental clashing of domain caching
|
88
|
+
# on a class variable
|
89
|
+
#
|
90
|
+
it 'caches endpoints for multiple domains' do
|
91
|
+
domain.send(:search_endpoint).should_not eq(needs_rebuild_domain.send(:search_endpoint))
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'endpoint selected is based on the domain name' do
|
95
|
+
domain.send(:search_endpoint).should eq describe_domain_response(domain.name)[:domain_status_list][0][:search_service][:endpoint]
|
96
|
+
domain.send(:doc_endpoint).should eq describe_domain_response(domain.name)[:domain_status_list][0][:doc_service][:endpoint]
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'sleeps, waiting for reindexing' do
|
100
|
+
CloudSearch.client.should_receive(:index_documents).with(:domain_name => needs_rebuild_domain.name)
|
101
|
+
CloudSearch.client.should_receive(:describe_domains).exactly(3).times
|
102
|
+
needs_rebuild_domain.apply_changes(3).should == true
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
#
|
108
|
+
# A mockup of the response to a describe_domain request
|
109
|
+
#
|
110
|
+
def describe_domain_response(domain_name, options = {})
|
111
|
+
{
|
112
|
+
:domain_status_list=> [
|
113
|
+
{
|
114
|
+
:search_partition_count=>1,
|
115
|
+
:search_service=>{
|
116
|
+
:arn=>"arn:aws:cs:us-east-1:510523556749:search/#{domain_name}",
|
117
|
+
:endpoint=>"search-#{domain_name}-7bq6utq4fdrwax5r6irje7xlra.us-east-1.cloudsearch.amazonaws.com"
|
118
|
+
},
|
119
|
+
:num_searchable_docs=>23,
|
120
|
+
:search_instance_type=>"search.m1.small",
|
121
|
+
:created=>true,
|
122
|
+
:domain_id=>"510523556749/#{domain_name}",
|
123
|
+
:processing=> options.fetch(:processing, false),
|
124
|
+
:search_instance_count=>1,
|
125
|
+
:domain_name=>"#{domain_name}",
|
126
|
+
:requires_index_documents=> options.fetch(:required_index_documents,false),
|
127
|
+
:deleted=>false,
|
128
|
+
:doc_service=>{
|
129
|
+
:arn=>"arn:aws:cs:us-east-1:510523556749:doc/#{domain_name}",
|
130
|
+
:endpoint=>"doc-#{domain_name}-7bq6utq4fdrwax5r6irje7xlra.us-east-1.cloudsearch.amazonaws.com"
|
131
|
+
}
|
132
|
+
},
|
133
|
+
{:search_partition_count=>1,
|
134
|
+
:search_service=>
|
135
|
+
{:arn=>
|
136
|
+
"arn:aws:cs:us-east-1:510523556749:search/dev-llarue-collection-items",
|
137
|
+
:endpoint=>
|
138
|
+
"search-dev-llarue-collection-items-hjopg2yzhcjdd4qxeglr2v5v7m.us-east-1.cloudsearch.amazonaws.com"},
|
139
|
+
:num_searchable_docs=>2,
|
140
|
+
:search_instance_type=>"search.m1.small",
|
141
|
+
:created=>true,
|
142
|
+
:domain_id=>"510523556749/dev-llarue-collection-items",
|
143
|
+
:processing=>false,
|
144
|
+
:search_instance_count=>1,
|
145
|
+
:domain_name=>"dev-llarue-collection-items",
|
146
|
+
:requires_index_documents=>false,
|
147
|
+
:deleted=>false,
|
148
|
+
:doc_service=>
|
149
|
+
{:arn=>"arn:aws:cs:us-east-1:510523556749:doc/dev-llarue-collection-items",
|
150
|
+
:endpoint=>
|
151
|
+
"doc-dev-llarue-collection-items-hjopg2yzhcjdd4qxeglr2v5v7m.us-east-1.cloudsearch.amazonaws.com"}}
|
152
|
+
],
|
153
|
+
:response_metadata=>{
|
154
|
+
:request_id=>"7d9487a7-1c9f-11e2-9f96-0958b8a97a74"
|
155
|
+
}
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Field do
|
5
|
+
it 'has a name' do
|
6
|
+
field = described_class.new 'fnord', :literal
|
7
|
+
field.name.should eq(:fnord)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can find its value' do
|
11
|
+
test_value = nil
|
12
|
+
field = described_class.new('foo', :literal, :source => Proc.new { test_value })
|
13
|
+
test_value = 123
|
14
|
+
field.value_for(Object.new).should eq(test_value)
|
15
|
+
|
16
|
+
record = OpenStruct.new :a => test_value
|
17
|
+
field2 = described_class.new('bar', :literal, :source => :a)
|
18
|
+
field.value_for(record).should eq(test_value)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'generates a field definition' do
|
22
|
+
domain_name = 'narnia'
|
23
|
+
field = described_class.new('fnord', :literal, :search_enabled => true)
|
24
|
+
CloudSearch.client.should_receive(:define_index_field) do |call|
|
25
|
+
call[:domain_name].should eq(domain_name)
|
26
|
+
call[:index_field][:literal_options][:search_enabled].should be_true
|
27
|
+
end
|
28
|
+
field.define_in_domain domain_name
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Query do
|
5
|
+
let(:clazz){ CloudSearchableSampleClassFactory.call }
|
6
|
+
|
7
|
+
it 'can build a simple search query' do
|
8
|
+
clazz.search.where(:customer_id, :eq, 12345).query.to_q[:bq].should =~ /customer_id:'12345'/
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'chains' do
|
12
|
+
query = clazz.search.where(customer_id: 12345).where(helpfulness: 42).query.to_q[:bq]
|
13
|
+
query.should =~ /customer_id:'12345'/
|
14
|
+
query.should =~ /helpfulness:'42'/
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can build a query with "not equal to" condition' do
|
18
|
+
query = clazz.search.where(:customer_id, :!=, 1234).query.to_q[:bq].should =~ /\(not customer_id:'1234'\)/
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can build a query from a hash' do
|
22
|
+
query = clazz.search.where(customer_id: 12345, helpfulness: 42).query.to_q[:bq]
|
23
|
+
query.should =~ /customer_id:'12345'/
|
24
|
+
query.should =~ /helpfulness:'42'/
|
25
|
+
end
|
26
|
+
|
27
|
+
it "doesn't build queries without a query term" do
|
28
|
+
expect do
|
29
|
+
query = clazz.search.limit(10).query.to_q
|
30
|
+
end.to raise_exception
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#where' do
|
34
|
+
describe 'uint data type' do
|
35
|
+
describe 'within_range' do
|
36
|
+
it 'supports range query with uint fields' do
|
37
|
+
clazz.search.where(:helpfulness, :within_range, "0..#{123}").query.to_q[:bq].should =~ /helpfulness:0..123/
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'supports range query with uint fields using a ruby range' do
|
41
|
+
clazz.search.where(:helpfulness, :within_range, 0..123).query.to_q[:bq].should =~ /helpfulness:0..123/
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'inequalities' do
|
46
|
+
it 'supports greater-than with uint fields' do
|
47
|
+
clazz.search.where(:helpfulness, :>, 123).query.to_q[:bq].should =~ /helpfulness:124../
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'supports greater-than-or-equal-to with uint fields' do
|
51
|
+
clazz.search.where(:helpfulness, :>=, 123).query.to_q[:bq].should =~ /helpfulness:123../
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'supports less-than with uint fields' do
|
55
|
+
clazz.search.where(:helpfulness, :<, 123).query.to_q[:bq].should =~ /helpfulness:..122/
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'supports less-than-or-equal-to with uint fields' do
|
59
|
+
clazz.search.where(:helpfulness, :<=, 123).query.to_q[:bq].should =~ /helpfulness:..123/
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not permit inequality operators with non-integer fields' do
|
63
|
+
[:>, :>=, :<, :<=].each do |op|
|
64
|
+
['123', nil, 12.0].each do |value|
|
65
|
+
expect { clazz.search.where(:helpfulness, op, value).query.to_q[:bq] }.to raise_error
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
it 'supports querying for any of several values of a field' do
|
75
|
+
clazz.search.where(:category, :any, %w{big small}).query.to_q[:bq].should include("(or category:'big' category:'small')")
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'supports text method' do
|
79
|
+
query = clazz.search.text('test').query.to_q[:q]
|
80
|
+
query.should =~ /test/
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'supports chaining text and where clauses together' do
|
84
|
+
query = clazz.search.text('test').where(:featured, :==, 'f').query
|
85
|
+
query.to_q[:q].should =~ /test/
|
86
|
+
query.to_q[:bq].should =~ /featured:'f'/
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'supports ordering with a rank expression' do
|
90
|
+
clazz.search.where(customer_id: 12345).order('-helpfulness').query.to_q[:rank].should eq '-helpfulness'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'supports limit' do
|
94
|
+
clazz.search.where(customer_id: 12345).limit(10).query.to_q[:size].should eq 10
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has high default limit' do
|
98
|
+
clazz.search.where(customer_id: 12345).query.to_q[:size].should eq 100000
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'supports offset' do
|
102
|
+
clazz.search.where(customer_id: 12345).offset(100).query.to_q[:start].should eq 100
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'queries' do
|
106
|
+
before(:each) do
|
107
|
+
clazz.cloudsearch_index.should_receive(:execute_query).and_return(cloudsearch_response)
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'query warning' do
|
111
|
+
before(:each) do
|
112
|
+
clazz.stub(:find).and_return([])
|
113
|
+
Cloudsearchable.logger.should_receive(:warn).with(/CS-InvalidFieldOrRankAliasInRankParameter/)
|
114
|
+
end
|
115
|
+
|
116
|
+
let(:query){clazz.search.where(customer_id: 12345).order("-adult")}
|
117
|
+
let(:cloudsearch_response) do
|
118
|
+
# generated by ranking a literal field that is search-enabled but not result-enabled
|
119
|
+
{
|
120
|
+
"rank" => "adult",
|
121
|
+
"match-expr" => "(label initialized_at:1363464074..1366056074)",
|
122
|
+
"hits" => {
|
123
|
+
"found" => 285,
|
124
|
+
"start" => 0,
|
125
|
+
"hit" => [
|
126
|
+
{"id" => "40bdd5072b6dbae6245fe4ee837d22e3","data" => {"collection_item_id" => ["PCxSz65GIcZTtc0UpRdT-i--w-1365550370"]}},
|
127
|
+
{"id" => "00af8f5f96aa1db7aff77be5651b3bb1","data" => {"collection_item_id" => ["PCxhksJTLRYnoGXvwZik82Fkw-1365020313"]}},
|
128
|
+
{"id" => "00b6ac84e3ae402e7698959bf692a53e","data" => {"collection_item_id" => ["PCxs-fIVZnBcTzZ4MtfDguS1A-1365020274"]}},
|
129
|
+
{"id" => "018fdee653bff74abd12ac30152a2837","data" => {"collection_item_id" => ["PCxmAGHFtAgyqUrgI3HgM_P6Q-1365548349"]}},
|
130
|
+
{"id" => "01d062d24c389906eea2d16b8193eb56","data" => {"collection_item_id" => ["PCxqjaTmwydKM82NqymbryNfg-1365470479"]}},
|
131
|
+
{"id" => "01e3ee5d848a30385a4e90eb851b094d","data" => {"collection_item_id" => ["PCxSz65GIcZTtc0UpRdT-i--w-1365550369"]}},
|
132
|
+
{"id" => "01fca44cc596adb295ca6ee9f9f36499","data" => {"collection_item_id" => ["PCx7XKbKwOVf1VvEWvTl5c1Eg-1365020176"]}},
|
133
|
+
{"id" => "02b85c9835b5045065ee389954a60c5f","data" => {"collection_item_id" => ["PCxp_xid_WeTfTmb5MySEfxhQ-1365115565"]}},
|
134
|
+
{"id" => "040c01be434552a1d9e99eef9db87bdd","data" => {"collection_item_id" => ["PCxLOYzA4bCt7-bP6wsZnl-ow-1365020297"]}},
|
135
|
+
{"id" => "048567c755e30d6d64d757508f1feaa0","data" => {"collection_item_id" => ["PCxJhhnpYkeSKrOxteQo5Jckw-1365115667"]}}
|
136
|
+
]
|
137
|
+
},
|
138
|
+
"info" => {
|
139
|
+
"rid" => "7df344e77e1076a903e1f2dc1effcf3dde0a89442fb459d00a6e60ac64b8bbfcab1fbc5b35c10949",
|
140
|
+
"time-ms" => 3,
|
141
|
+
"cpu-time-ms" => 0,
|
142
|
+
"messages" => [
|
143
|
+
{
|
144
|
+
"severity" => "warning",
|
145
|
+
"code" => "CS-InvalidFieldOrRankAliasInRankParameter",
|
146
|
+
"host" => "7df344e77e1076a9884a6c43665da57c",
|
147
|
+
"message" => "Unable to create score object for rank 'adult'"
|
148
|
+
}
|
149
|
+
]
|
150
|
+
}
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'causes WarningInQueryResult exception' do
|
155
|
+
lambda{ query.to_a }.should raise_error(Cloudsearchable::WarningInQueryResult)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'takes a :fatal_warnings option, and when set to false, does not raise' do
|
159
|
+
sample_query = Cloudsearchable::QueryChain.new(double, fatal_warnings: false)
|
160
|
+
sample_query.instance_variable_get(:@fatal_warnings).should eq false
|
161
|
+
|
162
|
+
q = query
|
163
|
+
q.query.instance_variable_set(:@fatal_warnings, false)
|
164
|
+
lambda{ q.to_a }.should_not raise_error(Cloudsearchable::WarningInQueryResult)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'valid query results' do
|
169
|
+
let(:customer_id){ '12345' }
|
170
|
+
let(:other_customer_id){ 'foo' }
|
171
|
+
|
172
|
+
let(:cloudsearch_response) do
|
173
|
+
{
|
174
|
+
"rank"=>"-text_relevance",
|
175
|
+
"match-expr"=>"(label customer_id:'12345')",
|
176
|
+
"hits"=>{
|
177
|
+
"found"=>11,
|
178
|
+
"start"=>0,
|
179
|
+
"info"=>{
|
180
|
+
"rid"=>"e2467862eecf73ec8dfcfe0cba1893abbe2e8803402f4da65b1195593c0f78ec3e8f1d29f6e40723",
|
181
|
+
"time-ms"=>2,
|
182
|
+
"cpu-time-ms"=>0
|
183
|
+
},
|
184
|
+
"hit"=>[
|
185
|
+
{"id"=>"0633e1c9793f5288c58b664356533e81", "data"=>{"test_class_id"=>["ANINSTANCEID"]}},
|
186
|
+
# {"id"=>"04931ebede796ae8b435f1fd5291e772", "data"=>{"test_class_id"=>["PCxTj26ZRmV_EnHigQWx0S06w"]}},
|
187
|
+
# {"id"=>"72159a172d3043bfcdadb5244862b9ee", "data"=>{"test_class_id"=>["PCxS_apFtZMrKuqyPhFNstzMQ"]}},
|
188
|
+
# {"id"=>"1eb815b075bc005e97dc5827e53b9615", "data"=>{"test_class_id"=>["PCxSksjDUBehPWhYYW2Dtj4KQ"]}},
|
189
|
+
# {"id"=>"3e4950b829456b13bf1460b25a7aca26", "data"=>{"test_class_id"=>["PCx1oiyh6vrHGSeLvis4USMfQ"]}},
|
190
|
+
# {"id"=>"00b441f55fff86d2d746227988da77a9", "data"=>{"test_class_id"=>["PCxpt-aW8topsnTGs-AIkzWCA"]}},
|
191
|
+
# {"id"=>"919ea27d21bbdc07ead4688a0d7ceca1", "data"=>{"test_class_id"=>["PCxFHGLbGJ2mzau_a6-gh5ORw"]}},
|
192
|
+
# {"id"=>"c663c2d9af342b0038fc808322143cfd", "data"=>{"test_class_id"=>["PCxyFwShwjWBp_WiXB0rFb2WA"]}},
|
193
|
+
# {"id"=>"de8f00af5636393e2553c4b4710d3393", "data"=>{"test_class_id"=>["PCxnqXfm8McflBgi4HsYoUXVw"]}},
|
194
|
+
# {"id"=>"e297cf21741a4c43697ea2586164a987", "data"=>{"test_class_id"=>["PCxrdk8gAEVbkuUCazu2-qLjQ"]}}
|
195
|
+
]
|
196
|
+
}
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'materializes' do
|
201
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return([customer_id])
|
202
|
+
query = clazz.search.where(customer_id: 12345)
|
203
|
+
query.to_a.should == [customer_id]
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'materializes db results only once' do
|
207
|
+
expected_results = [customer_id, other_customer_id]
|
208
|
+
clazz.should_receive(:find).once.and_return(expected_results)
|
209
|
+
|
210
|
+
query = clazz.search.where(customer_id: 12345)
|
211
|
+
query.materialize!
|
212
|
+
query.materialize!
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should not materialize if only asking for found_count' do
|
216
|
+
clazz.should_not_receive(:find)
|
217
|
+
clazz.search.where(customer_id: 12345).found_count
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'supports each for multiple results' do
|
221
|
+
expected_results = [customer_id, other_customer_id]
|
222
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(expected_results)
|
223
|
+
|
224
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
225
|
+
(0..results.length).each{ |i| results[i].should eq expected_results[i] }
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'supports each for single results' do
|
229
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(customer_id)
|
230
|
+
|
231
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
232
|
+
results.each{ |r| r.should eq customer_id }
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'supports each for nil result' do
|
236
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(nil)
|
237
|
+
|
238
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
239
|
+
results.each{ |r| r.should_not be }
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'uses materialized method' do
|
243
|
+
clazz.should_receive(:another_find).with(["ANINSTANCEID"]).and_return(customer_id)
|
244
|
+
clazz.materialize_method :another_find
|
245
|
+
clazz.search.where(customer_id: 12345).to_a
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'returns the correct found count' do
|
249
|
+
clazz.search.where(customer_id: 12345).found_count.should == 11
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'invalid query results' do
|
254
|
+
let(:cloudsearch_response) do
|
255
|
+
{
|
256
|
+
"error"=>"info",
|
257
|
+
"rid"=>"6ddcaa561c05c4cc85ddb10cb46568af2ef64b0583910e32210f551c238586e40fc3abe629ca87b250796d395a628af6",
|
258
|
+
"time-ms"=>20,
|
259
|
+
"cpu-time-ms"=>0,
|
260
|
+
"messages"=>[
|
261
|
+
{
|
262
|
+
"severity"=>"fatal",
|
263
|
+
"code"=>"CS-UnknownFieldInMatchExpression",
|
264
|
+
"message"=>"Field 'asdf' is not defined in the metadata for this collection."
|
265
|
+
}
|
266
|
+
]
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'raises an exception when requesting found count with an error response' do
|
271
|
+
expect { clazz.search.where(customer_id: 12345).found_count }.to raise_error
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'empty results with non-empty data' do
|
276
|
+
let(:cloudsearch_response) do
|
277
|
+
{
|
278
|
+
# Empty-yet-present data may occur with a NOT query, such as "(not customer_id:'XYZ')".
|
279
|
+
# Refer to: https://aws.amazon.com/support/case?caseId=107084141&language=en
|
280
|
+
"rank" => "-text_relevance",
|
281
|
+
"match-expr" => "(not customer_id:'A3E4T85Q6WPY4F')",
|
282
|
+
"hits" => {
|
283
|
+
"found" => 2087,
|
284
|
+
"start" => 0,
|
285
|
+
"hit" => [
|
286
|
+
{"id" => "fb9fb53e32c4b3714cf39be4b855d34b", "data" => { "test_class_id" => []}},
|
287
|
+
]
|
288
|
+
},
|
289
|
+
"info" => {
|
290
|
+
"rid" => "621cf310b88f32076b1908e45b4930aafb872497bdbf3b5e64065619c0dcec96bbe513281093d6c7",
|
291
|
+
"time-ms" => 3,
|
292
|
+
"cpu-time-ms" => 0
|
293
|
+
}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not raise an exception' do
|
298
|
+
clazz.should_receive(:find).with([]).and_return(nil)
|
299
|
+
clazz.search.where(:customer_id, :!=, 'ABCDE')
|
300
|
+
expect { clazz.search.where(:customer_id, :!=, 'ABCDE').to_a }.to_not raise_error
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|