Completed
Push — 7.5 ( f512f5...120086 )
by Marek
23:14 queued 11s
created

SearchViewTest   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 464
Duplicated Lines 9.91 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
dl 46
loc 464
rs 10
c 0
b 0
f 0
wmc 19
lcom 2
cbo 3

14 Methods

Rating   Name   Duplication   Size   Complexity  
A tearDown() 0 7 1
A setUp() 0 9 1
A testSimpleXmlContentQuery() 0 20 1
A testSimpleJsonContentQuery() 0 21 1
B createTestContentType() 0 68 1
A createTestContentWithTags() 0 51 1
A deleteContent() 0 6 1
A xmlProvider() 0 50 1
A jsonProvider() 46 46 1
A buildFieldXml() 0 24 4
A wrapIn() 0 13 2
A getXmlString() 0 4 1
A createContentWithUrlField() 0 50 1
A getQueryResultsCount() 0 20 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
declare(strict_types=1);
8
9
namespace eZ\Bundle\EzPublishRestBundle\Tests\Functional;
10
11
use DOMDocument;
12
use DOMElement;
13
use eZ\Bundle\EzPublishRestBundle\Tests\Functional\TestCase as RESTFunctionalTestCase;
14
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator;
15
16
class SearchViewTest extends RESTFunctionalTestCase
17
{
18
    /** @var string */
19
    protected $contentTypeHref;
20
21
    /** @var string[] */
22
    protected $contentHrefList;
23
24
    /** @var string */
25
    private $nonSearchableContentHref;
26
27
    /**
28
     * @throws \Psr\Http\Client\ClientException
29
     */
30
    protected function setUp(): void
31
    {
32
        parent::setUp();
33
        $this->contentTypeHref = $this->createTestContentType();
34
        $this->nonSearchableContentHref = $this->createContentWithUrlField();
35
        $this->contentHrefList[] = $this->createTestContentWithTags('test-name', ['foo', 'bar']);
36
        $this->contentHrefList[] = $this->createTestContentWithTags('fancy-name', ['baz', 'foobaz']);
37
        $this->contentHrefList[] = $this->createTestContentWithTags('even-fancier', ['bar', 'bazfoo']);
38
    }
39
40
    /**
41
     * @throws \Psr\Http\Client\ClientException
42
     */
43
    protected function tearDown(): void
44
    {
45
        parent::tearDown();
46
        array_map([$this, 'deleteContent'], $this->contentHrefList);
47
        $this->deleteContent($this->contentTypeHref);
48
        $this->deleteContent($this->nonSearchableContentHref);
49
    }
50
51
    /**
52
     * Covers POST with ContentQuery Logic on /api/ezp/v2/views using payload in the XML format.
53
     *
54
     * @dataProvider xmlProvider
55
     *
56
     * @throws \Psr\Http\Client\ClientException
57
     */
58
    public function testSimpleXmlContentQuery(string $xmlQueryBody, int $expectedCount): void
59
    {
60
        $format = 'xml';
61
        $body = <<< XML
62
<?xml version="1.0" encoding="UTF-8"?>
63
<ViewInput>
64
<identifier>your-query-id</identifier>
65
<public>false</public>
66
<ContentQuery>
67
  <Query>
68
    $xmlQueryBody
69
  </Query>  
70
  <limit>10</limit>  
71
  <offset>0</offset> 
72
</ContentQuery>
73
</ViewInput>
74
XML;
75
76
        self::assertEquals($expectedCount, $this->getQueryResultsCount($format, $body));
77
    }
78
79
    /**
80
     * Covers POST with LocationQuery Logic on /api/ezp/v2/views using payload in the JSON format.
81
     *
82
     * @dataProvider jsonProvider
83
     *
84
     * @throws \Psr\Http\Client\ClientException
85
     */
86
    public function testSimpleJsonContentQuery(string $jsonQueryBody, int $expectedCount): void
87
    {
88
        $format = 'json';
89
        $body = <<< JSON
90
{
91
    "ViewInput": {
92
        "identifier": "your-query-id",
93
        "public": "false",
94
        "LocationQuery": {
95
            "Filter": {
96
                $jsonQueryBody
97
            },
98
            "limit": "10",
99
            "offset": "0"
100
        }
101
    }
102
}
103
JSON;
104
105
        self::assertEquals($expectedCount, $this->getQueryResultsCount($format, $body));
106
    }
107
108
    /**
109
     * @throws \Psr\Http\Client\ClientException
110
     */
111
    private function createTestContentType(): string
112
    {
113
        $body = <<< XML
114
<?xml version="1.0" encoding="UTF-8"?>
115
<ContentTypeCreate>
116
  <identifier>tags-test</identifier>
117
  <names>
118
    <value languageCode="eng-GB">testContentQueryWithTags</value>
119
  </names>
120
  <remoteId>testContentQueryWithTags</remoteId>
121
  <urlAliasSchema>&lt;title&gt;</urlAliasSchema>
122
  <nameSchema>&lt;title&gt;</nameSchema>
123
  <isContainer>true</isContainer>
124
  <mainLanguageCode>eng-GB</mainLanguageCode>
125
  <defaultAlwaysAvailable>true</defaultAlwaysAvailable>
126
  <defaultSortField>PATH</defaultSortField>
127
  <defaultSortOrder>ASC</defaultSortOrder>
128
  <FieldDefinitions>
129
    <FieldDefinition>
130
      <identifier>title</identifier>
131
      <fieldType>ezstring</fieldType>
132
      <fieldGroup>content</fieldGroup>
133
      <position>1</position>
134
      <isTranslatable>true</isTranslatable>
135
      <isRequired>true</isRequired>
136
      <isInfoCollector>false</isInfoCollector>
137
      <defaultValue>New Title</defaultValue>
138
      <isSearchable>true</isSearchable>
139
      <names>
140
        <value languageCode="eng-GB">Title</value>
141
      </names>
142
      <descriptions>
143
        <value languageCode="eng-GB">This is the title</value>
144
      </descriptions>
145
    </FieldDefinition>
146
    <FieldDefinition>
147
      <identifier>tags</identifier>
148
      <fieldType>ezkeyword</fieldType>
149
      <fieldGroup>content</fieldGroup>
150
      <position>2</position>
151
      <isTranslatable>true</isTranslatable>
152
      <isRequired>true</isRequired>
153
      <isInfoCollector>false</isInfoCollector>
154
      <isSearchable>true</isSearchable>
155
      <names>
156
        <value languageCode="eng-GB">Tags</value>
157
      </names>
158
      <descriptions>
159
        <value languageCode="eng-GB">Those are searchable tags</value>
160
      </descriptions>
161
    </FieldDefinition>
162
   </FieldDefinitions>
163
</ContentTypeCreate>
164
XML;
165
166
        $request = $this->createHttpRequest(
167
            'POST',
168
            '/api/ezp/v2/content/typegroups/1/types?publish=true',
169
            'ContentTypeCreate+xml',
170
            'ContentType+json',
171
            $body
172
        );
173
        $response = $this->sendHttpRequest($request);
174
175
        self::assertHttpResponseHasHeader($response, 'Location');
176
177
        return $response->getHeader('Location')[0];
178
    }
179
180
    /**
181
     * @param string[] $tags
182
     *
183
     * @throws \Psr\Http\Client\ClientException
184
     */
185
    private function createTestContentWithTags(string $name, array $tags): string
186
    {
187
        $tagsString = implode(',', $tags);
188
        $body = <<< XML
189
<?xml version="1.0" encoding="UTF-8"?>
190
<ContentCreate>
191
  <ContentType href="$this->contentTypeHref" />
192
  <mainLanguageCode>eng-GB</mainLanguageCode>
193
  <LocationCreate>
194
    <ParentLocation href="/api/ezp/v2/content/locations/1" />
195
    <priority>0</priority>
196
    <hidden>false</hidden>
197
    <sortField>PATH</sortField>
198
    <sortOrder>ASC</sortOrder>
199
  </LocationCreate>
200
  <Section href="/api/ezp/v2/content/sections/1" />
201
  <alwaysAvailable>true</alwaysAvailable>
202
  <remoteId>$name</remoteId>
203
  <User href="/api/ezp/v2/user/users/14" />
204
  <modificationDate>2018-01-30T18:30:00</modificationDate>
205
  <fields>
206
    <field>
207
      <fieldDefinitionIdentifier>title</fieldDefinitionIdentifier>
208
      <languageCode>eng-GB</languageCode>
209
      <fieldValue>$name</fieldValue>
210
    </field>
211
    <field>
212
      <fieldDefinitionIdentifier>tags</fieldDefinitionIdentifier>
213
      <languageCode>eng-GB</languageCode>
214
      <fieldValue>$tagsString</fieldValue>
215
    </field>
216
    </fields>
217
</ContentCreate>
218
XML;
219
        $request = $this->createHttpRequest(
220
            'POST',
221
            '/api/ezp/v2/content/objects',
222
            'ContentCreate+xml',
223
            'ContentInfo+json',
224
            $body
225
        );
226
        $response = $this->sendHttpRequest($request);
227
228
        self::assertHttpResponseHasHeader($response, 'Location');
229
        $href = $response->getHeader('Location')[0];
230
        $this->sendHttpRequest(
231
            $this->createHttpRequest('PUBLISH', "$href/versions/1")
232
        );
233
234
        return $href;
235
    }
236
237
    /**
238
     * @throws \Psr\Http\Client\ClientException
239
     */
240
    private function deleteContent(string $href): void
241
    {
242
        $this->sendHttpRequest(
243
            $this->createHttpRequest('DELETE', $href)
244
        );
245
    }
246
247
    public function xmlProvider(): array
248
    {
249
        $fooTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'foo');
250
        $barTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'bar');
251
        $bazTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'baz');
252
        $foobazTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'foobaz');
253
        $foobazInTag = $this->buildFieldXml('tags', Operator::IN, ['foobaz']);
254
        $bazfooInTag = $this->buildFieldXml('tags', Operator::IN, ['bazfoo']);
255
        $fooAndBarInTag = $this->buildFieldXml('tags', Operator::IN, ['foo', 'bar']);
256
257
        return [
258
            [
259
                $this->getXmlString(
260
                    $this->wrapIn('AND', [$fooTag, $barTag])
261
                ),
262
                1,
263
            ],
264
            [
265
                $this->getXmlString(
266
                    $this->wrapIn('OR', [
267
                        $this->wrapIn('AND', [$fooTag, $barTag]),
268
                        $this->wrapIn('AND', [$bazTag, $foobazTag]),
269
                    ])
270
                ),
271
                2,
272
            ],
273
            [
274
                $this->getXmlString(
275
                    $this->wrapIn('AND', [
276
                        $this->wrapIn('NOT', [$fooTag]),
277
                        $barTag,
278
                    ])
279
                ),
280
                1,
281
            ],
282
            [
283
                $this->getXmlString(
284
                    $this->wrapIn('OR', [
285
                        $foobazInTag,
286
                        $bazfooInTag,
287
                    ])
288
                ),
289
                2,
290
            ],
291
            [
292
                $this->getXmlString($fooAndBarInTag),
293
                2,
294
            ],
295
        ];
296
    }
297
298 View Code Duplication
    public function jsonProvider(): array
299
    {
300
        return [
301
            [
302
                <<< JSON
303
"OR": [
304
    {
305
        "ContentRemoteIdCriterion": "test-name"
306
    },
307
    {
308
        "ContentRemoteIdCriterion": "fancy-name"
309
    }
310
]
311
JSON
312
,
313
                2,
314
            ],
315
            [
316
                <<< JSON
317
"OR": {
318
    "ContentRemoteIdCriterion": [
319
        "test-name",
320
        "fancy-name"
321
    ]
322
}
323
JSON
324
,
325
            2,
326
            ],
327
            [
328
                <<< JSON
329
"AND": {
330
    "OR": {
331
        "ContentRemoteIdCriterion": [
332
            "test-name",
333
            "fancy-name"
334
        ]
335
    },
336
    "ContentRemoteIdCriterion": "test-name"
337
}
338
JSON
339
,
340
                1,
341
            ],
342
        ];
343
    }
344
345
    /**
346
     * @param string $name
347
     * @param string $operator
348
     * @param string|string[] $value
349
     * @return DOMElement
350
     */
351
    private function buildFieldXml(string $name, string $operator, $value): DOMElement
352
    {
353
        $xml = new DOMDocument();
354
        $element = $xml->createElement('Field');
355
        $element->appendChild(new DOMElement('name', $name));
356
        $element->appendChild(new DOMElement('operator', $operator));
357
358
        //Force xml array with one value
359
        if (is_array($value)) {
360
            if (count($value) === 1) {
361
                $valueWrapper = $xml->createElement('value');
362
                $valueWrapper->appendChild(new DOMElement('value', $value[0]));
363
                $element->appendChild($valueWrapper);
364
            } else {
365
                foreach ($value as $singleValue) {
366
                    $element->appendChild(new DOMElement('value', $singleValue));
367
                }
368
            }
369
        } else {
370
            $element->appendChild(new DOMElement('value', $value));
371
        }
372
373
        return $element;
374
    }
375
376
    private function wrapIn(string $logicalOperator, array $toWrap): DOMElement
377
    {
378
        $xml = new DOMDocument();
379
        $wrapper = $xml->createElement($logicalOperator);
380
381
        foreach ($toWrap as $field) {
382
            $innerWrapper = $xml->createElement($logicalOperator);
383
            $innerWrapper->appendChild($xml->importNode($field, true));
384
            $wrapper->appendChild($innerWrapper);
385
        }
386
387
        return $wrapper;
388
    }
389
390
    private function getXmlString(DOMElement $simpleXMLElement): string
391
    {
392
        return $simpleXMLElement->ownerDocument->saveXML($simpleXMLElement);
393
    }
394
395
    /**
396
     * This is just to assure that field with same name but without legacy search engine implementation
397
     * does not block search in different content type.
398
     *
399
     * @throws \Psr\Http\Client\ClientException
400
     */
401
    private function createContentWithUrlField(): string
402
    {
403
        $body = <<< XML
404
<?xml version="1.0" encoding="UTF-8"?>
405
<ContentTypeCreate>
406
  <identifier>rich-text-test</identifier>
407
  <names>
408
    <value languageCode="eng-GB">urlContentType</value>
409
  </names>
410
  <remoteId>testUrlContentType</remoteId>
411
  <urlAliasSchema>&lt;title&gt;</urlAliasSchema>
412
  <nameSchema>&lt;title&gt;</nameSchema>
413
  <isContainer>true</isContainer>
414
  <mainLanguageCode>eng-GB</mainLanguageCode>
415
  <defaultAlwaysAvailable>true</defaultAlwaysAvailable>
416
  <defaultSortField>PATH</defaultSortField>
417
  <defaultSortOrder>ASC</defaultSortOrder>
418
  <FieldDefinitions>
419
    <FieldDefinition>
420
      <identifier>title</identifier>
421
      <fieldType>ezurl</fieldType>
422
      <fieldGroup>content</fieldGroup>
423
      <position>1</position>
424
      <isTranslatable>true</isTranslatable>
425
      <isRequired>true</isRequired>
426
      <isInfoCollector>false</isInfoCollector>
427
      <names>
428
        <value languageCode="eng-GB">Title</value>
429
      </names>
430
      <descriptions>
431
        <value languageCode="eng-GB">This is the title but in url type</value>
432
      </descriptions>
433
    </FieldDefinition>
434
   </FieldDefinitions>
435
</ContentTypeCreate>
436
XML;
437
438
        $request = $this->createHttpRequest(
439
            'POST',
440
            '/api/ezp/v2/content/typegroups/1/types?publish=true',
441
            'ContentTypeCreate+xml',
442
            'ContentType+json',
443
            $body
444
        );
445
446
        $response = $this->sendHttpRequest($request);
447
        self::assertHttpResponseHasHeader($response, 'Location');
448
449
        return $response->getHeader('Location')[0];
450
    }
451
452
    /**
453
     * Perform search View Query providing payload ($body) in a given $format.
454
     *
455
     * @param string $format xml or json
456
     *
457
     * @throws \Psr\Http\Client\ClientException
458
     */
459
    private function getQueryResultsCount(string $format, string $body): int
460
    {
461
        $request = $this->createHttpRequest(
462
            'POST',
463
            '/api/ezp/v2/views',
464
            "ViewInput+{$format}; version=1.1",
465
            'View+json',
466
            $body
467
        );
468
        $response = $this->sendHttpRequest($request);
469
470
        self::assertHttpResponseCodeEquals($response, 200);
471
        $jsonResponse = json_decode($response->getBody()->getContents());
472
473
        if (isset($jsonResponse->ErrorMessage)) {
474
            self::fail(var_export($jsonResponse));
475
        }
476
477
        return $jsonResponse->View->Result->count;
478
    }
479
}
480