Passed
Pull Request — 4 (#10028)
by Steve
11:35 queued 14s
created

SapphireTest::getAnnotations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use Exception;
6
use InvalidArgumentException;
7
use LogicException;
8
9
// use PHPUnit_Framework_Constraint_Not;
10
use PHPUnit\Framework\Constraint\LogicalNot;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Constraint\LogicalNot was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
// use PHPUnit_Framework_TestCase;
12
use PHPUnit\Framework\TestCase;
13
// use PHPUnit_Util_InvalidArgumentHelper;
14
use PHPUnit\Framework\Exception as PHPUnitFrameworkException;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use PHPUnit\Util\Test as TestUtil;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Util\Test was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use SilverStripe\CMS\Controllers\RootURLController;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Controllers\RootURLController was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use SilverStripe\Control\CLIRequestBuilder;
18
use SilverStripe\Control\Controller;
19
use SilverStripe\Control\Cookie;
20
use SilverStripe\Control\Director;
21
use SilverStripe\Control\Email\Email;
22
use SilverStripe\Control\Email\Mailer;
23
use SilverStripe\Control\HTTPApplication;
24
use SilverStripe\Control\HTTPRequest;
25
use SilverStripe\Core\Config\Config;
26
use SilverStripe\Core\Injector\Injector;
27
use SilverStripe\Core\Injector\InjectorLoader;
28
use SilverStripe\Core\Manifest\ClassLoader;
29
use SilverStripe\Core\Manifest\ModuleResourceLoader;
30
use SilverStripe\Dev\Constraint\SSListContains;
31
use SilverStripe\Dev\Constraint\SSListContainsOnly;
32
use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems;
33
use SilverStripe\Dev\State\FixtureTestState;
34
use SilverStripe\Dev\State\SapphireTestState;
35
use SilverStripe\i18n\i18n;
36
use SilverStripe\ORM\Connect\TempDatabase;
37
use SilverStripe\ORM\DataObject;
38
use SilverStripe\ORM\FieldType\DBDatetime;
39
use SilverStripe\ORM\FieldType\DBField;
40
use SilverStripe\ORM\SS_List;
41
use SilverStripe\Security\Group;
42
use SilverStripe\Security\IdentityStore;
43
use SilverStripe\Security\Member;
44
use SilverStripe\Security\Permission;
45
use SilverStripe\Security\Security;
46
use SilverStripe\View\SSViewer;
47
48
if (!class_exists(TestCase::class)) {
49
    return;
50
}
51
52
/**
53
 * Test case class for the Sapphire framework.
54
 * Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
55
 * to work with.
56
 *
57
 * This class should not be used anywhere outside of unit tests, as phpunit may not be installed
58
 * in production sites.
59
 */
60
class SapphireTest extends TestCase implements TestOnly
61
{
62
    /**
63
     * Path to fixture data for this test run.
64
     * If passed as an array, multiple fixture files will be loaded.
65
     * Please note that you won't be able to refer with "=>" notation
66
     * between the fixtures, they act independent of each other.
67
     *
68
     * @var string|array
69
     */
70
    protected static $fixture_file = null;
71
72
    /**
73
     * @deprecated 4.0..5.0 Use FixtureTestState instead
74
     * @var FixtureFactory
75
     */
76
    protected $fixtureFactory;
77
78
    /**
79
     * @var Boolean If set to TRUE, this will force a test database to be generated
80
     * in {@link setUp()}. Note that this flag is overruled by the presence of a
81
     * {@link $fixture_file}, which always forces a database build.
82
     *
83
     * @var bool
84
     */
85
    protected $usesDatabase = null;
86
87
    /**
88
     * This test will cleanup its state via transactions.
89
     * If set to false a full schema is forced between tests, but at a performance cost.
90
     *
91
     * @var bool
92
     */
93
    protected $usesTransactions = true;
94
95
    /**
96
     * @var bool
97
     */
98
    protected static $is_running_test = false;
99
100
    /**
101
     * By default, setUp() does not require default records. Pass
102
     * class names in here, and the require/augment default records
103
     * function will be called on them.
104
     *
105
     * @var array
106
     */
107
    protected $requireDefaultRecordsFrom = [];
108
109
    /**
110
     * A list of extensions that can't be applied during the execution of this run.  If they are
111
     * applied, they will be temporarily removed and a database migration called.
112
     *
113
     * The keys of the are the classes that the extensions can't be applied the extensions to, and
114
     * the values are an array of illegal extensions on that class.
115
     *
116
     * Set a class to `*` to remove all extensions (unadvised)
117
     *
118
     * @var array
119
     */
120
    protected static $illegal_extensions = [];
121
122
    /**
123
     * A list of extensions that must be applied during the execution of this run.  If they are
124
     * not applied, they will be temporarily added and a database migration called.
125
     *
126
     * The keys of the are the classes to apply the extensions to, and the values are an array
127
     * of required extensions on that class.
128
     *
129
     * Example:
130
     * <code>
131
     * array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
132
     * </code>
133
     *
134
     * @var array
135
     */
136
    protected static $required_extensions = [];
137
138
    /**
139
     * By default, the test database won't contain any DataObjects that have the interface TestOnly.
140
     * This variable lets you define additional TestOnly DataObjects to set up for this test.
141
     * Set it to an array of DataObject subclass names.
142
     *
143
     * @var array
144
     */
145
    protected static $extra_dataobjects = [];
146
147
    /**
148
     * List of class names of {@see Controller} objects to register routes for
149
     * Controllers must implement Link() method
150
     *
151
     * @var array
152
     */
153
    protected static $extra_controllers = [];
154
155
    /**
156
     * We need to disabling backing up of globals to avoid overriding
157
     * the few globals SilverStripe relies on, like $lang for the i18n subsystem.
158
     *
159
     * @see http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html
160
     */
161
    protected $backupGlobals = false;
162
163
    /**
164
     * State management container for SapphireTest
165
     *
166
     * @var SapphireTestState
167
     */
168
    protected static $state = null;
169
170
    /**
171
     * Temp database helper
172
     *
173
     * @var TempDatabase
174
     */
175
    protected static $tempDB = null;
176
177
    /**
178
     * @return TempDatabase
179
     */
180
    public static function tempDB()
181
    {
182
        if (!class_exists(TempDatabase::class)) {
183
            return null;
184
        }
185
186
        if (!static::$tempDB) {
187
            static::$tempDB = TempDatabase::create();
188
        }
189
        return static::$tempDB;
190
    }
191
192
    /**
193
     * Gets illegal extensions for this class
194
     *
195
     * @return array
196
     */
197
    public static function getIllegalExtensions()
198
    {
199
        return static::$illegal_extensions;
200
    }
201
202
    /**
203
     * Gets required extensions for this class
204
     *
205
     * @return array
206
     */
207
    public static function getRequiredExtensions()
208
    {
209
        return static::$required_extensions;
210
    }
211
212
    /**
213
     * Check if test bootstrapping has been performed. Must not be relied on
214
     * outside of unit tests.
215
     *
216
     * @return bool
217
     */
218
    protected static function is_running_test()
219
    {
220
        return self::$is_running_test;
221
    }
222
223
    /**
224
     * Set test running state
225
     *
226
     * @param bool $bool
227
     */
228
    protected static function set_is_running_test($bool)
229
    {
230
        self::$is_running_test = $bool;
231
    }
232
233
    /**
234
     * @return String
235
     */
236
    public static function get_fixture_file()
237
    {
238
        return static::$fixture_file;
239
    }
240
241
    /**
242
     * @return bool
243
     */
244
    public function getUsesDatabase()
245
    {
246
        return $this->usesDatabase;
247
    }
248
249
    /**
250
     * @return bool
251
     */
252
    public function getUsesTransactions()
253
    {
254
        return $this->usesTransactions;
255
    }
256
257
    /**
258
     * @return array
259
     */
260
    public function getRequireDefaultRecordsFrom()
261
    {
262
        return $this->requireDefaultRecordsFrom;
263
    }
264
265
    /**
266
     * Setup  the test.
267
     * Always sets up in order:
268
     *  - Reset php state
269
     *  - Nest
270
     *  - Custom state helpers
271
     *
272
     * User code should call parent::setUp() before custom setup code
273
     */
274
    protected function setUp(): void
275
    {
276
        if (!defined('FRAMEWORK_PATH')) {
277
            trigger_error(
278
                'Missing constants, did you remember to include the test bootstrap in your phpunit.xml file?',
279
                E_USER_WARNING
280
            );
281
        }
282
283
        // Call state helpers
284
        static::$state->setUp($this);
285
286
        // We cannot run the tests on this abstract class.
287
        if (static::class == __CLASS__) {
0 ignored issues
show
introduced by
The condition static::class == __CLASS__ is always true.
Loading history...
288
            $this->markTestSkipped(sprintf('Skipping %s ', static::class));
289
            return;
290
        }
291
292
        // i18n needs to be set to the defaults or tests fail
293
        if (class_exists(i18n::class)) {
294
            i18n::set_locale(i18n::config()->uninherited('default_locale'));
295
        }
296
297
        // Set default timezone consistently to avoid NZ-specific dependencies
298
        date_default_timezone_set('UTC');
299
300
        if (class_exists(Member::class)) {
301
            Member::set_password_validator(null);
302
        }
303
304
        if (class_exists(Cookie::class)) {
305
            Cookie::config()->update('report_errors', false);
306
        }
307
308
        if (class_exists(RootURLController::class)) {
309
            RootURLController::reset();
310
        }
311
312
        if (class_exists(Security::class)) {
313
            Security::clear_database_is_ready();
314
        }
315
316
        // Set up test routes
317
        $this->setUpRoutes();
318
319
        $fixtureFiles = $this->getFixturePaths();
320
321
        if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) {
322
            // Assign fixture factory to deprecated prop in case old tests use it over the getter
323
            /** @var FixtureTestState $fixtureState */
324
            $fixtureState = static::$state->getStateByName('fixtures');
325
            $this->fixtureFactory = $fixtureState->getFixtureFactory(static::class);
326
327
            $this->logInWithPermission('ADMIN');
328
        }
329
330
        // turn off template debugging
331
        if (class_exists(SSViewer::class)) {
332
            SSViewer::config()->update('source_file_comments', false);
333
        }
334
335
        // Set up the test mailer
336
        if (class_exists(TestMailer::class)) {
337
            Injector::inst()->registerService(new TestMailer(), Mailer::class);
338
        }
339
340
        if (class_exists(Email::class)) {
341
            Email::config()->remove('send_all_emails_to');
342
            Email::config()->remove('send_all_emails_from');
343
            Email::config()->remove('cc_all_emails_to');
344
            Email::config()->remove('bcc_all_emails_to');
345
        }
346
    }
347
348
349
350
    /**
351
     * Helper method to determine if the current test should enable a test database
352
     *
353
     * @param $fixtureFiles
354
     * @return bool
355
     */
356
    protected function shouldSetupDatabaseForCurrentTest($fixtureFiles)
357
    {
358
        $databaseEnabledByDefault = $fixtureFiles || $this->usesDatabase;
359
360
        return ($databaseEnabledByDefault && !$this->currentTestDisablesDatabase())
361
            || $this->currentTestEnablesDatabase();
362
    }
363
364
    /**
365
     * Helper method to check, if the current test uses the database.
366
     * This can be switched on with the annotation "@useDatabase"
367
     *
368
     * @return bool
369
     */
370
    protected function currentTestEnablesDatabase()
371
    {
372
        $annotations = $this->getAnnotations();
373
374
        return array_key_exists('useDatabase', $annotations['method'])
375
            && $annotations['method']['useDatabase'][0] !== 'false';
376
    }
377
378
    /**
379
     * Helper method to check, if the current test uses the database.
380
     * This can be switched on with the annotation "@useDatabase false"
381
     *
382
     * @return bool
383
     */
384
    protected function currentTestDisablesDatabase()
385
    {
386
        $annotations = $this->getAnnotations();
387
388
        return array_key_exists('useDatabase', $annotations['method'])
389
            && $annotations['method']['useDatabase'][0] === 'false';
390
    }
391
392
    /**
393
     * Called once per test case ({@link SapphireTest} subclass).
394
     * This is different to {@link setUp()}, which gets called once
395
     * per method. Useful to initialize expensive operations which
396
     * don't change state for any called method inside the test,
397
     * e.g. dynamically adding an extension. See {@link teardownAfterClass()}
398
     * for tearing down the state again.
399
     *
400
     * Always sets up in order:
401
     *  - Reset php state
402
     *  - Nest
403
     *  - Custom state helpers
404
     *
405
     * User code should call parent::setUpBeforeClass() before custom setup code
406
     *
407
     * @throws Exception
408
     */
409
    public static function setUpBeforeClass(): void
410
    {
411
        // Start tests
412
        static::start();
413
414
        if (!static::$state) {
415
            throw new Exception('SapphireTest failed to bootstrap!');
416
        }
417
418
        // Call state helpers
419
        static::$state->setUpOnce(static::class);
420
421
        // Build DB if we have objects
422
        if (class_exists(DataObject::class) && static::getExtraDataObjects()) {
423
            DataObject::reset();
424
            static::resetDBSchema(true, true);
425
        }
426
    }
427
428
    /**
429
     * tearDown method that's called once per test class rather once per test method.
430
     *
431
     * Always sets up in order:
432
     *  - Custom state helpers
433
     *  - Unnest
434
     *  - Reset php state
435
     *
436
     * User code should call parent::tearDownAfterClass() after custom tear down code
437
     */
438
    public static function tearDownAfterClass(): void
439
    {
440
        // Call state helpers
441
        static::$state->tearDownOnce(static::class);
442
443
        // Reset DB schema
444
        static::resetDBSchema();
445
    }
446
447
    /**
448
     * @deprecated 4.0.0:5.0.0
449
     * @return FixtureFactory|false
450
     */
451
    public function getFixtureFactory()
452
    {
453
        Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
454
        /** @var FixtureTestState $state */
455
        $state = static::$state->getStateByName('fixtures');
456
        return $state->getFixtureFactory(static::class);
457
    }
458
459
    /**
460
     * Sets a new fixture factory
461
     * @deprecated 4.0.0:5.0.0
462
     * @param FixtureFactory $factory
463
     * @return $this
464
     */
465
    public function setFixtureFactory(FixtureFactory $factory)
466
    {
467
        Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
468
        /** @var FixtureTestState $state */
469
        $state = static::$state->getStateByName('fixtures');
470
        $state->setFixtureFactory($factory, static::class);
471
        $this->fixtureFactory = $factory;
0 ignored issues
show
Deprecated Code introduced by
The property SilverStripe\Dev\SapphireTest::$fixtureFactory has been deprecated: 4.0..5.0 Use FixtureTestState instead ( Ignorable by Annotation )

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

471
        /** @scrutinizer ignore-deprecated */ $this->fixtureFactory = $factory;

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
472
        return $this;
473
    }
474
475
    /**
476
     * Get the ID of an object from the fixture.
477
     *
478
     * @param string $className The data class or table name, as specified in your fixture file.  Parent classes won't work
479
     * @param string $identifier The identifier string, as provided in your fixture file
480
     * @return int
481
     */
482
    protected function idFromFixture($className, $identifier)
483
    {
484
        /** @var FixtureTestState $state */
485
        $state = static::$state->getStateByName('fixtures');
486
        $id = $state->getFixtureFactory(static::class)->getId($className, $identifier);
487
488
        if (!$id) {
489
            throw new \InvalidArgumentException(sprintf(
490
                "Couldn't find object '%s' (class: %s)",
491
                $identifier,
492
                $className
493
            ));
494
        }
495
496
        return $id;
497
    }
498
499
    /**
500
     * Return all of the IDs in the fixture of a particular class name.
501
     * Will collate all IDs form all fixtures if multiple fixtures are provided.
502
     *
503
     * @param string $className The data class or table name, as specified in your fixture file
504
     * @return array A map of fixture-identifier => object-id
505
     */
506
    protected function allFixtureIDs($className)
507
    {
508
        /** @var FixtureTestState $state */
509
        $state = static::$state->getStateByName('fixtures');
510
        return $state->getFixtureFactory(static::class)->getIds($className);
511
    }
512
513
    /**
514
     * Get an object from the fixture.
515
     *
516
     * @param string $className The data class or table name, as specified in your fixture file. Parent classes won't work
517
     * @param string $identifier The identifier string, as provided in your fixture file
518
     *
519
     * @return DataObject
520
     */
521
    protected function objFromFixture($className, $identifier)
522
    {
523
        /** @var FixtureTestState $state */
524
        $state = static::$state->getStateByName('fixtures');
525
        $obj = $state->getFixtureFactory(static::class)->get($className, $identifier);
526
527
        if (!$obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
528
            throw new \InvalidArgumentException(sprintf(
529
                "Couldn't find object '%s' (class: %s)",
530
                $identifier,
531
                $className
532
            ));
533
        }
534
535
        return $obj;
536
    }
537
538
    /**
539
     * Load a YAML fixture file into the database.
540
     * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
541
     * Doesn't clear existing fixtures.
542
     * @deprecated 4.0.0:5.0.0
543
     *
544
     * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
545
     */
546
    public function loadFixture($fixtureFile)
547
    {
548
        Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
549
        $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
550
        $fixture->writeInto($this->getFixtureFactory());
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0.0:5.0.0 ( Ignorable by Annotation )

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

550
        $fixture->writeInto(/** @scrutinizer ignore-deprecated */ $this->getFixtureFactory());

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

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

Loading history...
551
    }
552
553
    /**
554
     * Clear all fixtures which were previously loaded through
555
     * {@link loadFixture()}
556
     */
557
    public function clearFixtures()
558
    {
559
        /** @var FixtureTestState $state */
560
        $state = static::$state->getStateByName('fixtures');
561
        $state->getFixtureFactory(static::class)->clear();
562
    }
563
564
    /**
565
     * Useful for writing unit tests without hardcoding folder structures.
566
     *
567
     * @return string Absolute path to current class.
568
     */
569
    protected function getCurrentAbsolutePath()
570
    {
571
        $filename = ClassLoader::inst()->getItemPath(static::class);
572
        if (!$filename) {
573
            throw new LogicException('getItemPath returned null for ' . static::class
574
                . '. Try adding flush=1 to the test run.');
575
        }
576
        return dirname($filename);
577
    }
578
579
    /**
580
     * @return string File path relative to webroot
581
     */
582
    protected function getCurrentRelativePath()
583
    {
584
        $base = Director::baseFolder();
585
        $path = $this->getCurrentAbsolutePath();
586
        if (substr($path, 0, strlen($base)) == $base) {
587
            $path = preg_replace('/^\/*/', '', substr($path, strlen($base)));
588
        }
589
        return $path;
590
    }
591
592
    /**
593
     * Setup  the test.
594
     * Always sets up in order:
595
     *  - Custom state helpers
596
     *  - Unnest
597
     *  - Reset php state
598
     *
599
     * User code should call parent::tearDown() after custom tear down code
600
     */
601
    protected function tearDown(): void
602
    {
603
        // Reset mocked datetime
604
        if (class_exists(DBDatetime::class)) {
605
            DBDatetime::clear_mock_now();
606
        }
607
608
        // Stop the redirection that might have been requested in the test.
609
        // Note: Ideally a clean Controller should be created for each test.
610
        // Now all tests executed in a batch share the same controller.
611
        if (class_exists(Controller::class)) {
612
            $controller = Controller::has_curr() ? Controller::curr() : null;
613
            if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) {
614
                $response->setStatusCode(200);
615
                $response->removeHeader('Location');
616
            }
617
        }
618
619
        // Call state helpers
620
        static::$state->tearDown($this);
621
    }
622
623
    public static function assertContains(
624
        $needle,
625
        $haystack,
626
        $message = '',
627
        $ignoreCase = false,
628
        $checkForObjectIdentity = true,
629
        $checkForNonObjectIdentity = false
630
    ):void {
631
        if ($haystack instanceof DBField) {
632
            $haystack = (string)$haystack;
633
        }
634
        if (is_iterable($haystack)) {
635
            $strict = is_object($needle) ? $checkForObjectIdentity : $checkForObjectIdentity;
636
            if ($strict) {
637
                parent::assertContains($needle, $haystack, $message);
638
            } else {
639
                parent::assertContainsEquals($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertContainsEquals() does not exist on PHPUnit\Framework\TestCase. Did you maybe mean assertContains()? ( Ignorable by Annotation )

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

639
                parent::/** @scrutinizer ignore-call */ 
640
                        assertContainsEquals($needle, $haystack, $message);

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

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

Loading history...
640
            }
641
        } else {
642
            if ($ignoreCase) {
643
                parent::assertStringContainsStringIgnoringCase($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertStringContainsStringIgnoringCase() does not exist on PHPUnit\Framework\TestCase. ( Ignorable by Annotation )

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

643
                parent::/** @scrutinizer ignore-call */ 
644
                        assertStringContainsStringIgnoringCase($needle, $haystack, $message);

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

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

Loading history...
644
            } else {
645
                parent::assertStringContainsString($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertStringContainsString() does not exist on PHPUnit\Framework\TestCase. ( Ignorable by Annotation )

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

645
                parent::/** @scrutinizer ignore-call */ 
646
                        assertStringContainsString($needle, $haystack, $message);

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

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

Loading history...
646
            }
647
        }
648
    }
649
650
    public static function assertNotContains(
651
        $needle,
652
        $haystack,
653
        $message = '',
654
        $ignoreCase = false,
655
        $checkForObjectIdentity = true,
656
        $checkForNonObjectIdentity = false
657
    ):void {
658
        if ($haystack instanceof DBField) {
659
            $haystack = (string)$haystack;
660
        }
661
        if (is_iterable($haystack)) {
662
            $strict = is_object($needle) ? $checkForObjectIdentity : $checkForObjectIdentity;
663
            if ($strict) {
664
                parent::assertNotContains($needle, $haystack, $message);
665
            } else {
666
                parent::assertNotContainsEquals($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertNotContainsEquals() does not exist on PHPUnit\Framework\TestCase. Did you maybe mean assertNotContains()? ( Ignorable by Annotation )

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

666
                parent::/** @scrutinizer ignore-call */ 
667
                        assertNotContainsEquals($needle, $haystack, $message);

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

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

Loading history...
667
            }
668
        } else {
669
            if ($ignoreCase) {
670
                parent::assertStringNotContainsStringIgnoringCase($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertStringNotContainsStringIgnoringCase() does not exist on PHPUnit\Framework\TestCase. ( Ignorable by Annotation )

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

670
                parent::/** @scrutinizer ignore-call */ 
671
                        assertStringNotContainsStringIgnoringCase($needle, $haystack, $message);

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

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

Loading history...
671
            } else {
672
                parent::assertStringNotContainsString($needle, $haystack, $message);
0 ignored issues
show
Bug introduced by
The method assertStringNotContainsString() does not exist on PHPUnit\Framework\TestCase. Did you maybe mean assertStringNotMatchesFormat()? ( Ignorable by Annotation )

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

672
                parent::/** @scrutinizer ignore-call */ 
673
                        assertStringNotContainsString($needle, $haystack, $message);

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

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

Loading history...
673
            }
674
        }
675
    }
676
677
    /**
678
     * Clear the log of emails sent
679
     *
680
     * @return bool True if emails cleared
681
     */
682
    public function clearEmails()
683
    {
684
        /** @var Mailer $mailer */
685
        $mailer = Injector::inst()->get(Mailer::class);
686
        if ($mailer instanceof TestMailer) {
687
            $mailer->clearEmails();
688
            return true;
689
        }
690
        return false;
691
    }
692
693
    /**
694
     * Search for an email that was sent.
695
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
696
     * @param string $to
697
     * @param string $from
698
     * @param string $subject
699
     * @param string $content
700
     * @return array|null Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles',
701
     *               'HtmlContent'
702
     */
703
    public static function findEmail($to, $from = null, $subject = null, $content = null)
704
    {
705
        /** @var Mailer $mailer */
706
        $mailer = Injector::inst()->get(Mailer::class);
707
        if ($mailer instanceof TestMailer) {
708
            return $mailer->findEmail($to, $from, $subject, $content);
709
        }
710
        return null;
711
    }
712
713
    /**
714
     * Assert that the matching email was sent since the last call to clearEmails()
715
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
716
     *
717
     * @param string $to
718
     * @param string $from
719
     * @param string $subject
720
     * @param string $content
721
     */
722
    public static function assertEmailSent($to, $from = null, $subject = null, $content = null)
723
    {
724
        $found = (bool)static::findEmail($to, $from, $subject, $content);
725
726
        $infoParts = '';
727
        $withParts = [];
728
        if ($to) {
729
            $infoParts .= " to '$to'";
730
        }
731
        if ($from) {
732
            $infoParts .= " from '$from'";
733
        }
734
        if ($subject) {
735
            $withParts[] = "subject '$subject'";
736
        }
737
        if ($content) {
738
            $withParts[] = "content '$content'";
739
        }
740
        if ($withParts) {
741
            $infoParts .= ' with ' . implode(' and ', $withParts);
742
        }
743
744
        static::assertTrue(
745
            $found,
746
            "Failed asserting that an email was sent$infoParts."
747
        );
748
    }
749
750
751
    /**
752
     * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
753
     * pairs.  Each match must correspond to 1 distinct record.
754
     *
755
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
756
     * either pass a single pattern or an array of patterns.
757
     * @param SS_List $list The {@link SS_List} to test.
758
     * @param string $message
759
     *
760
     * Examples
761
     * --------
762
     * Check that $members includes an entry with Email = [email protected]:
763
     *      $this->assertListContains(['Email' => '[email protected]'], $members);
764
     *
765
     * Check that $members includes entries with Email = [email protected] and with
766
     * Email = [email protected]:
767
     *      $this->assertListContains([
768
     *         ['Email' => '[email protected]'],
769
     *         ['Email' => '[email protected]'],
770
     *      ], $members);
771
     */
772
    public static function assertListContains($matches, SS_List $list, $message = '')
773
    {
774
        if (!is_array($matches)) {
775
            throw self::createPHPUnitFrameworkException(
776
                1,
777
                'array'
778
            );
779
        }
780
781
        static::assertThat(
782
            $list,
783
            new SSListContains(
784
                $matches
785
            ),
786
            $message
787
        );
788
    }
789
790
    /**
791
     * @deprecated 4.0.0:5.0.0 Use assertListContains() instead
792
     *
793
     * @param $matches
794
     * @param $dataObjectSet
795
     */
796
    public function assertDOSContains($matches, $dataObjectSet)
797
    {
798
        Deprecation::notice('5.0', 'Use assertListContains() instead');
799
        return static::assertListContains($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListContai...atches, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...t::assertListContains() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
800
    }
801
802
    /**
803
     * Asserts that no items in a given list appear in the given dataobject list
804
     *
805
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
806
     * either pass a single pattern or an array of patterns.
807
     * @param SS_List $list The {@link SS_List} to test.
808
     * @param string $message
809
     *
810
     * Examples
811
     * --------
812
     * Check that $members doesn't have an entry with Email = [email protected]:
813
     *      $this->assertListNotContains(['Email' => '[email protected]'], $members);
814
     *
815
     * Check that $members doesn't have entries with Email = [email protected] and with
816
     * Email = [email protected]:
817
     *      $this->assertListNotContains([
818
     *          ['Email' => '[email protected]'],
819
     *          ['Email' => '[email protected]'],
820
     *      ], $members);
821
     */
822
    public static function assertListNotContains($matches, SS_List $list, $message = '')
823
    {
824
        if (!is_array($matches)) {
825
            throw self::createPHPUnitFrameworkException(
826
                1,
827
                'array'
828
            );
829
        }
830
831
        $constraint =  new LogicalNot(
832
            new SSListContains(
833
                $matches
834
            )
835
        );
836
837
        static::assertThat(
838
            $list,
839
            $constraint,
840
            $message
841
        );
842
    }
843
844
    /**
845
     * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead
846
     *
847
     * @param $matches
848
     * @param $dataObjectSet
849
     */
850
    public static function assertNotDOSContains($matches, $dataObjectSet)
851
    {
852
        Deprecation::notice('5.0', 'Use assertListNotContains() instead');
853
        return static::assertListNotContains($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListNotCon...atches, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...assertListNotContains() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
854
    }
855
856
    /**
857
     * Assert that the given {@link SS_List} includes only DataObjects matching the given
858
     * key-value pairs.  Each match must correspond to 1 distinct record.
859
     *
860
     * Example
861
     * --------
862
     * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
863
     * matter:
864
     *     $this->assertListEquals([
865
     *        ['FirstName' =>'Sam', 'Surname' => 'Minnee'],
866
     *        ['FirstName' => 'Ingo', 'Surname' => 'Schommer'],
867
     *      ], $members);
868
     *
869
     * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
870
     * either pass a single pattern or an array of patterns.
871
     * @param mixed $list The {@link SS_List} to test.
872
     * @param string $message
873
     */
874
    public static function assertListEquals($matches, SS_List $list, $message = '')
875
    {
876
        if (!is_array($matches)) {
877
            throw self::createPHPUnitFrameworkException(
878
                1,
879
                'array'
880
            );
881
        }
882
883
        static::assertThat(
884
            $list,
885
            new SSListContainsOnly(
886
                $matches
887
            ),
888
            $message
889
        );
890
    }
891
892
    /**
893
     * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead
894
     *
895
     * @param $matches
896
     * @param SS_List $dataObjectSet
897
     */
898
    public function assertDOSEquals($matches, $dataObjectSet)
899
    {
900
        Deprecation::notice('5.0', 'Use assertListEquals() instead');
901
        return static::assertListEquals($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListEquals($matches, $dataObjectSet) targeting SilverStripe\Dev\SapphireTest::assertListEquals() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
902
    }
903
904
905
    /**
906
     * Assert that the every record in the given {@link SS_List} matches the given key-value
907
     * pairs.
908
     *
909
     * Example
910
     * --------
911
     * Check that every entry in $members has a Status of 'Active':
912
     *     $this->assertListAllMatch(['Status' => 'Active'], $members);
913
     *
914
     * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
915
     * @param mixed $list The {@link SS_List} to test.
916
     * @param string $message
917
     */
918
    public static function assertListAllMatch($match, SS_List $list, $message = '')
919
    {
920
        if (!is_array($match)) {
921
            throw self::createPHPUnitFrameworkException(
922
                1,
923
                'array'
924
            );
925
        }
926
927
        static::assertThat(
928
            $list,
929
            new SSListContainsOnlyMatchingItems(
930
                $match
931
            ),
932
            $message
933
        );
934
    }
935
936
    /**
937
     * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead
938
     *
939
     * @param $match
940
     * @param SS_List $dataObjectSet
941
     */
942
    public function assertDOSAllMatch($match, SS_List $dataObjectSet)
943
    {
944
        Deprecation::notice('5.0', 'Use assertListAllMatch() instead');
945
        return static::assertListAllMatch($match, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListAllMatch($match, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...t::assertListAllMatch() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
946
    }
947
948
    /**
949
     * Removes sequences of repeated whitespace characters from SQL queries
950
     * making them suitable for string comparison
951
     *
952
     * @param string $sql
953
     * @return string The cleaned and normalised SQL string
954
     */
955
    protected static function normaliseSQL($sql)
956
    {
957
        return trim(preg_replace('/\s+/m', ' ', $sql));
958
    }
959
960
    /**
961
     * Asserts that two SQL queries are equivalent
962
     *
963
     * @param string $expectedSQL
964
     * @param string $actualSQL
965
     * @param string $message
966
     * @param float|int $delta
967
     * @param integer $maxDepth
968
     * @param boolean $canonicalize
969
     * @param boolean $ignoreCase
970
     */
971
    public static function assertSQLEquals(
972
        $expectedSQL,
973
        $actualSQL,
974
        $message = '',
975
        $delta = 0,
976
        $maxDepth = 10,
977
        $canonicalize = false,
978
        $ignoreCase = false
979
    ) {
980
        // Normalise SQL queries to remove patterns of repeating whitespace
981
        $expectedSQL = static::normaliseSQL($expectedSQL);
982
        $actualSQL = static::normaliseSQL($actualSQL);
983
984
        static::assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
985
    }
986
987
    /**
988
     * Asserts that a SQL query contains a SQL fragment
989
     *
990
     * @param string $needleSQL
991
     * @param string $haystackSQL
992
     * @param string $message
993
     * @param boolean $ignoreCase
994
     * @param boolean $checkForObjectIdentity
995
     */
996
    public static function assertSQLContains(
997
        $needleSQL,
998
        $haystackSQL,
999
        $message = '',
1000
        $ignoreCase = false,
1001
        $checkForObjectIdentity = true
1002
    ) {
1003
        $needleSQL = static::normaliseSQL($needleSQL);
1004
        $haystackSQL = static::normaliseSQL($haystackSQL);
1005
1006
        static::assertContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1007
    }
1008
1009
    /**
1010
     * Asserts that a SQL query contains a SQL fragment
1011
     *
1012
     * @param string $needleSQL
1013
     * @param string $haystackSQL
1014
     * @param string $message
1015
     * @param boolean $ignoreCase
1016
     * @param boolean $checkForObjectIdentity
1017
     */
1018
    public static function assertSQLNotContains(
1019
        $needleSQL,
1020
        $haystackSQL,
1021
        $message = '',
1022
        $ignoreCase = false,
1023
        $checkForObjectIdentity = true
1024
    ) {
1025
        $needleSQL = static::normaliseSQL($needleSQL);
1026
        $haystackSQL = static::normaliseSQL($haystackSQL);
1027
1028
        static::assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1029
    }
1030
1031
    /**
1032
     * Start test environment
1033
     */
1034
    public static function start()
1035
    {
1036
        if (static::is_running_test()) {
1037
            return;
1038
        }
1039
1040
        // Health check
1041
        if (InjectorLoader::inst()->countManifests()) {
1042
            throw new LogicException('SapphireTest::start() cannot be called within another application');
1043
        }
1044
        static::set_is_running_test(true);
1045
1046
        // Test application
1047
        $kernel = new TestKernel(BASE_PATH);
1048
1049
        if (class_exists(HTTPApplication::class)) {
1050
            // Mock request
1051
            $_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
1052
            $request = CLIRequestBuilder::createFromEnvironment();
1053
1054
            $app = new HTTPApplication($kernel);
1055
            $flush = array_key_exists('flush', $request->getVars());
1056
1057
            // Custom application
1058
            $res = $app->execute($request, function (HTTPRequest $request) {
1059
                // Start session and execute
1060
                $request->getSession()->init($request);
1061
1062
                // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
1063
                // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
1064
                DataObject::reset();
1065
1066
                // Set dummy controller;
1067
                $controller = Controller::create();
1068
                $controller->setRequest($request);
1069
                $controller->pushCurrent();
1070
                $controller->doInit();
1071
            }, $flush);
1072
1073
            if ($res && $res->isError()) {
1074
                throw new LogicException($res->getBody());
1075
            }
1076
        } else {
1077
            // Allow flush from the command line in the absence of HTTPApplication's special sauce
1078
            $flush = false;
1079
            foreach ($_SERVER['argv'] as $arg) {
1080
                if (preg_match('/^(--)?flush(=1)?$/', $arg)) {
1081
                    $flush = true;
1082
                }
1083
            }
1084
            $kernel->boot($flush);
1085
        }
1086
1087
        // Register state
1088
        static::$state = SapphireTestState::singleton();
1089
        // Register temp DB holder
1090
        static::tempDB();
1091
    }
1092
1093
    /**
1094
     * Reset the testing database's schema, but only if it is active
1095
     * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
1096
     * @param bool $forceCreate Force DB to be created if it doesn't exist
1097
     */
1098
    public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
1099
    {
1100
        if (!static::$tempDB) {
1101
            return;
1102
        }
1103
1104
        // Check if DB is active before reset
1105
        if (!static::$tempDB->isUsed()) {
1106
            if (!$forceCreate) {
1107
                return;
1108
            }
1109
            static::$tempDB->build();
1110
        }
1111
        $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
1112
        static::$tempDB->resetDBSchema((array)$extraDataObjects);
1113
    }
1114
1115
    /**
1116
     * A wrapper for automatically performing callbacks as a user with a specific permission
1117
     *
1118
     * @param string|array $permCode
1119
     * @param callable $callback
1120
     * @return mixed
1121
     */
1122
    public function actWithPermission($permCode, $callback)
1123
    {
1124
        return Member::actAs($this->createMemberWithPermission($permCode), $callback);
1125
    }
1126
1127
    /**
1128
     * Create Member and Group objects on demand with specific permission code
1129
     *
1130
     * @param string|array $permCode
1131
     * @return Member
1132
     */
1133
    protected function createMemberWithPermission($permCode)
1134
    {
1135
        if (is_array($permCode)) {
1136
            $permArray = $permCode;
1137
            $permCode = implode('.', $permCode);
1138
        } else {
1139
            $permArray = [$permCode];
1140
        }
1141
1142
        // Check cached member
1143
        if (isset($this->cache_generatedMembers[$permCode])) {
1144
            $member = $this->cache_generatedMembers[$permCode];
1145
        } else {
1146
            // Generate group with these permissions
1147
            $group = Group::create();
1148
            $group->Title = "$permCode group";
1149
            $group->write();
1150
1151
            // Create each individual permission
1152
            foreach ($permArray as $permArrayItem) {
1153
                $permission = Permission::create();
1154
                $permission->Code = $permArrayItem;
1155
                $permission->write();
1156
                $group->Permissions()->add($permission);
1157
            }
1158
1159
            $member = Member::get()->filter([
1160
                'Email' => "[email protected]",
1161
            ])->first();
1162
            if (!$member) {
1163
                $member = Member::create();
1164
            }
1165
1166
            $member->FirstName = $permCode;
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1167
            $member->Surname = 'User';
0 ignored issues
show
Bug Best Practice introduced by
The property Surname does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1168
            $member->Email = "[email protected]";
0 ignored issues
show
Bug Best Practice introduced by
The property Email does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1169
            $member->write();
1170
            $group->Members()->add($member);
1171
1172
            $this->cache_generatedMembers[$permCode] = $member;
1173
        }
1174
        return $member;
1175
    }
1176
1177
    /**
1178
     * Create a member and group with the given permission code, and log in with it.
1179
     * Returns the member ID.
1180
     *
1181
     * @param string|array $permCode Either a permission, or list of permissions
1182
     * @return int Member ID
1183
     */
1184
    public function logInWithPermission($permCode = 'ADMIN')
1185
    {
1186
        $member = $this->createMemberWithPermission($permCode);
1187
        $this->logInAs($member);
1188
        return $member->ID;
1189
    }
1190
1191
    /**
1192
     * Log in as the given member
1193
     *
1194
     * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1195
     */
1196
    public function logInAs($member)
1197
    {
1198
        if (is_numeric($member)) {
1199
            $member = DataObject::get_by_id(Member::class, $member);
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type string; however, parameter $idOrCache of SilverStripe\ORM\DataObject::get_by_id() does only seem to accept boolean|integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1199
            $member = DataObject::get_by_id(Member::class, /** @scrutinizer ignore-type */ $member);
Loading history...
1200
        } elseif (!is_object($member)) {
1201
            $member = $this->objFromFixture(Member::class, $member);
1202
        }
1203
        Injector::inst()->get(IdentityStore::class)->logIn($member);
1204
    }
1205
1206
    /**
1207
     * Log out the current user
1208
     */
1209
    public function logOut()
1210
    {
1211
        /** @var IdentityStore $store */
1212
        $store = Injector::inst()->get(IdentityStore::class);
1213
        $store->logOut();
1214
    }
1215
1216
    /**
1217
     * Cache for logInWithPermission()
1218
     */
1219
    protected $cache_generatedMembers = [];
1220
1221
    /**
1222
     * Test against a theme.
1223
     *
1224
     * @param string $themeBaseDir themes directory
1225
     * @param string $theme Theme name
1226
     * @param callable $callback
1227
     * @throws Exception
1228
     */
1229
    protected function useTestTheme($themeBaseDir, $theme, $callback)
1230
    {
1231
        Config::nest();
1232
        if (strpos($themeBaseDir, BASE_PATH) === 0) {
1233
            $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1234
        }
1235
        SSViewer::config()->update('theme_enabled', true);
1236
        SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']);
1237
1238
        try {
1239
            $callback();
1240
        } finally {
1241
            Config::unnest();
1242
        }
1243
    }
1244
1245
    /**
1246
     * Get fixture paths for this test
1247
     *
1248
     * @return array List of paths
1249
     */
1250
    protected function getFixturePaths()
1251
    {
1252
        $fixtureFile = static::get_fixture_file();
1253
        if (empty($fixtureFile)) {
1254
            return [];
1255
        }
1256
1257
        $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile];
0 ignored issues
show
introduced by
The condition is_array($fixtureFile) is always false.
Loading history...
1258
1259
        return array_map(function ($fixtureFilePath) {
1260
            return $this->resolveFixturePath($fixtureFilePath);
1261
        }, $fixtureFiles);
1262
    }
1263
1264
    /**
1265
     * Return all extra objects to scaffold for this test
1266
     * @return array
1267
     */
1268
    public static function getExtraDataObjects()
1269
    {
1270
        return static::$extra_dataobjects;
1271
    }
1272
1273
    /**
1274
     * Get additional controller classes to register routes for
1275
     *
1276
     * @return array
1277
     */
1278
    public static function getExtraControllers()
1279
    {
1280
        return static::$extra_controllers;
1281
    }
1282
1283
    /**
1284
     * Map a fixture path to a physical file
1285
     *
1286
     * @param string $fixtureFilePath
1287
     * @return string
1288
     */
1289
    protected function resolveFixturePath($fixtureFilePath)
1290
    {
1291
        // support loading via composer name path.
1292
        if (strpos($fixtureFilePath, ':') !== false) {
1293
            return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath);
1294
        }
1295
1296
        // Support fixture paths relative to the test class, rather than relative to webroot
1297
        // String checking is faster than file_exists() calls.
1298
        $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1299
        if ($resolvedPath) {
1300
            return $resolvedPath;
1301
        }
1302
1303
        // Check if file exists relative to base dir
1304
        $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1305
        if ($resolvedPath) {
1306
            return $resolvedPath;
1307
        }
1308
1309
        return $fixtureFilePath;
1310
    }
1311
1312
    protected function setUpRoutes()
1313
    {
1314
        if (!class_exists(Director::class)) {
1315
            return;
1316
        }
1317
1318
        // Get overridden routes
1319
        $rules = $this->getExtraRoutes();
1320
1321
        // Add all other routes
1322
        foreach (Director::config()->uninherited('rules') as $route => $rule) {
1323
            if (!isset($rules[$route])) {
1324
                $rules[$route] = $rule;
1325
            }
1326
        }
1327
1328
        // Add default catch-all rule
1329
        $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1330
1331
        // Add controller-name auto-routing
1332
        Director::config()->set('rules', $rules);
1333
    }
1334
1335
    /**
1336
     * Get extra routes to merge into Director.rules
1337
     *
1338
     * @return array
1339
     */
1340
    protected function getExtraRoutes()
1341
    {
1342
        $rules = [];
1343
        foreach ($this->getExtraControllers() as $class) {
1344
            $controllerInst = Controller::singleton($class);
1345
            $link = Director::makeRelative($controllerInst->Link());
1346
            $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1347
            $rules[$route] = $class;
1348
        }
1349
        return $rules;
1350
    }
1351
1352
    // === REIMPLEMENTATION METHODS THAT EXISTED IN SAPPHIRETEST 5 ===
1353
1354
    /**
1355
     * Reimplementation of phpunit5 PHPUnit_Util_InvalidArgumentHelper::factory()
1356
     *
1357
     * @param $argument
1358
     * @param $type
1359
     * @param $value
1360
     */
1361
    public static function createPHPUnitFrameworkException($argument, $type, $value = null)
1362
    {
1363
        $stack = debug_backtrace(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $options of debug_backtrace(). ( Ignorable by Annotation )

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

1363
        $stack = debug_backtrace(/** @scrutinizer ignore-type */ false);
Loading history...
1364
1365
        return new PHPUnitFrameworkException(
1366
            sprintf(
1367
                'Argument #%d%sof %s::%s() must be a %s',
1368
                $argument,
1369
                $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ',
1370
                $stack[1]['class'],
1371
                $stack[1]['function'],
1372
                $type
1373
            )
1374
        );
1375
    }
1376
1377
    /**
1378
     * Returns the annotations for this test.
1379
     *
1380
     * @return array
1381
     */
1382
    public function getAnnotations()
1383
    {
1384
        return TestUtil::parseTestMethodAnnotations(
1385
            get_class($this),
1386
            $this->name
0 ignored issues
show
Bug introduced by
The property name is declared private in PHPUnit_Framework_TestCase and cannot be accessed from this context.
Loading history...
1387
        );
1388
    }
1389
}
1390