Issues (3641)

SprykerTest/Client/Search/SearchClientTest.php (1 issue)

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace SprykerTest\Client\Search;
9
10
use Codeception\Stub;
11
use Codeception\Test\Unit;
12
use Elastica\Client;
13
use Elastica\ResultSet;
14
use Elastica\Status;
15
use Generated\Shared\Transfer\ElasticsearchSearchContextTransfer;
16
use Generated\Shared\Transfer\SearchContextTransfer;
17
use Generated\Shared\Transfer\SearchDocumentTransfer;
18
use Spryker\Client\Search\Dependency\Plugin\QueryExpanderPluginInterface;
19
use Spryker\Client\Search\Exception\ConnectDelegatorException;
20
use Spryker\Client\Search\Model\Handler\ElasticsearchSearchHandler;
21
use Spryker\Client\Search\Plugin\Config\SearchConfig;
22
use Spryker\Client\Search\Plugin\Elasticsearch\Query\SearchKeysQuery;
23
use Spryker\Client\Search\Plugin\Elasticsearch\Query\SearchStringQuery;
24
use Spryker\Client\Search\SearchClient;
25
use Spryker\Client\Search\SearchContext\SearchContextExpanderInterface;
26
use Spryker\Client\Search\SearchDependencyProvider;
27
use Spryker\Client\Search\SearchFactory;
28
use Spryker\Client\SearchElasticsearch\Plugin\ElasticsearchSearchAdapterPlugin;
29
use Spryker\Client\SearchExtension\Dependency\Plugin\ConnectionCheckerAdapterPluginInterface;
30
use Spryker\Client\SearchExtension\Dependency\Plugin\QueryInterface;
31
use Spryker\Client\SearchExtension\Dependency\Plugin\ResultFormatterPluginInterface;
32
use Spryker\Client\SearchExtension\Dependency\Plugin\SearchAdapterPluginInterface;
33
use Spryker\Client\SearchExtension\Dependency\Plugin\SearchContextExpanderPluginInterface;
34
use SprykerTest\Shared\SearchElasticsearch\Helper\ElasticsearchHelper;
35
use StdClass;
36
37
/**
38
 * Auto-generated group annotations
39
 *
40
 * @group SprykerTest
41
 * @group Client
42
 * @group Search
43
 * @group SearchClientTest
44
 * Add your own group annotations below this line
45
 */
46
class SearchClientTest extends Unit
47
{
48
    /**
49
     * @var string
50
     */
51
    protected const INDEX_NAME = 'de_index_devtest';
52
53
    /**
54
     * @var \Spryker\Client\Search\SearchClientInterface|\Spryker\Client\Kernel\AbstractClient
55
     */
56
    protected $searchClient;
57
58
    /**
59
     * @var \SprykerTest\Client\Search\SearchClientTester
60
     */
61
    protected $tester;
62
63
    /**
64
     * @return void
65
     */
66
    protected function setUp(): void
67
    {
68
        $this->skipIfElasticsearch7();
69
70
        parent::setUp();
71
72
        $this->searchClient = new SearchClient();
73
        $this->setupEnvironmentForSearchTesting();
74
    }
75
76
    /**
77
     * @return void
78
     */
79
    public function testGetSearchConfigShouldReturnTheSameInstance(): void
80
    {
81
        /** @var \Spryker\Client\Search\SearchFactory|\PHPUnit\Framework\MockObject\MockObject $searchFactoryMock */
82
        $searchFactoryMock = $this->getMockBuilder(SearchFactory::class)
83
            ->setMethods(['createSearchConfig'])
84
            ->getMock();
85
        $searchFactoryMock
86
            ->expects($this->once())
87
            ->method('createSearchConfig')
88
            ->willReturnCallback(function () {
89
                return $this->getMockBuilder(SearchConfig::class)->disableOriginalConstructor()->getMock();
90
            });
91
92
        $this->searchClient->setFactory($searchFactoryMock);
93
94
        $searchConfig1 = $this->searchClient->getSearchConfig();
95
        $searchConfig2 = $this->searchClient->getSearchConfig();
96
97
        $this->assertSame($searchConfig1, $searchConfig2);
98
    }
99
100
    /**
101
     * @return void
102
     */
103
    public function testCheckConnectionIfClientAdapterPluginsIsEmpty(): void
104
    {
105
        $elasticaClientMock = $this
106
            ->getMockBuilder(Client::class)
107
            ->setMethods(['getStatus'])
108
            ->getMock();
109
        $elasticaClientMock
110
            ->expects($this->once())
111
            ->method('getStatus')
112
            ->willReturn($this->getMockBuilder(Status::class)->disableOriginalConstructor()->getMock());
113
114
        /** @var \Spryker\Client\Search\SearchFactory|\PHPUnit\Framework\MockObject\MockObject $searchFactoryMock */
115
        $searchFactoryMock = $this->getMockBuilder(SearchFactory::class)
116
            ->setMethods(['getElasticsearchClient', 'getClientAdapterPlugins'])
117
            ->getMock();
118
        $searchFactoryMock
119
            ->method('getElasticsearchClient')
120
            ->willReturn($elasticaClientMock);
121
        $searchFactoryMock
122
            ->method('getClientAdapterPlugins')
123
            ->willReturn([]);
124
125
        $client = $this->tester->getClient();
126
        $client->setFactory($searchFactoryMock);
127
        $client->checkConnection();
128
    }
129
130
    /**
131
     * @return void
132
     */
133
    public function testCheckConnectionIfClientAdapterPluginsIsNotEmpty(): void
134
    {
135
        $clientAdapterPluginMock = $this->createMock(ConnectionCheckerAdapterPluginInterface::class);
136
137
        $clientAdapterPluginMock
138
            ->expects($this->once())
139
            ->method('checkConnection');
140
141
        $client = $this->tester->getClient();
142
143
        /** @var \Spryker\Client\Search\SearchFactory|\PHPUnit\Framework\MockObject\MockObject $searchFactoryMock */
144
        $searchFactoryMock = $this->getMockBuilder(SearchFactory::class)
145
            ->setMethods(['getClientAdapterPlugins'])
146
            ->getMock();
147
148
        $searchFactoryMock
149
            ->method('getClientAdapterPlugins')
150
            ->willReturn([$clientAdapterPluginMock]);
151
152
        $client->setFactory($searchFactoryMock);
153
        $client->checkConnection();
154
    }
155
156
    /**
157
     * @return void
158
     */
159
    public function testCheckConnectionIfClientAdapterPluginsIsWrongType(): void
160
    {
161
        $this->expectException(ConnectDelegatorException::class);
162
        $this->expectExceptionMessage('No registered adapters with ConnectionCheckerAdapterPluginInterface.');
163
164
        $client = $this->tester->getClient();
165
166
        /** @var \Spryker\Client\Search\SearchFactory|\PHPUnit\Framework\MockObject\MockObject $searchFactoryMock */
167
        $searchFactoryMock = $this->getMockBuilder(SearchFactory::class)
168
            ->setMethods(['getClientAdapterPlugins'])
169
            ->getMock();
170
171
        $searchFactoryMock
172
            ->method('getClientAdapterPlugins')
173
            ->willReturn([new StdClass()]);
174
175
        $client->setFactory($searchFactoryMock);
176
        $client->checkConnection();
177
    }
178
179
    /**
180
     * @return void
181
     */
182
    public function testClientSearchWithoutResultFormatters(): void
183
    {
184
        $this->prepareSearchClientForSearchTest();
185
186
        /** @var \Spryker\Client\SearchExtension\Dependency\Plugin\QueryInterface|\PHPUnit\Framework\MockObject\MockObject $queryMock */
187
        $queryMock = $this->getMockBuilder(QueryInterface::class)->getMock();
188
189
        $result = $this->searchClient->search($queryMock);
190
        $this->assertEmpty($result);
191
    }
192
193
    /**
194
     * @return void
195
     */
196
    public function testClientSearchWithResultFormatters(): void
197
    {
198
        $this->prepareSearchClientForSearchTest();
199
200
        /** @var \Spryker\Client\SearchExtension\Dependency\Plugin\ResultFormatterPluginInterface|\PHPUnit\Framework\MockObject\MockObject $resultFormatterMock */
201
        $resultFormatterMock = $this
202
            ->getMockBuilder(ResultFormatterPluginInterface::class)
203
            ->setMethods(['getName', 'formatResult'])
204
            ->getMock();
205
        $resultFormatterMock
206
            ->method('getName')
207
            ->willReturn('fooResultFormatter');
208
209
        /** @var \Spryker\Client\SearchExtension\Dependency\Plugin\QueryInterface|\PHPUnit\Framework\MockObject\MockObject $queryMock */
210
        $queryMock = $this->getMockBuilder(QueryInterface::class)->getMock();
211
        $resultFormatters = [
212
            $resultFormatterMock,
213
        ];
214
215
        $result = $this->searchClient->search($queryMock, $resultFormatters);
216
217
        $expectedResult = [
218
            $resultFormatterMock->getName() => null,
219
        ];
220
221
        $this->assertEquals($result, $expectedResult);
222
    }
223
224
    /**
225
     * @return void
226
     */
227
    public function testExpandQuery(): void
228
    {
229
        /** @var \Spryker\Client\SearchExtension\Dependency\Plugin\QueryInterface|\PHPUnit\Framework\MockObject\MockObject $queryMock */
230
        $queryMock = $this->getMockBuilder(QueryInterface::class)->getMock();
231
232
        $queryExpanderMock = $this->getMockBuilder(QueryExpanderPluginInterface::class)
233
            ->setMethods(['expandQuery'])
234
            ->getMock();
235
        $queryExpanderMock
236
            ->expects($this->once())
237
            ->method('expandQuery')
238
            ->willReturn($this->getMockBuilder(QueryInterface::class)->getMock());
239
240
        $queryExpanders = [
241
            $queryExpanderMock,
242
        ];
243
244
        $result = $this->searchClient->expandQuery($queryMock, $queryExpanders);
245
246
        $this->assertInstanceOf(QueryInterface::class, $result);
247
    }
248
249
    /**
250
     * @return void
251
     */
252
    public function testSearchKeys(): void
253
    {
254
        $expectedQuery = new SearchKeysQuery('foo', 25, 100);
255
256
        /** @var \Spryker\Client\Search\SearchClient|\PHPUnit\Framework\MockObject\MockObject $clientMock */
257
        $clientMock = $this->getMockBuilder(SearchClient::class)
258
            ->setMethods(['search'])
259
            ->disableOriginalConstructor()
260
            ->getMock();
261
        $clientMock
262
            ->expects($this->once())
263
            ->method('search')
264
            ->with($expectedQuery);
265
266
        $clientMock->setFactory(new SearchFactory());
267
268
        $clientMock->searchKeys('foo', 25, 100);
269
    }
270
271
    /**
272
     * @return void
273
     */
274
    public function testSearchString(): void
275
    {
276
        $expectedQuery = new SearchStringQuery('foo:bar', 25, 100);
0 ignored issues
show
Deprecated Code introduced by
The class Spryker\Client\Search\Pl...Query\SearchStringQuery has been deprecated: Use {@link \Spryker\Client\SearchElasticsearch\Plugin\Query\SearchStringQuery} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

276
        $expectedQuery = /** @scrutinizer ignore-deprecated */ new SearchStringQuery('foo:bar', 25, 100);
Loading history...
277
278
        /** @var \Spryker\Client\Search\SearchClient|\PHPUnit\Framework\MockObject\MockObject $clientMock */
279
        $clientMock = $this->getMockBuilder(SearchClient::class)
280
            ->setMethods(['search'])
281
            ->disableOriginalConstructor()
282
            ->getMock();
283
        $clientMock
284
            ->expects($this->once())
285
            ->method('search')
286
            ->with($expectedQuery);
287
288
        $clientMock->setFactory(new SearchFactory());
289
290
        $clientMock->searchQueryString('foo:bar', 25, 100);
291
    }
292
293
    /**
294
     * @return void
295
     */
296
    protected function prepareSearchClientForSearchTest(): void
297
    {
298
        $elasticsearchSearchHandlerMock = $this->getMockBuilder(ElasticsearchSearchHandler::class)
299
            ->setMethods(['executeQuery'])
300
            ->disableOriginalConstructor()
301
            ->getMock();
302
        $elasticsearchSearchHandlerMock->method('executeQuery')->willReturn(
303
            $this->getMockBuilder(ResultSet::class)->disableOriginalConstructor()->getMock(),
304
        );
305
306
        /** @var \Spryker\Client\Search\SearchFactory|\PHPUnit\Framework\MockObject\MockObject $searchFactoryMock */
307
        $searchFactoryMock = $this->getMockBuilder(SearchFactory::class)
308
            ->setMethods(['createElasticsearchSearchHandler'])
309
            ->getMock();
310
        $searchFactoryMock
311
            ->method('createElasticsearchSearchHandler')
312
            ->willReturn($elasticsearchSearchHandlerMock);
313
314
        $this->searchClient->setFactory($searchFactoryMock);
315
    }
316
317
    /**
318
     * @return void
319
     */
320
    public function testCanWriteDocument(): void
321
    {
322
        // Arrange
323
        $documentId = 'document-id';
324
        $documentData = ['foo' => 'bar'];
325
        $dataSet = [
326
            $documentId => $documentData,
327
        ];
328
        $this->tester->haveIndex(static::INDEX_NAME);
329
330
        // Act
331
        $result = $this->tester->getClient()->write($dataSet, ElasticsearchHelper::DEFAULT_MAPPING_TYPE);
332
333
        // Assert
334
        $this->tester->assertDocumentExists($documentId, static::INDEX_NAME, $documentData);
335
    }
336
337
    /**
338
     * @return void
339
     */
340
    public function testCanWriteDocumentForSearchContext(): void
341
    {
342
        // Arrange
343
        $documentId = 'document-id';
344
        $documentData = ['foo' => 'bar'];
345
        $searchDocumentTransfer = $this->createSearchDocumentTransfer($documentId, $documentData);
346
347
        // Act
348
        $this->tester->getClient()->writeDocument($searchDocumentTransfer);
349
350
        // Assert
351
        $this->tester->assertDocumentExists($documentId, static::INDEX_NAME);
352
    }
353
354
    /**
355
     * @return void
356
     */
357
    public function testCanWriteMultipleDocuments(): void
358
    {
359
        // Arrange
360
        $documentId = 'new-document';
361
        $documentData = ['foo' => 'bar'];
362
        $anotherDocumentId = 'another-document';
363
        $anotherDocumentData = ['bar' => 'baz'];
364
365
        $searchDocumentTransfer = $this->createSearchDocumentTransfer($documentId, $documentData);
366
        $anotherSearchDocumentTransfer = $this->createSearchDocumentTransfer($anotherDocumentId, $anotherDocumentData);
367
368
        // Act
369
        $this->tester->getClient()->writeBulk([$searchDocumentTransfer, $anotherSearchDocumentTransfer]);
370
371
        // Assert
372
        foreach ([$documentId, $anotherDocumentId] as $currentDocumentId) {
373
            $this->tester->assertDocumentExists($currentDocumentId, static::INDEX_NAME);
374
        }
375
    }
376
377
    /**
378
     * @return void
379
     */
380
    public function testCanReadDocument(): void
381
    {
382
        // Arrange
383
        $documentId = 'new-document';
384
        $documentData = ['foo' => 'bar'];
385
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $documentId, $documentData);
386
387
        // Act
388
        $result = $this->tester->getClient()->read($documentId, ElasticsearchHelper::DEFAULT_MAPPING_TYPE);
389
390
        // Assert
391
        $this->assertSame($documentData, $result->getData());
392
    }
393
394
    /**
395
     * @return void
396
     */
397
    public function testCanReadDocumentForSearchContext(): void
398
    {
399
        // Arrange
400
        $documentId = 'document-id';
401
        $documentData = ['foo' => 'bar'];
402
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $documentId, $documentData);
403
        $searchDocumentTransfer = $this->createSearchDocumentTransfer($documentId);
404
405
        // Act
406
        $result = $this->tester->getClient()->readDocument($searchDocumentTransfer);
407
408
        // Assert
409
        $this->assertSame($documentData, $result->getData());
410
    }
411
412
    /**
413
     * @return void
414
     */
415
    public function testCanDeleteDocument(): void
416
    {
417
        // Arrange
418
        $documentId = 'document-id';
419
        $dataSet = [
420
            $documentId => [],
421
        ];
422
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $documentId);
423
424
        // Act
425
        $this->tester->getClient()->delete($dataSet, ElasticsearchHelper::DEFAULT_MAPPING_TYPE);
426
427
        // Assert
428
        $this->tester->assertDocumentDoesNotExist($documentId, static::INDEX_NAME);
429
    }
430
431
    /**
432
     * @return void
433
     */
434
    public function testCanDeleteDocumentForSearchContext(): void
435
    {
436
        // Arrange
437
        $documentId = 'document-id';
438
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $documentId);
439
        $searchDocumentTransfer = $this->createSearchDocumentTransfer($documentId);
440
441
        // Act
442
        $this->tester->getClient()->deleteDocument($searchDocumentTransfer);
443
444
        // Assert
445
        $this->tester->assertDocumentDoesNotExist($documentId, static::INDEX_NAME);
446
    }
447
448
    /**
449
     * @return void
450
     */
451
    public function testCanDeleteMultipleDocuments(): void
452
    {
453
        // Arrange
454
        $documentId = 'document-id';
455
        $anotherDocumentId = 'another-document-id';
456
457
        $searchDocumentTransfer = $this->createSearchDocumentTransfer($documentId);
458
        $anotherSearchDocumentTransfer = $this->createSearchDocumentTransfer($anotherDocumentId);
459
460
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $documentId);
461
        $this->tester->haveDocumentInIndex(static::INDEX_NAME, $anotherDocumentId);
462
463
        // Act
464
        $this->tester->getClient()->deleteBulk([$searchDocumentTransfer, $anotherSearchDocumentTransfer]);
465
466
        // Assert
467
        foreach ([$documentId, $anotherDocumentId] as $id) {
468
            $this->tester->assertDocumentDoesNotExist($id, static::INDEX_NAME);
469
        }
470
    }
471
472
    /**
473
     * @param string $documentId
474
     * @param array|string|null $documentData
475
     *
476
     * @return \Generated\Shared\Transfer\SearchDocumentTransfer
477
     */
478
    protected function createSearchDocumentTransfer(string $documentId, $documentData = null): SearchDocumentTransfer
479
    {
480
        $searchContextTransfer = (new SearchContextTransfer())->setSourceIdentifier(ElasticsearchHelper::DEFAULT_MAPPING_TYPE);
481
        $searchDocumentTransfer = (new SearchDocumentTransfer())->setId($documentId)
482
            ->setSearchContext($searchContextTransfer);
483
484
        if ($documentData) {
485
            $searchDocumentTransfer->setData($documentData);
486
        }
487
488
        return $searchDocumentTransfer;
489
    }
490
491
    /**
492
     * @return void
493
     */
494
    protected function setupEnvironmentForSearchTesting(): void
495
    {
496
        $this->tester->setDependency(SearchDependencyProvider::PLUGINS_SEARCH_CONTEXT_EXPANDER, [
497
            $this->createSearchContextExpanderPluginMock(),
498
        ]);
499
        $this->tester->setDependency(SearchDependencyProvider::PLUGINS_CLIENT_ADAPTER, [
500
            $this->createElasticsearchSearchAdapterPluginMock(),
501
        ]);
502
    }
503
504
    /**
505
     * @return \Spryker\Client\Search\SearchContext\SearchContextExpanderInterface|\PHPUnit\Framework\MockObject\MockObject
506
     */
507
    protected function getSearchContextExpanderMock(): SearchContextExpanderInterface
508
    {
509
        $searchContextExpanderMock = $this->createMock(SearchContextExpanderInterface::class);
510
        $searchContextExpanderMock->method('expandSearchContext')
511
            ->willReturnCallback(function (SearchContextTransfer $searchContextTransfer) {
512
                $searchContextTransfer->setElasticsearchContext(
513
                    (new ElasticsearchSearchContextTransfer())->setIndexName(static::INDEX_NAME)->setTypeName(ElasticsearchHelper::DEFAULT_MAPPING_TYPE),
514
                );
515
516
                return $searchContextTransfer;
517
            });
518
519
        return $searchContextExpanderMock;
520
    }
521
522
    /**
523
     * @return \Spryker\Client\SearchExtension\Dependency\Plugin\SearchAdapterPluginInterface
524
     */
525
    protected function createElasticsearchSearchAdapterPluginMock(): SearchAdapterPluginInterface
526
    {
527
        /** @var \Spryker\Client\SearchExtension\Dependency\Plugin\SearchAdapterPluginInterface $elasticsearchAdapterPluginMock */
528
        $elasticsearchAdapterPluginMock = Stub::make(ElasticsearchSearchAdapterPlugin::class, [
529
            'getClient' => $this->tester->getLocator()->searchElasticsearch()->client(),
530
            'isApplicable' => true,
531
        ]);
532
533
        return $elasticsearchAdapterPluginMock;
534
    }
535
536
    /**
537
     * @return \Spryker\Client\SearchExtension\Dependency\Plugin\SearchContextExpanderPluginInterface|\PHPUnit\Framework\MockObject\MockObject
538
     */
539
    protected function createSearchContextExpanderPluginMock(): SearchContextExpanderPluginInterface
540
    {
541
        $searchContextExpanderPluginMock = $this->createMock(SearchContextExpanderPluginInterface::class);
542
        $searchContextExpanderPluginMock->method('expandSearchContext')
543
            ->willReturnCallback(function (SearchContextTransfer $searchContextTransfer) {
544
                $searchContextTransfer->setElasticsearchContext(
545
                    (new ElasticsearchSearchContextTransfer())->setIndexName(static::INDEX_NAME)->setTypeName(ElasticsearchHelper::DEFAULT_MAPPING_TYPE),
546
                );
547
548
                return $searchContextTransfer;
549
            });
550
551
        return $searchContextExpanderPluginMock;
552
    }
553
554
    /**
555
     * @return void
556
     */
557
    protected function skipIfElasticsearch7(): void
558
    {
559
        if (!class_exists('\Elastica\Type')) {
560
            $this->markTestSkipped('This test is not suitable for Elasticsearch 7 or higher');
561
        }
562
    }
563
}
564