Completed
Push — 7.5 ( 164ebc...58bca6 )
by
unknown
33:22 queued 14:25
created

SearchServiceFulltextTest::mapSearchResultToIds()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 1
dl 0
loc 29
rs 8.8337
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\API\Repository\Tests;
8
9
use eZ\Publish\API\Repository\SearchService;
10
use eZ\Publish\API\Repository\Values\Content\Content;
11
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
12
use eZ\Publish\API\Repository\Values\Content\Location;
13
use eZ\Publish\API\Repository\Values\Content\LocationQuery;
14
use eZ\Publish\API\Repository\Values\Content\Query;
15
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
16
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
17
use RuntimeException;
18
19
/**
20
 * Test case for full text search in the SearchService.
21
 *
22
 * @see \eZ\Publish\API\Repository\SearchService
23
 * @group integration
24
 * @group search
25
 * @group fulltext
26
 */
27
class SearchServiceFulltextTest extends BaseTest
28
{
29
    protected function setUp()
30
    {
31
        parent::setUp();
32
33
        if (
34
            !$this
35
                ->getRepository(false)
36
                ->getSearchService()->supports(SearchService::CAPABILITY_ADVANCED_FULLTEXT)
37
        ) {
38
            $this->markTestSkipped('Engine says it does not support advance fulltext format');
39
        }
40
    }
41
42
    /**
43
     * Create test Content and return Content ID map for subsequent testing.
44
     */
45
    public function testPrepareContent()
46
    {
47
        $repository = $this->getRepository();
48
        $dataMap = [
49
            1 => 'quick',
50
            2 => 'brown',
51
            3 => 'fox',
52
            4 => 'news',
53
            5 => 'quick brown',
54
            6 => 'quick fox',
55
            7 => 'quick news',
56
            8 => 'brown fox',
57
            9 => 'brown news',
58
            10 => 'fox news',
59
            11 => 'quick brown fox',
60
            12 => 'quick brown news',
61
            13 => 'quick fox news',
62
            14 => 'brown fox news',
63
            15 => 'quick brown fox news',
64
        ];
65
66
        $contentService = $repository->getContentService();
67
        $contentTypeService = $repository->getContentTypeService();
68
        $locationService = $repository->getLocationService();
69
70
        $contentType = $contentTypeService->loadContentTypeByIdentifier('folder');
71
72
        $idMap = [];
73
74
        foreach ($dataMap as $key => $string) {
75
            $contentCreateStruct = $contentService->newContentCreateStruct($contentType, 'eng-GB');
76
            $contentCreateStruct->setField('name', $string);
77
78
            $content = $contentService->publishVersion(
79
                $contentService->createContent(
80
                    $contentCreateStruct,
81
                    [$locationService->newLocationCreateStruct(2)]
82
                )->versionInfo
83
            );
84
85
            $idMap[$key] = $content->id;
86
        }
87
88
        $this->refreshSearch($repository);
89
90
        return $idMap;
91
    }
92
93
    /**
94
     * Return pairs of arguments:
95
     *  - search string for testing
96
     *  - an array of corresponding Content keys as defined in testPrepareContent() method,
97
     *    ordered and grouped by relevancy.
98
     *
99
     * @see testPrepareContent
100
     */
101 View Code Duplication
    public function providerForTestFulltextSearchSolr6(): array
102
    {
103
        return [
104
            [
105
                'fox',
106
                [3, [6, 8, 10], [11, 13, 14, 15]],
107
            ],
108
            [
109
                'quick fox',
110
                $quickOrFox = [6, [11, 13, 15], [1, 3], [5, 7, 8, 10], [12, 14]],
111
            ],
112
            [
113
                'quick OR fox',
114
                $quickOrFox,
115
            ],
116
            [
117
                'quick AND () OR AND fox',
118
                $quickOrFox,
119
            ],
120
            [
121
                '+quick +fox',
122
                $quickAndFox = [6, [11, 13, 15]],
123
            ],
124
            [
125
                'quick AND fox',
126
                $quickAndFox,
127
            ],
128
            [
129
                'brown +fox -news',
130
                [8, 11, 3, 6],
131
            ],
132
            [
133
                'quick +fox -news',
134
                [6, 11, 3, 8],
135
            ],
136
            [
137
                'quick brown +fox -news',
138
                $notNewsFox = [11, [6, 8], 3],
139
            ],
140
            [
141
                '((quick AND fox) OR (brown AND fox) OR fox) AND NOT news',
142
                $notNewsFox,
143
            ],
144
            [
145
                '"quick brown"',
146
                [5, [11, 12, 15]],
147
            ],
148
            [
149
                '"quick brown" AND fox',
150
                [[11, 15]],
151
            ],
152
            [
153
                'quick OR brown AND fox AND NOT news',
154
                [11, 8],
155
            ],
156
            [
157
                '(quick OR brown) AND fox AND NOT news',
158
                [11, [6, 8]],
159
            ],
160
            [
161
                '"fox brown"',
162
                [],
163
            ],
164
            [
165
                'qui*',
166
                [[1, 5, 6, 7, 11, 12, 13, 15]],
167
            ],
168
            [
169
                '+qui* +fox',
170
                [6, [11, 13, 15]],
171
            ],
172
        ];
173
    }
174
175
    /**
176
     * Return pairs of arguments:
177
     *  - search string for testing
178
     *  - an array of corresponding Content keys as defined in testPrepareContent() method,
179
     *    ordered and grouped by relevancy.
180
     *
181
     * @see testPrepareContent
182
     */
183 View Code Duplication
    public function providerForTestFulltextSearchSolr7(): array
184
    {
185
        return [
186
            [
187
                'fox',
188
                [3, [6, 8, 10], [11, 13, 14], 15],
189
            ],
190
            [
191
                'quick fox',
192
                $quickOrFox = [6, [11, 13], 15, [1, 3], [5, 7, 8, 10], [12, 14]],
193
            ],
194
            [
195
                'quick OR fox',
196
                $quickOrFox,
197
            ],
198
            [
199
                'quick AND () OR AND fox',
200
                $quickOrFox,
201
            ],
202
            [
203
                '+quick +fox',
204
                $quickAndFox = [6, [11, 13], 15],
205
            ],
206
            [
207
                'quick AND fox',
208
                $quickAndFox,
209
            ],
210
            [
211
                'brown +fox -news',
212
                [8, 11, 3, 6],
213
            ],
214
            [
215
                'quick +fox -news',
216
                [6, 11, 3, 8],
217
            ],
218
            [
219
                'quick brown +fox -news',
220
                $notNewsFox = [11, [6, 8], 3],
221
            ],
222
            [
223
                '((quick AND fox) OR (brown AND fox) OR fox) AND NOT news',
224
                $notNewsFox,
225
            ],
226
            [
227
                '"quick brown"',
228
                [5, [11, 12], 15],
229
            ],
230
            [
231
                '"quick brown" AND fox',
232
                [11, 15],
233
            ],
234
            [
235
                'quick OR brown AND fox AND NOT news',
236
                [11, 8],
237
            ],
238
            [
239
                '(quick OR brown) AND fox AND NOT news',
240
                [11, [6, 8]],
241
            ],
242
            [
243
                '"fox brown"',
244
                [],
245
            ],
246
            [
247
                'qui*',
248
                [[1, 5, 6, 7, 11, 12, 13, 15]],
249
            ],
250
            [
251
                '+qui* +fox',
252
                [6, [11, 13], 15],
253
            ],
254
        ];
255
    }
256
257
    /**
258
     * Test for the findContent() method on Solr 6.
259
     *
260
     * @param string $searchString
261
     * @param array $expectedKeys
262
     * @param array $idMap
263
     *
264
     * @depends testPrepareContent
265
     * @dataProvider providerForTestFulltextSearchSolr6
266
     */
267 View Code Duplication
    public function testFulltextContentSearchSolr6(string $searchString, array $expectedKeys, array $idMap): void
268
    {
269
        if (($solrVersion = getenv('SOLR_VERSION')) >= 7) {
270
            $this->markTestSkipped('This test is only relevant for Solr 6');
271
        }
272
273
        $this->doTestFulltextContentSearch($searchString, $expectedKeys, $idMap);
274
    }
275
276
    /**
277
     * Test for the findContent() method on Solr >= 7.
278
     *
279
     * @param string $searchString
280
     * @param array $expectedKeys
281
     * @param array $idMap
282
     *
283
     * @depends testPrepareContent
284
     * @dataProvider providerForTestFulltextSearchSolr7
285
     */
286 View Code Duplication
    public function testFulltextContentSearchSolr7(string $searchString, array $expectedKeys, array $idMap): void
287
    {
288
        if (($solrVersion = getenv('SOLR_VERSION')) < 7) {
289
            $this->markTestSkipped('This test is only relevant for Solr >= 7');
290
        }
291
292
        $this->doTestFulltextContentSearch($searchString, $expectedKeys, $idMap);
293
    }
294
295
    private function doTestFulltextContentSearch(string $searchString, array $expectedKeys, array $idMap): void
296
    {
297
        $repository = $this->getRepository(false);
298
        $searchService = $repository->getSearchService();
299
300
        $query = new Query(['query' => new Criterion\FullText($searchString)]);
301
        $searchResult = $searchService->findContent($query);
302
303
        $this->assertFulltextSearch($searchResult, $expectedKeys, $idMap);
304
    }
305
306
    /**
307
     * Test for the findLocations() method on Solr 6.
308
     *
309
     * @param $searchString
310
     * @param array $expectedKeys
311
     * @param array $idMap
312
     *
313
     * @depends testPrepareContent
314
     * @dataProvider providerForTestFulltextSearchSolr6
315
     */
316
    public function testFulltextLocationSearchSolr6($searchString, array $expectedKeys, array $idMap): void
317
    {
318
        if (!$this->isSolrMajorVersionInRange('6.0.0', '7.0.0')) {
319
            $this->markTestSkipped('This test is only relevant for Solr 6');
320
        }
321
322
        $this->doTestFulltextLocationSearch($searchString, $expectedKeys, $idMap);
323
    }
324
325
    /**
326
     * Test for the findLocations() method on Solr >= 7.
327
     *
328
     * @param $searchString
329
     * @param array $expectedKeys
330
     * @param array $idMap
331
     *
332
     * @depends testPrepareContent
333
     * @dataProvider providerForTestFulltextSearchSolr7
334
     */
335 View Code Duplication
    public function testFulltextLocationSearchSolr7($searchString, array $expectedKeys, array $idMap): void
336
    {
337
        if (($solrVersion = getenv('SOLR_VERSION')) < 7) {
338
            $this->markTestSkipped('This test is only relevant for Solr >= 7');
339
        }
340
341
        $this->doTestFulltextLocationSearch($searchString, $expectedKeys, $idMap);
342
    }
343
344
    private function doTestFulltextLocationSearch($searchString, array $expectedKeys, array $idMap): void
345
    {
346
        $repository = $this->getRepository(false);
347
        $searchService = $repository->getSearchService();
348
349
        $query = new LocationQuery(['query' => new Criterion\FullText($searchString)]);
350
        $searchResult = $searchService->findLocations($query);
351
352
        $this->assertFulltextSearch($searchResult, $expectedKeys, $idMap);
353
    }
354
355
    /**
356
     * Assert given $searchResult using $expectedKeys and $idMap.
357
     *
358
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $searchResult
359
     * @param array $expectedKeys
360
     * @param array $idMap
361
     */
362
    public function assertFulltextSearch(SearchResult $searchResult, array $expectedKeys, array $idMap)
363
    {
364
        $this->assertEquals(
365
            array_reduce(
366
                $expectedKeys,
367
                function ($carry, $item) {
368
                    $carry += count((array)$item);
369
370
                    return $carry;
371
                },
372
                0
373
            ),
374
            $searchResult->totalCount
375
        );
376
377
        $expectedIds = $this->mapKeysToIds($expectedKeys, $idMap);
378
        $actualIds = $this->mapSearchResultToIds($searchResult);
379
380
        $this->assertEquals($expectedIds, $actualIds);
381
    }
382
383
    /**
384
     * Map given array of $expectedKeys to Content IDs, using $idMap.
385
     *
386
     * @param array $expectedKeys
387
     * @param array $idMap
388
     *
389
     * @return array
390
     */
391
    private function mapKeysToIds(array $expectedKeys, array $idMap)
392
    {
393
        $expectedIds = [];
394
395
        foreach ($expectedKeys as $keyGroup) {
396
            if (is_array($keyGroup)) {
397
                $idGroup = [];
398
399
                /** @var array $keyGroup */
400
                foreach ($keyGroup as $key) {
401
                    $idGroup[] = $idMap[$key];
402
                }
403
404
                sort($idGroup);
405
                $expectedIds[] = $idGroup;
406
407
                continue;
408
            }
409
410
            $key = $keyGroup;
411
            $expectedIds[] = $idMap[$key];
412
        }
413
414
        return $expectedIds;
415
    }
416
417
    /**
418
     * Map given $searchResult to an array of Content IDs, ordered and grouped by relevancy score.
419
     *
420
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $searchResult
421
     *
422
     * @return array
423
     */
424
    private function mapSearchResultToIds(SearchResult $searchResult)
425
    {
426
        $scoreGroupedIds = [];
427
428
        foreach ($searchResult->searchHits as $index => $searchHit) {
429
            if ($searchHit->valueObject instanceof Content || $searchHit->valueObject instanceof Location) {
430
                $contentInfo = $searchHit->valueObject->contentInfo;
431
            } elseif ($searchHit->valueObject instanceof ContentInfo) {
432
                $contentInfo = $searchHit->valueObject;
433
            } else {
434
                throw new RuntimeException('Unknown search hit value');
435
            }
436
437
            $scoreGroupedIds[(string)$searchHit->score][] = $contentInfo->id;
438
        }
439
440
        return array_map(
441
            function (array $idGroup) {
442
                if (count($idGroup) === 1) {
443
                    return reset($idGroup);
444
                }
445
446
                sort($idGroup);
447
448
                return $idGroup;
449
            },
450
            array_values($scoreGroupedIds)
451
        );
452
    }
453
454
    /**
455
     * Checks if Solr version is in the given range.
456
     *
457
     * @param string $minVersion
458
     * @param string $maxVersion
459
     *
460
     * @return bool
461
     */
462
    private function isSolrMajorVersionInRange(string $minVersion, string $maxVersion): bool
463
    {
464
        $version = getenv('SOLR_VERSION');
465
        if (is_string($version) && !empty($version)) {
466
            return version_compare($version, $minVersion, '>=') && version_compare($version, $maxVersion, '<');
467
        }
468
469
        return false;
470
    }
471
}
472