Completed
Push — 7.5 ( 05f391...b433d9 )
by
unknown
19:31 queued 10s
created

SearchViewTest   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 350
rs 10
c 0
b 0
f 0
wmc 15
lcom 2
cbo 2
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
    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
                2,
313
            ],
314
            [
315
                <<< JSON
316
"OR": {
317
    "ContentRemoteIdCriterion": [
318
        "test-name",
319
        "fancy-name"
320
    ]
321
}
322
JSON,
323
            2,
324
            ],
325
            [
326
                <<< JSON
327
"AND": {
328
    "OR": {
329
        "ContentRemoteIdCriterion": [
330
            "test-name",
331
            "fancy-name"
332
        ]
333
    },
334
    "ContentRemoteIdCriterion": "test-name"
335
}
336
JSON,
337
                1,
338
            ],
339
        ];
340
    }
341
342
    /**
343
     * @param string $name
344
     * @param string $operator
345
     * @param string|string[] $value
346
     * @return DOMElement
347
     */
348
    private function buildFieldXml(string $name, string $operator, $value): DOMElement
349
    {
350
        $xml = new DOMDocument();
351
        $element = $xml->createElement('Field');
352
        $element->appendChild(new DOMElement('name', $name));
353
        $element->appendChild(new DOMElement('operator', $operator));
354
355
        //Force xml array with one value
356
        if (is_array($value)) {
357
            if (count($value) === 1) {
358
                $valueWrapper = $xml->createElement('value');
359
                $valueWrapper->appendChild(new DOMElement('value', $value[0]));
360
                $element->appendChild($valueWrapper);
361
            } else {
362
                foreach ($value as $singleValue) {
363
                    $element->appendChild(new DOMElement('value', $singleValue));
364
                }
365
            }
366
        } else {
367
            $element->appendChild(new DOMElement('value', $value));
368
        }
369
370
        return $element;
371
    }
372
373
    private function wrapIn(string $logicalOperator, array $toWrap): DOMElement
374
    {
375
        $xml = new DOMDocument();
376
        $wrapper = $xml->createElement($logicalOperator);
377
378
        foreach ($toWrap as $field) {
379
            $innerWrapper = $xml->createElement($logicalOperator);
380
            $innerWrapper->appendChild($xml->importNode($field, true));
381
            $wrapper->appendChild($innerWrapper);
382
        }
383
384
        return $wrapper;
385
    }
386
387
    private function getXmlString(DOMElement $simpleXMLElement): string
388
    {
389
        return $simpleXMLElement->ownerDocument->saveXML($simpleXMLElement);
390
    }
391
392
    /**
393
     * This is just to assure that field with same name but without legacy search engine implementation
394
     * does not block search in different content type.
395
     *
396
     * @throws \Psr\Http\Client\ClientException
397
     */
398
    private function createContentWithUrlField(): string
399
    {
400
        $body = <<< XML
401
<?xml version="1.0" encoding="UTF-8"?>
402
<ContentTypeCreate>
403
  <identifier>rich-text-test</identifier>
404
  <names>
405
    <value languageCode="eng-GB">urlContentType</value>
406
  </names>
407
  <remoteId>testUrlContentType</remoteId>
408
  <urlAliasSchema>&lt;title&gt;</urlAliasSchema>
409
  <nameSchema>&lt;title&gt;</nameSchema>
410
  <isContainer>true</isContainer>
411
  <mainLanguageCode>eng-GB</mainLanguageCode>
412
  <defaultAlwaysAvailable>true</defaultAlwaysAvailable>
413
  <defaultSortField>PATH</defaultSortField>
414
  <defaultSortOrder>ASC</defaultSortOrder>
415
  <FieldDefinitions>
416
    <FieldDefinition>
417
      <identifier>title</identifier>
418
      <fieldType>ezurl</fieldType>
419
      <fieldGroup>content</fieldGroup>
420
      <position>1</position>
421
      <isTranslatable>true</isTranslatable>
422
      <isRequired>true</isRequired>
423
      <isInfoCollector>false</isInfoCollector>
424
      <names>
425
        <value languageCode="eng-GB">Title</value>
426
      </names>
427
      <descriptions>
428
        <value languageCode="eng-GB">This is the title but in url type</value>
429
      </descriptions>
430
    </FieldDefinition>
431
   </FieldDefinitions>
432
</ContentTypeCreate>
433
XML;
434
435
        $request = $this->createHttpRequest(
436
            'POST',
437
            '/api/ezp/v2/content/typegroups/1/types?publish=true',
438
            'ContentTypeCreate+xml',
439
            'ContentType+json',
440
            $body
441
        );
442
443
        $response = $this->sendHttpRequest($request);
444
        self::assertHttpResponseHasHeader($response, 'Location');
445
446
        return $response->getHeader('Location')[0];
447
    }
448
449
    /**
450
     * Perform search View Query providing payload ($body) in a given $format.
451
     *
452
     * @param string $format xml or json
453
     *
454
     * @throws \Psr\Http\Client\ClientException
455
     */
456
    private function getQueryResultsCount(string $format, string $body): int
457
    {
458
        $request = $this->createHttpRequest(
459
            'POST',
460
            '/api/ezp/v2/views',
461
            "ViewInput+{$format}; version=1.1",
462
            'View+json',
463
            $body
464
        );
465
        $response = $this->sendHttpRequest($request);
466
467
        self::assertHttpResponseCodeEquals($response, 200);
468
        $jsonResponse = json_decode($response->getBody()->getContents());
469
470
        if (isset($jsonResponse->ErrorMessage)) {
471
            self::fail(var_export($jsonResponse));
472
        }
473
474
        return $jsonResponse->View->Result->count;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected EOF
Loading history...
475
    }
476
}
477