1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use SilverStripe\Elastica\ElasticSearcher; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Test the functionality of the Searchable extension |
7
|
|
|
* @package elastica |
8
|
|
|
*/ |
9
|
|
|
class SearchAndIndexingTest extends ElasticsearchBaseTest { |
10
|
|
|
public static $fixture_file = 'elastica/tests/lotsOfPhotos.yml'; |
11
|
|
|
|
12
|
|
|
|
13
|
|
|
/* |
14
|
|
|
Notes: |
15
|
|
|
Searching string on number fields fails |
16
|
|
|
http://elasticsearch-users.115913.n3.nabble.com/Error-when-searching-multiple-fields-with-different-types-td3897459.html |
17
|
|
|
*/ |
18
|
|
|
public function testNonTextFields() { |
19
|
|
|
$numericFields = array( |
20
|
|
|
'FlickrID' => 1, |
21
|
|
|
'TakenAt' => 1, |
22
|
|
|
'FirstViewed' => 1, |
23
|
|
|
'Aperture' => 1, |
24
|
|
|
'ShutterSpeed' => 1, |
25
|
|
|
'FocalLength35mm' => 1, |
26
|
|
|
'ISO' => 1 |
27
|
|
|
); |
28
|
|
|
|
29
|
|
|
$this->search('New Zealand', 0, $numericFields); |
30
|
|
|
|
31
|
|
|
//There are 10 entries in the fixtures, 3 default indexed pages |
32
|
|
|
$this->search(400, 13, $numericFields); |
33
|
|
|
|
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function testInvalidSearchFields() { |
37
|
|
|
// FIXME test to check unweighted fields |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
public function testSetStopwordsConfigurationCSV() { |
41
|
|
|
$stopwords = "a,the,then,this"; |
42
|
|
|
$englishIndex = new EnglishIndexSettings(); |
43
|
|
|
$englishIndex->setStopwords($stopwords); |
44
|
|
|
$expected = array('a', 'the', 'then', 'this'); |
45
|
|
|
$this->assertEquals($expected, $englishIndex->getStopwords()); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
public function testSetStopwordsConfigurationArray() { |
50
|
|
|
$stopwords = array('a', 'the', 'then', 'this'); |
51
|
|
|
$englishIndex = new EnglishIndexSettings(); |
52
|
|
|
$englishIndex->setStopwords($stopwords); |
53
|
|
|
$expected = array('a', 'the', 'then', 'this'); |
54
|
|
|
$this->assertEquals($expected, $englishIndex->getStopwords()); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
public function testConfigurationInvalidStopwords() { |
59
|
|
|
$stopwords = 45; // deliberately invalid |
60
|
|
|
$englishIndex = new EnglishIndexSettings(); |
61
|
|
|
try { |
62
|
|
|
$englishIndex->setStopwords($stopwords); |
63
|
|
|
// should not get this far, should fail |
64
|
|
|
$this->assertTrue(false, "Invalid stopwords were not correctly prevented"); |
65
|
|
|
} catch (Exception $e) { |
66
|
|
|
$this->assertTrue(true, "Invalid stopwords correctly prevented"); |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
/* |
72
|
|
|
Search for stop words and assert that they are not found |
73
|
|
|
*/ |
74
|
|
|
public function testConfiguredStopWords() { |
75
|
|
|
$englishIndex = new EnglishIndexSettings(); |
76
|
|
|
$stopwords = $englishIndex->getStopwords(); |
77
|
|
|
$expected = array('that', 'into', 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'if', |
78
|
|
|
'in', 'into', 'is', 'it', 'of', 'on', 'or', 'such', 'that', 'the', 'their', 'then', 'there', 'these', |
79
|
|
|
'they', 'this', 'to', 'was', 'will', 'with'); |
80
|
|
|
$this->assertEquals($expected, $stopwords); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
|
84
|
|
|
|
85
|
|
|
public function testGetResults() { |
86
|
|
|
// several checks needed here including aggregations |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
public function testResultListGetMap() { |
91
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
92
|
|
|
//default is ID -> Title, useful for dropdowns |
93
|
|
|
$mapping = $resultList->map(); |
94
|
|
|
$ctr = 0; |
95
|
|
|
foreach($resultList->getIterator() as $item) { |
96
|
|
|
$mappedTitle = $mapping[$item->ID]; |
97
|
|
|
$this->assertEquals($item->Title, $mappedTitle); |
98
|
|
|
$ctr++; |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
public function testResultListColumn() { |
104
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
105
|
|
|
$ids = $resultList->column(); |
106
|
|
|
|
107
|
|
|
$expected = array(); |
108
|
|
|
foreach($resultList as $item) { |
109
|
|
|
array_push($expected, $item->ID); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
$this->assertEquals($expected, $ids); |
113
|
|
|
|
114
|
|
|
$expected = array(); |
115
|
|
|
foreach($resultList as $item) { |
116
|
|
|
array_push($expected, $item->Title); |
117
|
|
|
} |
118
|
|
|
$titles = $resultList->column('Title'); |
119
|
|
|
$this->assertEquals($expected, $titles); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
public function testEach() { |
124
|
|
|
$callback = function($fp) { |
125
|
|
|
$this->assertTrue(true, 'Callback reached'); |
126
|
|
|
}; |
127
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
128
|
|
|
$resultList->each($callback); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
|
132
|
|
|
/* |
133
|
|
|
The search term 'New Zealand' was used against Flickr to create the fixtures file, so this means |
134
|
|
|
that all of the fixtures should have 'New Zealand' in them. Test page length from 1 to 100 |
135
|
|
|
*/ |
136
|
|
|
public function testResultListPageLength() { |
137
|
|
|
for($i = 1; $i <= 100; $i++) { |
138
|
|
|
$resultList = $this->getResultsFor('New Zealand', $i); |
139
|
|
|
$this->assertEquals($i, $resultList->count()); |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
|
144
|
|
|
public function testResultListIndex() { |
145
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
146
|
|
|
$index = $resultList->getService()->getIndex(); |
147
|
|
|
$this->assertEquals('elastica_ss_module_test_en_us', $index->getName()); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
public function testResultListGetQuery() { |
152
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
153
|
|
|
$query = $resultList->getQuery()->toArray(); |
154
|
|
|
|
155
|
|
|
$expected = array(); |
156
|
|
|
$expected['query'] = array('multi_match' => array( |
157
|
|
|
'query' => 'New Zealand', |
158
|
|
|
'fields' => array('Title', 'Title.*', 'Description', 'Description.*'), |
159
|
|
|
'type' => 'most_fields', |
160
|
|
|
'lenient' => true |
161
|
|
|
) |
162
|
|
|
); |
163
|
|
|
$expected['size'] = 10; |
164
|
|
|
$expected['from'] = 0; |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
$this->assertEquals($expected['size'], $query['size']); |
168
|
|
|
$this->assertEquals($expected['from'], $query['from']); |
169
|
|
|
$this->assertEquals($expected['query'], $query['query']); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
|
|
/* |
174
|
|
|
Check that the time for the search was more than zero |
175
|
|
|
*/ |
176
|
|
|
public function testResultListGetTotalTime() { |
177
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
178
|
|
|
$time = $resultList->getTotalTime(); |
179
|
|
|
$this->assertGreaterThan(0, $time); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
|
183
|
|
|
/* |
184
|
|
|
Test the result list iterator function |
185
|
|
|
*/ |
186
|
|
|
public function testResultListGetIterator() { |
187
|
|
|
$resultList = $this->getResultsFor('New Zealand', 100); |
188
|
|
|
$ctr = 0; |
189
|
|
|
foreach($resultList->getIterator() as $result) { |
190
|
|
|
$ctr++; |
191
|
|
|
} |
192
|
|
|
$this->assertEquals(100, $ctr); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
|
196
|
|
|
/* |
197
|
|
|
Check some basic properties of the array returned for a result |
198
|
|
|
*/ |
199
|
|
|
public function testToArrayFunction() { |
200
|
|
|
$resultList = $this->getResultsFor('New Zealand', 1); |
201
|
|
|
$result = $resultList->toArray(); |
202
|
|
|
|
203
|
|
|
$this->assertEquals(1, sizeof($result)); |
204
|
|
|
$fp = $result[0]; |
205
|
|
|
$this->assertEquals('FlickrPhotoTO', $fp->ClassName); |
206
|
|
|
$this->assertEquals(2147483647, $fp->FlickrID); |
207
|
|
|
$this->assertTrue(preg_match('/New Zealand/', $fp->Title) == 1); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
|
211
|
|
|
/* |
212
|
|
|
Check some basic properties of the array returned for a result |
213
|
|
|
*/ |
214
|
|
|
public function testToNestedArrayFunction() { |
215
|
|
|
$resultList = $this->getResultsFor('New Zealand', 4); |
216
|
|
|
$result = $resultList->toNestedArray(); |
217
|
|
|
|
218
|
|
|
$this->assertEquals(4, sizeof($result)); |
219
|
|
|
$fp = $result[0]; |
220
|
|
|
$this->assertEquals('FlickrPhotoTO', $fp['ClassName']); |
221
|
|
|
$this->assertEquals(2147483647, $fp['FlickrID']); |
222
|
|
|
$this->assertTrue(preg_match('/New Zealand/', $fp['Title']) == 1); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
|
226
|
|
View Code Duplication |
public function testResultListOffsetExistsNotImplemented() { |
|
|
|
|
227
|
|
|
try { |
228
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
229
|
|
|
$resultList->offsetExists(10); |
230
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
231
|
|
|
} catch (Exception $e) { |
232
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
|
237
|
|
View Code Duplication |
public function testResultListOffsetGetNotImplemented() { |
|
|
|
|
238
|
|
|
try { |
239
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
240
|
|
|
$resultList->offsetGet(10); |
241
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
242
|
|
|
} catch (Exception $e) { |
243
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
|
248
|
|
View Code Duplication |
public function testResultListOffsetSetNotImplemented() { |
|
|
|
|
249
|
|
|
try { |
250
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
251
|
|
|
$resultList->offsetSet(10, null); |
252
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
253
|
|
|
} catch (Exception $e) { |
254
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
|
259
|
|
View Code Duplication |
public function testResultListOffsetUnsetNotImplemented() { |
|
|
|
|
260
|
|
|
try { |
261
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
262
|
|
|
$resultList->offsetUnset(10); |
263
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
264
|
|
|
} catch (Exception $e) { |
265
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
|
270
|
|
View Code Duplication |
public function testResultListAddNotImplemented() { |
|
|
|
|
271
|
|
|
try { |
272
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
273
|
|
|
$fp = new FlickrPhotoTO(); |
274
|
|
|
$resultList->add($fp); |
275
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
276
|
|
|
} catch (Exception $e) { |
277
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
|
282
|
|
View Code Duplication |
public function testResultListRemoveNotImplemented() { |
|
|
|
|
283
|
|
|
try { |
284
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
285
|
|
|
$fp = new FlickrPhotoTO(); |
286
|
|
|
$resultList->remove($fp); |
287
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
288
|
|
|
} catch (Exception $e) { |
289
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
|
294
|
|
View Code Duplication |
public function testResultListFindNotImplemented() { |
|
|
|
|
295
|
|
|
try { |
296
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
297
|
|
|
$fp = new FlickrPhotoTO(); |
298
|
|
|
$resultList->find(4, $fp); |
299
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
300
|
|
|
} catch (Exception $e) { |
301
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
|
306
|
|
|
|
307
|
|
|
|
308
|
|
|
public function testResultListToArray() { |
309
|
|
|
$sfs = SearchableField::get()->filter(array('ClazzName' => 'FlickrPhotoTO', 'Type' => 'string')); |
310
|
|
|
foreach($sfs->getIterator() as $sf) { |
311
|
|
|
$sf->ShowHighlights = true; |
312
|
|
|
$sf->write(); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
$fields = array('Title' => 1, 'Description' => 1); |
316
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10, $fields); |
317
|
|
|
|
318
|
|
|
$toarr = $resultList->toArray(); |
319
|
|
|
|
320
|
|
|
foreach($toarr as $item) { |
321
|
|
|
$hl = $item->SearchHighlights; |
322
|
|
|
foreach ($hl as $shl) { |
323
|
|
|
$html = $shl->Snippet; |
324
|
|
|
$splits = explode('<strong class="hl">', $html); |
325
|
|
|
if (sizeof($splits) > 1) { |
326
|
|
|
$splits = explode('</strong>', $splits[1]); |
327
|
|
|
$term = $splits[0]; |
328
|
|
|
$term = strtolower($term); |
329
|
|
|
$this->assertEquals('new', $term); |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
|
335
|
|
|
} |
336
|
|
|
|
337
|
|
View Code Duplication |
public function testResultListFirstNotImplemented() { |
|
|
|
|
338
|
|
|
try { |
339
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
340
|
|
|
$resultList->first(); |
341
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
342
|
|
|
} catch (Exception $e) { |
343
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
|
348
|
|
View Code Duplication |
public function testResultListLastNotImplemented() { |
|
|
|
|
349
|
|
|
try { |
350
|
|
|
$resultList = $this->getResultsFor('New Zealand', 10); |
351
|
|
|
$resultList->last(); |
352
|
|
|
$this->assertFalse(true, "This line should not have been reached"); |
353
|
|
|
} catch (Exception $e) { |
354
|
|
|
$this->assertEquals('Not implemented', $e->getMessage()); |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
|
359
|
|
|
public function testFoldedIndexes() { |
360
|
|
|
$this->markTestIncomplete('Folded test to do'); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
|
364
|
|
|
public function testSynonymIndexes() { |
365
|
|
|
$this->markTestIncomplete('Synonym test to do'); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
|
369
|
|
|
public function testNonExistentField() { |
370
|
|
|
try { |
371
|
|
|
// this should fail as field Fwlubble does not exist |
372
|
|
|
$this->search('zealand', 0, array('Fwlubble' => 1)); |
373
|
|
|
$this->assertTrue(false, "Field Fwlubble does not exist and an exception should have been thrown"); |
374
|
|
|
} catch (Exception $e) { |
375
|
|
|
$this->assertTrue(true, "Field Fwlubble does not exist, exception thrown as expected"); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
|
381
|
|
|
public function testPriming() { |
382
|
|
|
$searchableClasses = SearchableClass::get(); |
383
|
|
|
$sortedNames = $searchableClasses->Map('Name')->toArray(); |
384
|
|
|
sort($sortedNames); |
385
|
|
|
|
386
|
|
|
$expected = array( |
387
|
|
|
'0' => 'FlickrAuthorTO', |
388
|
|
|
'1' => 'FlickrPhotoTO', |
389
|
|
|
'2' => 'FlickrSetTO', |
390
|
|
|
'3' => 'FlickrTagTO', |
391
|
|
|
'4' => 'Page', |
392
|
|
|
'5' => 'SearchableTestPage', |
393
|
|
|
'6' => 'SiteTree' |
394
|
|
|
); |
395
|
|
|
$this->assertEquals($expected, $sortedNames); |
396
|
|
|
|
397
|
|
|
$searchableFields = SearchableField::get(); |
398
|
|
|
$expected = array( |
399
|
|
|
'0' => 'Aperture', |
400
|
|
|
'1' => 'AspectRatio', |
401
|
|
|
'2' => 'Content', |
402
|
|
|
'3' => 'Country', |
403
|
|
|
'4' => 'Description', |
404
|
|
|
'5' => 'DisplayName', |
405
|
|
|
'6' => 'FirstViewed', |
406
|
|
|
'7' => 'FlickrID', |
407
|
|
|
'8' => 'FlickrPhotoTOs', |
408
|
|
|
'9' => 'FlickrSetTOs', |
409
|
|
|
'10' => 'FlickrTagTOs', |
410
|
|
|
'11' => 'FocalLength35mm', |
411
|
|
|
'12' => 'ISO', |
412
|
|
|
'13' => 'PageDate', |
413
|
|
|
'14' => 'PathAlias', |
414
|
|
|
'15' => 'Photographer', |
415
|
|
|
'16' => 'RawValue', |
416
|
|
|
'17' => 'ShutterSpeed', |
417
|
|
|
'18' => 'TakenAt', |
418
|
|
|
'19' => 'TakenAtDT', |
419
|
|
|
'20' => 'TestMethod', |
420
|
|
|
'21' => 'TestMethodHTML', |
421
|
|
|
'22' => 'Title' |
422
|
|
|
); |
423
|
|
|
|
424
|
|
|
$sortedNames = array_keys($searchableFields->Map('Name')->toArray()); |
425
|
|
|
sort($sortedNames); |
426
|
|
|
$this->assertEquals($expected, $sortedNames); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Test searching |
432
|
|
|
* http://stackoverflow.com/questions/28305250/elasticsearch-customize-score-for-synonyms-stemming |
433
|
|
|
*/ |
434
|
|
View Code Duplication |
private function search($query, $resultsExpected = 10, $fields = null) { |
435
|
|
|
$es = new ElasticSearcher(); |
436
|
|
|
$es->setStart(0); |
437
|
|
|
$es->setPageLength(100); |
438
|
|
|
$es->setClasses('FlickrPhotoTO'); |
439
|
|
|
$results = $es->search($query, $fields); |
440
|
|
|
$this->assertEquals($resultsExpected, $results->count()); |
441
|
|
|
return $results->count(); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
|
445
|
|
View Code Duplication |
private function getResultsFor($query, $pageLength = 10, $fields = array('Title' => 1, 'Description' => 1)) { |
|
|
|
|
446
|
|
|
$es = new ElasticSearcher(); |
447
|
|
|
$es->setStart(0); |
448
|
|
|
$es->setPageLength($pageLength); |
449
|
|
|
$es->setClasses('FlickrPhotoTO'); |
450
|
|
|
$resultList = $es->search($query, $fields)->getList(); |
451
|
|
|
$this->assertEquals('SilverStripe\Elastica\ResultList', get_class($resultList)); |
452
|
|
|
return $resultList; |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.