Completed
Push — 6.12 ( 1cff2f )
by Łukasz
64:51
created

SearchServiceFulltextTest   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 0
loc 293
rs 10
c 0
b 0
f 0
wmc 20
lcom 1
cbo 13

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 12 2
A testPrepareContent() 0 47 2
A providerForTestFulltextSearch() 0 73 1
A testFulltextContentSearch() 0 10 1
A testFulltextLocationSearch() 0 14 3
A assertFulltextSearch() 0 20 1
B mapKeysToIds() 0 25 4
B mapSearchResultToIds() 0 29 6
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
    public function providerForTestFulltextSearch()
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
     * Test for the findContent() method.
177
     *
178
     * @param $searchString
179
     * @param array $expectedKeys
180
     * @param array $idMap
181
     *
182
     * @depends testPrepareContent
183
     * @dataProvider providerForTestFulltextSearch
184
     */
185
    public function testFulltextContentSearch($searchString, array $expectedKeys, array $idMap)
186
    {
187
        $repository = $this->getRepository(false);
188
        $searchService = $repository->getSearchService();
189
190
        $query = new Query(['query' => new Criterion\FullText($searchString)]);
191
        $searchResult = $searchService->findContent($query);
192
193
        $this->assertFulltextSearch($searchResult, $expectedKeys, $idMap);
194
    }
195
196
    /**
197
     * Test for the findLocations() method.
198
     *
199
     * @param $searchString
200
     * @param array $expectedKeys
201
     * @param array $idMap
202
     *
203
     * @depends testPrepareContent
204
     * @dataProvider providerForTestFulltextSearch
205
     */
206
    public function testFulltextLocationSearch($searchString, array $expectedKeys, array $idMap)
207
    {
208
        if (($solrVersion = getenv('SOLR_VERSION')) && $solrVersion < 6) {
209
            $this->markTestSkipped('Solr 4 detected, skipping as scoring won\'t match');
210
        }
211
212
        $repository = $this->getRepository(false);
213
        $searchService = $repository->getSearchService();
214
215
        $query = new LocationQuery(['query' => new Criterion\FullText($searchString)]);
216
        $searchResult = $searchService->findLocations($query);
217
218
        $this->assertFulltextSearch($searchResult, $expectedKeys, $idMap);
219
    }
220
221
    /**
222
     * Assert given $searchResult using $expectedKeys and $idMap.
223
     *
224
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $searchResult
225
     * @param array $expectedKeys
226
     * @param array $idMap
227
     */
228
    public function assertFulltextSearch(SearchResult $searchResult, array $expectedKeys, array $idMap)
229
    {
230
        $this->assertEquals(
231
            array_reduce(
232
                $expectedKeys,
233
                function ($carry, $item) {
234
                    $carry += count((array)$item);
235
236
                    return $carry;
237
                },
238
                0
239
            ),
240
            $searchResult->totalCount
241
        );
242
243
        $expectedIds = $this->mapKeysToIds($expectedKeys, $idMap);
244
        $actualIds = $this->mapSearchResultToIds($searchResult);
245
246
        $this->assertEquals($expectedIds, $actualIds);
247
    }
248
249
    /**
250
     * Map given array of $expectedKeys to Content IDs, using $idMap.
251
     *
252
     * @param array $expectedKeys
253
     * @param array $idMap
254
     *
255
     * @return array
256
     */
257
    private function mapKeysToIds(array $expectedKeys, array $idMap)
258
    {
259
        $expectedIds = [];
260
261
        foreach ($expectedKeys as $keyGroup) {
262
            if (is_array($keyGroup)) {
263
                $idGroup = [];
264
265
                /** @var array $keyGroup */
266
                foreach ($keyGroup as $key) {
267
                    $idGroup[] = $idMap[$key];
268
                }
269
270
                sort($idGroup);
271
                $expectedIds[] = $idGroup;
272
273
                continue;
274
            }
275
276
            $key = $keyGroup;
277
            $expectedIds[] = $idMap[$key];
278
        }
279
280
        return $expectedIds;
281
    }
282
283
    /**
284
     * Map given $searchResult to an array of Content IDs, ordered and grouped by relevancy score.
285
     *
286
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchResult $searchResult
287
     *
288
     * @return array
289
     */
290
    private function mapSearchResultToIds(SearchResult $searchResult)
291
    {
292
        $scoreGroupedIds = [];
293
294
        foreach ($searchResult->searchHits as $index => $searchHit) {
295
            if ($searchHit->valueObject instanceof Content || $searchHit->valueObject instanceof Location) {
296
                $contentInfo = $searchHit->valueObject->contentInfo;
297
            } elseif ($searchHit->valueObject instanceof ContentInfo) {
298
                $contentInfo = $searchHit->valueObject;
299
            } else {
300
                throw new RuntimeException('Unknown search hit value');
301
            }
302
303
            $scoreGroupedIds[(string)$searchHit->score][] = $contentInfo->id;
304
        }
305
306
        return array_map(
307
            function (array $idGroup) {
308
                if (count($idGroup) === 1) {
309
                    return reset($idGroup);
310
                }
311
312
                sort($idGroup);
313
314
                return $idGroup;
315
            },
316
            array_values($scoreGroupedIds)
317
        );
318
    }
319
}
320