syphon 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/.gitignore +2 -0
- data/CHANGELOG +3 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.markdown +5 -0
- data/Rakefile +1 -0
- data/lib/syphon/builder.rb +57 -0
- data/lib/syphon/index.rb +108 -0
- data/lib/syphon/railtie.rb +28 -0
- data/lib/syphon/schema.rb +167 -0
- data/lib/syphon/source.rb +76 -0
- data/lib/syphon/tasks.rb +26 -0
- data/lib/syphon/version.rb +11 -0
- data/lib/syphon.rb +41 -0
- data/syphon.gemspec +22 -0
- data/test/config.yml.sample +8 -0
- data/test/syphon/test_builder.rb +73 -0
- data/test/syphon/test_index.rb +144 -0
- data/test/syphon/test_schema.rb +271 -0
- data/test/syphon/test_source.rb +141 -0
- data/test/test_helper.rb +42 -0
- data/test/test_syphon.rb +28 -0
- metadata +144 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe Syphon::Index do
|
4
|
+
before do
|
5
|
+
Object.const_set(:TestIndex, Class.new)
|
6
|
+
TestIndex.send :include, Syphon::Index
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
Object.send(:remove_const, :TestIndex)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".index_name" do
|
14
|
+
describe "when no index namespace is set" do
|
15
|
+
use_attribute_value Syphon, :index_namespace, nil
|
16
|
+
|
17
|
+
it "it derived from the class name" do
|
18
|
+
TestIndex.index_name.must_equal 'tests'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "when the index namespace is empty" do
|
23
|
+
use_attribute_value Syphon, :index_namespace, ''
|
24
|
+
|
25
|
+
it "it is treated the same as nil" do
|
26
|
+
TestIndex.index_name.must_equal 'tests'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when an index namespace is set" do
|
31
|
+
it "prefixes with the namespace and an underscore" do
|
32
|
+
TestIndex.index_name.must_equal 'syphon_tests'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be overridden by the class" do
|
37
|
+
TestIndex.class_eval { self.index_name = 'wibble' }
|
38
|
+
TestIndex.index_name.must_equal 'wibble'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".define_source" do
|
43
|
+
it "defaults the name and type" do
|
44
|
+
TestIndex.class_eval do
|
45
|
+
define_source
|
46
|
+
end
|
47
|
+
|
48
|
+
source = TestIndex.source
|
49
|
+
source.name.must_be_nil
|
50
|
+
source.type.must_equal :test
|
51
|
+
end
|
52
|
+
|
53
|
+
it "defines a source with the given name and fields" do
|
54
|
+
TestIndex.class_eval do
|
55
|
+
define_source :custom_name, type: :thing do
|
56
|
+
string :value, 'x'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
source = TestIndex.source(:custom_name)
|
61
|
+
source.name.must_equal :custom_name
|
62
|
+
source.type.must_equal :thing
|
63
|
+
source.schema.fields.keys.must_equal [:value]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ".build" do
|
68
|
+
uses_users_table
|
69
|
+
uses_elasticsearch
|
70
|
+
|
71
|
+
before do
|
72
|
+
clear_indices
|
73
|
+
|
74
|
+
TestIndex.class_eval do
|
75
|
+
define_source do
|
76
|
+
string :login, "users.login"
|
77
|
+
from 'users'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "builds the index (as an alias of an underlying index) if it does not yet exist" do
|
83
|
+
db.query "INSERT INTO users(login) VALUES('alice')"
|
84
|
+
TestIndex.build
|
85
|
+
|
86
|
+
hits = TestIndex.search['hits']['hits']
|
87
|
+
hits.map { |doc| doc['_source']['login'] }.must_equal ['alice']
|
88
|
+
|
89
|
+
db.query "DELETE FROM users"
|
90
|
+
db.query "INSERT INTO users(login) VALUES('bob')"
|
91
|
+
|
92
|
+
TestIndex.build
|
93
|
+
|
94
|
+
hits = TestIndex.search['hits']['hits']
|
95
|
+
hits.map { |doc| doc['_source']['login'] }.must_equal ['bob']
|
96
|
+
end
|
97
|
+
|
98
|
+
it "runs all warmups between building the new index and rotating it in" do
|
99
|
+
this = self
|
100
|
+
runs = []
|
101
|
+
TestIndex.class_eval do
|
102
|
+
define_warmup do |new_index|
|
103
|
+
client.indices.exists(index: new_index)
|
104
|
+
-> { client.indices.get_alias(name: TestIndex.index_name) }.
|
105
|
+
must_raise(Elasticsearch::Transport::Transport::Errors::NotFound)
|
106
|
+
runs << 1
|
107
|
+
end
|
108
|
+
|
109
|
+
define_warmup do |new_index|
|
110
|
+
runs << 2
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
TestIndex.build
|
115
|
+
runs.must_equal [1, 2]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe ".destroy" do
|
120
|
+
uses_users_table
|
121
|
+
uses_elasticsearch
|
122
|
+
|
123
|
+
before do
|
124
|
+
TestIndex.class_eval do
|
125
|
+
define_source do
|
126
|
+
string :login, "users.login"
|
127
|
+
from 'users'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
TestIndex.build
|
131
|
+
end
|
132
|
+
|
133
|
+
it "deletes the index and any aliases to it" do
|
134
|
+
client.indices.exists(index: TestIndex.index_name).must_equal true
|
135
|
+
client.indices.get_alias(name: TestIndex.index_name).size.must_equal 1
|
136
|
+
|
137
|
+
TestIndex.destroy
|
138
|
+
|
139
|
+
-> { client.indices.get_alias(name: TestIndex.index_name) }.
|
140
|
+
must_raise(Elasticsearch::Transport::Transport::Errors::NotFound)
|
141
|
+
client.indices.exists(index: TestIndex.index_name).must_equal false
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe Syphon::Schema do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "configures the schema with the given block" do
|
6
|
+
schema = Syphon::Schema.new do
|
7
|
+
string :s, 'STRING'
|
8
|
+
end
|
9
|
+
schema.fields.values.map(&:name).must_equal [:s]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#configure" do
|
14
|
+
describe "type methods" do
|
15
|
+
it "builds fields with type methods" do
|
16
|
+
schema = Syphon::Schema.new do
|
17
|
+
string :string_field, 'STRING'
|
18
|
+
short :short_field, 'SHORT'
|
19
|
+
byte :byte_field, 'BYTE'
|
20
|
+
integer :integer_field, 'INTEGER'
|
21
|
+
long :long_field, 'LONG'
|
22
|
+
float :float_field, 'FLOAT'
|
23
|
+
double :double_field, 'DOUBLE'
|
24
|
+
date :date_field, 'DATE'
|
25
|
+
boolean :boolean_field, 'BOOLEAN'
|
26
|
+
binary :binary_field, 'BINARY'
|
27
|
+
geo_point :geo_point_field, 'GEO_POINT'
|
28
|
+
nested :nested_field do
|
29
|
+
string :string_field, 'NESTED.STRING'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
schema.fields.values.map(&:name).must_equal [
|
33
|
+
:string_field, :short_field, :byte_field, :integer_field,
|
34
|
+
:long_field, :float_field, :double_field, :date_field,
|
35
|
+
:boolean_field, :binary_field, :geo_point_field, :nested_field,
|
36
|
+
]
|
37
|
+
schema.fields.values.map(&:type).must_equal [
|
38
|
+
:string, :short, :byte, :integer, :long, :float, :double, :date,
|
39
|
+
:boolean, :binary, :geo_point, :nested,
|
40
|
+
]
|
41
|
+
schema.fields.values.map(&:expression).must_equal [
|
42
|
+
'STRING', 'SHORT', 'BYTE', 'INTEGER', 'LONG', 'FLOAT', 'DOUBLE',
|
43
|
+
'DATE', 'BOOLEAN', 'BINARY', 'GEO_POINT', nil,
|
44
|
+
]
|
45
|
+
nested = schema.fields[:nested_field]
|
46
|
+
nested.nested_schema.fields.values.map(&:name).must_equal [:string_field]
|
47
|
+
nested.nested_schema.fields.values.map(&:type).must_equal [:string]
|
48
|
+
nested.nested_schema.fields.values.map(&:expression).must_equal ['NESTED.STRING']
|
49
|
+
end
|
50
|
+
|
51
|
+
it "supports dynamic expressions by passing a proc for a field expression" do
|
52
|
+
i = nil
|
53
|
+
schema = Syphon::Schema.new do
|
54
|
+
string :string_field, -> { i.to_s }
|
55
|
+
end
|
56
|
+
i = 1
|
57
|
+
schema.fields.values.map { |v| v.expression.call }.must_equal ['1']
|
58
|
+
i = 2
|
59
|
+
schema.fields.values.map { |v| v.expression.call }.must_equal ['2']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#from" do
|
64
|
+
it "sets the relation" do
|
65
|
+
schema = Syphon::Schema.new do
|
66
|
+
from 'things'
|
67
|
+
end
|
68
|
+
schema.relation.must_equal 'things'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "supports a dynamic value by passing a block" do
|
72
|
+
i = nil
|
73
|
+
schema = Syphon::Schema.new do
|
74
|
+
from { i.to_s }
|
75
|
+
end
|
76
|
+
i = 1
|
77
|
+
schema.relation.call.must_equal '1'
|
78
|
+
i = 2
|
79
|
+
schema.relation.call.must_equal '2'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#join" do
|
84
|
+
it "accumulates joins" do
|
85
|
+
schema = Syphon::Schema.new do
|
86
|
+
from 'things'
|
87
|
+
join 'INNER JOIN a ON 1'
|
88
|
+
join 'INNER JOIN b ON 2'
|
89
|
+
end
|
90
|
+
schema.joins.must_equal ['INNER JOIN a ON 1', 'INNER JOIN b ON 2']
|
91
|
+
end
|
92
|
+
|
93
|
+
it "supports a dynamic value by passing a block" do
|
94
|
+
i = j = nil
|
95
|
+
schema = Syphon::Schema.new do
|
96
|
+
from 'things'
|
97
|
+
join { i.to_s }
|
98
|
+
join { j.to_s }
|
99
|
+
end
|
100
|
+
i, j = 1, 2
|
101
|
+
schema.joins.map(&:call).must_equal ['1', '2']
|
102
|
+
i, j = 3, 4
|
103
|
+
schema.joins.map(&:call).must_equal ['3', '4']
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#where" do
|
108
|
+
it "sets the conditions" do
|
109
|
+
schema = Syphon::Schema.new do
|
110
|
+
where 'x = 1'
|
111
|
+
end
|
112
|
+
schema.conditions.must_equal 'x = 1'
|
113
|
+
end
|
114
|
+
|
115
|
+
it "supports a dynamic value by passing a block" do
|
116
|
+
i = nil
|
117
|
+
schema = Syphon::Schema.new do
|
118
|
+
where { i.to_s }
|
119
|
+
end
|
120
|
+
i = 1
|
121
|
+
schema.conditions.call.must_equal '1'
|
122
|
+
i = 2
|
123
|
+
schema.conditions.call.must_equal '2'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#group_by" do
|
128
|
+
it "sets the group clause" do
|
129
|
+
schema = Syphon::Schema.new do
|
130
|
+
group_by 'x, y'
|
131
|
+
end
|
132
|
+
schema.group_clause.must_equal 'x, y'
|
133
|
+
end
|
134
|
+
|
135
|
+
it "supports a dynamic value by passing a block" do
|
136
|
+
i = nil
|
137
|
+
schema = Syphon::Schema.new do
|
138
|
+
group_by { i.to_s }
|
139
|
+
end
|
140
|
+
i = 1
|
141
|
+
schema.group_clause.call.must_equal '1'
|
142
|
+
i = 2
|
143
|
+
schema.group_clause.call.must_equal '2'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#having" do
|
148
|
+
it "sets the having clause" do
|
149
|
+
schema = Syphon::Schema.new do
|
150
|
+
having 'x = 1'
|
151
|
+
end
|
152
|
+
schema.having_clause.must_equal 'x = 1'
|
153
|
+
end
|
154
|
+
|
155
|
+
it "supports a dynamic value by passing a block" do
|
156
|
+
i = nil
|
157
|
+
schema = Syphon::Schema.new do
|
158
|
+
having { i.to_s }
|
159
|
+
end
|
160
|
+
i = 1
|
161
|
+
schema.having_clause.call.must_equal '1'
|
162
|
+
i = 2
|
163
|
+
schema.having_clause.call.must_equal '2'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "#query" do
|
169
|
+
it "returns the complete query" do
|
170
|
+
schema = Syphon::Schema.new do
|
171
|
+
string :s, 'S'
|
172
|
+
nested :inner do
|
173
|
+
string :t, 'T'
|
174
|
+
end
|
175
|
+
from 'things'
|
176
|
+
join 'INNER JOIN a ON 1'
|
177
|
+
join 'INNER JOIN b ON 2'
|
178
|
+
where 'a = 1'
|
179
|
+
group_by 'x'
|
180
|
+
having 'count(*) = 1'
|
181
|
+
end
|
182
|
+
schema.query.must_equal <<-EOS.strip.gsub(/\s+/, ' ')
|
183
|
+
SELECT S AS `s`, T AS `inner[t]`
|
184
|
+
FROM things
|
185
|
+
INNER JOIN a ON 1
|
186
|
+
INNER JOIN b ON 2
|
187
|
+
WHERE a = 1
|
188
|
+
GROUP BY x
|
189
|
+
HAVING count(*) = 1
|
190
|
+
EOS
|
191
|
+
end
|
192
|
+
|
193
|
+
it "omits optional clauses when in the minimal case" do
|
194
|
+
schema = Syphon::Schema.new do
|
195
|
+
string :s, 'S'
|
196
|
+
from 'things'
|
197
|
+
end
|
198
|
+
schema.query.must_equal "SELECT S AS `s` FROM things"
|
199
|
+
end
|
200
|
+
|
201
|
+
it "supports overriding the select expression" do
|
202
|
+
schema = Syphon::Schema.new do
|
203
|
+
string :s, 'S'
|
204
|
+
from 'things'
|
205
|
+
end
|
206
|
+
schema.query(select: '1').must_equal "SELECT 1 FROM things"
|
207
|
+
end
|
208
|
+
|
209
|
+
it "applies the given extra scope, order, and limit" do
|
210
|
+
schema = Syphon::Schema.new do
|
211
|
+
string :s, 'S'
|
212
|
+
from 'things'
|
213
|
+
where 'a = 1'
|
214
|
+
group_by 'x'
|
215
|
+
having 'count(*) = 1'
|
216
|
+
end
|
217
|
+
options = {scope: 'b = 2', order: 'c DESC', limit: 2}
|
218
|
+
schema.query(options).must_equal <<-EOS.strip.gsub(/\s+/, ' ')
|
219
|
+
SELECT S AS `s`
|
220
|
+
FROM things
|
221
|
+
WHERE (a = 1) AND (b = 2)
|
222
|
+
GROUP BY x
|
223
|
+
HAVING count(*) = 1
|
224
|
+
ORDER BY c DESC
|
225
|
+
LIMIT 2
|
226
|
+
EOS
|
227
|
+
end
|
228
|
+
|
229
|
+
it "inverts the condition if :invert is true" do
|
230
|
+
schema = Syphon::Schema.new do
|
231
|
+
string :s, 'S'
|
232
|
+
from 'things'
|
233
|
+
where 'a = 1'
|
234
|
+
end
|
235
|
+
schema.query(invert: true).must_equal "SELECT S AS `s` FROM things WHERE NOT (a = 1)"
|
236
|
+
end
|
237
|
+
|
238
|
+
it "does not invert the scope" do
|
239
|
+
schema = Syphon::Schema.new do
|
240
|
+
string :s, 'S'
|
241
|
+
from 'things'
|
242
|
+
where 'a = 1'
|
243
|
+
end
|
244
|
+
schema.query(invert: true, scope: 'id = 1').must_equal "SELECT S AS `s` FROM things WHERE (NOT (a = 1)) AND (id = 1)"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "#properties" do
|
249
|
+
it "returns the properties hash for the fields" do
|
250
|
+
schema = Syphon::Schema.new do
|
251
|
+
string :name, 'x'
|
252
|
+
integer :value, 'x'
|
253
|
+
nested :inner do
|
254
|
+
string :s, 'x'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
schema.properties.must_equal({
|
259
|
+
name: {type: :string},
|
260
|
+
value: {type: :integer},
|
261
|
+
inner: {
|
262
|
+
type: :nested,
|
263
|
+
properties: {
|
264
|
+
s: {type: :string},
|
265
|
+
},
|
266
|
+
},
|
267
|
+
})
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe Syphon::Source do
|
4
|
+
before do
|
5
|
+
Object.const_set(:TestIndex, Class.new)
|
6
|
+
TestIndex.send :include, Syphon::Index
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
Object.send(:remove_const, :TestIndex)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:source) { TestIndex.source }
|
14
|
+
|
15
|
+
describe "#initialize" do
|
16
|
+
it "sets a default type" do
|
17
|
+
source = Syphon::Source.new(TestIndex, :things_source)
|
18
|
+
source.name.must_equal :things_source
|
19
|
+
source.type.must_equal :test
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets the name and type" do
|
23
|
+
source = Syphon::Source.new(TestIndex, :things_source, type: :custom_type)
|
24
|
+
source.name.must_equal :things_source
|
25
|
+
source.type.must_equal :custom_type
|
26
|
+
end
|
27
|
+
|
28
|
+
it "initializes the schema from the given block" do
|
29
|
+
source = Syphon::Source.new(TestIndex, :things_source) do
|
30
|
+
string :name, 'x'
|
31
|
+
end
|
32
|
+
source.schema.fields.keys.must_equal [:name]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#mapping" do
|
37
|
+
it "returns the ElasticSearch mapping" do
|
38
|
+
source = Syphon::Source.new(TestIndex, :things_source, type: :thing) do
|
39
|
+
string :name, 'x'
|
40
|
+
end
|
41
|
+
|
42
|
+
source.mapping.must_equal({
|
43
|
+
thing: {
|
44
|
+
properties: {
|
45
|
+
name: {
|
46
|
+
type: :string
|
47
|
+
},
|
48
|
+
},
|
49
|
+
},
|
50
|
+
})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#import" do
|
55
|
+
uses_users_table
|
56
|
+
uses_elasticsearch
|
57
|
+
|
58
|
+
before do
|
59
|
+
TestIndex.class_eval do
|
60
|
+
define_source do
|
61
|
+
integer :id, 'id'
|
62
|
+
string :login, 'login', stored: true
|
63
|
+
from 'users'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
TestIndex.build(schema_only: true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "imports the data as configured by the SQL query" do
|
70
|
+
db.query "INSERT INTO users(login) VALUES('alice')"
|
71
|
+
source.import
|
72
|
+
|
73
|
+
hits = client.search(index: TestIndex.index_name)['hits']['hits']
|
74
|
+
hits.map { |doc| doc['_source']['login'] }.must_equal ['alice']
|
75
|
+
end
|
76
|
+
|
77
|
+
it "imports data correctly the second time" do
|
78
|
+
db.query "INSERT INTO users(login) VALUES('alice')"
|
79
|
+
source.import
|
80
|
+
|
81
|
+
db.query "INSERT INTO users(login) VALUES('bob')"
|
82
|
+
source.import
|
83
|
+
|
84
|
+
hits = client.search(index: TestIndex.index_name)['hits']['hits']
|
85
|
+
hits.map { |doc| doc['_source']['login'] }.sort.must_equal ['alice', 'bob']
|
86
|
+
end
|
87
|
+
|
88
|
+
it "uses the given index name" do
|
89
|
+
client.indices.create(index: 'syphon_custom')
|
90
|
+
db.query "INSERT INTO users(login) VALUES('alice')"
|
91
|
+
source.import(index: 'syphon_custom')
|
92
|
+
|
93
|
+
hits = client.search(index: 'syphon_custom')['hits']['hits']
|
94
|
+
hits.map { |doc| doc['_source']['login'] }.must_equal ['alice']
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#update" do
|
99
|
+
uses_users_table
|
100
|
+
uses_elasticsearch
|
101
|
+
|
102
|
+
before do
|
103
|
+
TestIndex.class_eval do
|
104
|
+
define_source do
|
105
|
+
integer :id, 'id'
|
106
|
+
string :login, 'login', stored: true
|
107
|
+
from 'users'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
TestIndex.build(schema_only: true)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "updates the index from the database, scoping with the given conditions" do
|
114
|
+
db.query "INSERT INTO users(login) VALUES('alice'), ('bob')"
|
115
|
+
TestIndex.build
|
116
|
+
|
117
|
+
hits = client.search(index: TestIndex.index_name)['hits']['hits']
|
118
|
+
hits.map { |doc| doc['_source']['login'] }.sort.must_equal ['alice', 'bob']
|
119
|
+
|
120
|
+
db.query "UPDATE users SET login = 'superalice' WHERE login = 'alice'"
|
121
|
+
db.query "UPDATE users SET login = 'wonderbob' WHERE login = 'bob'"
|
122
|
+
bob_id = db.query("SELECT id FROM users WHERE login = 'wonderbob'", as: :array).to_a[0][0]
|
123
|
+
source.update_ids([bob_id])
|
124
|
+
|
125
|
+
hits = client.search(index: TestIndex.index_name)['hits']['hits']
|
126
|
+
hits.map { |doc| doc['_source']['login'] }.sort.must_equal ['alice', 'wonderbob']
|
127
|
+
end
|
128
|
+
|
129
|
+
it "deletes records that should no longer exist" do
|
130
|
+
db.query "INSERT INTO users(login) VALUES('alice'), ('bob')"
|
131
|
+
TestIndex.build
|
132
|
+
|
133
|
+
bob_id = db.query("SELECT id FROM users WHERE login = 'bob'", as: :array).to_a[0][0]
|
134
|
+
db.query "DELETE FROM users WHERE id = #{bob_id}"
|
135
|
+
source.update_ids([bob_id])
|
136
|
+
|
137
|
+
hits = client.search(index: TestIndex.index_name)['hits']['hits']
|
138
|
+
hits.map { |doc| doc['_source']['login'] }.must_equal ['alice']
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
ROOT = File.expand_path('..', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
$:.unshift "#{ROOT}/lib"
|
4
|
+
require 'minitest/spec'
|
5
|
+
require 'yaml'
|
6
|
+
require 'temporaries'
|
7
|
+
require 'debugger' if RUBY_VERSION < '2.0'
|
8
|
+
require 'looksee'
|
9
|
+
|
10
|
+
require 'syphon'
|
11
|
+
|
12
|
+
config = YAML.load_file("#{ROOT}/test/config.yml")
|
13
|
+
Syphon.database_configuration = config['database']
|
14
|
+
Syphon.configuration = config['elasticsearch']
|
15
|
+
Syphon.index_namespace = 'syphon'
|
16
|
+
|
17
|
+
MiniTest::Spec.class_eval do
|
18
|
+
def self.uses_users_table
|
19
|
+
let(:db) { Syphon.database_connection }
|
20
|
+
|
21
|
+
before do
|
22
|
+
columns = "id int auto_increment PRIMARY KEY, login VARCHAR(20)"
|
23
|
+
db.query "CREATE TABLE IF NOT EXISTS users(#{columns})"
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
db.query "DROP TABLE IF EXISTS users"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.uses_elasticsearch
|
32
|
+
let(:client) { Syphon.client }
|
33
|
+
|
34
|
+
before { clear_indices }
|
35
|
+
after { clear_indices }
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear_indices
|
39
|
+
client.indices.status['indices'].keys.grep(/\Asyphon_/).
|
40
|
+
each { |name| client.indices.delete(index: name) }
|
41
|
+
end
|
42
|
+
end
|
data/test/test_syphon.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe Syphon do
|
4
|
+
describe ".configuration" do
|
5
|
+
use_attribute_value Syphon, :configuration, nil
|
6
|
+
|
7
|
+
it "defaults to an empty hash" do
|
8
|
+
Syphon.configuration.must_equal({})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".database_configuration" do
|
13
|
+
use_instance_variable_value Syphon, :database_configuration, nil
|
14
|
+
|
15
|
+
it "defaults to an empty hash" do
|
16
|
+
Syphon.database_configuration.must_equal({})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".index_namespace" do
|
21
|
+
use_instance_variable_value Syphon, :index_namespace, nil
|
22
|
+
use_instance_variable_value Syphon, :configuration, {index_namespace: 'NAMESPACE'}
|
23
|
+
|
24
|
+
it "defaults to the configured index namespace" do
|
25
|
+
Syphon.index_namespace.must_equal('NAMESPACE')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|