Completed
Push — 3.x ( e95e95...638cd1 )
by Oskar
05:54
created

tests/Controller/CRUDControllerTest.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Tests\Controller;
15
16
use PHPUnit\Framework\TestCase;
17
use Psr\Log\LoggerInterface;
18
use Sonata\AdminBundle\Admin\AbstractAdmin;
19
use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
20
use Sonata\AdminBundle\Admin\Pool;
21
use Sonata\AdminBundle\Controller\CRUDController;
22
use Sonata\AdminBundle\Datagrid\DatagridInterface;
23
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
24
use Sonata\AdminBundle\Exception\LockException;
25
use Sonata\AdminBundle\Exception\ModelManagerException;
26
use Sonata\AdminBundle\Export\Exporter as SonataExporter;
27
use Sonata\AdminBundle\Model\AuditManager;
28
use Sonata\AdminBundle\Model\AuditReaderInterface;
29
use Sonata\AdminBundle\Model\ModelManagerInterface;
30
use Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap;
31
use Sonata\AdminBundle\Security\Handler\AclSecurityHandler;
32
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
33
use Sonata\AdminBundle\Tests\Fixtures\Admin\PostAdmin;
34
use Sonata\AdminBundle\Tests\Fixtures\Controller\BatchAdminController;
35
use Sonata\AdminBundle\Tests\Fixtures\Controller\PreCRUDController;
36
use Sonata\AdminBundle\Util\AdminObjectAclData;
37
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
38
use Sonata\Exporter\Exporter;
39
use Sonata\Exporter\Source\SourceIteratorInterface;
40
use Sonata\Exporter\Writer\JsonWriter;
41
use Symfony\Bridge\Twig\Extension\FormExtension;
42
use Symfony\Bridge\Twig\Form\TwigRenderer;
43
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
44
use Symfony\Bundle\FrameworkBundle\Templating\DelegatingEngine;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
46
use Symfony\Component\Form\Form;
47
use Symfony\Component\Form\FormRenderer;
48
use Symfony\Component\Form\FormView;
49
use Symfony\Component\HttpFoundation\RedirectResponse;
50
use Symfony\Component\HttpFoundation\Request;
51
use Symfony\Component\HttpFoundation\RequestStack;
52
use Symfony\Component\HttpFoundation\Response;
53
use Symfony\Component\HttpFoundation\Session\Session;
54
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
55
use Symfony\Component\HttpFoundation\StreamedResponse;
56
use Symfony\Component\HttpKernel\Exception\HttpException;
57
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
58
use Symfony\Component\HttpKernel\KernelInterface;
59
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
60
use Symfony\Component\Security\Csrf\CsrfToken;
61
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
62
use Symfony\Component\Translation\TranslatorInterface;
63
64
/**
65
 * Test for CRUDController.
66
 *
67
 * @author Andrej Hudec <[email protected]>
68
 *
69
 * @group legacy
70
 */
71
class CRUDControllerTest extends TestCase
72
{
73
    /**
74
     * @var CRUDController
75
     */
76
    private $controller;
77
78
    /**
79
     * @var Request
80
     */
81
    private $request;
82
83
    /**
84
     * @var AbstractAdmin
85
     */
86
    private $admin;
87
88
    /**
89
     * @var TemplateRegistryInterface
90
     */
91
    private $templateRegistry;
92
93
    /**
94
     * @var Pool
95
     */
96
    private $pool;
97
98
    /**
99
     * @var array
100
     */
101
    private $parameters;
102
103
    /**
104
     * @var Session
105
     */
106
    private $session;
107
108
    /**
109
     * @var AuditManager
110
     */
111
    private $auditManager;
112
113
    /**
114
     * @var ContainerInterface
115
     */
116
    private $container;
117
118
    /**
119
     * @var AdminObjectAclManipulator
120
     */
121
    private $adminObjectAclManipulator;
122
123
    /**
124
     * @var string
125
     */
126
    private $template;
127
128
    /**
129
     * @var array
130
     */
131
    private $protectedTestedMethods;
132
133
    /**
134
     * @var CsrfTokenManagerInterface
135
     */
136
    private $csrfProvider;
137
138
    /**
139
     * @var KernelInterface
140
     */
141
    private $kernel;
142
143
    /**
144
     * @var TranslatorInterface
145
     */
146
    private $translator;
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    protected function setUp(): void
152
    {
153
        $this->container = $this->createMock(ContainerInterface::class);
154
155
        $this->request = new Request();
156
        $this->pool = new Pool($this->container, 'title', 'logo.png');
157
        $this->pool->setAdminServiceIds(['foo.admin']);
158
        $this->request->attributes->set('_sonata_admin', 'foo.admin');
159
        $this->admin = $this->getMockBuilder(AbstractAdmin::class)
160
            ->disableOriginalConstructor()
161
            ->getMock();
162
        $this->translator = $this->createMock(TranslatorInterface::class);
163
        $this->parameters = [];
164
        $this->template = '';
165
166
        $this->templateRegistry = $this->prophesize(TemplateRegistryInterface::class);
167
168
        $templating = $this->getMockBuilder(DelegatingEngine::class)
169
            ->setConstructorArgs([$this->container, []])
170
            ->getMock();
171
172
        $templatingRenderReturnCallback = $this->returnCallback(function (
173
            $view,
174
            array $parameters = [],
175
            Response $response = null
176
        ) {
177
            $this->template = $view;
178
179
            if (null === $response) {
180
                $response = new Response();
181
            }
182
183
            $this->parameters = $parameters;
184
185
            return $response;
186
        });
187
188
        // SF < 3.3.10 BC
189
        $templating->expects($this->any())
190
            ->method('renderResponse')
191
            ->will($templatingRenderReturnCallback);
192
193
        $templating->expects($this->any())
194
            ->method('render')
195
            ->will($templatingRenderReturnCallback);
196
197
        $this->session = new Session(new MockArraySessionStorage());
198
199
        $twig = $this->getMockBuilder(\Twig_Environment::class)
200
            ->disableOriginalConstructor()
201
            ->getMock();
202
203
        $twig->expects($this->any())
204
            ->method('getExtension')
205
            ->willReturnCallback(function ($name) {
206
                switch ($name) {
207
                    case FormExtension::class:
208
                        return new FormExtension($this->createMock(TwigRenderer::class));
209
                }
210
            });
211
212
        $twig->expects($this->any())
213
            ->method('getRuntime')
214
            ->willReturnCallback(function ($name) {
215
                switch ($name) {
216
                    case TwigRenderer::class:
217
                        return $this->createMock(TwigRenderer::class);
218
                    case FormRenderer::class:
219
                        return $this->createMock(FormRenderer::class);
220
                }
221
            });
222
223
        // NEXT_MAJOR : require sonata/exporter ^1.7 and remove conditional
224
        if (class_exists(Exporter::class)) {
225
            $exporter = new Exporter([new JsonWriter('/tmp/sonataadmin/export.json')]);
226
        } else {
227
            $exporter = $this->createMock(SonataExporter::class);
228
229
            $exporter->expects($this->any())
230
                ->method('getResponse')
231
                ->willReturn(new StreamedResponse());
232
        }
233
234
        $this->auditManager = $this->getMockBuilder(AuditManager::class)
235
            ->disableOriginalConstructor()
236
            ->getMock();
237
238
        $this->adminObjectAclManipulator = $this->getMockBuilder(AdminObjectAclManipulator::class)
239
            ->disableOriginalConstructor()
240
            ->getMock();
241
242
        $this->csrfProvider = $this->getMockBuilder(CsrfTokenManagerInterface::class)
243
            ->getMock();
244
245
        $this->csrfProvider->expects($this->any())
246
            ->method('getToken')
247
            ->willReturnCallback(static function ($intention) {
248
                return new CsrfToken($intention, 'csrf-token-123_'.$intention);
249
            });
250
251
        $this->csrfProvider->expects($this->any())
252
            ->method('isTokenValid')
253
            ->willReturnCallback(static function (CsrfToken $token) {
254
                if ($token->getValue() === 'csrf-token-123_'.$token->getId()) {
255
                    return true;
256
                }
257
258
                return false;
259
            });
260
261
        $this->logger = $this->createMock(LoggerInterface::class);
262
263
        $requestStack = new RequestStack();
264
        $requestStack->push($this->request);
265
266
        $this->kernel = $this->createMock(KernelInterface::class);
267
268
        $this->container->expects($this->any())
269
            ->method('get')
270
            ->willReturnCallback(function ($id) use (
271
                $templating,
272
                $twig,
273
                $exporter,
274
                $requestStack
275
            ) {
276
                switch ($id) {
277
                    case 'sonata.admin.pool':
278
                        return $this->pool;
279
                    case 'request_stack':
280
                        return $requestStack;
281
                    case 'foo.admin':
282
                        return $this->admin;
283
                    case 'foo.admin.template_registry':
284
                        return $this->templateRegistry->reveal();
285
                    case 'templating':
286
                        return $templating;
287
                    case 'twig':
288
                        return $twig;
289
                    case 'session':
290
                        return $this->session;
291
                    case 'sonata.admin.exporter':
292
                        return $exporter;
293
                    case 'sonata.admin.audit.manager':
294
                        return $this->auditManager;
295
                    case 'sonata.admin.object.manipulator.acl.admin':
296
                        return $this->adminObjectAclManipulator;
297
                    case 'security.csrf.token_manager':
298
                        return $this->csrfProvider;
299
                    case 'logger':
300
                        return $this->logger;
301
                    case 'kernel':
302
                        return $this->kernel;
303
                    case 'translator':
304
                        return $this->translator;
305
                }
306
            });
307
308
        $this->container->expects($this->any())
309
            ->method('has')
310
            ->willReturnCallback(function ($id) {
311
                if ('security.csrf.token_manager' === $id && null !== $this->getCsrfProvider()) {
312
                    return true;
313
                }
314
315
                if ('logger' === $id) {
316
                    return true;
317
                }
318
319
                if ('session' === $id) {
320
                    return true;
321
                }
322
323
                if ('templating' === $id) {
324
                    return true;
325
                }
326
327
                if ('translator' === $id) {
328
                    return true;
329
                }
330
331
                return false;
332
            });
333
334
        $this->container->expects($this->any())
335
            ->method('getParameter')
336
            ->willReturnCallback(static function ($name) {
337
                switch ($name) {
338
                    case 'security.role_hierarchy.roles':
339
                       return ['ROLE_SUPER_ADMIN' => ['ROLE_USER', 'ROLE_SONATA_ADMIN', 'ROLE_ADMIN']];
340
                }
341
            });
342
343
        $this->templateRegistry->getTemplate('ajax')->willReturn('@SonataAdmin/ajax_layout.html.twig');
344
        $this->templateRegistry->getTemplate('layout')->willReturn('@SonataAdmin/standard_layout.html.twig');
345
        $this->templateRegistry->getTemplate('show')->willReturn('@SonataAdmin/CRUD/show.html.twig');
346
        $this->templateRegistry->getTemplate('show_compare')->willReturn('@SonataAdmin/CRUD/show_compare.html.twig');
347
        $this->templateRegistry->getTemplate('edit')->willReturn('@SonataAdmin/CRUD/edit.html.twig');
348
        $this->templateRegistry->getTemplate('dashboard')->willReturn('@SonataAdmin/Core/dashboard.html.twig');
349
        $this->templateRegistry->getTemplate('search')->willReturn('@SonataAdmin/Core/search.html.twig');
350
        $this->templateRegistry->getTemplate('list')->willReturn('@SonataAdmin/CRUD/list.html.twig');
351
        $this->templateRegistry->getTemplate('preview')->willReturn('@SonataAdmin/CRUD/preview.html.twig');
352
        $this->templateRegistry->getTemplate('history')->willReturn('@SonataAdmin/CRUD/history.html.twig');
353
        $this->templateRegistry->getTemplate('acl')->willReturn('@SonataAdmin/CRUD/acl.html.twig');
354
        $this->templateRegistry->getTemplate('delete')->willReturn('@SonataAdmin/CRUD/delete.html.twig');
355
        $this->templateRegistry->getTemplate('batch')->willReturn('@SonataAdmin/CRUD/list__batch.html.twig');
356
        $this->templateRegistry->getTemplate('batch_confirmation')->willReturn('@SonataAdmin/CRUD/batch_confirmation.html.twig');
357
358
        // NEXT_MAJOR: Remove this call
359
        $this->admin->method('getTemplate')->willReturnMap([
360
            ['ajax', '@SonataAdmin/ajax_layout.html.twig'],
361
            ['layout', '@SonataAdmin/standard_layout.html.twig'],
362
            ['show', '@SonataAdmin/CRUD/show.html.twig'],
363
            ['show_compare', '@SonataAdmin/CRUD/show_compare.html.twig'],
364
            ['edit', '@SonataAdmin/CRUD/edit.html.twig'],
365
            ['dashboard', '@SonataAdmin/Core/dashboard.html.twig'],
366
            ['search', '@SonataAdmin/Core/search.html.twig'],
367
            ['list', '@SonataAdmin/CRUD/list.html.twig'],
368
            ['preview', '@SonataAdmin/CRUD/preview.html.twig'],
369
            ['history', '@SonataAdmin/CRUD/history.html.twig'],
370
            ['acl', '@SonataAdmin/CRUD/acl.html.twig'],
371
            ['delete', '@SonataAdmin/CRUD/delete.html.twig'],
372
            ['batch', '@SonataAdmin/CRUD/list__batch.html.twig'],
373
            ['batch_confirmation', '@SonataAdmin/CRUD/batch_confirmation.html.twig'],
374
        ]);
375
376
        $this->admin->expects($this->any())
377
            ->method('getIdParameter')
378
            ->willReturn('id');
379
380
        $this->admin->expects($this->any())
381
            ->method('getAccessMapping')
382
            ->willReturn([]);
383
384
        $this->admin->expects($this->any())
385
            ->method('generateUrl')
386
            ->willReturnCallback(
387
388
                    static function ($name, array $parameters = [], $absolute = false) {
389
                        $result = $name;
390
                        if (!empty($parameters)) {
391
                            $result .= '?'.http_build_query($parameters);
392
                        }
393
394
                        return $result;
395
                    }
396
397
            );
398
399
        $this->admin->expects($this->any())
400
            ->method('generateObjectUrl')
401
            ->willReturnCallback(
402
403
                    static function ($name, $object, array $parameters = [], $absolute = false) {
404
                        $result = \get_class($object).'_'.$name;
405
                        if (!empty($parameters)) {
406
                            $result .= '?'.http_build_query($parameters);
407
                        }
408
409
                        return $result;
410
                    }
411
412
            );
413
414
        $this->admin->expects($this->any())
415
            ->method('getCode')
416
            ->willReturn('foo.admin');
417
418
        $this->controller = new CRUDController();
419
        $this->controller->setContainer($this->container);
420
421
        // Make some methods public to test them
422
        $testedMethods = [
423
            'renderJson',
424
            'isXmlHttpRequest',
425
            'configure',
426
            'getBaseTemplate',
427
            'redirectTo',
428
            'addFlash',
429
        ];
430
        foreach ($testedMethods as $testedMethod) {
431
            // NEXT_MAJOR: Remove this check and only use CRUDController
432
            if (method_exists(CRUDController::class, $testedMethod)) {
433
                $method = new \ReflectionMethod(CRUDController::class, $testedMethod);
434
            } else {
435
                $method = new \ReflectionMethod(Controller::class, $testedMethod);
436
            }
437
438
            $method->setAccessible(true);
439
            $this->protectedTestedMethods[$testedMethod] = $method;
440
        }
441
    }
442
443
    public function testRenderJson1(): void
444
    {
445
        $data = ['example' => '123', 'foo' => 'bar'];
446
447
        $this->request->headers->set('Content-Type', 'application/x-www-form-urlencoded');
448
        $response = $this->protectedTestedMethods['renderJson']->invoke($this->controller, $data, 200, [], $this->request);
449
450
        $this->assertSame($response->headers->get('Content-Type'), 'application/json');
451
        $this->assertSame(json_encode($data), $response->getContent());
452
    }
453
454
    public function testRenderJson2(): void
455
    {
456
        $data = ['example' => '123', 'foo' => 'bar'];
457
458
        $this->request->headers->set('Content-Type', 'multipart/form-data');
459
        $response = $this->protectedTestedMethods['renderJson']->invoke($this->controller, $data, 200, [], $this->request);
460
461
        $this->assertSame($response->headers->get('Content-Type'), 'application/json');
462
        $this->assertSame(json_encode($data), $response->getContent());
463
    }
464
465
    public function testRenderJsonAjax(): void
466
    {
467
        $data = ['example' => '123', 'foo' => 'bar'];
468
469
        $this->request->attributes->set('_xml_http_request', true);
470
        $this->request->headers->set('Content-Type', 'multipart/form-data');
471
        $response = $this->protectedTestedMethods['renderJson']->invoke($this->controller, $data, 200, [], $this->request);
472
473
        $this->assertSame($response->headers->get('Content-Type'), 'application/json');
474
        $this->assertSame(json_encode($data), $response->getContent());
475
    }
476
477
    public function testIsXmlHttpRequest(): void
478
    {
479
        $this->assertFalse($this->protectedTestedMethods['isXmlHttpRequest']->invoke($this->controller, $this->request));
480
481
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
482
483
        $this->assertTrue($this->protectedTestedMethods['isXmlHttpRequest']->invoke($this->controller, $this->request));
484
485
        $this->request->headers->remove('X-Requested-With');
486
        $this->assertFalse($this->protectedTestedMethods['isXmlHttpRequest']->invoke($this->controller, $this->request));
487
488
        $this->request->attributes->set('_xml_http_request', true);
489
        $this->assertTrue($this->protectedTestedMethods['isXmlHttpRequest']->invoke($this->controller, $this->request));
490
    }
491
492
    public function testConfigure(): void
493
    {
494
        $uniqueId = '';
495
496
        $this->admin->expects($this->once())
497
            ->method('setUniqid')
498
            ->willReturnCallback(static function ($uniqid) use (&$uniqueId): void {
499
                $uniqueId = $uniqid;
500
            });
501
502
        $this->request->query->set('uniqid', 123456);
503
        $this->protectedTestedMethods['configure']->invoke($this->controller);
504
505
        $this->assertSame(123456, $uniqueId);
506
        $this->assertAttributeSame($this->admin, 'admin', $this->controller);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit\Framework\Assert::assertAttributeSame() has been deprecated with message: https://github.com/sebastianbergmann/phpunit/issues/3338

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
507
    }
508
509
    public function testConfigureChild(): void
510
    {
511
        $uniqueId = '';
512
513
        $this->admin->expects($this->once())
514
            ->method('setUniqid')
515
            ->willReturnCallback(static function ($uniqid) use (&$uniqueId): void {
516
                $uniqueId = $uniqid;
517
            });
518
519
        $this->admin->expects($this->once())
520
            ->method('isChild')
521
            ->willReturn(true);
522
523
        $adminParent = $this->getMockBuilder(AbstractAdmin::class)
524
            ->disableOriginalConstructor()
525
            ->getMock();
526
        $this->admin->expects($this->once())
527
            ->method('getParent')
528
            ->willReturn($adminParent);
529
530
        $this->request->query->set('uniqid', 123456);
531
        $this->protectedTestedMethods['configure']->invoke($this->controller);
532
533
        $this->assertSame(123456, $uniqueId);
534
        $this->assertAttributeInstanceOf(\get_class($adminParent), 'admin', $this->controller);
535
    }
536
537
    public function testConfigureWithException(): void
538
    {
539
        $this->expectException(
540
            \RuntimeException::class,
541
            'There is no `_sonata_admin` defined for the controller `Sonata\AdminBundle\Controller\CRUDController`'
542
        );
543
544
        $this->request->attributes->remove('_sonata_admin');
545
        $this->protectedTestedMethods['configure']->invoke($this->controller);
546
    }
547
548
    public function testConfigureWithException2(): void
549
    {
550
        $this->expectException(
551
            \InvalidArgumentException::class,
552
            'Found service "nonexistent.admin" is not a valid admin service'
553
        );
554
555
        $this->pool->setAdminServiceIds(['nonexistent.admin']);
556
        $this->request->attributes->set('_sonata_admin', 'nonexistent.admin');
557
        $this->protectedTestedMethods['configure']->invoke($this->controller);
558
    }
559
560
    public function testGetBaseTemplate(): void
561
    {
562
        $this->assertSame(
563
            '@SonataAdmin/standard_layout.html.twig',
564
            $this->protectedTestedMethods['getBaseTemplate']->invoke($this->controller, $this->request)
565
        );
566
567
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
568
        $this->assertSame(
569
            '@SonataAdmin/ajax_layout.html.twig',
570
            $this->protectedTestedMethods['getBaseTemplate']->invoke($this->controller, $this->request)
571
        );
572
573
        $this->request->headers->remove('X-Requested-With');
574
        $this->assertSame(
575
            '@SonataAdmin/standard_layout.html.twig',
576
            $this->protectedTestedMethods['getBaseTemplate']->invoke($this->controller, $this->request)
577
        );
578
579
        $this->request->attributes->set('_xml_http_request', true);
580
        $this->assertSame(
581
            '@SonataAdmin/ajax_layout.html.twig',
582
            $this->protectedTestedMethods['getBaseTemplate']->invoke($this->controller, $this->request)
583
        );
584
    }
585
586
    public function testRender(): void
587
    {
588
        $this->parameters = [];
589
        $this->assertInstanceOf(
590
            Response::class,
591
            $this->controller->renderWithExtraParams('@FooAdmin/foo.html.twig', [], null)
592
        );
593
        $this->assertSame($this->admin, $this->parameters['admin']);
594
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
595
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
596
        $this->assertSame('@FooAdmin/foo.html.twig', $this->template);
597
    }
598
599
    public function testRenderWithResponse(): void
600
    {
601
        $this->parameters = [];
602
        $response = $response = new Response();
603
        $response->headers->set('X-foo', 'bar');
604
        $responseResult = $this->controller->renderWithExtraParams('@FooAdmin/foo.html.twig', [], $response);
605
606
        $this->assertSame($response, $responseResult);
607
        $this->assertSame('bar', $responseResult->headers->get('X-foo'));
608
        $this->assertSame($this->admin, $this->parameters['admin']);
609
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
610
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
611
        $this->assertSame('@FooAdmin/foo.html.twig', $this->template);
612
    }
613
614
    public function testRenderCustomParams(): void
615
    {
616
        $this->parameters = [];
617
        $this->assertInstanceOf(
618
            Response::class,
619
            $this->controller->renderWithExtraParams(
620
                '@FooAdmin/foo.html.twig',
621
                ['foo' => 'bar'],
622
                null
623
            )
624
        );
625
        $this->assertSame($this->admin, $this->parameters['admin']);
626
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
627
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
628
        $this->assertSame('bar', $this->parameters['foo']);
629
        $this->assertSame('@FooAdmin/foo.html.twig', $this->template);
630
    }
631
632
    public function testRenderAjax(): void
633
    {
634
        $this->parameters = [];
635
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
636
        $this->assertInstanceOf(
637
            Response::class,
638
            $this->controller->renderWithExtraParams(
639
                '@FooAdmin/foo.html.twig',
640
                ['foo' => 'bar'],
641
                null
642
            )
643
        );
644
        $this->assertSame($this->admin, $this->parameters['admin']);
645
        $this->assertSame('@SonataAdmin/ajax_layout.html.twig', $this->parameters['base_template']);
646
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
647
        $this->assertSame('bar', $this->parameters['foo']);
648
        $this->assertSame('@FooAdmin/foo.html.twig', $this->template);
649
    }
650
651
    public function testListActionAccessDenied(): void
652
    {
653
        $this->expectException(AccessDeniedException::class);
654
655
        $this->admin->expects($this->once())
656
            ->method('checkAccess')
657
            ->with($this->equalTo('list'))
658
            ->will($this->throwException(new AccessDeniedException()));
659
660
        $this->controller->listAction($this->request);
661
    }
662
663
    public function testPreList(): void
664
    {
665
        $this->admin->expects($this->any())
666
            ->method('hasRoute')
667
            ->with($this->equalTo('list'))
668
            ->willReturn(true);
669
670
        $this->admin->expects($this->once())
671
            ->method('checkAccess')
672
            ->with($this->equalTo('list'))
673
            ->willReturn(true);
674
675
        $controller = new PreCRUDController();
676
        $controller->setContainer($this->container);
677
678
        $response = $controller->listAction($this->request);
679
        $this->assertInstanceOf(Response::class, $response);
680
        $this->assertSame('preList called', $response->getContent());
681
    }
682
683
    public function testListAction(): void
684
    {
685
        $datagrid = $this->createMock(DatagridInterface::class);
686
687
        $this->admin->expects($this->any())
688
            ->method('hasRoute')
689
            ->with($this->equalTo('list'))
690
            ->willReturn(true);
691
692
        $this->admin->expects($this->once())
693
            ->method('checkAccess')
694
            ->with($this->equalTo('list'))
695
            ->willReturn(true);
696
697
        $form = $this->getMockBuilder(Form::class)
698
            ->disableOriginalConstructor()
699
            ->getMock();
700
701
        $form->expects($this->once())
702
            ->method('createView')
703
            ->willReturn($this->createMock(FormView::class));
704
705
        $this->admin->expects($this->once())
706
            ->method('getDatagrid')
707
            ->willReturn($datagrid);
708
709
        $datagrid->expects($this->once())
710
            ->method('getForm')
711
            ->willReturn($form);
712
713
        $this->parameters = [];
714
        $this->assertInstanceOf(Response::class, $this->controller->listAction($this->request));
715
716
        $this->assertSame($this->admin, $this->parameters['admin']);
717
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
718
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
719
720
        $this->assertSame('list', $this->parameters['action']);
721
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
722
        $this->assertInstanceOf(DatagridInterface::class, $this->parameters['datagrid']);
723
        $this->assertSame('csrf-token-123_sonata.batch', $this->parameters['csrf_token']);
724
        $this->assertSame([], $this->session->getFlashBag()->all());
725
        $this->assertSame('@SonataAdmin/CRUD/list.html.twig', $this->template);
726
    }
727
728
    public function testBatchActionDeleteAccessDenied(): void
729
    {
730
        $this->expectException(AccessDeniedException::class);
731
732
        $this->admin->expects($this->once())
733
            ->method('checkAccess')
734
            ->with($this->equalTo('batchDelete'))
735
            ->will($this->throwException(new AccessDeniedException()));
736
737
        $this->controller->batchActionDelete($this->createMock(ProxyQueryInterface::class));
738
    }
739
740
    public function testBatchActionDelete(): void
741
    {
742
        $modelManager = $this->createMock(ModelManagerInterface::class);
743
744
        $this->admin->expects($this->once())
745
            ->method('checkAccess')
746
            ->with($this->equalTo('batchDelete'))
747
            ->willReturn(true);
748
749
        $this->admin->expects($this->once())
750
            ->method('getModelManager')
751
            ->willReturn($modelManager);
752
753
        $this->admin->expects($this->once())
754
            ->method('getFilterParameters')
755
            ->willReturn(['foo' => 'bar']);
756
757
        $this->expectTranslate('flash_batch_delete_success', [], 'SonataAdminBundle');
758
759
        $result = $this->controller->batchActionDelete($this->createMock(ProxyQueryInterface::class));
760
761
        $this->assertInstanceOf(RedirectResponse::class, $result);
762
        $this->assertSame(['flash_batch_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
763
        $this->assertSame('list?filter%5Bfoo%5D=bar', $result->getTargetUrl());
764
    }
765
766
    public function testBatchActionDeleteWithModelManagerException(): void
767
    {
768
        $modelManager = $this->createMock(ModelManagerInterface::class);
769
        $this->assertLoggerLogsModelManagerException($modelManager, 'batchDelete');
770
771
        $this->admin->expects($this->once())
772
            ->method('getModelManager')
773
            ->willReturn($modelManager);
774
775
        $this->admin->expects($this->once())
776
            ->method('getFilterParameters')
777
            ->willReturn(['foo' => 'bar']);
778
779
        $this->expectTranslate('flash_batch_delete_error', [], 'SonataAdminBundle');
780
781
        $result = $this->controller->batchActionDelete($this->createMock(ProxyQueryInterface::class));
782
783
        $this->assertInstanceOf(RedirectResponse::class, $result);
784
        $this->assertSame(['flash_batch_delete_error'], $this->session->getFlashBag()->get('sonata_flash_error'));
785
        $this->assertSame('list?filter%5Bfoo%5D=bar', $result->getTargetUrl());
786
    }
787
788
    public function testBatchActionDeleteWithModelManagerExceptionInDebugMode(): void
789
    {
790
        $modelManager = $this->createMock(ModelManagerInterface::class);
791
        $this->expectException(ModelManagerException::class);
792
793
        $modelManager->expects($this->once())
794
            ->method('batchDelete')
795
            ->willReturnCallback(static function (): void {
796
                throw new ModelManagerException();
797
            });
798
799
        $this->admin->expects($this->once())
800
            ->method('getModelManager')
801
            ->willReturn($modelManager);
802
803
        $this->kernel->expects($this->once())
804
            ->method('isDebug')
805
            ->willReturn(true);
806
807
        $this->controller->batchActionDelete($this->createMock(ProxyQueryInterface::class));
808
    }
809
810
    public function testShowActionNotFoundException(): void
811
    {
812
        $this->expectException(NotFoundHttpException::class);
813
814
        $this->admin->expects($this->once())
815
            ->method('getObject')
816
            ->willReturn(false);
817
818
        $this->controller->showAction(null, $this->request);
819
    }
820
821
    public function testShowActionAccessDenied(): void
822
    {
823
        $this->expectException(AccessDeniedException::class);
824
825
        $this->admin->expects($this->once())
826
            ->method('getObject')
827
            ->willReturn(new \stdClass());
828
829
        $this->admin->expects($this->once())
830
            ->method('checkAccess')
831
            ->with($this->equalTo('show'))
832
            ->will($this->throwException(new AccessDeniedException()));
833
834
        $this->controller->showAction(null, $this->request);
835
    }
836
837
    /**
838
     * @group legacy
839
     * @expectedDeprecation Calling this method without implementing "configureShowFields" is not supported since 3.40.0 and will no longer be possible in 4.0
840
     */
841
    public function testShowActionDeprecation(): void
842
    {
843
        $object = new \stdClass();
844
845
        $this->admin->expects($this->once())
846
            ->method('getObject')
847
            ->willReturn($object);
848
849
        $this->admin->expects($this->once())
850
            ->method('checkAccess')
851
            ->with($this->equalTo('show'))
852
            ->willReturn(true);
853
854
        $show = $this->createMock(FieldDescriptionCollection::class);
855
856
        $this->admin->expects($this->once())
857
            ->method('getShow')
858
            ->willReturn($show);
859
860
        $show->expects($this->once())
861
            ->method('getElements')
862
            ->willReturn([]);
863
864
        $show->expects($this->once())
865
            ->method('count')
866
            ->willReturn(0);
867
868
        $this->controller->showAction(null, $this->request);
869
    }
870
871
    public function testPreShow(): void
872
    {
873
        $object = new \stdClass();
874
        $object->foo = 123456;
875
876
        $this->admin->expects($this->once())
877
            ->method('getObject')
878
            ->willReturn($object);
879
880
        $this->admin->expects($this->once())
881
            ->method('checkAccess')
882
            ->with($this->equalTo('show'))
883
            ->willReturn(true);
884
885
        $controller = new PreCRUDController();
886
        $controller->setContainer($this->container);
887
888
        $response = $controller->showAction(null, $this->request);
889
        $this->assertInstanceOf(Response::class, $response);
890
        $this->assertSame('preShow called: 123456', $response->getContent());
891
    }
892
893
    public function testShowAction(): void
894
    {
895
        $object = new \stdClass();
896
897
        $this->admin->expects($this->once())
898
            ->method('getObject')
899
            ->willReturn($object);
900
901
        $this->admin->expects($this->once())
902
            ->method('checkAccess')
903
            ->with($this->equalTo('show'))
904
            ->willReturn(true);
905
906
        $show = $this->createMock(FieldDescriptionCollection::class);
907
908
        $this->admin->expects($this->once())
909
            ->method('getShow')
910
            ->willReturn($show);
911
912
        $show->expects($this->once())
913
            ->method('getElements')
914
            ->willReturn(['field' => 'fielddata']);
915
916
        $this->assertInstanceOf(Response::class, $this->controller->showAction(null, $this->request));
917
918
        $this->assertSame($this->admin, $this->parameters['admin']);
919
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
920
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
921
922
        $this->assertSame('show', $this->parameters['action']);
923
        $this->assertInstanceOf(FieldDescriptionCollection::class, $this->parameters['elements']);
924
        $this->assertSame($object, $this->parameters['object']);
925
926
        $this->assertSame([], $this->session->getFlashBag()->all());
927
        $this->assertSame('@SonataAdmin/CRUD/show.html.twig', $this->template);
928
    }
929
930
    /**
931
     * @dataProvider getRedirectToTests
932
     */
933
    public function testRedirectTo($expected, $route, $queryParams, $requestParams, $hasActiveSubclass): void
934
    {
935
        $this->admin->expects($this->any())
936
            ->method('hasActiveSubclass')
937
            ->willReturn($hasActiveSubclass);
938
939
        $object = new \stdClass();
940
941
        foreach ($queryParams as $key => $value) {
942
            $this->request->query->set($key, $value);
943
        }
944
945
        foreach ($requestParams as $key => $value) {
946
            $this->request->request->set($key, $value);
947
        }
948
949
        $this->admin->expects($this->any())
950
            ->method('hasRoute')
951
            ->with($this->equalTo($route))
952
            ->willReturn(true);
953
954
        $this->admin->expects($this->any())
955
            ->method('hasAccess')
956
            ->with($this->equalTo($route))
957
            ->willReturn(true);
958
959
        $response = $this->protectedTestedMethods['redirectTo']->invoke($this->controller, $object, $this->request);
960
        $this->assertInstanceOf(RedirectResponse::class, $response);
961
        $this->assertSame($expected, $response->getTargetUrl());
962
    }
963
964
    public function testRedirectToWithObject(): void
965
    {
966
        $this->admin->expects($this->any())
967
            ->method('hasActiveSubclass')
968
            ->willReturn(false);
969
970
        $object = new \stdClass();
971
972
        $this->admin->expects($this->at(0))
973
            ->method('hasRoute')
974
            ->with($this->equalTo('edit'))
975
            ->willReturn(true);
976
977
        $this->admin->expects($this->any())
978
            ->method('hasAccess')
979
            ->with($this->equalTo('edit'), $object)
980
            ->willReturn(false);
981
982
        $response = $this->protectedTestedMethods['redirectTo']->invoke($this->controller, $object, $this->request);
983
        $this->assertInstanceOf(RedirectResponse::class, $response);
984
        $this->assertSame('list', $response->getTargetUrl());
985
    }
986
987
    public function getRedirectToTests()
988
    {
989
        return [
990
            ['stdClass_edit', 'edit', [], [], false],
991
            ['list', 'list', ['btn_update_and_list' => true], [], false],
992
            ['list', 'list', ['btn_create_and_list' => true], [], false],
993
            ['create', 'create', ['btn_create_and_create' => true], [], false],
994
            ['create?subclass=foo', 'create', ['btn_create_and_create' => true, 'subclass' => 'foo'], [], true],
995
            ['stdClass_edit?_tab=first_tab', 'edit', ['btn_update_and_edit' => true], ['_tab' => 'first_tab'], false],
996
        ];
997
    }
998
999
    public function testDeleteActionNotFoundException(): void
1000
    {
1001
        $this->expectException(NotFoundHttpException::class);
1002
1003
        $this->admin->expects($this->once())
1004
            ->method('getObject')
1005
            ->willReturn(false);
1006
1007
        $this->controller->deleteAction(1, $this->request);
1008
    }
1009
1010
    public function testDeleteActionAccessDenied(): void
1011
    {
1012
        $this->expectException(AccessDeniedException::class);
1013
1014
        $this->admin->expects($this->once())
1015
            ->method('getObject')
1016
            ->willReturn(new \stdClass());
1017
1018
        $this->admin->expects($this->once())
1019
            ->method('checkAccess')
1020
            ->with($this->equalTo('delete'))
1021
            ->will($this->throwException(new AccessDeniedException()));
1022
1023
        $this->controller->deleteAction(1, $this->request);
1024
    }
1025
1026
    public function testPreDelete(): void
1027
    {
1028
        $object = new \stdClass();
1029
        $object->foo = 123456;
1030
1031
        $this->admin->expects($this->once())
1032
            ->method('getObject')
1033
            ->willReturn($object);
1034
1035
        $this->admin->expects($this->once())
1036
            ->method('checkAccess')
1037
            ->with($this->equalTo('delete'))
1038
            ->willReturn(true);
1039
1040
        $controller = new PreCRUDController();
1041
        $controller->setContainer($this->container);
1042
1043
        $response = $controller->deleteAction(null, $this->request);
1044
        $this->assertInstanceOf(Response::class, $response);
1045
        $this->assertSame('preDelete called: 123456', $response->getContent());
1046
    }
1047
1048
    public function testDeleteAction(): void
1049
    {
1050
        $object = new \stdClass();
1051
1052
        $this->admin->expects($this->once())
1053
            ->method('getObject')
1054
            ->willReturn($object);
1055
1056
        $this->admin->expects($this->once())
1057
            ->method('checkAccess')
1058
            ->with($this->equalTo('delete'))
1059
            ->willReturn(true);
1060
1061
        $this->assertInstanceOf(Response::class, $this->controller->deleteAction(1, $this->request));
1062
1063
        $this->assertSame($this->admin, $this->parameters['admin']);
1064
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1065
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1066
1067
        $this->assertSame('delete', $this->parameters['action']);
1068
        $this->assertSame($object, $this->parameters['object']);
1069
        $this->assertSame('csrf-token-123_sonata.delete', $this->parameters['csrf_token']);
1070
1071
        $this->assertSame([], $this->session->getFlashBag()->all());
1072
        $this->assertSame('@SonataAdmin/CRUD/delete.html.twig', $this->template);
1073
    }
1074
1075
    /**
1076
     * @group legacy
1077
     * @expectedDeprecation Accessing a child that isn't connected to a given parent is deprecated since 3.34 and won't be allowed in 4.0.
1078
     */
1079
    public function testDeleteActionChildDeprecation(): void
1080
    {
1081
        $object = new \stdClass();
1082
        $object->parent = 'test';
1083
1084
        $object2 = new \stdClass();
1085
1086
        $admin = $this->createMock(PostAdmin::class);
1087
1088
        $admin->expects($this->once())
1089
            ->method('getObject')
1090
            ->willReturn($object2);
1091
1092
        $this->admin->expects($this->once())
1093
            ->method('getObject')
1094
            ->willReturn($object);
1095
1096
        $this->admin->expects($this->once())
1097
            ->method('getParent')
1098
            ->willReturn($admin);
1099
1100
        $this->admin->expects($this->exactly(2))
1101
            ->method('getParentAssociationMapping')
1102
            ->willReturn('parent');
1103
1104
        $this->controller->deleteAction(1, $this->request);
1105
    }
1106
1107
    public function testDeleteActionNoParentMappings(): void
1108
    {
1109
        $object = new \stdClass();
1110
1111
        $admin = $this->createMock(PostAdmin::class);
1112
1113
        $admin->expects($this->never())
1114
            ->method('getObject');
1115
1116
        $this->admin->expects($this->once())
1117
            ->method('getObject')
1118
            ->willReturn($object);
1119
1120
        $this->admin->expects($this->once())
1121
            ->method('getParent')
1122
            ->willReturn($admin);
1123
1124
        $this->admin->expects($this->once())
1125
            ->method('getParentAssociationMapping')
1126
            ->willReturn(false);
1127
1128
        $this->controller->deleteAction(1, $this->request);
1129
    }
1130
1131
    public function testDeleteActionNoCsrfToken(): void
1132
    {
1133
        $this->csrfProvider = null;
1134
1135
        $object = new \stdClass();
1136
1137
        $this->admin->expects($this->once())
1138
            ->method('getObject')
1139
            ->willReturn($object);
1140
1141
        $this->admin->expects($this->once())
1142
            ->method('checkAccess')
1143
            ->with($this->equalTo('delete'))
1144
            ->willReturn(true);
1145
1146
        $this->assertInstanceOf(Response::class, $this->controller->deleteAction(1, $this->request));
1147
1148
        $this->assertSame($this->admin, $this->parameters['admin']);
1149
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1150
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1151
1152
        $this->assertSame('delete', $this->parameters['action']);
1153
        $this->assertSame($object, $this->parameters['object']);
1154
        $this->assertFalse($this->parameters['csrf_token']);
1155
1156
        $this->assertSame([], $this->session->getFlashBag()->all());
1157
        $this->assertSame('@SonataAdmin/CRUD/delete.html.twig', $this->template);
1158
    }
1159
1160
    public function testDeleteActionAjaxSuccess1(): void
1161
    {
1162
        $object = new \stdClass();
1163
1164
        $this->admin->expects($this->once())
1165
            ->method('getObject')
1166
            ->willReturn($object);
1167
1168
        $this->admin->expects($this->once())
1169
            ->method('checkAccess')
1170
            ->with($this->equalTo('delete'))
1171
            ->willReturn(true);
1172
1173
        $this->request->setMethod('DELETE');
1174
1175
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
1176
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1177
1178
        $response = $this->controller->deleteAction(1, $this->request);
1179
1180
        $this->assertInstanceOf(Response::class, $response);
1181
        $this->assertSame(json_encode(['result' => 'ok']), $response->getContent());
1182
        $this->assertSame([], $this->session->getFlashBag()->all());
1183
    }
1184
1185
    public function testDeleteActionAjaxSuccess2(): void
1186
    {
1187
        $object = new \stdClass();
1188
1189
        $this->admin->expects($this->once())
1190
            ->method('getObject')
1191
            ->willReturn($object);
1192
1193
        $this->admin->expects($this->once())
1194
            ->method('checkAccess')
1195
            ->with($this->equalTo('delete'))
1196
            ->willReturn(true);
1197
1198
        $this->request->setMethod('POST');
1199
        $this->request->request->set('_method', 'DELETE');
1200
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1201
1202
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
1203
1204
        $response = $this->controller->deleteAction(1, $this->request);
1205
1206
        $this->assertInstanceOf(Response::class, $response);
1207
        $this->assertSame(json_encode(['result' => 'ok']), $response->getContent());
1208
        $this->assertSame([], $this->session->getFlashBag()->all());
1209
    }
1210
1211
    public function testDeleteActionAjaxError(): void
1212
    {
1213
        $object = new \stdClass();
1214
1215
        $this->admin->expects($this->once())
1216
            ->method('getObject')
1217
            ->willReturn($object);
1218
1219
        $this->admin->expects($this->once())
1220
            ->method('checkAccess')
1221
            ->with($this->equalTo('delete'))
1222
            ->willReturn(true);
1223
1224
        $this->admin->expects($this->any())
1225
            ->method('getClass')
1226
            ->willReturn('stdClass');
1227
1228
        $this->assertLoggerLogsModelManagerException($this->admin, 'delete');
1229
1230
        $this->request->setMethod('DELETE');
1231
1232
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1233
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
1234
1235
        $response = $this->controller->deleteAction(1, $this->request);
1236
1237
        $this->assertInstanceOf(Response::class, $response);
1238
        $this->assertSame(json_encode(['result' => 'error']), $response->getContent());
1239
        $this->assertSame([], $this->session->getFlashBag()->all());
1240
    }
1241
1242
    public function testDeleteActionWithModelManagerExceptionInDebugMode(): void
1243
    {
1244
        $this->expectException(ModelManagerException::class);
1245
1246
        $object = new \stdClass();
1247
1248
        $this->admin->expects($this->once())
1249
            ->method('getObject')
1250
            ->willReturn($object);
1251
1252
        $this->admin->expects($this->once())
1253
            ->method('checkAccess')
1254
            ->with($this->equalTo('delete'))
1255
            ->willReturn(true);
1256
1257
        $this->admin->expects($this->once())
1258
            ->method('delete')
1259
            ->willReturnCallback(static function (): void {
1260
                throw new ModelManagerException();
1261
            });
1262
1263
        $this->kernel->expects($this->once())
1264
            ->method('isDebug')
1265
            ->willReturn(true);
1266
1267
        $this->request->setMethod('DELETE');
1268
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1269
1270
        $this->controller->deleteAction(1, $this->request);
1271
    }
1272
1273
    /**
1274
     * @dataProvider getToStringValues
1275
     */
1276
    public function testDeleteActionSuccess1($expectedToStringValue, $toStringValue): void
1277
    {
1278
        $object = new \stdClass();
1279
1280
        $this->admin->expects($this->once())
1281
            ->method('getObject')
1282
            ->willReturn($object);
1283
1284
        $this->admin->expects($this->once())
1285
            ->method('toString')
1286
            ->with($this->equalTo($object))
1287
            ->willReturn($toStringValue);
1288
1289
        $this->expectTranslate('flash_delete_success', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1290
1291
        $this->admin->expects($this->once())
1292
            ->method('checkAccess')
1293
            ->with($this->equalTo('delete'))
1294
            ->willReturn(true);
1295
1296
        $this->request->setMethod('DELETE');
1297
1298
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1299
1300
        $response = $this->controller->deleteAction(1, $this->request);
1301
1302
        $this->assertInstanceOf(RedirectResponse::class, $response);
1303
        $this->assertSame(['flash_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
1304
        $this->assertSame('list', $response->getTargetUrl());
1305
    }
1306
1307
    /**
1308
     * @dataProvider getToStringValues
1309
     */
1310
    public function testDeleteActionSuccess2($expectedToStringValue, $toStringValue): void
1311
    {
1312
        $object = new \stdClass();
1313
1314
        $this->admin->expects($this->once())
1315
            ->method('getObject')
1316
            ->willReturn($object);
1317
1318
        $this->admin->expects($this->once())
1319
            ->method('checkAccess')
1320
            ->with($this->equalTo('delete'))
1321
            ->willReturn(true);
1322
1323
        $this->admin->expects($this->once())
1324
            ->method('toString')
1325
            ->with($this->equalTo($object))
1326
            ->willReturn($toStringValue);
1327
1328
        $this->expectTranslate('flash_delete_success', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1329
1330
        $this->request->setMethod('POST');
1331
        $this->request->request->set('_method', 'DELETE');
1332
1333
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1334
1335
        $response = $this->controller->deleteAction(1, $this->request);
1336
1337
        $this->assertInstanceOf(RedirectResponse::class, $response);
1338
        $this->assertSame(['flash_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
1339
        $this->assertSame('list', $response->getTargetUrl());
1340
    }
1341
1342
    /**
1343
     * @dataProvider getToStringValues
1344
     */
1345
    public function testDeleteActionSuccessNoCsrfTokenProvider($expectedToStringValue, $toStringValue): void
1346
    {
1347
        $this->csrfProvider = null;
1348
1349
        $object = new \stdClass();
1350
1351
        $this->admin->expects($this->once())
1352
            ->method('getObject')
1353
            ->willReturn($object);
1354
1355
        $this->admin->expects($this->once())
1356
            ->method('checkAccess')
1357
            ->with($this->equalTo('delete'))
1358
            ->willReturn(true);
1359
1360
        $this->admin->expects($this->once())
1361
            ->method('toString')
1362
            ->with($this->equalTo($object))
1363
            ->willReturn($toStringValue);
1364
1365
        $this->expectTranslate('flash_delete_success', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1366
1367
        $this->request->setMethod('POST');
1368
        $this->request->request->set('_method', 'DELETE');
1369
1370
        $response = $this->controller->deleteAction(1, $this->request);
1371
1372
        $this->assertInstanceOf(RedirectResponse::class, $response);
1373
        $this->assertSame(['flash_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
1374
        $this->assertSame('list', $response->getTargetUrl());
1375
    }
1376
1377
    public function testDeleteActionWrongRequestMethod(): void
1378
    {
1379
        $object = new \stdClass();
1380
1381
        $this->admin->expects($this->once())
1382
            ->method('getObject')
1383
            ->willReturn($object);
1384
1385
        $this->admin->expects($this->once())
1386
            ->method('checkAccess')
1387
            ->with($this->equalTo('delete'))
1388
            ->willReturn(true);
1389
1390
        //without POST request parameter "_method" should not be used as real REST method
1391
        $this->request->query->set('_method', 'DELETE');
1392
1393
        $this->assertInstanceOf(Response::class, $this->controller->deleteAction(1, $this->request));
1394
1395
        $this->assertSame($this->admin, $this->parameters['admin']);
1396
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1397
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1398
1399
        $this->assertSame('delete', $this->parameters['action']);
1400
        $this->assertSame($object, $this->parameters['object']);
1401
        $this->assertSame('csrf-token-123_sonata.delete', $this->parameters['csrf_token']);
1402
1403
        $this->assertSame([], $this->session->getFlashBag()->all());
1404
        $this->assertSame('@SonataAdmin/CRUD/delete.html.twig', $this->template);
1405
    }
1406
1407
    /**
1408
     * @dataProvider getToStringValues
1409
     */
1410
    public function testDeleteActionError($expectedToStringValue, $toStringValue): void
1411
    {
1412
        $object = new \stdClass();
1413
1414
        $this->admin->expects($this->once())
1415
            ->method('getObject')
1416
            ->willReturn($object);
1417
1418
        $this->admin->expects($this->once())
1419
            ->method('checkAccess')
1420
            ->with($this->equalTo('delete'))
1421
            ->willReturn(true);
1422
1423
        $this->admin->expects($this->once())
1424
            ->method('toString')
1425
            ->with($this->equalTo($object))
1426
            ->willReturn($toStringValue);
1427
1428
        $this->expectTranslate('flash_delete_error', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1429
1430
        $this->assertLoggerLogsModelManagerException($this->admin, 'delete');
1431
1432
        $this->request->setMethod('DELETE');
1433
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
1434
1435
        $response = $this->controller->deleteAction(1, $this->request);
1436
1437
        $this->assertInstanceOf(RedirectResponse::class, $response);
1438
        $this->assertSame(['flash_delete_error'], $this->session->getFlashBag()->get('sonata_flash_error'));
1439
        $this->assertSame('list', $response->getTargetUrl());
1440
    }
1441
1442
    public function testDeleteActionInvalidCsrfToken(): void
1443
    {
1444
        $object = new \stdClass();
1445
1446
        $this->admin->expects($this->once())
1447
            ->method('getObject')
1448
            ->willReturn($object);
1449
1450
        $this->admin->expects($this->once())
1451
            ->method('checkAccess')
1452
            ->with($this->equalTo('delete'))
1453
            ->willReturn(true);
1454
1455
        $this->request->setMethod('POST');
1456
        $this->request->request->set('_method', 'DELETE');
1457
        $this->request->request->set('_sonata_csrf_token', 'CSRF-INVALID');
1458
1459
        try {
1460
            $this->controller->deleteAction(1, $this->request);
1461
        } catch (HttpException $e) {
1462
            $this->assertSame('The csrf token is not valid, CSRF attack?', $e->getMessage());
1463
            $this->assertSame(400, $e->getStatusCode());
1464
        }
1465
    }
1466
1467
    public function testEditActionNotFoundException(): void
1468
    {
1469
        $this->expectException(NotFoundHttpException::class);
1470
1471
        $this->admin->expects($this->once())
1472
            ->method('getObject')
1473
            ->willReturn(false);
1474
1475
        $this->controller->editAction(null, $this->request);
1476
    }
1477
1478
    public function testEditActionRuntimeException(): void
1479
    {
1480
        $this->expectException(\RuntimeException::class);
1481
1482
        $this->admin->expects($this->once())
1483
            ->method('getObject')
1484
            ->willReturn(new \stdClass());
1485
1486
        $this->admin->expects($this->once())
1487
            ->method('checkAccess')
1488
            ->with($this->equalTo('edit'))
1489
            ->willReturn(true);
1490
1491
        $form = $this->createMock(Form::class);
1492
1493
        $this->admin->expects($this->once())
1494
            ->method('getForm')
1495
            ->willReturn($form);
1496
1497
        $form->expects($this->once())
1498
            ->method('all')
1499
            ->willReturn([]);
1500
1501
        $this->controller->editAction(null, $this->request);
1502
    }
1503
1504
    public function testEditActionAccessDenied(): void
1505
    {
1506
        $this->expectException(AccessDeniedException::class);
1507
1508
        $this->admin->expects($this->once())
1509
            ->method('getObject')
1510
            ->willReturn(new \stdClass());
1511
1512
        $this->admin->expects($this->once())
1513
            ->method('checkAccess')
1514
            ->with($this->equalTo('edit'))
1515
            ->will($this->throwException(new AccessDeniedException()));
1516
1517
        $this->controller->editAction(null, $this->request);
1518
    }
1519
1520
    public function testPreEdit(): void
1521
    {
1522
        $object = new \stdClass();
1523
        $object->foo = 123456;
1524
1525
        $this->admin->expects($this->once())
1526
            ->method('getObject')
1527
            ->willReturn($object);
1528
1529
        $this->admin->expects($this->once())
1530
            ->method('checkAccess')
1531
            ->with($this->equalTo('edit'))
1532
            ->willReturn(true);
1533
1534
        $controller = new PreCRUDController();
1535
        $controller->setContainer($this->container);
1536
1537
        $response = $controller->editAction(null, $this->request);
1538
        $this->assertInstanceOf(Response::class, $response);
1539
        $this->assertSame('preEdit called: 123456', $response->getContent());
1540
    }
1541
1542
    public function testEditAction(): void
1543
    {
1544
        $object = new \stdClass();
1545
1546
        $this->admin->expects($this->once())
1547
            ->method('getObject')
1548
            ->willReturn($object);
1549
1550
        $this->admin->expects($this->once())
1551
            ->method('checkAccess')
1552
            ->with($this->equalTo('edit'))
1553
            ->willReturn(true);
1554
1555
        $form = $this->createMock(Form::class);
1556
1557
        $this->admin->expects($this->once())
1558
            ->method('getForm')
1559
            ->willReturn($form);
1560
1561
        $formView = $this->createMock(FormView::class);
1562
1563
        $form->expects($this->any())
1564
            ->method('createView')
1565
            ->willReturn($formView);
1566
1567
        $form->expects($this->once())
1568
            ->method('all')
1569
            ->willReturn(['field' => 'fielddata']);
1570
1571
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
1572
1573
        $this->assertSame($this->admin, $this->parameters['admin']);
1574
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1575
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1576
1577
        $this->assertSame('edit', $this->parameters['action']);
1578
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
1579
        $this->assertSame($object, $this->parameters['object']);
1580
        $this->assertSame([], $this->session->getFlashBag()->all());
1581
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
1582
    }
1583
1584
    /**
1585
     * @dataProvider getToStringValues
1586
     */
1587
    public function testEditActionSuccess($expectedToStringValue, $toStringValue): void
1588
    {
1589
        $object = new \stdClass();
1590
1591
        $this->admin->expects($this->once())
1592
            ->method('getObject')
1593
            ->willReturn($object);
1594
1595
        $this->admin->expects($this->once())
1596
            ->method('update')
1597
            ->willReturnArgument(0);
1598
1599
        $this->admin->expects($this->once())
1600
            ->method('checkAccess')
1601
            ->with($this->equalTo('edit'));
1602
1603
        $this->admin->expects($this->once())
1604
            ->method('hasRoute')
1605
            ->with($this->equalTo('edit'))
1606
            ->willReturn(true);
1607
1608
        $this->admin->expects($this->once())
1609
            ->method('hasAccess')
1610
            ->with($this->equalTo('edit'))
1611
            ->willReturn(true);
1612
1613
        $form = $this->createMock(Form::class);
1614
1615
        $form->expects($this->once())
1616
            ->method('getData')
1617
            ->willReturn($object);
1618
1619
        $this->admin->expects($this->once())
1620
            ->method('getForm')
1621
            ->willReturn($form);
1622
1623
        $form->expects($this->once())
1624
            ->method('isSubmitted')
1625
            ->willReturn(true);
1626
1627
        $form->expects($this->once())
1628
            ->method('isValid')
1629
            ->willReturn(true);
1630
1631
        $form->expects($this->once())
1632
            ->method('all')
1633
            ->willReturn(['field' => 'fielddata']);
1634
1635
        $this->admin->expects($this->once())
1636
            ->method('toString')
1637
            ->with($this->equalTo($object))
1638
            ->willReturn($toStringValue);
1639
1640
        $this->expectTranslate('flash_edit_success', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1641
1642
        $this->request->setMethod('POST');
1643
1644
        $response = $this->controller->editAction(null, $this->request);
1645
1646
        $this->assertInstanceOf(RedirectResponse::class, $response);
1647
        $this->assertSame(['flash_edit_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
1648
        $this->assertSame('stdClass_edit', $response->getTargetUrl());
1649
    }
1650
1651
    /**
1652
     * @dataProvider getToStringValues
1653
     */
1654
    public function testEditActionError($expectedToStringValue, $toStringValue): void
1655
    {
1656
        $object = new \stdClass();
1657
1658
        $this->admin->expects($this->once())
1659
            ->method('getObject')
1660
            ->willReturn($object);
1661
1662
        $this->admin->expects($this->once())
1663
            ->method('checkAccess')
1664
            ->with($this->equalTo('edit'))
1665
            ->willReturn(true);
1666
1667
        $form = $this->createMock(Form::class);
1668
1669
        $this->admin->expects($this->once())
1670
            ->method('getForm')
1671
            ->willReturn($form);
1672
1673
        $form->expects($this->once())
1674
            ->method('isSubmitted')
1675
            ->willReturn(true);
1676
1677
        $form->expects($this->once())
1678
            ->method('isValid')
1679
            ->willReturn(false);
1680
1681
        $form->expects($this->once())
1682
            ->method('all')
1683
            ->willReturn(['field' => 'fielddata']);
1684
1685
        $this->admin->expects($this->once())
1686
            ->method('toString')
1687
            ->with($this->equalTo($object))
1688
            ->willReturn($toStringValue);
1689
1690
        $this->expectTranslate('flash_edit_error', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1691
1692
        $this->request->setMethod('POST');
1693
1694
        $formView = $this->createMock(FormView::class);
1695
1696
        $form->expects($this->any())
1697
            ->method('createView')
1698
            ->willReturn($formView);
1699
1700
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
1701
1702
        $this->assertSame($this->admin, $this->parameters['admin']);
1703
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1704
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1705
1706
        $this->assertSame('edit', $this->parameters['action']);
1707
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
1708
        $this->assertSame($object, $this->parameters['object']);
1709
1710
        $this->assertSame(['sonata_flash_error' => ['flash_edit_error']], $this->session->getFlashBag()->all());
1711
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
1712
    }
1713
1714
    public function testEditActionAjaxSuccess(): void
1715
    {
1716
        $object = new \stdClass();
1717
1718
        $this->admin->expects($this->once())
1719
            ->method('getObject')
1720
            ->willReturn($object);
1721
1722
        $this->admin->expects($this->once())
1723
            ->method('update')
1724
            ->willReturnArgument(0);
1725
1726
        $this->admin->expects($this->once())
1727
            ->method('checkAccess')
1728
            ->with($this->equalTo('edit'))
1729
            ->willReturn(true);
1730
1731
        $form = $this->createMock(Form::class);
1732
1733
        $this->admin->expects($this->once())
1734
            ->method('getForm')
1735
            ->willReturn($form);
1736
1737
        $form->expects($this->once())
1738
            ->method('isSubmitted')
1739
            ->willReturn(true);
1740
1741
        $form->expects($this->once())
1742
            ->method('isValid')
1743
            ->willReturn(true);
1744
1745
        $form->expects($this->once())
1746
            ->method('getData')
1747
            ->willReturn($object);
1748
1749
        $form->expects($this->once())
1750
            ->method('all')
1751
            ->willReturn(['field' => 'fielddata']);
1752
1753
        $this->admin->expects($this->once())
1754
            ->method('getNormalizedIdentifier')
1755
            ->with($this->equalTo($object))
1756
            ->willReturn('foo_normalized');
1757
1758
        $this->admin->expects($this->once())
1759
            ->method('toString')
1760
            ->willReturn('foo');
1761
1762
        $this->request->setMethod('POST');
1763
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
1764
1765
        $response = $this->controller->editAction(null, $this->request);
1766
1767
        $this->assertInstanceOf(Response::class, $response);
1768
        $this->assertSame(json_encode(['result' => 'ok', 'objectId' => 'foo_normalized', 'objectName' => 'foo']), $response->getContent());
1769
        $this->assertSame([], $this->session->getFlashBag()->all());
1770
    }
1771
1772
    public function testEditActionAjaxError(): void
1773
    {
1774
        $object = new \stdClass();
1775
1776
        $this->admin->expects($this->once())
1777
            ->method('getObject')
1778
            ->willReturn($object);
1779
1780
        $this->admin->expects($this->once())
1781
            ->method('checkAccess')
1782
            ->with($this->equalTo('edit'))
1783
            ->willReturn(true);
1784
1785
        $form = $this->createMock(Form::class);
1786
1787
        $this->admin->expects($this->once())
1788
            ->method('getForm')
1789
            ->willReturn($form);
1790
1791
        $form->expects($this->once())
1792
            ->method('isSubmitted')
1793
            ->willReturn(true);
1794
1795
        $form->expects($this->once())
1796
            ->method('isValid')
1797
            ->willReturn(false);
1798
1799
        $form->expects($this->once())
1800
            ->method('all')
1801
            ->willReturn(['field' => 'fielddata']);
1802
1803
        $this->request->setMethod('POST');
1804
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
1805
1806
        $formView = $this->createMock(FormView::class);
1807
1808
        $form->expects($this->any())
1809
            ->method('createView')
1810
            ->willReturn($formView);
1811
1812
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
1813
1814
        $this->assertSame($this->admin, $this->parameters['admin']);
1815
        $this->assertSame('@SonataAdmin/ajax_layout.html.twig', $this->parameters['base_template']);
1816
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1817
1818
        $this->assertSame('edit', $this->parameters['action']);
1819
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
1820
        $this->assertSame($object, $this->parameters['object']);
1821
1822
        $this->assertSame([], $this->session->getFlashBag()->all());
1823
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
1824
    }
1825
1826
    /**
1827
     * @dataProvider getToStringValues
1828
     */
1829
    public function testEditActionWithModelManagerException($expectedToStringValue, $toStringValue): void
1830
    {
1831
        $object = new \stdClass();
1832
1833
        $this->admin->expects($this->once())
1834
            ->method('getObject')
1835
            ->willReturn($object);
1836
1837
        $this->admin->expects($this->once())
1838
            ->method('checkAccess')
1839
            ->with($this->equalTo('edit'))
1840
            ->willReturn(true);
1841
1842
        $this->admin->expects($this->any())
1843
            ->method('getClass')
1844
            ->willReturn('stdClass');
1845
1846
        $form = $this->createMock(Form::class);
1847
1848
        $this->admin->expects($this->once())
1849
            ->method('getForm')
1850
            ->willReturn($form);
1851
1852
        $form->expects($this->once())
1853
            ->method('isValid')
1854
            ->willReturn(true);
1855
1856
        $form->expects($this->once())
1857
            ->method('getData')
1858
            ->willReturn($object);
1859
1860
        $form->expects($this->once())
1861
            ->method('all')
1862
            ->willReturn(['field' => 'fielddata']);
1863
1864
        $this->admin->expects($this->once())
1865
            ->method('toString')
1866
            ->with($this->equalTo($object))
1867
            ->willReturn($toStringValue);
1868
1869
        $this->expectTranslate('flash_edit_error', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
1870
1871
        $form->expects($this->once())
1872
            ->method('isSubmitted')
1873
            ->willReturn(true);
1874
        $this->request->setMethod('POST');
1875
1876
        $formView = $this->createMock(FormView::class);
1877
1878
        $form->expects($this->any())
1879
            ->method('createView')
1880
            ->willReturn($formView);
1881
1882
        $this->assertLoggerLogsModelManagerException($this->admin, 'update');
1883
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
1884
1885
        $this->assertSame($this->admin, $this->parameters['admin']);
1886
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1887
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1888
1889
        $this->assertSame('edit', $this->parameters['action']);
1890
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
1891
        $this->assertSame($object, $this->parameters['object']);
1892
1893
        $this->assertSame(['sonata_flash_error' => ['flash_edit_error']], $this->session->getFlashBag()->all());
1894
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
1895
    }
1896
1897
    public function testEditActionWithPreview(): void
1898
    {
1899
        $object = new \stdClass();
1900
1901
        $this->admin->expects($this->once())
1902
            ->method('getObject')
1903
            ->willReturn($object);
1904
1905
        $this->admin->expects($this->once())
1906
            ->method('checkAccess')
1907
            ->with($this->equalTo('edit'))
1908
            ->willReturn(true);
1909
1910
        $form = $this->createMock(Form::class);
1911
1912
        $this->admin->expects($this->once())
1913
            ->method('getForm')
1914
            ->willReturn($form);
1915
1916
        $this->admin->expects($this->once())
1917
            ->method('supportsPreviewMode')
1918
            ->willReturn(true);
1919
1920
        $formView = $this->createMock(FormView::class);
1921
1922
        $form->expects($this->any())
1923
            ->method('createView')
1924
            ->willReturn($formView);
1925
1926
        $form->expects($this->once())
1927
            ->method('isSubmitted')
1928
            ->willReturn(true);
1929
1930
        $form->expects($this->once())
1931
            ->method('isValid')
1932
            ->willReturn(true);
1933
1934
        $form->expects($this->once())
1935
            ->method('all')
1936
            ->willReturn(['field' => 'fielddata']);
1937
1938
        $this->request->setMethod('POST');
1939
        $this->request->request->set('btn_preview', 'Preview');
1940
1941
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
1942
1943
        $this->assertSame($this->admin, $this->parameters['admin']);
1944
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
1945
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
1946
1947
        $this->assertSame('edit', $this->parameters['action']);
1948
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
1949
        $this->assertSame($object, $this->parameters['object']);
1950
1951
        $this->assertSame([], $this->session->getFlashBag()->all());
1952
        $this->assertSame('@SonataAdmin/CRUD/preview.html.twig', $this->template);
1953
    }
1954
1955
    public function testEditActionWithLockException(): void
1956
    {
1957
        $object = new \stdClass();
1958
        $class = \get_class($object);
1959
1960
        $this->admin->expects($this->any())
1961
            ->method('getObject')
1962
            ->willReturn($object);
1963
1964
        $this->admin->expects($this->any())
1965
            ->method('checkAccess')
1966
            ->with($this->equalTo('edit'))
1967
            ->willReturn(true);
1968
1969
        $this->admin->expects($this->any())
1970
            ->method('getClass')
1971
            ->willReturn($class);
1972
1973
        $form = $this->createMock(Form::class);
1974
1975
        $form->expects($this->any())
1976
            ->method('isValid')
1977
            ->willReturn(true);
1978
1979
        $form->expects($this->once())
1980
            ->method('getData')
1981
            ->willReturn($object);
1982
1983
        $form->expects($this->once())
1984
            ->method('all')
1985
            ->willReturn(['field' => 'fielddata']);
1986
1987
        $this->admin->expects($this->any())
1988
            ->method('getForm')
1989
            ->willReturn($form);
1990
1991
        $form->expects($this->any())
1992
            ->method('isSubmitted')
1993
            ->willReturn(true);
1994
        $this->request->setMethod('POST');
1995
1996
        $this->admin->expects($this->any())
1997
            ->method('update')
1998
            ->will($this->throwException(new LockException()));
1999
2000
        $this->admin->expects($this->any())
2001
            ->method('toString')
2002
            ->with($this->equalTo($object))
2003
            ->willReturn($class);
2004
2005
        $formView = $this->createMock(FormView::class);
2006
2007
        $form->expects($this->any())
2008
            ->method('createView')
2009
            ->willReturn($formView);
2010
2011
        $this->expectTranslate('flash_lock_error', [
2012
            '%name%' => $class,
2013
            '%link_start%' => '<a href="stdClass_edit">',
2014
            '%link_end%' => '</a>',
2015
        ], 'SonataAdminBundle');
2016
2017
        $this->assertInstanceOf(Response::class, $this->controller->editAction(null, $this->request));
2018
    }
2019
2020
    public function testCreateActionAccessDenied(): void
2021
    {
2022
        $this->expectException(AccessDeniedException::class);
2023
2024
        $this->admin->expects($this->once())
2025
            ->method('checkAccess')
2026
            ->with($this->equalTo('create'))
2027
            ->will($this->throwException(new AccessDeniedException()));
2028
2029
        $this->controller->createAction($this->request);
2030
    }
2031
2032
    public function testCreateActionRuntimeException(): void
2033
    {
2034
        $this->expectException(\RuntimeException::class);
2035
2036
        $this->admin->expects($this->once())
2037
            ->method('checkAccess')
2038
            ->with($this->equalTo('create'))
2039
            ->willReturn(true);
2040
2041
        $this->admin->expects($this->any())
2042
            ->method('getClass')
2043
            ->willReturn('stdClass');
2044
2045
        $this->admin->expects($this->once())
2046
            ->method('getNewInstance')
2047
            ->willReturn(new \stdClass());
2048
2049
        $form = $this->createMock(Form::class);
2050
2051
        $this->admin->expects($this->once())
2052
            ->method('getForm')
2053
            ->willReturn($form);
2054
2055
        $form->expects($this->once())
2056
            ->method('all')
2057
            ->willReturn([]);
2058
2059
        $this->controller->createAction($this->request);
2060
    }
2061
2062
    public function testPreCreate(): void
2063
    {
2064
        $object = new \stdClass();
2065
        $object->foo = 123456;
2066
2067
        $this->admin->expects($this->once())
2068
            ->method('checkAccess')
2069
            ->with($this->equalTo('create'))
2070
            ->willReturn(true);
2071
2072
        $this->admin->expects($this->any())
2073
            ->method('getClass')
2074
            ->willReturn('stdClass');
2075
2076
        $this->admin->expects($this->once())
2077
            ->method('getNewInstance')
2078
            ->willReturn($object);
2079
2080
        $controller = new PreCRUDController();
2081
        $controller->setContainer($this->container);
2082
2083
        $response = $controller->createAction($this->request);
2084
        $this->assertInstanceOf(Response::class, $response);
2085
        $this->assertSame('preCreate called: 123456', $response->getContent());
2086
    }
2087
2088
    public function testCreateAction(): void
2089
    {
2090
        $this->admin->expects($this->once())
2091
            ->method('checkAccess')
2092
            ->with($this->equalTo('create'))
2093
            ->willReturn(true);
2094
2095
        $object = new \stdClass();
2096
2097
        $this->admin->expects($this->any())
2098
            ->method('getClass')
2099
            ->willReturn('stdClass');
2100
2101
        $this->admin->expects($this->once())
2102
            ->method('getNewInstance')
2103
            ->willReturn($object);
2104
2105
        $form = $this->createMock(Form::class);
2106
2107
        $this->admin->expects($this->once())
2108
            ->method('getForm')
2109
            ->willReturn($form);
2110
2111
        $form->expects($this->once())
2112
            ->method('all')
2113
            ->willReturn(['field' => 'fielddata']);
2114
2115
        $formView = $this->createMock(FormView::class);
2116
2117
        $form->expects($this->any())
2118
            ->method('createView')
2119
            ->willReturn($formView);
2120
2121
        $this->assertInstanceOf(Response::class, $this->controller->createAction($this->request));
2122
2123
        $this->assertSame($this->admin, $this->parameters['admin']);
2124
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2125
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2126
2127
        $this->assertSame('create', $this->parameters['action']);
2128
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
2129
        $this->assertSame($object, $this->parameters['object']);
2130
2131
        $this->assertSame([], $this->session->getFlashBag()->all());
2132
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
2133
    }
2134
2135
    /**
2136
     * @dataProvider getToStringValues
2137
     */
2138
    public function testCreateActionSuccess($expectedToStringValue, $toStringValue): void
2139
    {
2140
        $object = new \stdClass();
2141
2142
        $this->admin->expects($this->exactly(2))
2143
            ->method('checkAccess')
2144
            ->willReturnCallback(static function ($name, $objectIn = null) use ($object) {
2145
                if ('edit' === $name) {
2146
                    return true;
2147
                }
2148
2149
                if ('create' !== $name) {
2150
                    return false;
2151
                }
2152
2153
                if (null === $objectIn) {
2154
                    return true;
2155
                }
2156
2157
                return $objectIn === $object;
2158
            });
2159
2160
        $this->admin->expects($this->once())
2161
            ->method('hasRoute')
2162
            ->with($this->equalTo('edit'))
2163
            ->willReturn(true);
2164
2165
        $this->admin->expects($this->once())
2166
            ->method('hasAccess')
2167
            ->with($this->equalTo('edit'))
2168
            ->willReturn(true);
2169
2170
        $this->admin->expects($this->once())
2171
            ->method('getNewInstance')
2172
            ->willReturn($object);
2173
2174
        $this->admin->expects($this->once())
2175
            ->method('create')
2176
            ->willReturnArgument(0);
2177
2178
        $form = $this->createMock(Form::class);
2179
2180
        $this->admin->expects($this->any())
2181
            ->method('getClass')
2182
            ->willReturn('stdClass');
2183
2184
        $this->admin->expects($this->once())
2185
            ->method('getForm')
2186
            ->willReturn($form);
2187
2188
        $form->expects($this->once())
2189
            ->method('all')
2190
            ->willReturn(['field' => 'fielddata']);
2191
2192
        $form->expects($this->once())
2193
            ->method('isSubmitted')
2194
            ->willReturn(true);
2195
2196
        $form->expects($this->once())
2197
            ->method('isValid')
2198
            ->willReturn(true);
2199
2200
        $form->expects($this->once())
2201
            ->method('getData')
2202
            ->willReturn($object);
2203
2204
        $this->admin->expects($this->once())
2205
            ->method('toString')
2206
            ->with($this->equalTo($object))
2207
            ->willReturn($toStringValue);
2208
2209
        $this->expectTranslate('flash_create_success', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
2210
2211
        $this->request->setMethod('POST');
2212
2213
        $response = $this->controller->createAction($this->request);
2214
2215
        $this->assertInstanceOf(RedirectResponse::class, $response);
2216
        $this->assertSame(['flash_create_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
2217
        $this->assertSame('stdClass_edit', $response->getTargetUrl());
2218
    }
2219
2220
    public function testCreateActionAccessDenied2(): void
2221
    {
2222
        $this->expectException(AccessDeniedException::class);
2223
2224
        $object = new \stdClass();
2225
2226
        $this->admin->expects($this->any())
2227
            ->method('checkAccess')
2228
            ->willReturnCallback(static function ($name, $object = null) {
2229
                if ('create' !== $name) {
2230
                    throw new AccessDeniedException();
2231
                }
2232
                if (null === $object) {
2233
                    return true;
2234
                }
2235
2236
                throw new AccessDeniedException();
2237
            });
2238
2239
        $this->admin->expects($this->once())
2240
            ->method('getNewInstance')
2241
            ->willReturn($object);
2242
2243
        $form = $this->createMock(Form::class);
2244
2245
        $this->admin->expects($this->any())
2246
            ->method('getClass')
2247
            ->willReturn('stdClass');
2248
2249
        $this->admin->expects($this->once())
2250
            ->method('getForm')
2251
            ->willReturn($form);
2252
2253
        $form->expects($this->once())
2254
            ->method('all')
2255
            ->willReturn(['field' => 'fielddata']);
2256
2257
        $form->expects($this->once())
2258
            ->method('isSubmitted')
2259
            ->willReturn(true);
2260
2261
        $form->expects($this->once())
2262
            ->method('getData')
2263
            ->willReturn($object);
2264
2265
        $form->expects($this->once())
2266
            ->method('isValid')
2267
            ->willReturn(true);
2268
2269
        $this->request->setMethod('POST');
2270
2271
        $this->controller->createAction($this->request);
2272
    }
2273
2274
    /**
2275
     * @dataProvider getToStringValues
2276
     */
2277
    public function testCreateActionError($expectedToStringValue, $toStringValue): void
2278
    {
2279
        $this->admin->expects($this->once())
2280
            ->method('checkAccess')
2281
            ->with($this->equalTo('create'))
2282
            ->willReturn(true);
2283
2284
        $object = new \stdClass();
2285
2286
        $this->admin->expects($this->any())
2287
            ->method('getClass')
2288
            ->willReturn('stdClass');
2289
2290
        $this->admin->expects($this->once())
2291
            ->method('getNewInstance')
2292
            ->willReturn($object);
2293
2294
        $form = $this->createMock(Form::class);
2295
2296
        $this->admin->expects($this->once())
2297
            ->method('getForm')
2298
            ->willReturn($form);
2299
2300
        $form->expects($this->once())
2301
            ->method('all')
2302
            ->willReturn(['field' => 'fielddata']);
2303
2304
        $form->expects($this->once())
2305
            ->method('isSubmitted')
2306
            ->willReturn(true);
2307
2308
        $form->expects($this->once())
2309
            ->method('isValid')
2310
            ->willReturn(false);
2311
2312
        $this->admin->expects($this->once())
2313
            ->method('toString')
2314
            ->with($this->equalTo($object))
2315
            ->willReturn($toStringValue);
2316
2317
        $this->expectTranslate('flash_create_error', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
2318
2319
        $this->request->setMethod('POST');
2320
2321
        $formView = $this->createMock(FormView::class);
2322
2323
        $form->expects($this->any())
2324
            ->method('createView')
2325
            ->willReturn($formView);
2326
2327
        $this->assertInstanceOf(Response::class, $this->controller->createAction($this->request));
2328
2329
        $this->assertSame($this->admin, $this->parameters['admin']);
2330
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2331
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2332
2333
        $this->assertSame('create', $this->parameters['action']);
2334
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
2335
        $this->assertSame($object, $this->parameters['object']);
2336
2337
        $this->assertSame(['sonata_flash_error' => ['flash_create_error']], $this->session->getFlashBag()->all());
2338
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
2339
    }
2340
2341
    /**
2342
     * @dataProvider getToStringValues
2343
     */
2344
    public function testCreateActionWithModelManagerException($expectedToStringValue, $toStringValue): void
2345
    {
2346
        $this->admin->expects($this->exactly(2))
2347
            ->method('checkAccess')
2348
            ->with($this->equalTo('create'))
2349
            ->willReturn(true);
2350
2351
        $this->admin->expects($this->any())
2352
            ->method('getClass')
2353
            ->willReturn('stdClass');
2354
2355
        $object = new \stdClass();
2356
2357
        $this->admin->expects($this->once())
2358
            ->method('getNewInstance')
2359
            ->willReturn($object);
2360
2361
        $form = $this->createMock(Form::class);
2362
2363
        $this->admin->expects($this->once())
2364
            ->method('getForm')
2365
            ->willReturn($form);
2366
2367
        $form->expects($this->once())
2368
            ->method('all')
2369
            ->willReturn(['field' => 'fielddata']);
2370
2371
        $form->expects($this->once())
2372
            ->method('isValid')
2373
            ->willReturn(true);
2374
2375
        $this->admin->expects($this->once())
2376
            ->method('toString')
2377
            ->with($this->equalTo($object))
2378
            ->willReturn($toStringValue);
2379
2380
        $this->expectTranslate('flash_create_error', ['%name%' => $expectedToStringValue], 'SonataAdminBundle');
2381
2382
        $form->expects($this->once())
2383
            ->method('isSubmitted')
2384
            ->willReturn(true);
2385
2386
        $form->expects($this->once())
2387
            ->method('getData')
2388
            ->willReturn($object);
2389
2390
        $this->request->setMethod('POST');
2391
2392
        $formView = $this->createMock(FormView::class);
2393
2394
        $form->expects($this->any())
2395
            ->method('createView')
2396
            ->willReturn($formView);
2397
2398
        $this->assertLoggerLogsModelManagerException($this->admin, 'create');
2399
2400
        $this->assertInstanceOf(Response::class, $this->controller->createAction($this->request));
2401
2402
        $this->assertSame($this->admin, $this->parameters['admin']);
2403
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2404
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2405
2406
        $this->assertSame('create', $this->parameters['action']);
2407
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
2408
        $this->assertSame($object, $this->parameters['object']);
2409
2410
        $this->assertSame(['sonata_flash_error' => ['flash_create_error']], $this->session->getFlashBag()->all());
2411
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
2412
    }
2413
2414
    public function testCreateActionAjaxSuccess(): void
2415
    {
2416
        $object = new \stdClass();
2417
2418
        $this->admin->expects($this->exactly(2))
2419
            ->method('checkAccess')
2420
            ->willReturnCallback(static function ($name, $objectIn = null) use ($object) {
2421
                if ('create' !== $name) {
2422
                    return false;
2423
                }
2424
2425
                if (null === $objectIn) {
2426
                    return true;
2427
                }
2428
2429
                return $objectIn === $object;
2430
            });
2431
2432
        $this->admin->expects($this->once())
2433
            ->method('getNewInstance')
2434
            ->willReturn($object);
2435
2436
        $this->admin->expects($this->once())
2437
            ->method('create')
2438
            ->willReturnArgument(0);
2439
2440
        $form = $this->createMock(Form::class);
2441
2442
        $this->admin->expects($this->once())
2443
            ->method('getForm')
2444
            ->willReturn($form);
2445
2446
        $form->expects($this->once())
2447
            ->method('all')
2448
            ->willReturn(['field' => 'fielddata']);
2449
2450
        $form->expects($this->once())
2451
            ->method('isSubmitted')
2452
            ->willReturn(true);
2453
2454
        $form->expects($this->once())
2455
            ->method('isValid')
2456
            ->willReturn(true);
2457
2458
        $form->expects($this->once())
2459
            ->method('getData')
2460
            ->willReturn($object);
2461
2462
        $this->admin->expects($this->any())
2463
            ->method('getClass')
2464
            ->willReturn('stdClass');
2465
2466
        $this->admin->expects($this->once())
2467
            ->method('getNormalizedIdentifier')
2468
            ->with($this->equalTo($object))
2469
            ->willReturn('foo_normalized');
2470
2471
        $this->admin->expects($this->once())
2472
            ->method('toString')
2473
            ->willReturn('foo');
2474
2475
        $this->request->setMethod('POST');
2476
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
2477
2478
        $response = $this->controller->createAction($this->request);
2479
2480
        $this->assertInstanceOf(Response::class, $response);
2481
        $this->assertSame(json_encode(['result' => 'ok', 'objectId' => 'foo_normalized', 'objectName' => 'foo']), $response->getContent());
2482
        $this->assertSame([], $this->session->getFlashBag()->all());
2483
    }
2484
2485
    public function testCreateActionAjaxError(): void
2486
    {
2487
        $this->admin->expects($this->once())
2488
            ->method('checkAccess')
2489
            ->with($this->equalTo('create'))
2490
            ->willReturn(true);
2491
2492
        $object = new \stdClass();
2493
2494
        $this->admin->expects($this->once())
2495
            ->method('getNewInstance')
2496
            ->willReturn($object);
2497
2498
        $form = $this->createMock(Form::class);
2499
2500
        $this->admin->expects($this->any())
2501
            ->method('getClass')
2502
            ->willReturn('stdClass');
2503
2504
        $this->admin->expects($this->once())
2505
            ->method('getForm')
2506
            ->willReturn($form);
2507
2508
        $form->expects($this->once())
2509
            ->method('all')
2510
            ->willReturn(['field' => 'fielddata']);
2511
2512
        $form->expects($this->once())
2513
            ->method('isSubmitted')
2514
            ->willReturn(true);
2515
2516
        $form->expects($this->once())
2517
            ->method('isValid')
2518
            ->willReturn(false);
2519
2520
        $this->request->setMethod('POST');
2521
        $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
2522
2523
        $formView = $this->createMock(FormView::class);
2524
2525
        $form->expects($this->any())
2526
            ->method('createView')
2527
            ->willReturn($formView);
2528
2529
        $this->assertInstanceOf(Response::class, $this->controller->createAction($this->request));
2530
2531
        $this->assertSame($this->admin, $this->parameters['admin']);
2532
        $this->assertSame('@SonataAdmin/ajax_layout.html.twig', $this->parameters['base_template']);
2533
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2534
2535
        $this->assertSame('create', $this->parameters['action']);
2536
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
2537
        $this->assertSame($object, $this->parameters['object']);
2538
2539
        $this->assertSame([], $this->session->getFlashBag()->all());
2540
        $this->assertSame('@SonataAdmin/CRUD/edit.html.twig', $this->template);
2541
    }
2542
2543
    public function testCreateActionWithPreview(): void
2544
    {
2545
        $this->admin->expects($this->once())
2546
            ->method('checkAccess')
2547
            ->with($this->equalTo('create'))
2548
            ->willReturn(true);
2549
2550
        $object = new \stdClass();
2551
2552
        $this->admin->expects($this->once())
2553
            ->method('getNewInstance')
2554
            ->willReturn($object);
2555
2556
        $form = $this->createMock(Form::class);
2557
2558
        $this->admin->expects($this->any())
2559
            ->method('getClass')
2560
            ->willReturn('stdClass');
2561
2562
        $this->admin->expects($this->once())
2563
            ->method('getForm')
2564
            ->willReturn($form);
2565
2566
        $form->expects($this->once())
2567
            ->method('all')
2568
            ->willReturn(['field' => 'fielddata']);
2569
2570
        $this->admin->expects($this->once())
2571
            ->method('supportsPreviewMode')
2572
            ->willReturn(true);
2573
2574
        $formView = $this->createMock(FormView::class);
2575
2576
        $form->expects($this->any())
2577
            ->method('createView')
2578
            ->willReturn($formView);
2579
2580
        $form->expects($this->once())
2581
            ->method('isSubmitted')
2582
            ->willReturn(true);
2583
2584
        $form->expects($this->once())
2585
            ->method('isValid')
2586
            ->willReturn(true);
2587
2588
        $this->request->setMethod('POST');
2589
        $this->request->request->set('btn_preview', 'Preview');
2590
2591
        $this->assertInstanceOf(Response::class, $this->controller->createAction($this->request));
2592
2593
        $this->assertSame($this->admin, $this->parameters['admin']);
2594
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2595
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2596
2597
        $this->assertSame('create', $this->parameters['action']);
2598
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
2599
        $this->assertSame($object, $this->parameters['object']);
2600
2601
        $this->assertSame([], $this->session->getFlashBag()->all());
2602
        $this->assertSame('@SonataAdmin/CRUD/preview.html.twig', $this->template);
2603
    }
2604
2605
    public function testExportActionAccessDenied(): void
2606
    {
2607
        $this->expectException(AccessDeniedException::class);
2608
2609
        $this->admin->expects($this->once())
2610
            ->method('checkAccess')
2611
            ->with($this->equalTo('export'))
2612
            ->will($this->throwException(new AccessDeniedException()));
2613
2614
        $this->controller->exportAction($this->request);
2615
    }
2616
2617
    public function testExportActionWrongFormat(): void
2618
    {
2619
        $this->expectException(\RuntimeException::class, 'Export in format `csv` is not allowed for class: `Foo`. Allowed formats are: `json`');
2620
2621
        $this->admin->expects($this->once())
2622
            ->method('checkAccess')
2623
            ->with($this->equalTo('export'))
2624
            ->willReturn(true);
2625
2626
        $this->admin->expects($this->once())
2627
            ->method('getExportFormats')
2628
            ->willReturn(['json']);
2629
2630
        $this->admin->expects($this->any())
2631
            ->method('getClass')
2632
            ->willReturn('Foo');
2633
2634
        $this->request->query->set('format', 'csv');
2635
2636
        $this->controller->exportAction($this->request);
2637
    }
2638
2639
    public function testExportAction(): void
2640
    {
2641
        $this->admin->expects($this->once())
2642
            ->method('checkAccess')
2643
            ->with($this->equalTo('export'))
2644
            ->willReturn(true);
2645
2646
        $this->admin->expects($this->once())
2647
            ->method('getExportFormats')
2648
            ->willReturn(['json']);
2649
2650
        $dataSourceIterator = $this->createMock(SourceIteratorInterface::class);
2651
2652
        $this->admin->expects($this->once())
2653
            ->method('getDataSourceIterator')
2654
            ->willReturn($dataSourceIterator);
2655
2656
        $this->request->query->set('format', 'json');
2657
2658
        $response = $this->controller->exportAction($this->request);
2659
        $this->assertInstanceOf(StreamedResponse::class, $response);
2660
        $this->assertSame(200, $response->getStatusCode());
2661
        $this->assertSame([], $this->session->getFlashBag()->all());
2662
    }
2663
2664
    public function testHistoryActionAccessDenied(): void
2665
    {
2666
        $this->expectException(AccessDeniedException::class);
2667
2668
        $this->admin->expects($this->any())
2669
            ->method('getObject')
2670
            ->willReturn(new \StdClass());
2671
2672
        $this->admin->expects($this->once())
2673
            ->method('checkAccess')
2674
            ->with($this->equalTo('history'))
2675
            ->will($this->throwException(new AccessDeniedException()));
2676
2677
        $this->controller->historyAction(null, $this->request);
2678
    }
2679
2680
    public function testHistoryActionNotFoundException(): void
2681
    {
2682
        $this->expectException(NotFoundHttpException::class);
2683
2684
        $this->admin->expects($this->once())
2685
            ->method('getObject')
2686
            ->willReturn(false);
2687
2688
        $this->controller->historyAction(null, $this->request);
2689
    }
2690
2691
    public function testHistoryActionNoReader(): void
2692
    {
2693
        $this->expectException(NotFoundHttpException::class, 'unable to find the audit reader for class : Foo');
2694
2695
        $this->request->query->set('id', 123);
2696
2697
        $this->admin->expects($this->once())
2698
            ->method('checkAccess')
2699
            ->with($this->equalTo('history'))
2700
            ->willReturn(true);
2701
2702
        $object = new \stdClass();
2703
2704
        $this->admin->expects($this->once())
2705
            ->method('getObject')
2706
            ->willReturn($object);
2707
2708
        $this->admin->expects($this->any())
2709
            ->method('getClass')
2710
            ->willReturn('Foo');
2711
2712
        $this->auditManager->expects($this->once())
2713
            ->method('hasReader')
2714
            ->with($this->equalTo('Foo'))
2715
            ->willReturn(false);
2716
2717
        $this->controller->historyAction(null, $this->request);
2718
    }
2719
2720
    public function testHistoryAction(): void
2721
    {
2722
        $this->request->query->set('id', 123);
2723
2724
        $this->admin->expects($this->once())
2725
            ->method('checkAccess')
2726
            ->with($this->equalTo('history'))
2727
            ->willReturn(true);
2728
2729
        $object = new \stdClass();
2730
2731
        $this->admin->expects($this->once())
2732
            ->method('getObject')
2733
            ->willReturn($object);
2734
2735
        $this->admin->expects($this->any())
2736
            ->method('getClass')
2737
            ->willReturn('Foo');
2738
2739
        $this->auditManager->expects($this->once())
2740
            ->method('hasReader')
2741
            ->with($this->equalTo('Foo'))
2742
            ->willReturn(true);
2743
2744
        $reader = $this->createMock(AuditReaderInterface::class);
2745
2746
        $this->auditManager->expects($this->once())
2747
            ->method('getReader')
2748
            ->with($this->equalTo('Foo'))
2749
            ->willReturn($reader);
2750
2751
        $reader->expects($this->once())
2752
            ->method('findRevisions')
2753
            ->with($this->equalTo('Foo'), $this->equalTo(123))
2754
            ->willReturn([]);
2755
2756
        $this->assertInstanceOf(Response::class, $this->controller->historyAction(null, $this->request));
2757
2758
        $this->assertSame($this->admin, $this->parameters['admin']);
2759
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2760
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2761
2762
        $this->assertSame('history', $this->parameters['action']);
2763
        $this->assertSame([], $this->parameters['revisions']);
2764
        $this->assertSame($object, $this->parameters['object']);
2765
2766
        $this->assertSame([], $this->session->getFlashBag()->all());
2767
        $this->assertSame('@SonataAdmin/CRUD/history.html.twig', $this->template);
2768
    }
2769
2770
    public function testAclActionAclNotEnabled(): void
2771
    {
2772
        $this->expectException(NotFoundHttpException::class, 'ACL are not enabled for this admin');
2773
2774
        $this->controller->aclAction(null, $this->request);
2775
    }
2776
2777
    public function testAclActionNotFoundException(): void
2778
    {
2779
        $this->expectException(NotFoundHttpException::class);
2780
2781
        $this->admin->expects($this->once())
2782
            ->method('isAclEnabled')
2783
            ->willReturn(true);
2784
2785
        $this->admin->expects($this->once())
2786
            ->method('getObject')
2787
            ->willReturn(false);
2788
2789
        $this->controller->aclAction(null, $this->request);
2790
    }
2791
2792
    public function testAclActionAccessDenied(): void
2793
    {
2794
        $this->expectException(AccessDeniedException::class);
2795
2796
        $this->admin->expects($this->once())
2797
            ->method('isAclEnabled')
2798
            ->willReturn(true);
2799
2800
        $object = new \stdClass();
2801
2802
        $this->admin->expects($this->once())
2803
            ->method('getObject')
2804
            ->willReturn($object);
2805
2806
        $this->admin->expects($this->once())
2807
            ->method('checkAccess')
2808
            ->with($this->equalTo('acl'), $this->equalTo($object))
2809
            ->will($this->throwException(new AccessDeniedException()));
2810
2811
        $this->controller->aclAction(null, $this->request);
2812
    }
2813
2814
    public function testAclAction(): void
2815
    {
2816
        $this->request->query->set('id', 123);
2817
2818
        $this->admin->expects($this->once())
2819
            ->method('isAclEnabled')
2820
            ->willReturn(true);
2821
2822
        $object = new \stdClass();
2823
2824
        $this->admin->expects($this->once())
2825
            ->method('getObject')
2826
            ->willReturn($object);
2827
2828
        $this->admin->expects($this->any())
2829
            ->method('checkAccess')
2830
            ->willReturn(true);
2831
2832
        $this->admin->expects($this->any())
2833
            ->method('getSecurityInformation')
2834
            ->willReturn([]);
2835
2836
        $this->adminObjectAclManipulator->expects($this->once())
2837
            ->method('getMaskBuilderClass')
2838
            ->willReturn(AdminPermissionMap::class);
2839
2840
        $aclUsersForm = $this->getMockBuilder(Form::class)
2841
            ->disableOriginalConstructor()
2842
            ->getMock();
2843
2844
        $aclUsersForm->expects($this->once())
2845
            ->method('createView')
2846
            ->willReturn($this->createMock(FormView::class));
2847
2848
        $aclRolesForm = $this->getMockBuilder(Form::class)
2849
            ->disableOriginalConstructor()
2850
            ->getMock();
2851
2852
        $aclRolesForm->expects($this->once())
2853
            ->method('createView')
2854
            ->willReturn($this->createMock(FormView::class));
2855
2856
        $this->adminObjectAclManipulator->expects($this->once())
2857
            ->method('createAclUsersForm')
2858
            ->with($this->isInstanceOf(AdminObjectAclData::class))
2859
            ->willReturn($aclUsersForm);
2860
2861
        $this->adminObjectAclManipulator->expects($this->once())
2862
            ->method('createAclRolesForm')
2863
            ->with($this->isInstanceOf(AdminObjectAclData::class))
2864
            ->willReturn($aclRolesForm);
2865
2866
        $aclSecurityHandler = $this->getMockBuilder(AclSecurityHandler::class)
2867
            ->disableOriginalConstructor()
2868
            ->getMock();
2869
2870
        $aclSecurityHandler->expects($this->any())
2871
            ->method('getObjectPermissions')
2872
            ->willReturn([]);
2873
2874
        $this->admin->expects($this->any())
2875
            ->method('getSecurityHandler')
2876
            ->willReturn($aclSecurityHandler);
2877
2878
        $this->assertInstanceOf(Response::class, $this->controller->aclAction(null, $this->request));
2879
2880
        $this->assertSame($this->admin, $this->parameters['admin']);
2881
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2882
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2883
2884
        $this->assertSame('acl', $this->parameters['action']);
2885
        $this->assertSame([], $this->parameters['permissions']);
2886
        $this->assertSame($object, $this->parameters['object']);
2887
        $this->assertInstanceOf(\ArrayIterator::class, $this->parameters['users']);
2888
        $this->assertInstanceOf(\ArrayIterator::class, $this->parameters['roles']);
2889
        $this->assertInstanceOf(FormView::class, $this->parameters['aclUsersForm']);
2890
        $this->assertInstanceOf(FormView::class, $this->parameters['aclRolesForm']);
2891
2892
        $this->assertSame([], $this->session->getFlashBag()->all());
2893
        $this->assertSame('@SonataAdmin/CRUD/acl.html.twig', $this->template);
2894
    }
2895
2896
    public function testAclActionInvalidUpdate(): void
2897
    {
2898
        $this->request->query->set('id', 123);
2899
        $this->request->request->set(AdminObjectAclManipulator::ACL_USERS_FORM_NAME, []);
2900
2901
        $this->admin->expects($this->once())
2902
            ->method('isAclEnabled')
2903
            ->willReturn(true);
2904
2905
        $object = new \stdClass();
2906
2907
        $this->admin->expects($this->once())
2908
            ->method('getObject')
2909
            ->willReturn($object);
2910
2911
        $this->admin->expects($this->any())
2912
            ->method('checkAccess')
2913
            ->willReturn(true);
2914
2915
        $this->admin->expects($this->any())
2916
            ->method('getSecurityInformation')
2917
            ->willReturn([]);
2918
2919
        $this->adminObjectAclManipulator->expects($this->once())
2920
            ->method('getMaskBuilderClass')
2921
            ->willReturn(AdminPermissionMap::class);
2922
2923
        $aclUsersForm = $this->getMockBuilder(Form::class)
2924
            ->disableOriginalConstructor()
2925
            ->getMock();
2926
2927
        $aclUsersForm->expects($this->once())
2928
            ->method('isValid')
2929
            ->willReturn(false);
2930
2931
        $aclUsersForm->expects($this->once())
2932
            ->method('createView')
2933
            ->willReturn($this->createMock(FormView::class));
2934
2935
        $aclRolesForm = $this->getMockBuilder(Form::class)
2936
            ->disableOriginalConstructor()
2937
            ->getMock();
2938
2939
        $aclRolesForm->expects($this->once())
2940
            ->method('createView')
2941
            ->willReturn($this->createMock(FormView::class));
2942
2943
        $this->adminObjectAclManipulator->expects($this->once())
2944
            ->method('createAclUsersForm')
2945
            ->with($this->isInstanceOf(AdminObjectAclData::class))
2946
            ->willReturn($aclUsersForm);
2947
2948
        $this->adminObjectAclManipulator->expects($this->once())
2949
            ->method('createAclRolesForm')
2950
            ->with($this->isInstanceOf(AdminObjectAclData::class))
2951
            ->willReturn($aclRolesForm);
2952
2953
        $aclSecurityHandler = $this->getMockBuilder(AclSecurityHandler::class)
2954
            ->disableOriginalConstructor()
2955
            ->getMock();
2956
2957
        $aclSecurityHandler->expects($this->any())
2958
            ->method('getObjectPermissions')
2959
            ->willReturn([]);
2960
2961
        $this->admin->expects($this->any())
2962
            ->method('getSecurityHandler')
2963
            ->willReturn($aclSecurityHandler);
2964
2965
        $this->request->setMethod('POST');
2966
2967
        $this->assertInstanceOf(Response::class, $this->controller->aclAction(null, $this->request));
2968
2969
        $this->assertSame($this->admin, $this->parameters['admin']);
2970
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
2971
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
2972
2973
        $this->assertSame('acl', $this->parameters['action']);
2974
        $this->assertSame([], $this->parameters['permissions']);
2975
        $this->assertSame($object, $this->parameters['object']);
2976
        $this->assertInstanceOf(\ArrayIterator::class, $this->parameters['users']);
2977
        $this->assertInstanceOf(\ArrayIterator::class, $this->parameters['roles']);
2978
        $this->assertInstanceOf(FormView::class, $this->parameters['aclUsersForm']);
2979
        $this->assertInstanceOf(FormView::class, $this->parameters['aclRolesForm']);
2980
2981
        $this->assertSame([], $this->session->getFlashBag()->all());
2982
        $this->assertSame('@SonataAdmin/CRUD/acl.html.twig', $this->template);
2983
    }
2984
2985
    public function testAclActionSuccessfulUpdate(): void
2986
    {
2987
        $this->request->query->set('id', 123);
2988
        $this->request->request->set(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME, []);
2989
2990
        $this->admin->expects($this->once())
2991
            ->method('isAclEnabled')
2992
            ->willReturn(true);
2993
2994
        $object = new \stdClass();
2995
2996
        $this->admin->expects($this->once())
2997
            ->method('getObject')
2998
            ->willReturn($object);
2999
3000
        $this->admin->expects($this->any())
3001
            ->method('checkAccess')
3002
            ->willReturn(true);
3003
3004
        $this->admin->expects($this->any())
3005
            ->method('getSecurityInformation')
3006
            ->willReturn([]);
3007
3008
        $this->adminObjectAclManipulator->expects($this->once())
3009
            ->method('getMaskBuilderClass')
3010
            ->willReturn(AdminPermissionMap::class);
3011
3012
        $aclUsersForm = $this->getMockBuilder(Form::class)
3013
            ->disableOriginalConstructor()
3014
            ->getMock();
3015
3016
        $aclUsersForm->expects($this->any())
3017
            ->method('createView')
3018
            ->willReturn($this->createMock(FormView::class));
3019
3020
        $aclRolesForm = $this->getMockBuilder(Form::class)
3021
            ->disableOriginalConstructor()
3022
            ->getMock();
3023
3024
        $aclRolesForm->expects($this->any())
3025
            ->method('createView')
3026
            ->willReturn($this->createMock(FormView::class));
3027
3028
        $aclRolesForm->expects($this->once())
3029
            ->method('isValid')
3030
            ->willReturn(true);
3031
3032
        $this->adminObjectAclManipulator->expects($this->once())
3033
            ->method('createAclUsersForm')
3034
            ->with($this->isInstanceOf(AdminObjectAclData::class))
3035
            ->willReturn($aclUsersForm);
3036
3037
        $this->adminObjectAclManipulator->expects($this->once())
3038
            ->method('createAclRolesForm')
3039
            ->with($this->isInstanceOf(AdminObjectAclData::class))
3040
            ->willReturn($aclRolesForm);
3041
3042
        $aclSecurityHandler = $this->getMockBuilder(AclSecurityHandler::class)
3043
            ->disableOriginalConstructor()
3044
            ->getMock();
3045
3046
        $aclSecurityHandler->expects($this->any())
3047
            ->method('getObjectPermissions')
3048
            ->willReturn([]);
3049
3050
        $this->admin->expects($this->any())
3051
            ->method('getSecurityHandler')
3052
            ->willReturn($aclSecurityHandler);
3053
3054
        $this->expectTranslate('flash_acl_edit_success', [], 'SonataAdminBundle');
3055
3056
        $this->request->setMethod('POST');
3057
3058
        $response = $this->controller->aclAction(null, $this->request);
3059
3060
        $this->assertInstanceOf(RedirectResponse::class, $response);
3061
3062
        $this->assertSame(['flash_acl_edit_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
3063
        $this->assertSame('stdClass_acl', $response->getTargetUrl());
3064
    }
3065
3066
    public function testHistoryViewRevisionActionAccessDenied(): void
3067
    {
3068
        $this->expectException(AccessDeniedException::class);
3069
3070
        $this->admin->expects($this->any())
3071
            ->method('getObject')
3072
            ->willReturn(new \StdClass());
3073
3074
        $this->admin->expects($this->once())
3075
            ->method('checkAccess')
3076
            ->with($this->equalTo('historyViewRevision'))
3077
            ->will($this->throwException(new AccessDeniedException()));
3078
3079
        $this->controller->historyViewRevisionAction(null, null, $this->request);
3080
    }
3081
3082
    public function testHistoryViewRevisionActionNotFoundException(): void
3083
    {
3084
        $this->expectException(NotFoundHttpException::class, 'unable to find the object with id: 123');
3085
3086
        $this->request->query->set('id', 123);
3087
3088
        $this->admin->expects($this->once())
3089
            ->method('getObject')
3090
            ->willReturn(false);
3091
3092
        $this->controller->historyViewRevisionAction(null, null, $this->request);
3093
    }
3094
3095
    public function testHistoryViewRevisionActionNoReader(): void
3096
    {
3097
        $this->expectException(NotFoundHttpException::class, 'unable to find the audit reader for class : Foo');
3098
3099
        $this->request->query->set('id', 123);
3100
3101
        $this->admin->expects($this->once())
3102
            ->method('checkAccess')
3103
            ->with($this->equalTo('historyViewRevision'))
3104
            ->willReturn(true);
3105
3106
        $object = new \stdClass();
3107
3108
        $this->admin->expects($this->once())
3109
            ->method('getObject')
3110
            ->willReturn($object);
3111
3112
        $this->admin->expects($this->any())
3113
            ->method('getClass')
3114
            ->willReturn('Foo');
3115
3116
        $this->auditManager->expects($this->once())
3117
            ->method('hasReader')
3118
            ->with($this->equalTo('Foo'))
3119
            ->willReturn(false);
3120
3121
        $this->controller->historyViewRevisionAction(null, null, $this->request);
3122
    }
3123
3124
    public function testHistoryViewRevisionActionNotFoundRevision(): void
3125
    {
3126
        $this->expectException(NotFoundHttpException::class, 'unable to find the targeted object `123` from the revision `456` with classname : `Foo`');
3127
3128
        $this->request->query->set('id', 123);
3129
3130
        $this->admin->expects($this->once())
3131
            ->method('checkAccess')
3132
            ->with($this->equalTo('historyViewRevision'))
3133
            ->willReturn(true);
3134
3135
        $object = new \stdClass();
3136
3137
        $this->admin->expects($this->once())
3138
            ->method('getObject')
3139
            ->willReturn($object);
3140
3141
        $this->admin->expects($this->any())
3142
            ->method('getClass')
3143
            ->willReturn('Foo');
3144
3145
        $this->auditManager->expects($this->once())
3146
            ->method('hasReader')
3147
            ->with($this->equalTo('Foo'))
3148
            ->willReturn(true);
3149
3150
        $reader = $this->createMock(AuditReaderInterface::class);
3151
3152
        $this->auditManager->expects($this->once())
3153
            ->method('getReader')
3154
            ->with($this->equalTo('Foo'))
3155
            ->willReturn($reader);
3156
3157
        $reader->expects($this->once())
3158
            ->method('find')
3159
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(456))
3160
            ->willReturn(null);
3161
3162
        $this->controller->historyViewRevisionAction(123, 456, $this->request);
3163
    }
3164
3165
    public function testHistoryViewRevisionAction(): void
3166
    {
3167
        $this->request->query->set('id', 123);
3168
3169
        $this->admin->expects($this->once())
3170
            ->method('checkAccess')
3171
            ->with($this->equalTo('historyViewRevision'))
3172
            ->willReturn(true);
3173
3174
        $object = new \stdClass();
3175
3176
        $this->admin->expects($this->once())
3177
            ->method('getObject')
3178
            ->willReturn($object);
3179
3180
        $this->admin->expects($this->any())
3181
            ->method('getClass')
3182
            ->willReturn('Foo');
3183
3184
        $this->auditManager->expects($this->once())
3185
            ->method('hasReader')
3186
            ->with($this->equalTo('Foo'))
3187
            ->willReturn(true);
3188
3189
        $reader = $this->createMock(AuditReaderInterface::class);
3190
3191
        $this->auditManager->expects($this->once())
3192
            ->method('getReader')
3193
            ->with($this->equalTo('Foo'))
3194
            ->willReturn($reader);
3195
3196
        $objectRevision = new \stdClass();
3197
        $objectRevision->revision = 456;
3198
3199
        $reader->expects($this->once())
3200
            ->method('find')
3201
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(456))
3202
            ->willReturn($objectRevision);
3203
3204
        $this->admin->expects($this->once())
3205
            ->method('setSubject')
3206
            ->with($this->equalTo($objectRevision))
3207
            ->willReturn(null);
3208
3209
        $fieldDescriptionCollection = new FieldDescriptionCollection();
3210
        $this->admin->expects($this->once())
3211
            ->method('getShow')
3212
            ->willReturn($fieldDescriptionCollection);
3213
3214
        $this->assertInstanceOf(Response::class, $this->controller->historyViewRevisionAction(123, 456, $this->request));
3215
3216
        $this->assertSame($this->admin, $this->parameters['admin']);
3217
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
3218
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
3219
3220
        $this->assertSame('show', $this->parameters['action']);
3221
        $this->assertSame($objectRevision, $this->parameters['object']);
3222
        $this->assertSame($fieldDescriptionCollection, $this->parameters['elements']);
3223
3224
        $this->assertSame([], $this->session->getFlashBag()->all());
3225
        $this->assertSame('@SonataAdmin/CRUD/show.html.twig', $this->template);
3226
    }
3227
3228
    public function testHistoryCompareRevisionsActionAccessDenied(): void
3229
    {
3230
        $this->expectException(AccessDeniedException::class);
3231
3232
        $this->admin->expects($this->once())
3233
            ->method('checkAccess')
3234
            ->with($this->equalTo('historyCompareRevisions'))
3235
            ->will($this->throwException(new AccessDeniedException()));
3236
3237
        $this->controller->historyCompareRevisionsAction(null, null, null, $this->request);
3238
    }
3239
3240
    public function testHistoryCompareRevisionsActionNotFoundException(): void
3241
    {
3242
        $this->expectException(NotFoundHttpException::class, 'unable to find the object with id: 123');
3243
3244
        $this->request->query->set('id', 123);
3245
3246
        $this->admin->expects($this->once())
3247
            ->method('checkAccess')
3248
            ->with($this->equalTo('historyCompareRevisions'))
3249
            ->willReturn(true);
3250
3251
        $this->admin->expects($this->once())
3252
            ->method('getObject')
3253
            ->willReturn(false);
3254
3255
        $this->controller->historyCompareRevisionsAction(null, null, null, $this->request);
3256
    }
3257
3258
    public function testHistoryCompareRevisionsActionNoReader(): void
3259
    {
3260
        $this->expectException(NotFoundHttpException::class, 'unable to find the audit reader for class : Foo');
3261
3262
        $this->request->query->set('id', 123);
3263
3264
        $this->admin->expects($this->once())
3265
            ->method('checkAccess')
3266
            ->with($this->equalTo('historyCompareRevisions'))
3267
            ->willReturn(true);
3268
3269
        $object = new \stdClass();
3270
3271
        $this->admin->expects($this->once())
3272
            ->method('getObject')
3273
            ->willReturn($object);
3274
3275
        $this->admin->expects($this->any())
3276
            ->method('getClass')
3277
            ->willReturn('Foo');
3278
3279
        $this->auditManager->expects($this->once())
3280
            ->method('hasReader')
3281
            ->with($this->equalTo('Foo'))
3282
            ->willReturn(false);
3283
3284
        $this->controller->historyCompareRevisionsAction(null, null, null, $this->request);
3285
    }
3286
3287
    public function testHistoryCompareRevisionsActionNotFoundBaseRevision(): void
3288
    {
3289
        $this->expectException(NotFoundHttpException::class, 'unable to find the targeted object `123` from the revision `456` with classname : `Foo`');
3290
3291
        $this->request->query->set('id', 123);
3292
3293
        $this->admin->expects($this->once())
3294
            ->method('checkAccess')
3295
            ->with($this->equalTo('historyCompareRevisions'))
3296
            ->willReturn(true);
3297
3298
        $object = new \stdClass();
3299
3300
        $this->admin->expects($this->once())
3301
            ->method('getObject')
3302
            ->willReturn($object);
3303
3304
        $this->admin->expects($this->any())
3305
            ->method('getClass')
3306
            ->willReturn('Foo');
3307
3308
        $this->auditManager->expects($this->once())
3309
            ->method('hasReader')
3310
            ->with($this->equalTo('Foo'))
3311
            ->willReturn(true);
3312
3313
        $reader = $this->createMock(AuditReaderInterface::class);
3314
3315
        $this->auditManager->expects($this->once())
3316
            ->method('getReader')
3317
            ->with($this->equalTo('Foo'))
3318
            ->willReturn($reader);
3319
3320
        // once because it will not be found and therefore the second call won't be executed
3321
        $reader->expects($this->once())
3322
            ->method('find')
3323
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(456))
3324
            ->willReturn(null);
3325
3326
        $this->controller->historyCompareRevisionsAction(123, 456, 789, $this->request);
3327
    }
3328
3329
    public function testHistoryCompareRevisionsActionNotFoundCompareRevision(): void
3330
    {
3331
        $this->expectException(NotFoundHttpException::class, 'unable to find the targeted object `123` from the revision `789` with classname : `Foo`');
3332
3333
        $this->request->query->set('id', 123);
3334
3335
        $this->admin->expects($this->once())
3336
            ->method('checkAccess')
3337
            ->with($this->equalTo('historyCompareRevisions'))
3338
            ->willReturn(true);
3339
3340
        $object = new \stdClass();
3341
3342
        $this->admin->expects($this->once())
3343
            ->method('getObject')
3344
            ->willReturn($object);
3345
3346
        $this->admin->expects($this->any())
3347
            ->method('getClass')
3348
            ->willReturn('Foo');
3349
3350
        $this->auditManager->expects($this->once())
3351
            ->method('hasReader')
3352
            ->with($this->equalTo('Foo'))
3353
            ->willReturn(true);
3354
3355
        $reader = $this->createMock(AuditReaderInterface::class);
3356
3357
        $this->auditManager->expects($this->once())
3358
            ->method('getReader')
3359
            ->with($this->equalTo('Foo'))
3360
            ->willReturn($reader);
3361
3362
        $objectRevision = new \stdClass();
3363
        $objectRevision->revision = 456;
3364
3365
        // first call should return, so the second call will throw an exception
3366
        $reader->expects($this->at(0))
3367
            ->method('find')
3368
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(456))
3369
            ->willReturn($objectRevision);
3370
3371
        $reader->expects($this->at(1))
3372
            ->method('find')
3373
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(789))
3374
            ->willReturn(null);
3375
3376
        $this->controller->historyCompareRevisionsAction(123, 456, 789, $this->request);
3377
    }
3378
3379
    public function testHistoryCompareRevisionsActionAction(): void
3380
    {
3381
        $this->request->query->set('id', 123);
3382
3383
        $this->admin->expects($this->once())
3384
            ->method('checkAccess')
3385
            ->with($this->equalTo('historyCompareRevisions'))
3386
            ->willReturn(true);
3387
3388
        $object = new \stdClass();
3389
3390
        $this->admin->expects($this->once())
3391
            ->method('getObject')
3392
            ->willReturn($object);
3393
3394
        $this->admin->expects($this->any())
3395
            ->method('getClass')
3396
            ->willReturn('Foo');
3397
3398
        $this->auditManager->expects($this->once())
3399
            ->method('hasReader')
3400
            ->with($this->equalTo('Foo'))
3401
            ->willReturn(true);
3402
3403
        $reader = $this->createMock(AuditReaderInterface::class);
3404
3405
        $this->auditManager->expects($this->once())
3406
            ->method('getReader')
3407
            ->with($this->equalTo('Foo'))
3408
            ->willReturn($reader);
3409
3410
        $objectRevision = new \stdClass();
3411
        $objectRevision->revision = 456;
3412
3413
        $compareObjectRevision = new \stdClass();
3414
        $compareObjectRevision->revision = 789;
3415
3416
        $reader->expects($this->at(0))
3417
            ->method('find')
3418
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(456))
3419
            ->willReturn($objectRevision);
3420
3421
        $reader->expects($this->at(1))
3422
            ->method('find')
3423
            ->with($this->equalTo('Foo'), $this->equalTo(123), $this->equalTo(789))
3424
            ->willReturn($compareObjectRevision);
3425
3426
        $this->admin->expects($this->once())
3427
            ->method('setSubject')
3428
            ->with($this->equalTo($objectRevision))
3429
            ->willReturn(null);
3430
3431
        $fieldDescriptionCollection = new FieldDescriptionCollection();
3432
        $this->admin->expects($this->once())
3433
            ->method('getShow')
3434
            ->willReturn($fieldDescriptionCollection);
3435
3436
        $this->assertInstanceOf(Response::class, $this->controller->historyCompareRevisionsAction(123, 456, 789, $this->request));
3437
3438
        $this->assertSame($this->admin, $this->parameters['admin']);
3439
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
3440
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
3441
3442
        $this->assertSame('show', $this->parameters['action']);
3443
        $this->assertSame($objectRevision, $this->parameters['object']);
3444
        $this->assertSame($compareObjectRevision, $this->parameters['object_compare']);
3445
        $this->assertSame($fieldDescriptionCollection, $this->parameters['elements']);
3446
3447
        $this->assertSame([], $this->session->getFlashBag()->all());
3448
        $this->assertSame('@SonataAdmin/CRUD/show_compare.html.twig', $this->template);
3449
    }
3450
3451
    public function testBatchActionWrongMethod(): void
3452
    {
3453
        $this->expectException(NotFoundHttpException::class, 'Invalid request type "GET", POST expected');
3454
3455
        $this->controller->batchAction($this->request);
3456
    }
3457
3458
    /**
3459
     * NEXT_MAJOR: Remove this legacy group.
3460
     *
3461
     * @group legacy
3462
     */
3463
    public function testBatchActionActionNotDefined(): void
3464
    {
3465
        $this->expectException(\RuntimeException::class, 'The `foo` batch action is not defined');
3466
3467
        $batchActions = [];
3468
3469
        $this->admin->expects($this->once())
3470
            ->method('getBatchActions')
3471
            ->willReturn($batchActions);
3472
3473
        $this->request->setMethod('POST');
3474
        $this->request->request->set('data', json_encode(['action' => 'foo', 'idx' => ['123', '456'], 'all_elements' => false]));
3475
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3476
3477
        $this->controller->batchAction($this->request);
3478
    }
3479
3480
    public function testBatchActionActionInvalidCsrfToken(): void
3481
    {
3482
        $this->request->setMethod('POST');
3483
        $this->request->request->set('data', json_encode(['action' => 'foo', 'idx' => ['123', '456'], 'all_elements' => false]));
3484
        $this->request->request->set('_sonata_csrf_token', 'CSRF-INVALID');
3485
3486
        try {
3487
            $this->controller->batchAction($this->request);
3488
        } catch (HttpException $e) {
3489
            $this->assertSame('The csrf token is not valid, CSRF attack?', $e->getMessage());
3490
            $this->assertSame(400, $e->getStatusCode());
3491
        }
3492
    }
3493
3494
    /**
3495
     * NEXT_MAJOR: Remove this legacy group.
3496
     *
3497
     * @group legacy
3498
     */
3499
    public function testBatchActionMethodNotExist(): void
3500
    {
3501
        $this->expectException(\RuntimeException::class, 'A `Sonata\AdminBundle\Controller\CRUDController::batchActionFoo` method must be callable');
3502
3503
        $batchActions = ['foo' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3504
3505
        $this->admin->expects($this->once())
3506
            ->method('getBatchActions')
3507
            ->willReturn($batchActions);
3508
3509
        $datagrid = $this->createMock(DatagridInterface::class);
3510
        $this->admin->expects($this->once())
3511
            ->method('getDatagrid')
3512
            ->willReturn($datagrid);
3513
3514
        $this->request->setMethod('POST');
3515
        $this->request->request->set('data', json_encode(['action' => 'foo', 'idx' => ['123', '456'], 'all_elements' => false]));
3516
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3517
3518
        $this->controller->batchAction($this->request);
3519
    }
3520
3521
    /**
3522
     * NEXT_MAJOR: Remove this legacy group.
3523
     *
3524
     * @group legacy
3525
     */
3526
    public function testBatchActionWithoutConfirmation(): void
3527
    {
3528
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3529
3530
        $this->admin->expects($this->once())
3531
            ->method('getBatchActions')
3532
            ->willReturn($batchActions);
3533
3534
        $datagrid = $this->createMock(DatagridInterface::class);
3535
3536
        $query = $this->createMock(ProxyQueryInterface::class);
3537
        $datagrid->expects($this->once())
3538
            ->method('getQuery')
3539
            ->willReturn($query);
3540
3541
        $this->admin->expects($this->once())
3542
            ->method('getDatagrid')
3543
            ->willReturn($datagrid);
3544
3545
        $modelManager = $this->createMock(ModelManagerInterface::class);
3546
3547
        $this->admin->expects($this->once())
3548
            ->method('checkAccess')
3549
            ->with($this->equalTo('batchDelete'))
3550
            ->willReturn(true);
3551
3552
        $this->admin->expects($this->any())
3553
            ->method('getModelManager')
3554
            ->willReturn($modelManager);
3555
3556
        $this->admin->expects($this->any())
3557
            ->method('getClass')
3558
            ->willReturn('Foo');
3559
3560
        $modelManager->expects($this->once())
3561
            ->method('addIdentifiersToQuery')
3562
            ->with($this->equalTo('Foo'), $this->equalTo($query), $this->equalTo(['123', '456']))
3563
            ->willReturn(true);
3564
3565
        $this->expectTranslate('flash_batch_delete_success', [], 'SonataAdminBundle');
3566
3567
        $this->request->setMethod('POST');
3568
        $this->request->request->set('data', json_encode(['action' => 'delete', 'idx' => ['123', '456'], 'all_elements' => false]));
3569
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3570
3571
        $result = $this->controller->batchAction($this->request);
3572
3573
        $this->assertInstanceOf(RedirectResponse::class, $result);
3574
        $this->assertSame(['flash_batch_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
3575
        $this->assertSame('list', $result->getTargetUrl());
3576
    }
3577
3578
    /**
3579
     * NEXT_MAJOR: Remove this legacy group.
3580
     *
3581
     * @group legacy
3582
     */
3583
    public function testBatchActionWithoutConfirmation2(): void
3584
    {
3585
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3586
3587
        $this->admin->expects($this->once())
3588
            ->method('getBatchActions')
3589
            ->willReturn($batchActions);
3590
3591
        $datagrid = $this->createMock(DatagridInterface::class);
3592
3593
        $query = $this->createMock(ProxyQueryInterface::class);
3594
        $datagrid->expects($this->once())
3595
            ->method('getQuery')
3596
            ->willReturn($query);
3597
3598
        $this->admin->expects($this->once())
3599
            ->method('getDatagrid')
3600
            ->willReturn($datagrid);
3601
3602
        $modelManager = $this->createMock(ModelManagerInterface::class);
3603
3604
        $this->admin->expects($this->once())
3605
            ->method('checkAccess')
3606
            ->with($this->equalTo('batchDelete'))
3607
            ->willReturn(true);
3608
3609
        $this->admin->expects($this->any())
3610
            ->method('getModelManager')
3611
            ->willReturn($modelManager);
3612
3613
        $this->admin->expects($this->any())
3614
            ->method('getClass')
3615
            ->willReturn('Foo');
3616
3617
        $modelManager->expects($this->once())
3618
            ->method('addIdentifiersToQuery')
3619
            ->with($this->equalTo('Foo'), $this->equalTo($query), $this->equalTo(['123', '456']))
3620
            ->willReturn(true);
3621
3622
        $this->expectTranslate('flash_batch_delete_success', [], 'SonataAdminBundle');
3623
3624
        $this->request->setMethod('POST');
3625
        $this->request->request->set('action', 'delete');
3626
        $this->request->request->set('idx', ['123', '456']);
3627
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3628
3629
        $result = $this->controller->batchAction($this->request);
3630
3631
        $this->assertInstanceOf(RedirectResponse::class, $result);
3632
        $this->assertSame(['flash_batch_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
3633
        $this->assertSame('list', $result->getTargetUrl());
3634
    }
3635
3636
    /**
3637
     * NEXT_MAJOR: Remove this legacy group.
3638
     *
3639
     * @group legacy
3640
     */
3641
    public function testBatchActionWithConfirmation(): void
3642
    {
3643
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'translation_domain' => 'FooBarBaz', 'ask_confirmation' => true]];
3644
3645
        $this->admin->expects($this->once())
3646
            ->method('getBatchActions')
3647
            ->willReturn($batchActions);
3648
3649
        $data = ['action' => 'delete', 'idx' => ['123', '456'], 'all_elements' => false];
3650
3651
        $this->request->setMethod('POST');
3652
        $this->request->request->set('data', json_encode($data));
3653
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3654
3655
        $datagrid = $this->createMock(DatagridInterface::class);
3656
3657
        $this->admin->expects($this->once())
3658
            ->method('getDatagrid')
3659
            ->willReturn($datagrid);
3660
3661
        $form = $this->getMockBuilder(Form::class)
3662
            ->disableOriginalConstructor()
3663
            ->getMock();
3664
3665
        $form->expects($this->once())
3666
            ->method('createView')
3667
            ->willReturn($this->createMock(FormView::class));
3668
3669
        $datagrid->expects($this->once())
3670
            ->method('getForm')
3671
            ->willReturn($form);
3672
3673
        $this->assertInstanceOf(Response::class, $this->controller->batchAction($this->request));
3674
3675
        $this->assertSame($this->admin, $this->parameters['admin']);
3676
        $this->assertSame('@SonataAdmin/standard_layout.html.twig', $this->parameters['base_template']);
3677
        $this->assertSame($this->pool, $this->parameters['admin_pool']);
3678
3679
        $this->assertSame('list', $this->parameters['action']);
3680
        $this->assertSame($datagrid, $this->parameters['datagrid']);
3681
        $this->assertInstanceOf(FormView::class, $this->parameters['form']);
3682
        $this->assertSame($data, $this->parameters['data']);
3683
        $this->assertSame('csrf-token-123_sonata.batch', $this->parameters['csrf_token']);
3684
        $this->assertSame('Foo Bar', $this->parameters['action_label']);
3685
3686
        $this->assertSame([], $this->session->getFlashBag()->all());
3687
        $this->assertSame('@SonataAdmin/CRUD/batch_confirmation.html.twig', $this->template);
3688
    }
3689
3690
    /**
3691
     * NEXT_MAJOR: Remove this legacy group.
3692
     *
3693
     * @group legacy
3694
     */
3695
    public function testBatchActionNonRelevantAction(): void
3696
    {
3697
        $controller = new BatchAdminController();
3698
        $controller->setContainer($this->container);
3699
3700
        $batchActions = ['foo' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3701
3702
        $this->admin->expects($this->once())
3703
            ->method('getBatchActions')
3704
            ->willReturn($batchActions);
3705
3706
        $datagrid = $this->createMock(DatagridInterface::class);
3707
3708
        $this->admin->expects($this->once())
3709
            ->method('getDatagrid')
3710
            ->willReturn($datagrid);
3711
3712
        $this->expectTranslate('flash_batch_empty', [], 'SonataAdminBundle');
3713
3714
        $this->request->setMethod('POST');
3715
        $this->request->request->set('action', 'foo');
3716
        $this->request->request->set('idx', ['789']);
3717
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3718
3719
        $result = $controller->batchAction($this->request);
3720
3721
        $this->assertInstanceOf(RedirectResponse::class, $result);
3722
        $this->assertSame(['flash_batch_empty'], $this->session->getFlashBag()->get('sonata_flash_info'));
3723
        $this->assertSame('list', $result->getTargetUrl());
3724
    }
3725
3726
    public function testBatchActionWithCustomConfirmationTemplate(): void
3727
    {
3728
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'ask_confirmation' => true, 'template' => 'custom_template.html.twig']];
3729
3730
        $this->admin->expects($this->once())
3731
            ->method('getBatchActions')
3732
            ->willReturn($batchActions);
3733
3734
        $data = ['action' => 'delete', 'idx' => ['123', '456'], 'all_elements' => false];
3735
3736
        $this->request->setMethod('POST');
3737
        $this->request->request->set('data', json_encode($data));
3738
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3739
3740
        $datagrid = $this->createMock(DatagridInterface::class);
3741
3742
        $this->admin->expects($this->once())
3743
            ->method('getDatagrid')
3744
            ->willReturn($datagrid);
3745
3746
        $form = $this->createMock(Form::class);
3747
3748
        $form->expects($this->once())
3749
            ->method('createView')
3750
            ->willReturn($this->createMock(FormView::class));
3751
3752
        $datagrid->expects($this->once())
3753
            ->method('getForm')
3754
            ->willReturn($form);
3755
3756
        $this->controller->batchAction($this->request);
3757
3758
        $this->assertSame('custom_template.html.twig', $this->template);
3759
    }
3760
3761
    /**
3762
     * NEXT_MAJOR: Remove this legacy group.
3763
     *
3764
     * @group legacy
3765
     */
3766
    public function testBatchActionNonRelevantAction2(): void
3767
    {
3768
        $controller = new BatchAdminController();
3769
        $controller->setContainer($this->container);
3770
3771
        $batchActions = ['foo' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3772
3773
        $this->admin->expects($this->once())
3774
            ->method('getBatchActions')
3775
            ->willReturn($batchActions);
3776
3777
        $datagrid = $this->createMock(DatagridInterface::class);
3778
3779
        $this->admin->expects($this->once())
3780
            ->method('getDatagrid')
3781
            ->willReturn($datagrid);
3782
3783
        $this->expectTranslate('flash_foo_error', [], 'SonataAdminBundle');
3784
3785
        $this->request->setMethod('POST');
3786
        $this->request->request->set('action', 'foo');
3787
        $this->request->request->set('idx', ['999']);
3788
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3789
3790
        $result = $controller->batchAction($this->request);
3791
3792
        $this->assertInstanceOf(RedirectResponse::class, $result);
3793
        $this->assertSame(['flash_foo_error'], $this->session->getFlashBag()->get('sonata_flash_info'));
3794
        $this->assertSame('list', $result->getTargetUrl());
3795
    }
3796
3797
    /**
3798
     * NEXT_MAJOR: Remove this legacy group.
3799
     *
3800
     * @group legacy
3801
     */
3802
    public function testBatchActionNoItems(): void
3803
    {
3804
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'ask_confirmation' => true]];
3805
3806
        $this->admin->expects($this->once())
3807
            ->method('getBatchActions')
3808
            ->willReturn($batchActions);
3809
3810
        $datagrid = $this->createMock(DatagridInterface::class);
3811
3812
        $this->admin->expects($this->once())
3813
            ->method('getDatagrid')
3814
            ->willReturn($datagrid);
3815
3816
        $this->expectTranslate('flash_batch_empty', [], 'SonataAdminBundle');
3817
3818
        $this->request->setMethod('POST');
3819
        $this->request->request->set('action', 'delete');
3820
        $this->request->request->set('idx', []);
3821
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3822
3823
        $result = $this->controller->batchAction($this->request);
3824
3825
        $this->assertInstanceOf(RedirectResponse::class, $result);
3826
        $this->assertSame(['flash_batch_empty'], $this->session->getFlashBag()->get('sonata_flash_info'));
3827
        $this->assertSame('list', $result->getTargetUrl());
3828
    }
3829
3830
    /**
3831
     * NEXT_MAJOR: Remove this legacy group.
3832
     *
3833
     * @group legacy
3834
     */
3835
    public function testBatchActionNoItemsEmptyQuery(): void
3836
    {
3837
        $controller = new BatchAdminController();
3838
        $controller->setContainer($this->container);
3839
3840
        $batchActions = ['bar' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3841
3842
        $this->admin->expects($this->once())
3843
            ->method('getBatchActions')
3844
            ->willReturn($batchActions);
3845
3846
        $datagrid = $this->createMock(DatagridInterface::class);
3847
3848
        $query = $this->createMock(ProxyQueryInterface::class);
3849
        $datagrid->expects($this->once())
3850
            ->method('getQuery')
3851
            ->willReturn($query);
3852
3853
        $this->admin->expects($this->once())
3854
            ->method('getDatagrid')
3855
            ->willReturn($datagrid);
3856
3857
        $modelManager = $this->createMock(ModelManagerInterface::class);
3858
3859
        $this->admin->expects($this->any())
3860
            ->method('getModelManager')
3861
            ->willReturn($modelManager);
3862
3863
        $this->admin->expects($this->any())
3864
            ->method('getClass')
3865
            ->willReturn('Foo');
3866
3867
        $this->request->setMethod('POST');
3868
        $this->request->request->set('action', 'bar');
3869
        $this->request->request->set('idx', []);
3870
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3871
3872
        $this->expectTranslate('flash_batch_no_elements_processed', [], 'SonataAdminBundle');
3873
        $result = $controller->batchAction($this->request);
3874
3875
        $this->assertInstanceOf(Response::class, $result);
3876
        $this->assertRegExp('/Redirecting to list/', $result->getContent());
3877
    }
3878
3879
    /**
3880
     * NEXT_MAJOR: Remove this legacy group.
3881
     *
3882
     * @group legacy
3883
     */
3884
    public function testBatchActionWithRequesData(): void
3885
    {
3886
        $batchActions = ['delete' => ['label' => 'Foo Bar', 'ask_confirmation' => false]];
3887
3888
        $this->admin->expects($this->once())
3889
            ->method('getBatchActions')
3890
            ->willReturn($batchActions);
3891
3892
        $datagrid = $this->createMock(DatagridInterface::class);
3893
3894
        $query = $this->createMock(ProxyQueryInterface::class);
3895
        $datagrid->expects($this->once())
3896
            ->method('getQuery')
3897
            ->willReturn($query);
3898
3899
        $this->admin->expects($this->once())
3900
            ->method('getDatagrid')
3901
            ->willReturn($datagrid);
3902
3903
        $modelManager = $this->createMock(ModelManagerInterface::class);
3904
3905
        $this->admin->expects($this->once())
3906
            ->method('checkAccess')
3907
            ->with($this->equalTo('batchDelete'))
3908
            ->willReturn(true);
3909
3910
        $this->admin->expects($this->any())
3911
            ->method('getModelManager')
3912
            ->willReturn($modelManager);
3913
3914
        $this->admin->expects($this->any())
3915
            ->method('getClass')
3916
            ->willReturn('Foo');
3917
3918
        $modelManager->expects($this->once())
3919
            ->method('addIdentifiersToQuery')
3920
            ->with($this->equalTo('Foo'), $this->equalTo($query), $this->equalTo(['123', '456']))
3921
            ->willReturn(true);
3922
3923
        $this->expectTranslate('flash_batch_delete_success', [], 'SonataAdminBundle');
3924
3925
        $this->request->setMethod('POST');
3926
        $this->request->request->set('data', json_encode(['action' => 'delete', 'idx' => ['123', '456'], 'all_elements' => false]));
3927
        $this->request->request->set('foo', 'bar');
3928
        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
3929
3930
        $result = $this->controller->batchAction($this->request);
3931
3932
        $this->assertInstanceOf(RedirectResponse::class, $result);
3933
        $this->assertSame(['flash_batch_delete_success'], $this->session->getFlashBag()->get('sonata_flash_success'));
3934
        $this->assertSame('list', $result->getTargetUrl());
3935
        $this->assertSame('bar', $this->request->request->get('foo'));
3936
    }
3937
3938
    public function testItThrowsWhenCallingAnUndefinedMethod(): void
3939
    {
3940
        $this->expectException(
3941
            \LogicException::class
3942
        );
3943
        $this->expectExceptionMessage(
3944
            'Call to undefined method Sonata\AdminBundle\Controller\CRUDController::doesNotExist'
3945
        );
3946
        $this->controller->doesNotExist();
3947
    }
3948
3949
    /**
3950
     * @expectedDeprecation Method Sonata\AdminBundle\Controller\CRUDController::render has been renamed to Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams.
3951
     */
3952
    public function testRenderIsDeprecated(): void
3953
    {
3954
        $this->controller->render('toto.html.twig');
3955
    }
3956
3957
    public function getCsrfProvider()
3958
    {
3959
        return $this->csrfProvider;
3960
    }
3961
3962
    public function getToStringValues()
3963
    {
3964
        return [
3965
            ['', ''],
3966
            ['Foo', 'Foo'],
3967
            ['&lt;a href=&quot;http://foo&quot;&gt;Bar&lt;/a&gt;', '<a href="http://foo">Bar</a>'],
3968
            ['&lt;&gt;&amp;&quot;&#039;abcdefghijklmnopqrstuvwxyz*-+.,?_()[]\/', '<>&"\'abcdefghijklmnopqrstuvwxyz*-+.,?_()[]\/'],
3969
        ];
3970
    }
3971
3972
    private function assertLoggerLogsModelManagerException($subject, string $method): void
3973
    {
3974
        $exception = new ModelManagerException(
3975
            $message = 'message',
3976
            1234,
3977
            new \Exception($previousExceptionMessage = 'very useful message')
3978
        );
3979
3980
        $subject->expects($this->once())
3981
            ->method($method)
3982
            ->willReturnCallback(static function () use ($exception): void {
3983
                throw $exception;
3984
            });
3985
3986
        $this->logger->expects($this->once())
3987
            ->method('error')
3988
            ->with($message, [
3989
                'exception' => $exception,
3990
                'previous_exception_message' => $previousExceptionMessage,
3991
            ]);
3992
    }
3993
3994
    private function expectTranslate(
3995
        string $id,
3996
        array $parameters = [],
3997
        ?string $domain = null,
3998
        ?string $locale = null
3999
    ): void {
4000
        $this->translator->expects($this->once())
4001
            ->method('trans')
4002
            ->with($this->equalTo($id), $this->equalTo($parameters), $this->equalTo($domain), $this->equalTo($locale))
4003
            ->willReturn($id);
4004
    }
4005
}
4006