Completed
Push — master ( 945efb...5cd333 )
by André
85:29 queued 68:53
created

SearchViewTest::testSimpleContentQuery()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 2
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Functional\SearchViewTest class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
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
    /**
19
     * @var string
20
     */
21
    protected $contentTypeHref;
22
23
    /**
24
     * @var string[]
25
     */
26
    protected $contentHrefList;
27
28
    /**
29
     * @var string
30
     */
31
    private $nonSearchableContentHref;
32
33
    protected function setUp()
34
    {
35
        parent::setUp();
36
        $this->contentTypeHref = $this->createTestContentType();
37
        $this->nonSearchableContentHref = $this->createContentWithUrlField();
38
        $this->contentHrefList[] = $this->createTestContentWithTags('test-name', ['foo', 'bar']);
39
        $this->contentHrefList[] = $this->createTestContentWithTags('fancy-name', ['baz', 'foobaz']);
40
        $this->contentHrefList[] = $this->createTestContentWithTags('even-fancier', ['bar', 'bazfoo']);
41
    }
42
43
    protected function tearDown()
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
     * @dataProvider xmlProvider
53
     * Covers POST with ContentQuery Logic on /api/ezp/v2/views.
54
     *
55
     * @param string $xmlQueryBody
56
     * @param int $expectedCount
57
     */
58
    public function testSimpleContentQuery(string $xmlQueryBody, int $expectedCount)
59
    {
60
        $body = <<< XML
61
<?xml version="1.0" encoding="UTF-8"?>
62
<ViewInput>
63
<identifier>your-query-id</identifier>
64
<public>false</public>
65
<ContentQuery>
66
  <Query>
67
    $xmlQueryBody
68
  </Query>  
69
  <limit>10</limit>  
70
  <offset>0</offset> 
71
</ContentQuery>
72
</ViewInput>
73
XML;
74
        $request = $this->createHttpRequest(
75
            'POST',
76
            '/api/ezp/v2/views',
77
            'ViewInput+xml; version=1.1',
78
            'ContentInfo+json',
79
            $body
80
        );
81
        $response = $this->sendHttpRequest($request);
82
83
        self::assertHttpResponseCodeEquals($response, 200);
84
        $jsonResponse = json_decode($response->getBody());
85
        self::assertEquals($expectedCount, $jsonResponse->View->Result->count);
86
    }
87
88
    private function createTestContentType(): string
89
    {
90
        $body = <<< XML
91
<?xml version="1.0" encoding="UTF-8"?>
92
<ContentTypeCreate>
93
  <identifier>tags-test</identifier>
94
  <names>
95
    <value languageCode="eng-GB">testContentQueryWithTags</value>
96
  </names>
97
  <remoteId>testContentQueryWithTags</remoteId>
98
  <urlAliasSchema>&lt;title&gt;</urlAliasSchema>
99
  <nameSchema>&lt;title&gt;</nameSchema>
100
  <isContainer>true</isContainer>
101
  <mainLanguageCode>eng-GB</mainLanguageCode>
102
  <defaultAlwaysAvailable>true</defaultAlwaysAvailable>
103
  <defaultSortField>PATH</defaultSortField>
104
  <defaultSortOrder>ASC</defaultSortOrder>
105
  <FieldDefinitions>
106
    <FieldDefinition>
107
      <identifier>title</identifier>
108
      <fieldType>ezstring</fieldType>
109
      <fieldGroup>content</fieldGroup>
110
      <position>1</position>
111
      <isTranslatable>true</isTranslatable>
112
      <isRequired>true</isRequired>
113
      <isInfoCollector>false</isInfoCollector>
114
      <defaultValue>New Title</defaultValue>
115
      <isSearchable>true</isSearchable>
116
      <names>
117
        <value languageCode="eng-GB">Title</value>
118
      </names>
119
      <descriptions>
120
        <value languageCode="eng-GB">This is the title</value>
121
      </descriptions>
122
    </FieldDefinition>
123
    <FieldDefinition>
124
      <identifier>tags</identifier>
125
      <fieldType>ezkeyword</fieldType>
126
      <fieldGroup>content</fieldGroup>
127
      <position>2</position>
128
      <isTranslatable>true</isTranslatable>
129
      <isRequired>true</isRequired>
130
      <isInfoCollector>false</isInfoCollector>
131
      <isSearchable>true</isSearchable>
132
      <names>
133
        <value languageCode="eng-GB">Tags</value>
134
      </names>
135
      <descriptions>
136
        <value languageCode="eng-GB">Those are searchable tags</value>
137
      </descriptions>
138
    </FieldDefinition>
139
   </FieldDefinitions>
140
</ContentTypeCreate>
141
XML;
142
143
        $request = $this->createHttpRequest(
144
            'POST',
145
            '/api/ezp/v2/content/typegroups/1/types?publish=true',
146
            'ContentTypeCreate+xml',
147
            'ContentType+json',
148
            $body
149
        );
150
        $response = $this->sendHttpRequest($request);
151
152
        self::assertHttpResponseHasHeader($response, 'Location');
153
154
        return $response->getHeader('Location')[0];
155
    }
156
157
    private function createTestContentWithTags(string $name, array $tags): string
158
    {
159
        $tagsString = implode(',', $tags);
160
        $body = <<< XML
161
<?xml version="1.0" encoding="UTF-8"?>
162
<ContentCreate>
163
  <ContentType href="$this->contentTypeHref" />
164
  <mainLanguageCode>eng-GB</mainLanguageCode>
165
  <LocationCreate>
166
    <ParentLocation href="/api/ezp/v2/content/locations/1" />
167
    <priority>0</priority>
168
    <hidden>false</hidden>
169
    <sortField>PATH</sortField>
170
    <sortOrder>ASC</sortOrder>
171
  </LocationCreate>
172
  <Section href="/api/ezp/v2/content/sections/1" />
173
  <alwaysAvailable>true</alwaysAvailable>
174
  <remoteId>$name</remoteId>
175
  <User href="/api/ezp/v2/user/users/14" />
176
  <modificationDate>2018-01-30T18:30:00</modificationDate>
177
  <fields>
178
    <field>
179
      <fieldDefinitionIdentifier>title</fieldDefinitionIdentifier>
180
      <languageCode>eng-GB</languageCode>
181
      <fieldValue>$name</fieldValue>
182
    </field>
183
    <field>
184
      <fieldDefinitionIdentifier>tags</fieldDefinitionIdentifier>
185
      <languageCode>eng-GB</languageCode>
186
      <fieldValue>$tagsString</fieldValue>
187
    </field>
188
    </fields>
189
</ContentCreate>
190
XML;
191
        $request = $this->createHttpRequest(
192
            'POST',
193
            '/api/ezp/v2/content/objects',
194
            'ContentCreate+xml',
195
            'ContentInfo+json',
196
            $body
197
        );
198
        $response = $this->sendHttpRequest($request);
199
200
        self::assertHttpResponseHasHeader($response, 'Location');
201
        $href = $response->getHeader('Location')[0];
202
        $this->sendHttpRequest(
203
            $this->createHttpRequest('PUBLISH', "$href/versions/1")
204
        );
205
206
        return $href;
207
    }
208
209
    private function deleteContent($href)
210
    {
211
        $this->sendHttpRequest(
212
            $this->createHttpRequest('DELETE', $href)
213
        );
214
    }
215
216
    public function xmlProvider()
217
    {
218
        $fooTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'foo');
219
        $barTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'bar');
220
        $bazTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'baz');
221
        $foobazTag = $this->buildFieldXml('tags', Operator::CONTAINS, 'foobaz');
222
        $foobazInTag = $this->buildFieldXml('tags', Operator::IN, ['foobaz']);
223
        $bazfooInTag = $this->buildFieldXml('tags', Operator::IN, ['bazfoo']);
224
        $fooAndBarInTag = $this->buildFieldXml('tags', Operator::IN, ['foo', 'bar']);
225
226
        return [
227
            [
228
                $this->getXmlString(
229
                    $this->wrapIn('AND', [$fooTag, $barTag])
230
                ),
231
                1,
232
            ],
233
            [
234
                $this->getXmlString(
235
                    $this->wrapIn('OR', [
236
                        $this->wrapIn('AND', [$fooTag, $barTag]),
237
                        $this->wrapIn('AND', [$bazTag, $foobazTag]),
238
                    ])
239
                ),
240
                2,
241
            ],
242
            [
243
                $this->getXmlString(
244
                    $this->wrapIn('AND', [
245
                        $this->wrapIn('NOT', [$fooTag]),
246
                        $barTag,
247
                    ])
248
                ),
249
                1,
250
            ],
251
            [
252
                $this->getXmlString(
253
                    $this->wrapIn('OR', [
254
                        $foobazInTag,
255
                        $bazfooInTag,
256
                    ])
257
                ),
258
                2,
259
            ],
260
            [
261
                $this->getXmlString($fooAndBarInTag),
262
                2,
263
            ],
264
        ];
265
    }
266
267
    /**
268
     * @param string $name
269
     * @param string $operator
270
     * @param string|string[] $value
271
     * @return DOMElement
272
     */
273
    private function buildFieldXml(string $name, string $operator, $value): DOMElement
274
    {
275
        $xml = new DOMDocument();
276
        $element = $xml->createElement('Field');
277
        $element->appendChild(new DOMElement('name', $name));
278
        $element->appendChild(new DOMElement('operator', $operator));
279
280
        //Force xml array with one value
281
        if (is_array($value)) {
282
            if (count($value) === 1) {
283
                $valueWrapper = $xml->createElement('value');
284
                $valueWrapper->appendChild(new DOMElement('value', $value[0]));
285
                $element->appendChild($valueWrapper);
286
            } else {
287
                foreach ($value as $key => $singleValue) {
288
                    $element->appendChild(new DOMElement('value', $singleValue));
289
                }
290
            }
291
        } else {
292
            $element->appendChild(new DOMElement('value', $value));
293
        }
294
295
        return $element;
296
    }
297
298
    private function wrapIn(string $logicalOperator, array $toWrap): DOMElement
299
    {
300
        $xml = new DOMDocument();
301
        $wrapper = $xml->createElement($logicalOperator);
302
303
        foreach ($toWrap as $key => $field) {
304
            $innerWrapper = $xml->createElement($logicalOperator);
305
            $innerWrapper->appendChild($xml->importNode($field, true));
306
            $wrapper->appendChild($innerWrapper);
307
        }
308
309
        return $wrapper;
310
    }
311
312
    private function getXmlString(DOMElement $simpleXMLElement): string
313
    {
314
        return $simpleXMLElement->ownerDocument->saveXML($simpleXMLElement);
315
    }
316
317
    /**
318
     * This is just to assure that field with same name but without legacy search engine implementation
319
     * does not block search in different content type.
320
     */
321
    private function createContentWithUrlField(): string
322
    {
323
        $body = <<< XML
324
<?xml version="1.0" encoding="UTF-8"?>
325
<ContentTypeCreate>
326
  <identifier>rich-text-test</identifier>
327
  <names>
328
    <value languageCode="eng-GB">urlContentType</value>
329
  </names>
330
  <remoteId>testUrlContentType</remoteId>
331
  <urlAliasSchema>&lt;title&gt;</urlAliasSchema>
332
  <nameSchema>&lt;title&gt;</nameSchema>
333
  <isContainer>true</isContainer>
334
  <mainLanguageCode>eng-GB</mainLanguageCode>
335
  <defaultAlwaysAvailable>true</defaultAlwaysAvailable>
336
  <defaultSortField>PATH</defaultSortField>
337
  <defaultSortOrder>ASC</defaultSortOrder>
338
  <FieldDefinitions>
339
    <FieldDefinition>
340
      <identifier>title</identifier>
341
      <fieldType>ezurl</fieldType>
342
      <fieldGroup>content</fieldGroup>
343
      <position>1</position>
344
      <isTranslatable>true</isTranslatable>
345
      <isRequired>true</isRequired>
346
      <isInfoCollector>false</isInfoCollector>
347
      <names>
348
        <value languageCode="eng-GB">Title</value>
349
      </names>
350
      <descriptions>
351
        <value languageCode="eng-GB">This is the title but in url type</value>
352
      </descriptions>
353
    </FieldDefinition>
354
   </FieldDefinitions>
355
</ContentTypeCreate>
356
XML;
357
358
        $request = $this->createHttpRequest(
359
            'POST',
360
            '/api/ezp/v2/content/typegroups/1/types?publish=true',
361
            'ContentTypeCreate+xml',
362
            'ContentType+json',
363
            $body
364
        );
365
366
        $response = $this->sendHttpRequest($request);
367
        self::assertHttpResponseHasHeader($response, 'Location');
368
369
        return $response->getHeader('Location')[0];
370
    }
371
}
372