testFilterApplyArrayFilterCriteria()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 50
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 2
eloc 30
nc 2
nop 0
dl 0
loc 50
rs 9.44
c 5
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\DoctrineQueryFilter;
6
7
use Arp\DoctrineQueryFilter\Exception\QueryFilterManagerException;
8
use Arp\DoctrineQueryFilter\Filter\Exception\FilterException;
9
use Arp\DoctrineQueryFilter\Filter\Exception\FilterFactoryException;
10
use Arp\DoctrineQueryFilter\Filter\FilterFactoryInterface;
11
use Arp\DoctrineQueryFilter\Filter\FilterInterface;
12
use Arp\DoctrineQueryFilter\Filter\IsEqual;
13
use Arp\DoctrineQueryFilter\Filter\IsNotEqual;
14
use Arp\DoctrineQueryFilter\Metadata\MetadataInterface;
15
use Arp\DoctrineQueryFilter\QueryBuilderInterface;
16
use Arp\DoctrineQueryFilter\QueryFilterManager;
17
use Arp\DoctrineQueryFilter\QueryFilterManagerInterface;
18
use Arp\DoctrineQueryFilter\Sort\SortFactoryInterface;
19
use Doctrine\ORM\EntityManager;
20
use Doctrine\ORM\EntityManagerInterface;
21
use Doctrine\ORM\Mapping\ClassMetadata;
22
use PHPUnit\Framework\MockObject\MockObject;
23
use PHPUnit\Framework\TestCase;
24
25
/**
26
 * @covers \Arp\DoctrineQueryFilter\QueryFilterManager
27
 */
28
final class QueryFilterManagerTest extends TestCase
29
{
30
    /**
31
     * @var FilterFactoryInterface&MockObject
32
     */
33
    private FilterFactoryInterface $filterFactory;
34
35
    /**
36
     * @var SortFactoryInterface&MockObject
37
     */
38
    private SortFactoryInterface $sortFactory;
39
40
    /**
41
     * @var EntityManagerInterface&MockObject
42
     */
43
    private EntityManagerInterface $entityManager;
44
45
    /**
46
     * @var QueryBuilderInterface&MockObject
47
     */
48
    private QueryBuilderInterface $queryBuilder;
49
50
    /**
51
     * @var ClassMetadata<object>&MockObject
52
     */
53
    private ClassMetadata $metadata;
54
55
    private string $entityName;
56
57
    public function setUp(): void
58
    {
59
        $this->entityName = 'TestEntityName';
60
61
        $this->filterFactory = $this->createMock(FilterFactoryInterface::class);
62
        $this->sortFactory = $this->createMock(SortFactoryInterface::class);
63
        $this->queryBuilder = $this->createMock(QueryBuilderInterface::class);
64
        $this->entityManager = $this->createMock(EntityManager::class);
65
        $this->metadata = $this->createMock(ClassMetadata::class);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->createMock(Doctri...g\ClassMetadata::class) of type PHPUnit\Framework\MockObject\MockObject is incompatible with the declared type Doctrine\ORM\Mapping\ClassMetadata of property $metadata.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66
    }
67
68
    /**
69
     * Assert that the manager implements QueryFilterManagerInterface
70
     */
71
    public function testImplementsQueryFilterManagerInterface(): void
72
    {
73
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
74
75
        $this->assertInstanceOf(QueryFilterManagerInterface::class, $manager);
76
    }
77
78
    /**
79
     * Assert no filtering will be applied if filter() is provided configuration without the required 'filters' key
80
     *
81
     * @throws QueryFilterManagerException
82
     */
83
    public function testFilterWillNotPerformFilteringWithoutFilterKey(): void
84
    {
85
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
86
87
        $this->queryBuilder->expects($this->once())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Arp\DoctrineQueryFilter\QueryBuilderInterface. ( Ignorable by Annotation )

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

87
        $this->queryBuilder->/** @scrutinizer ignore-call */ 
88
                             expects($this->once())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
88
            ->method('getEntityManager')
89
            ->willReturn($this->entityManager);
90
91
        $this->entityManager->expects($this->once())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Doctrine\ORM\EntityManagerInterface. ( Ignorable by Annotation )

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

91
        $this->entityManager->/** @scrutinizer ignore-call */ 
92
                              expects($this->once())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
92
            ->method('getClassMetadata')
93
            ->with($this->entityName)
94
            ->willReturn($this->metadata);
95
96
        $this->filterFactory->expects($this->never())->method('create');
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Arp\DoctrineQueryFilter\...\FilterFactoryInterface. ( Ignorable by Annotation )

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

96
        $this->filterFactory->/** @scrutinizer ignore-call */ 
97
                              expects($this->never())->method('create');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
97
98
        $manager->filter($this->queryBuilder, $this->entityName, []);
99
    }
100
101
    /**
102
     * Assert that failure to create a entity metadata instance will result in a
103
     * QueryFilterManagerException being thrown
104
     *
105
     * @throws QueryFilterManagerException
106
     */
107
    public function testFilterWillThrowQueryFilterManagerExceptionIfProvidedWithAnInvalidEntityName(): void
108
    {
109
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
110
111
        $criteria = [
112
            'filters' => [
113
                [
114
                    'name' => 'foo',
115
                ],
116
            ],
117
        ];
118
119
        $this->queryBuilder->expects($this->once())
120
            ->method('getEntityManager')
121
            ->willReturn($this->entityManager);
122
123
        $exceptionCode = 123;
124
        $exception = new \Exception('This is an exception message', $exceptionCode);
125
126
        $this->entityManager->expects($this->once())
127
            ->method('getClassMetadata')
128
            ->with($this->entityName)
129
            ->willThrowException($exception);
130
131
        $this->expectException(QueryFilterManagerException::class);
132
        $this->expectExceptionCode($exceptionCode);
133
        $this->expectExceptionMessage(sprintf('Failed to fetch entity metadata for class \'%s\'', $this->entityName));
134
135
        $manager->filter($this->queryBuilder, $this->entityName, $criteria);
136
    }
137
138
    /**
139
     * Assert that a QueryFilterManagerException if thrown when providing an array query filter specification that
140
     * does not contain a 'name' property
141
     *
142
     * @throws QueryFilterManagerException
143
     */
144
    public function testMissingFilterNameWillThrowQueryFilterManagerException(): void
145
    {
146
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
147
148
        $criteria = [
149
            'filters' => [
150
                [],
151
            ],
152
        ];
153
154
        $this->queryBuilder->expects($this->once())
155
            ->method('getEntityManager')
156
            ->willReturn($this->entityManager);
157
158
        $this->entityManager->expects($this->once())
159
            ->method('getClassMetadata')
160
            ->with($this->entityName)
161
            ->willReturn($this->metadata);
162
163
        $this->expectException(QueryFilterManagerException::class);
164
        $this->expectExceptionMessage(
165
            sprintf('The required \'name\' configuration option is missing in \'%s\'', QueryFilterManager::class)
166
        );
167
168
        $manager->filter($this->queryBuilder, $this->entityName, $criteria);
169
    }
170
171
    /**
172
     * Assert that a QueryFilterManagerException is thrown when unable to create the filters in createFilter().
173
     *
174
     * @throws QueryFilterManagerException
175
     */
176
    public function testFailureToCreateFilterWillResultInQueryFilterManagerException(): void
177
    {
178
        $filterName = 'eq';
179
        $filterOptions = [
180
            'testing' => 123,
181
            'hello'   => 'world!',
182
        ];
183
184
        /** @var FilterInterface[]&MockObject[] $filters */
185
        $filters = [
186
            [
187
                'name'    => $filterName,
188
                'options' => $filterOptions, // Creation options
189
            ],
190
        ];
191
192
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
193
194
        $criteria = [
195
            'filters' => $filters,
196
        ];
197
198
        $this->queryBuilder->expects($this->once())
199
            ->method('getEntityManager')
200
            ->willReturn($this->entityManager);
201
202
        $this->entityManager->expects($this->once())
203
            ->method('getClassMetadata')
204
            ->with($this->entityName)
205
            ->willReturn($this->metadata);
206
207
        $exceptionCode = 456;
208
        $filterException = new FilterFactoryException(
209
            'This is a test filter factory exception message',
210
            $exceptionCode
211
        );
212
213
        $this->filterFactory->expects($this->once())
214
            ->method('create')
215
            ->with($manager, $filterName, $filterOptions)
216
            ->willThrowException($filterException);
217
218
        $this->expectException(QueryFilterManagerException::class);
219
        $this->expectExceptionCode($exceptionCode);
220
        $this->expectExceptionMessage(sprintf('Failed to create filter \'%s\'', $filterName));
221
222
        $manager->filter($this->queryBuilder, $this->entityName, $criteria);
223
    }
224
225
    /**
226
     * Assert that a QueryFilterManagerException is thrown when unable to apply the filters in applyFilter().
227
     *
228
     * @throws QueryFilterManagerException
229
     */
230
    public function testFailureToApplyFilterWillResultInQueryFilterManagerException(): void
231
    {
232
        /** @var FilterInterface[]&MockObject[] $filters */
233
        $filters = [
234
            $this->createMock(FilterInterface::class),
235
            $this->createMock(FilterInterface::class),
236
        ];
237
238
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
239
240
        $criteria = [
241
            'filters' => $filters,
242
        ];
243
244
        $this->queryBuilder->expects($this->once())
245
            ->method('getEntityManager')
246
            ->willReturn($this->entityManager);
247
248
        $this->entityManager->expects($this->once())
249
            ->method('getClassMetadata')
250
            ->with($this->entityName)
251
            ->willReturn($this->metadata);
252
253
        $exceptionCode = 999;
254
        $filterException = new FilterException('This is a test filter exception message', $exceptionCode);
255
256
        $filters[0]->expects($this->once())
257
            ->method('filter')
258
            ->with($this->queryBuilder, $this->isInstanceOf(MetadataInterface::class), [])
259
            ->willThrowException($filterException);
260
261
        $this->metadata->expects($this->once())
0 ignored issues
show
Bug introduced by
The method expects() does not exist on Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

261
        $this->metadata->/** @scrutinizer ignore-call */ 
262
                         expects($this->once())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
262
            ->method('getName')
263
            ->willReturn($this->entityName);
264
265
        $this->expectException(QueryFilterManagerException::class);
266
        $this->expectExceptionCode($exceptionCode);
267
        $this->expectExceptionMessage(
268
            sprintf('Failed to apply query filter for entity \'%s\'', $this->entityName)
269
        );
270
271
        $manager->filter($this->queryBuilder, $this->entityName, $criteria);
272
    }
273
274
    /**
275
     * Assert that the expected $criteria filters will be applied when calling filter()
276
     *
277
     * @throws QueryFilterManagerException
278
     */
279
    public function testFilterApplyArrayFilterCriteria(): void
280
    {
281
        $filterData = [
282
            [
283
                'name'  => IsEqual::class,
284
                'field' => 'test',
285
                'value' => 123,
286
            ],
287
            [
288
                'name'  => IsNotEqual::class,
289
                'field' => 'test2',
290
                'value' => 'Hello World!',
291
            ],
292
        ];
293
294
        $manager = new QueryFilterManager($this->filterFactory, $this->sortFactory);
295
296
        $criteria = [
297
            'filters' => $filterData,
298
        ];
299
300
        $this->queryBuilder->expects($this->once())
301
            ->method('getEntityManager')
302
            ->willReturn($this->entityManager);
303
304
        $this->entityManager->expects($this->once())
305
            ->method('getClassMetadata')
306
            ->with($this->entityName)
307
            ->willReturn($this->metadata);
308
309
        $factoryArgs = $createdFilters = [];
310
        foreach ($filterData as $data) {
311
            /** @var FilterInterface&MockObject $createdFilter */
312
            $createdFilter = $this->createMock(FilterInterface::class);
313
314
            $factoryArgs[] = [$manager, $data['name'], []];
315
316
            $createdFilter->expects($this->once())
317
                ->method('filter')
318
                ->with($this->queryBuilder, $this->isInstanceOf(MetadataInterface::class), $data);
319
320
            $createdFilters[] = $createdFilter;
321
        }
322
323
        $this->filterFactory->expects($this->exactly(count($filterData)))
324
            ->method('create')
325
            ->withConsecutive(...$factoryArgs)
326
            ->willReturnOnConsecutiveCalls(...$createdFilters);
327
328
        $manager->filter($this->queryBuilder, $this->entityName, $criteria);
329
    }
330
}
331