• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 13203378522

07 Feb 2025 03:56PM UTC coverage: 8.501% (+0.7%) from 7.837%
13203378522

push

github

soyuka
Merge 4.1

111 of 490 new or added lines in 51 files covered. (22.65%)

5590 existing lines in 163 files now uncovered.

13345 of 156987 relevant lines covered (8.5%)

22.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Doctrine/Odm/Tests/Extension/PaginationExtensionTest.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Doctrine\Odm\Tests\Extension;
15

16
use ApiPlatform\Doctrine\Odm\Extension\PaginationExtension;
17
use ApiPlatform\Doctrine\Odm\Tests\DoctrineMongoDbOdmSetup;
18
use ApiPlatform\Doctrine\Odm\Tests\Fixtures\Document\Dummy;
19
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
20
use ApiPlatform\Metadata\GetCollection;
21
use ApiPlatform\State\Pagination\Pagination;
22
use ApiPlatform\State\Pagination\PaginatorInterface;
23
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
24
use Doctrine\ODM\MongoDB\Aggregation\Builder;
25
use Doctrine\ODM\MongoDB\Aggregation\Stage\AddFields;
26
use Doctrine\ODM\MongoDB\Aggregation\Stage\Count;
27
use Doctrine\ODM\MongoDB\Aggregation\Stage\Facet;
28
use Doctrine\ODM\MongoDB\Aggregation\Stage\Skip;
29
use Doctrine\ODM\MongoDB\DocumentManager;
30
use Doctrine\ODM\MongoDB\Iterator\IterableResult;
31
use Doctrine\ODM\MongoDB\Iterator\Iterator;
32
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
33
use Doctrine\Persistence\ManagerRegistry;
34
use PHPUnit\Framework\TestCase;
35
use Prophecy\PhpUnit\ProphecyTrait;
36
use Prophecy\Prophecy\ObjectProphecy;
37

38
/**
39
 * @author Alan Poulain <contact@alanpoulain.eu>
40
 */
41
class PaginationExtensionTest extends TestCase
42
{
43
    use ProphecyTrait;
44

45
    /** @var ObjectProphecy<ManagerRegistry> */
46
    private ObjectProphecy $managerRegistryProphecy;
47

48
    /**
49
     * {@inheritdoc}
50
     */
51
    protected function setUp(): void
52
    {
UNCOV
53
        $this->managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
×
54
    }
55

56
    public function testApplyToCollection(): void
57
    {
58
        $pagination = new Pagination([
×
59
            'page_parameter_name' => '_page',
×
UNCOV
60
        ]);
×
61

UNCOV
62
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(40, 40);
×
63

UNCOV
64
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => 20, '_page' => 2]];
×
65

66
        $extension = new PaginationExtension(
×
67
            $this->managerRegistryProphecy->reveal(),
×
68
            $pagination
×
69
        );
×
UNCOV
70
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationItemsPerPage(40), $context);
×
71
    }
72

73
    public function testApplyToCollectionWithItemPerPageZero(): void
74
    {
75
        $pagination = new Pagination([
×
76
            'items_per_page' => 0,
×
77
            'page_parameter_name' => '_page',
×
UNCOV
78
        ]);
×
79

UNCOV
80
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(0, 0);
×
81

UNCOV
82
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 1]];
×
83

84
        $extension = new PaginationExtension(
×
85
            $this->managerRegistryProphecy->reveal(),
×
86
            $pagination
×
87
        );
×
UNCOV
88
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationItemsPerPage(0), $context);
×
89
    }
90

91
    public function testApplyToCollectionWithItemPerPageZeroAndPage2(): void
92
    {
93
        $this->expectException(InvalidArgumentException::class);
×
UNCOV
94
        $this->expectExceptionMessage('Page should not be greater than 1 if limit is equal to 0');
×
95

96
        $pagination = new Pagination([
×
97
            'items_per_page' => 0,
×
98
            'page_parameter_name' => '_page',
×
UNCOV
99
        ]);
×
100

101
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
102
        $aggregationBuilderProphecy->facet()->shouldNotBeCalled();
×
103

UNCOV
104
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 2]];
×
105

106
        $extension = new PaginationExtension(
×
107
            $this->prophesize(ManagerRegistry::class)->reveal(),
×
108
            $pagination
×
109
        );
×
UNCOV
110
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationItemsPerPage(0), $context);
×
111
    }
112

113
    public function testApplyToCollectionWithItemPerPageLessThan0(): void
114
    {
115
        $this->expectException(InvalidArgumentException::class);
×
UNCOV
116
        $this->expectExceptionMessage('Limit should not be less than 0');
×
117

118
        $pagination = new Pagination([
×
119
            'items_per_page' => -20,
×
120
            'page_parameter_name' => '_page',
×
UNCOV
121
        ]);
×
122

123
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
124
        $aggregationBuilderProphecy->facet()->shouldNotBeCalled();
×
125

UNCOV
126
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => -20, '_page' => 2]];
×
127

128
        $extension = new PaginationExtension(
×
129
            $this->managerRegistryProphecy->reveal(),
×
130
            $pagination
×
131
        );
×
UNCOV
132
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationItemsPerPage(-20), $context);
×
133
    }
134

135
    public function testApplyToCollectionWithItemPerPageTooHigh(): void
136
    {
137
        $pagination = new Pagination([
×
138
            'page_parameter_name' => '_page',
×
139
            'maximum_items_per_page' => 300,
×
UNCOV
140
        ]);
×
141

UNCOV
142
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(300, 300);
×
143

UNCOV
144
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => 301, '_page' => 2]];
×
145

146
        $extension = new PaginationExtension(
×
147
            $this->managerRegistryProphecy->reveal(),
×
148
            $pagination
×
149
        );
×
UNCOV
150
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationClientItemsPerPage(true), $context);
×
151
    }
152

153
    public function testApplyToCollectionWithGraphql(): void
154
    {
UNCOV
155
        $pagination = new Pagination();
×
156

UNCOV
157
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(10, 5);
×
158

UNCOV
159
        $context = ['filters' => ['pagination' => true, 'first' => 5, 'after' => 'OQ=='], 'graphql_operation_name' => 'query'];
×
160

161
        $extension = new PaginationExtension(
×
162
            $this->managerRegistryProphecy->reveal(),
×
163
            $pagination
×
164
        );
×
UNCOV
165
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationClientItemsPerPage(true), $context);
×
166
    }
167

168
    public function testApplyToCollectionWithGraphqlAndCountContext(): void
169
    {
UNCOV
170
        $pagination = new Pagination();
×
171

172
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(4, 5);
×
173
        $iteratorProphecy = $this->prophesize(Iterator::class);
×
174
        $iteratorProphecy->toArray()->willReturn([
×
175
            [
×
176
                'count' => 9,
×
177
            ],
×
178
        ]);
×
179
        $countProphecy = $this->prophesize(Count::class);
×
180
        $countProphecy->execute()->shouldBeCalled()->willReturn($iteratorProphecy->reveal());
×
UNCOV
181
        $aggregationBuilderProphecy->count('count')->shouldBeCalled()->willReturn($countProphecy->reveal());
×
182

UNCOV
183
        $context = ['filters' => ['pagination' => true, 'last' => 5], 'graphql_operation_name' => 'query'];
×
184

185
        $extension = new PaginationExtension(
×
186
            $this->managerRegistryProphecy->reveal(),
×
187
            $pagination
×
188
        );
×
UNCOV
189
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationClientItemsPerPage(true), $context);
×
190
    }
191

192
    public function testApplyToCollectionNoFilters(): void
193
    {
UNCOV
194
        $pagination = new Pagination();
×
195

UNCOV
196
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(0, 30);
×
197

UNCOV
198
        $context = [];
×
199

200
        $extension = new PaginationExtension(
×
201
            $this->managerRegistryProphecy->reveal(),
×
202
            $pagination
×
203
        );
×
UNCOV
204
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true), $context);
×
205
    }
206

207
    public function testApplyToCollectionPaginationDisabled(): void
208
    {
209
        $pagination = new Pagination([
×
210
            'enabled' => false,
×
UNCOV
211
        ]);
×
212

213
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
214
        $aggregationBuilderProphecy->facet()->shouldNotBeCalled();
×
215

UNCOV
216
        $context = [];
×
217

218
        $extension = new PaginationExtension(
×
219
            $this->managerRegistryProphecy->reveal(),
×
220
            $pagination
×
221
        );
×
UNCOV
222
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', new GetCollection(), $context);
×
223
    }
224

225
    public function testApplyToCollectionGraphQlPaginationDisabled(): void
226
    {
227
        $pagination = new Pagination([], [
×
228
            'enabled' => false,
×
UNCOV
229
        ]);
×
230

231
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
232
        $aggregationBuilderProphecy->facet()->shouldNotBeCalled();
×
233

UNCOV
234
        $context = ['graphql_operation_name' => 'get'];
×
235

236
        $extension = new PaginationExtension(
×
237
            $this->managerRegistryProphecy->reveal(),
×
238
            $pagination
×
239
        );
×
UNCOV
240
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', new GetCollection(), $context);
×
241
    }
242

243
    public function testApplyToCollectionWithMaximumItemsPerPage(): void
244
    {
245
        $pagination = new Pagination([
×
246
            'client_enabled' => true,
×
247
            'client_items_per_page' => true,
×
248
            'pagination_maximum_items_per_page' => 50,
×
UNCOV
249
        ]);
×
250

UNCOV
251
        $aggregationBuilderProphecy = $this->mockAggregationBuilder(0, 80);
×
252

UNCOV
253
        $context = ['filters' => ['pagination' => true, 'itemsPerPage' => 80, 'page' => 1]];
×
254

255
        $extension = new PaginationExtension(
×
256
            $this->managerRegistryProphecy->reveal(),
×
257
            $pagination
×
258
        );
×
UNCOV
259
        $extension->applyToCollection($aggregationBuilderProphecy->reveal(), 'Foo', (new GetCollection())->withPaginationEnabled(true)->withPaginationClientEnabled(true)->withPaginationMaximumItemsPerPage(80), $context);
×
260
    }
261

262
    public function testSupportsResult(): void
263
    {
UNCOV
264
        $pagination = new Pagination();
×
265

266
        $extension = new PaginationExtension(
×
267
            $this->managerRegistryProphecy->reveal(),
×
268
            $pagination
×
269
        );
×
UNCOV
270
        $this->assertTrue($extension->supportsResult('Foo', (new GetCollection())->withPaginationEnabled(true)));
×
271
    }
272

273
    public function testSupportsResultClientNotAllowedToPaginate(): void
274
    {
275
        $pagination = new Pagination([
×
276
            'enabled' => false,
×
277
            'client_enabled' => false,
×
UNCOV
278
        ]);
×
279

280
        $extension = new PaginationExtension(
×
281
            $this->managerRegistryProphecy->reveal(),
×
282
            $pagination
×
283
        );
×
UNCOV
284
        $this->assertFalse($extension->supportsResult('Foo', new GetCollection(), ['filters' => ['pagination' => true]]));
×
285
    }
286

287
    public function testSupportsResultPaginationDisabled(): void
288
    {
289
        $pagination = new Pagination([
×
290
            'enabled' => false,
×
UNCOV
291
        ]);
×
292

293
        $extension = new PaginationExtension(
×
294
            $this->managerRegistryProphecy->reveal(),
×
295
            $pagination
×
296
        );
×
UNCOV
297
        $this->assertFalse($extension->supportsResult('Foo', new GetCollection(), ['filters' => ['enabled' => false]]));
×
298
    }
299

300
    public function testSupportsResultGraphQlPaginationDisabled(): void
301
    {
302
        $pagination = new Pagination([], [
×
303
            'enabled' => false,
×
UNCOV
304
        ]);
×
305

306
        $extension = new PaginationExtension(
×
307
            $this->managerRegistryProphecy->reveal(),
×
308
            $pagination
×
309
        );
×
UNCOV
310
        $this->assertFalse($extension->supportsResult('Foo', new GetCollection(), ['filters' => ['enabled' => false], 'graphql_operation_name' => 'query']));
×
311
    }
312

313
    public function testGetResult(): void
314
    {
UNCOV
315
        $pagination = new Pagination();
×
316

317
        $fixturesPath = \dirname((string) (new \ReflectionClass(Dummy::class))->getFileName());
×
318
        $config = DoctrineMongoDbOdmSetup::createAttributeMetadataConfiguration([$fixturesPath], true);
×
UNCOV
319
        $documentManager = DocumentManager::create(null, $config);
×
320

UNCOV
321
        $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($documentManager);
×
322

323
        $iteratorProphecy = $this->prophesize(Iterator::class);
×
324
        $iteratorProphecy->toArray()->willReturn([
×
325
            [
×
326
                'results' => [],
×
327
                'count' => [
×
328
                    [
×
329
                        'count' => 9,
×
330
                    ],
×
331
                ],
×
UNCOV
332
                '__api_first_result__' => 3,
×
333
                '__api_max_results__' => 6,
×
334
            ],
×
335
        ]);
×
336

337
        $aggregationProphecy = $this->prophesize(IterableResult::class);
×
338
        $aggregationProphecy->getIterator()->willReturn($iteratorProphecy->reveal());
×
339

340
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
341
        $aggregationBuilderProphecy->getAggregation([])->willReturn($aggregationProphecy->reveal());
×
342
        $aggregationBuilderProphecy->getPipeline()->willReturn([
×
343
            [
×
344
                '$facet' => [
×
345
                    'results' => [
×
346
                        ['$skip' => 3],
×
347
                        ['$limit' => 6],
×
UNCOV
348
                    ],
×
349
                    'count' => [
×
350
                        ['$count' => 'count'],
×
351
                    ],
×
352
                ],
×
UNCOV
353
            ],
×
354
            [
×
UNCOV
355
                '$addFields' => [
×
356
                    '__api_first_result__' => ['$literal' => 3],
×
357
                    '__api_max_results__' => ['$literal' => 6],
×
UNCOV
358
                ],
×
UNCOV
359
            ],
×
UNCOV
360
        ]);
×
361

362
        $paginationExtension = new PaginationExtension(
×
UNCOV
363
            $this->managerRegistryProphecy->reveal(),
×
364
            $pagination
×
365
        );
×
366

UNCOV
367
        $result = $paginationExtension->getResult($aggregationBuilderProphecy->reveal(), Dummy::class, new GetCollection());
×
368

UNCOV
369
        $this->assertInstanceOf(PartialPaginatorInterface::class, $result);
×
370
        $this->assertInstanceOf(PaginatorInterface::class, $result);
×
371
    }
372

373
    public function testGetResultWithExecuteOptions(): void
374
    {
375
        $pagination = new Pagination();
×
376

377
        $fixturesPath = \dirname((string) (new \ReflectionClass(Dummy::class))->getFileName());
×
378
        $config = DoctrineMongoDbOdmSetup::createAttributeMetadataConfiguration([$fixturesPath], true);
×
379
        $documentManager = DocumentManager::create(null, $config);
×
380

381
        $this->managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($documentManager);
×
382

383
        $iteratorProphecy = $this->prophesize(Iterator::class);
×
384
        $iteratorProphecy->toArray()->willReturn([
×
385
            [
×
386
                'results' => [],
×
387
                'count' => [
×
388
                    [
×
389
                        'count' => 9,
×
390
                    ],
×
391
                ],
×
392
                '__api_first_result__' => 3,
×
393
                '__api_max_results__' => 6,
×
394
            ],
×
395
        ]);
×
396

397
        $aggregationProphecy = $this->prophesize(IterableResult::class);
×
398
        $aggregationProphecy->getIterator()->willReturn($iteratorProphecy->reveal());
×
399

400
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
401
        $aggregationBuilderProphecy->getAggregation(['allowDiskUse' => true])->willReturn($aggregationProphecy->reveal());
×
402
        $aggregationBuilderProphecy->getPipeline()->willReturn([
×
UNCOV
403
            [
×
404
                '$facet' => [
×
405
                    'results' => [
×
UNCOV
406
                        ['$skip' => 3],
×
UNCOV
407
                        ['$limit' => 6],
×
UNCOV
408
                    ],
×
UNCOV
409
                    'count' => [
×
410
                        ['$count' => 'count'],
×
411
                    ],
×
412
                ],
×
UNCOV
413
            ],
×
414
            [
×
415
                '$addFields' => [
×
416
                    '__api_first_result__' => ['$literal' => 3],
×
417
                    '__api_max_results__' => ['$literal' => 6],
×
UNCOV
418
                ],
×
UNCOV
419
            ],
×
420
        ]);
×
421

UNCOV
422
        $paginationExtension = new PaginationExtension(
×
423
            $this->managerRegistryProphecy->reveal(),
×
UNCOV
424
            $pagination
×
425
        );
×
426

UNCOV
427
        $result = $paginationExtension->getResult($aggregationBuilderProphecy->reveal(), Dummy::class, (new GetCollection())->withExtraProperties(['doctrine_mongodb' => ['execute_options' => ['allowDiskUse' => true]]]));
×
428

429
        $this->assertInstanceOf(PartialPaginatorInterface::class, $result);
×
430
        $this->assertInstanceOf(PaginatorInterface::class, $result);
×
431
    }
432

433
    private function mockAggregationBuilder(int $expectedOffset, int $expectedLimit): ObjectProphecy
434
    {
435
        $countProphecy = $this->prophesize(Count::class);
×
UNCOV
436
        $countAggregationBuilderProphecy = $this->prophesize(Builder::class);
×
437
        $countAggregationBuilderProphecy->count('count')->shouldBeCalled()->willReturn($countProphecy->reveal());
×
438

439
        $repositoryProphecy = $this->prophesize(DocumentRepository::class);
×
440

441
        $objectManagerProphecy = $this->prophesize(DocumentManager::class);
×
442
        $objectManagerProphecy->getRepository('Foo')->shouldBeCalled()->willReturn($repositoryProphecy->reveal());
×
443

UNCOV
444
        $this->managerRegistryProphecy->getManagerForClass('Foo')->shouldBeCalled()->willReturn($objectManagerProphecy->reveal());
×
445

446
        $facetProphecy = $this->prophesize(Facet::class);
×
UNCOV
447
        $addFieldsProphecy = $this->prophesize(AddFields::class);
×
448

UNCOV
449
        if ($expectedLimit > 0) {
×
UNCOV
450
            $resultsAggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
451
            $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn(
×
UNCOV
452
                $resultsAggregationBuilderProphecy->reveal(),
×
UNCOV
453
                $countAggregationBuilderProphecy->reveal()
×
UNCOV
454
            );
×
455

UNCOV
456
            $skipProphecy = $this->prophesize(Skip::class);
×
UNCOV
457
            $skipProphecy->limit($expectedLimit)->shouldBeCalled()->willReturn($skipProphecy->reveal());
×
UNCOV
458
            $resultsAggregationBuilderProphecy->skip($expectedOffset)->shouldBeCalled()->willReturn($skipProphecy->reveal());
×
UNCOV
459
            $facetProphecy->field('results')->shouldBeCalled()->willReturn($facetProphecy);
×
UNCOV
460
            $facetProphecy->pipeline($skipProphecy)->shouldBeCalled()->willReturn($facetProphecy->reveal());
×
461
        } else {
UNCOV
462
            $repositoryProphecy->createAggregationBuilder()->shouldBeCalled()->willReturn(
×
UNCOV
463
                $countAggregationBuilderProphecy->reveal()
×
UNCOV
464
            );
×
465

UNCOV
466
            $addFieldsProphecy->field('results')->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
UNCOV
467
            $addFieldsProphecy->literal([])->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
468
        }
469

UNCOV
470
        $facetProphecy->field('count')->shouldBeCalled()->willReturn($facetProphecy->reveal());
×
UNCOV
471
        $facetProphecy->pipeline($countProphecy)->shouldBeCalled()->willReturn($facetProphecy->reveal());
×
472

UNCOV
473
        $addFieldsProphecy->field('__api_first_result__')->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
UNCOV
474
        $addFieldsProphecy->literal($expectedOffset)->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
UNCOV
475
        $addFieldsProphecy->field('__api_max_results__')->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
UNCOV
476
        $addFieldsProphecy->literal($expectedLimit)->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
477

UNCOV
478
        $aggregationBuilderProphecy = $this->prophesize(Builder::class);
×
UNCOV
479
        $aggregationBuilderProphecy->facet()->shouldBeCalled()->willReturn($facetProphecy->reveal());
×
UNCOV
480
        $aggregationBuilderProphecy->addFields()->shouldBeCalled()->willReturn($addFieldsProphecy->reveal());
×
481

UNCOV
482
        return $aggregationBuilderProphecy;
×
483
    }
484
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc