Passed
Push — 4 ( 77a45c...ec956a )
by Damian
07:50 queued 13s
created

SapphireTest   F

Complexity

Total Complexity 110

Size/Duplication

Total Lines 1193
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 110
dl 0
loc 1193
rs 0.6314
c 0
b 0
f 0

55 Methods

Rating   Name   Duplication   Size   Complexity  
A getExtraDataObjects() 0 3 1
A assertListEquals() 0 15 2
A getExtraRoutes() 0 10 2
A clearEmails() 0 9 2
A getCurrentRelativePath() 0 8 2
A tearDownAfterClass() 0 7 1
A objFromFixture() 0 13 2
B resolveFixturePath() 0 27 6
A resetDBSchema() 0 11 4
A shouldSetupDatabaseForCurrentTest() 0 6 4
A tempDB() 0 6 2
A is_running_test() 0 3 1
B start() 0 40 3
B setUp() 0 53 5
A assertSQLEquals() 0 14 1
A logOut() 0 5 1
B tearDown() 0 16 5
A allFixtureIDs() 0 3 1
A getRequireDefaultRecordsFrom() 0 3 1
A getIllegalExtensions() 0 3 1
A set_is_running_test() 0 3 1
A currentTestDisablesDatabase() 0 6 2
A assertDOSContains() 0 4 1
A setFixtureFactory() 0 8 1
A assertDOSEquals() 0 4 1
A idFromFixture() 0 13 2
A getUsesDatabase() 0 3 1
A assertSQLNotContains() 0 11 1
A getCurrentAbsolutePath() 0 8 2
A getRequiredExtensions() 0 3 1
A logInAs() 0 8 3
A currentTestEnablesDatabase() 0 6 2
A setUpRoutes() 0 17 3
A getExtraControllers() 0 3 1
A assertListAllMatch() 0 15 2
A getFixtureFactory() 0 6 1
A useTestTheme() 0 13 2
A findEmail() 0 8 2
A setUpBeforeClass() 0 16 3
A actWithPermission() 0 3 1
A assertNotDOSContains() 0 4 1
A assertNotContains() 0 12 2
A assertDOSAllMatch() 0 4 1
A assertSQLContains() 0 11 1
A normaliseSQL() 0 3 1
A assertListNotContains() 0 19 2
A get_fixture_file() 0 3 1
B assertEmailSent() 0 25 6
A clearFixtures() 0 3 1
A assertListContains() 0 15 2
A logInWithPermission() 0 5 1
A assertContains() 0 12 2
A loadFixture() 0 5 1
B createMemberWithPermission() 0 42 5
A getFixturePaths() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like SapphireTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SapphireTest, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 55 and the first side effect is on line 44.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

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

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

439
        $id = /** @scrutinizer ignore-deprecated */ $this->getFixtureFactory()->getId($className, $identifier);

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...
440
441
        if (!$id) {
442
            user_error(sprintf(
443
                "Couldn't find object '%s' (class: %s)",
444
                $identifier,
445
                $className
446
            ), E_USER_ERROR);
447
        }
448
449
        return $id;
450
    }
451
452
    /**
453
     * Return all of the IDs in the fixture of a particular class name.
454
     * Will collate all IDs form all fixtures if multiple fixtures are provided.
455
     *
456
     * @param string $className The data class or table name, as specified in your fixture file
457
     * @return array A map of fixture-identifier => object-id
458
     */
459
    protected function allFixtureIDs($className)
460
    {
461
        return $this->getFixtureFactory()->getIds($className);
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0..5.0 ( Ignorable by Annotation )

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

461
        return /** @scrutinizer ignore-deprecated */ $this->getFixtureFactory()->getIds($className);

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...
462
    }
463
464
    /**
465
     * Get an object from the fixture.
466
     *
467
     * @param string $className The data class or table name, as specified in your fixture file. Parent classes won't work
468
     * @param string $identifier The identifier string, as provided in your fixture file
469
     *
470
     * @return DataObject
471
     */
472
    protected function objFromFixture($className, $identifier)
473
    {
474
        $obj = $this->getFixtureFactory()->get($className, $identifier);
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0..5.0 ( Ignorable by Annotation )

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

474
        $obj = /** @scrutinizer ignore-deprecated */ $this->getFixtureFactory()->get($className, $identifier);

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...
475
476
        if (!$obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
477
            user_error(sprintf(
478
                "Couldn't find object '%s' (class: %s)",
479
                $identifier,
480
                $className
481
            ), E_USER_ERROR);
482
        }
483
484
        return $obj;
485
    }
486
487
    /**
488
     * Load a YAML fixture file into the database.
489
     * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
490
     * Doesn't clear existing fixtures.
491
     * @deprecated 4.0...5.0
492
     *
493
     * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
494
     */
495
    public function loadFixture($fixtureFile)
496
    {
497
        Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
498
        $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
499
        $fixture->writeInto($this->getFixtureFactory());
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0..5.0 ( Ignorable by Annotation )

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

499
        $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...
500
    }
501
502
    /**
503
     * Clear all fixtures which were previously loaded through
504
     * {@link loadFixture()}
505
     */
506
    public function clearFixtures()
507
    {
508
        $this->getFixtureFactory()->clear();
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0..5.0 ( Ignorable by Annotation )

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

508
        /** @scrutinizer ignore-deprecated */ $this->getFixtureFactory()->clear();

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...
509
    }
510
511
    /**
512
     * Useful for writing unit tests without hardcoding folder structures.
513
     *
514
     * @return string Absolute path to current class.
515
     */
516
    protected function getCurrentAbsolutePath()
517
    {
518
        $filename = ClassLoader::inst()->getItemPath(static::class);
519
        if (!$filename) {
520
            throw new LogicException('getItemPath returned null for ' . static::class
521
                . '. Try adding flush=1 to the test run.');
522
        }
523
        return dirname($filename);
524
    }
525
526
    /**
527
     * @return string File path relative to webroot
528
     */
529
    protected function getCurrentRelativePath()
530
    {
531
        $base = Director::baseFolder();
532
        $path = $this->getCurrentAbsolutePath();
533
        if (substr($path, 0, strlen($base)) == $base) {
534
            $path = preg_replace('/^\/*/', '', substr($path, strlen($base)));
535
        }
536
        return $path;
537
    }
538
539
    /**
540
     * Setup  the test.
541
     * Always sets up in order:
542
     *  - Custom state helpers
543
     *  - Unnest
544
     *  - Reset php state
545
     *
546
     * User code should call parent::tearDown() after custom tear down code
547
     */
548
    protected function tearDown()
549
    {
550
        // Reset mocked datetime
551
        DBDatetime::clear_mock_now();
552
553
        // Stop the redirection that might have been requested in the test.
554
        // Note: Ideally a clean Controller should be created for each test.
555
        // Now all tests executed in a batch share the same controller.
556
        $controller = Controller::has_curr() ? Controller::curr() : null;
557
        if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) {
558
            $response->setStatusCode(200);
559
            $response->removeHeader('Location');
560
        }
561
562
        // Call state helpers
563
        static::$state->tearDown($this);
564
    }
565
566
    public static function assertContains(
567
        $needle,
568
        $haystack,
569
        $message = '',
570
        $ignoreCase = false,
571
        $checkForObjectIdentity = true,
572
        $checkForNonObjectIdentity = false
573
    ) {
574
        if ($haystack instanceof DBField) {
575
            $haystack = (string)$haystack;
576
        }
577
        parent::assertContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
578
    }
579
580
    public static function assertNotContains(
581
        $needle,
582
        $haystack,
583
        $message = '',
584
        $ignoreCase = false,
585
        $checkForObjectIdentity = true,
586
        $checkForNonObjectIdentity = false
587
    ) {
588
        if ($haystack instanceof DBField) {
589
            $haystack = (string)$haystack;
590
        }
591
        parent::assertNotContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
592
    }
593
594
    /**
595
     * Clear the log of emails sent
596
     *
597
     * @return bool True if emails cleared
598
     */
599
    public function clearEmails()
600
    {
601
        /** @var Mailer $mailer */
602
        $mailer = Injector::inst()->get(Mailer::class);
603
        if ($mailer instanceof TestMailer) {
604
            $mailer->clearEmails();
605
            return true;
606
        }
607
        return false;
608
    }
609
610
    /**
611
     * Search for an email that was sent.
612
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
613
     * @param string $to
614
     * @param string $from
615
     * @param string $subject
616
     * @param string $content
617
     * @return array|null Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles',
618
     *               'HtmlContent'
619
     */
620
    public static function findEmail($to, $from = null, $subject = null, $content = null)
621
    {
622
        /** @var Mailer $mailer */
623
        $mailer = Injector::inst()->get(Mailer::class);
624
        if ($mailer instanceof TestMailer) {
625
            return $mailer->findEmail($to, $from, $subject, $content);
626
        }
627
        return null;
628
    }
629
630
    /**
631
     * Assert that the matching email was sent since the last call to clearEmails()
632
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
633
     *
634
     * @param string $to
635
     * @param string $from
636
     * @param string $subject
637
     * @param string $content
638
     */
639
    public static function assertEmailSent($to, $from = null, $subject = null, $content = null)
640
    {
641
        $found = (bool)static::findEmail($to, $from, $subject, $content);
642
643
        $infoParts = '';
644
        $withParts = array();
645
        if ($to) {
646
            $infoParts .= " to '$to'";
647
        }
648
        if ($from) {
649
            $infoParts .= " from '$from'";
650
        }
651
        if ($subject) {
652
            $withParts[] = "subject '$subject'";
653
        }
654
        if ($content) {
655
            $withParts[] = "content '$content'";
656
        }
657
        if ($withParts) {
658
            $infoParts .= ' with ' . implode(' and ', $withParts);
659
        }
660
661
        static::assertTrue(
662
            $found,
663
            "Failed asserting that an email was sent$infoParts."
664
        );
665
    }
666
667
668
    /**
669
     * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
670
     * pairs.  Each match must correspond to 1 distinct record.
671
     *
672
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
673
     * either pass a single pattern or an array of patterns.
674
     * @param SS_List $list The {@link SS_List} to test.
675
     * @param string $message
676
     *
677
     * Examples
678
     * --------
679
     * Check that $members includes an entry with Email = [email protected]:
680
     *      $this->assertListContains(['Email' => '[email protected]'], $members);
681
     *
682
     * Check that $members includes entries with Email = [email protected] and with
683
     * Email = [email protected]:
684
     *      $this->assertListContains([
685
     *         ['Email' => '[email protected]'],
686
     *         ['Email' => '[email protected]'],
687
     *      ], $members);
688
     */
689
    public static function assertListContains($matches, SS_List $list, $message = '')
690
    {
691
        if (!is_array($matches)) {
692
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
693
                1,
694
                'array'
695
            );
696
        }
697
698
        static::assertThat(
699
            $list,
700
            new SSListContains(
701
                $matches
702
            ),
703
            $message
704
        );
705
    }
706
707
    /**
708
     * @deprecated 4.0.0:5.0.0 Use assertListContains() instead
709
     *
710
     * @param $matches
711
     * @param $dataObjectSet
712
     */
713
    public function assertDOSContains($matches, $dataObjectSet)
714
    {
715
        Deprecation::notice('5.0', 'Use assertListContains() instead');
716
        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...
717
    }
718
719
    /**
720
     * Asserts that no items in a given list appear in the given dataobject list
721
     *
722
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
723
     * either pass a single pattern or an array of patterns.
724
     * @param SS_List $list The {@link SS_List} to test.
725
     * @param string $message
726
     *
727
     * Examples
728
     * --------
729
     * Check that $members doesn't have an entry with Email = [email protected]:
730
     *      $this->assertListNotContains(['Email' => '[email protected]'], $members);
731
     *
732
     * Check that $members doesn't have entries with Email = [email protected] and with
733
     * Email = [email protected]:
734
     *      $this->assertListNotContains([
735
     *          ['Email' => '[email protected]'],
736
     *          ['Email' => '[email protected]'],
737
     *      ], $members);
738
     */
739
    public static function assertListNotContains($matches, SS_List $list, $message = '')
740
    {
741
        if (!is_array($matches)) {
742
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
743
                1,
744
                'array'
745
            );
746
        }
747
748
        $constraint =  new PHPUnit_Framework_Constraint_Not(
749
            new SSListContains(
750
                $matches
751
            )
752
        );
753
754
        static::assertThat(
755
            $list,
756
            $constraint,
757
            $message
758
        );
759
    }
760
761
    /**
762
     * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead
763
     *
764
     * @param $matches
765
     * @param $dataObjectSet
766
     */
767
    public static function assertNotDOSContains($matches, $dataObjectSet)
768
    {
769
        Deprecation::notice('5.0', 'Use assertListNotContains() instead');
770
        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...
771
    }
772
773
    /**
774
     * Assert that the given {@link SS_List} includes only DataObjects matching the given
775
     * key-value pairs.  Each match must correspond to 1 distinct record.
776
     *
777
     * Example
778
     * --------
779
     * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
780
     * matter:
781
     *     $this->assertListEquals([
782
     *        ['FirstName' =>'Sam', 'Surname' => 'Minnee'],
783
     *        ['FirstName' => 'Ingo', 'Surname' => 'Schommer'],
784
     *      ], $members);
785
     *
786
     * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
787
     * either pass a single pattern or an array of patterns.
788
     * @param mixed $list The {@link SS_List} to test.
789
     * @param string $message
790
     */
791
    public static function assertListEquals($matches, SS_List $list, $message = '')
792
    {
793
        if (!is_array($matches)) {
794
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
795
                1,
796
                'array'
797
            );
798
        }
799
800
        static::assertThat(
801
            $list,
802
            new SSListContainsOnly(
803
                $matches
804
            ),
805
            $message
806
        );
807
    }
808
809
    /**
810
     * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead
811
     *
812
     * @param $matches
813
     * @param SS_List $dataObjectSet
814
     */
815
    public function assertDOSEquals($matches, $dataObjectSet)
816
    {
817
        Deprecation::notice('5.0', 'Use assertListEquals() instead');
818
        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...
819
    }
820
821
822
    /**
823
     * Assert that the every record in the given {@link SS_List} matches the given key-value
824
     * pairs.
825
     *
826
     * Example
827
     * --------
828
     * Check that every entry in $members has a Status of 'Active':
829
     *     $this->assertListAllMatch(['Status' => 'Active'], $members);
830
     *
831
     * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
832
     * @param mixed $list The {@link SS_List} to test.
833
     * @param string $message
834
     */
835
    public static function assertListAllMatch($match, SS_List $list, $message = '')
836
    {
837
        if (!is_array($match)) {
838
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
839
                1,
840
                'array'
841
            );
842
        }
843
844
        static::assertThat(
845
            $list,
846
            new SSListContainsOnlyMatchingItems(
847
                $match
848
            ),
849
            $message
850
        );
851
    }
852
853
    /**
854
     * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead
855
     *
856
     * @param $match
857
     * @param SS_List $dataObjectSet
858
     */
859
    public function assertDOSAllMatch($match, SS_List $dataObjectSet)
860
    {
861
        Deprecation::notice('5.0', 'Use assertListAllMatch() instead');
862
        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...
863
    }
864
865
    /**
866
     * Removes sequences of repeated whitespace characters from SQL queries
867
     * making them suitable for string comparison
868
     *
869
     * @param string $sql
870
     * @return string The cleaned and normalised SQL string
871
     */
872
    protected static function normaliseSQL($sql)
873
    {
874
        return trim(preg_replace('/\s+/m', ' ', $sql));
875
    }
876
877
    /**
878
     * Asserts that two SQL queries are equivalent
879
     *
880
     * @param string $expectedSQL
881
     * @param string $actualSQL
882
     * @param string $message
883
     * @param float|int $delta
884
     * @param integer $maxDepth
885
     * @param boolean $canonicalize
886
     * @param boolean $ignoreCase
887
     */
888
    public static function assertSQLEquals(
889
        $expectedSQL,
890
        $actualSQL,
891
        $message = '',
892
        $delta = 0,
893
        $maxDepth = 10,
894
        $canonicalize = false,
895
        $ignoreCase = false
896
    ) {
897
        // Normalise SQL queries to remove patterns of repeating whitespace
898
        $expectedSQL = static::normaliseSQL($expectedSQL);
899
        $actualSQL = static::normaliseSQL($actualSQL);
900
901
        static::assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
902
    }
903
904
    /**
905
     * Asserts that a SQL query contains a SQL fragment
906
     *
907
     * @param string $needleSQL
908
     * @param string $haystackSQL
909
     * @param string $message
910
     * @param boolean $ignoreCase
911
     * @param boolean $checkForObjectIdentity
912
     */
913
    public static function assertSQLContains(
914
        $needleSQL,
915
        $haystackSQL,
916
        $message = '',
917
        $ignoreCase = false,
918
        $checkForObjectIdentity = true
919
    ) {
920
        $needleSQL = static::normaliseSQL($needleSQL);
921
        $haystackSQL = static::normaliseSQL($haystackSQL);
922
923
        static::assertContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
924
    }
925
926
    /**
927
     * Asserts that a SQL query contains a SQL fragment
928
     *
929
     * @param string $needleSQL
930
     * @param string $haystackSQL
931
     * @param string $message
932
     * @param boolean $ignoreCase
933
     * @param boolean $checkForObjectIdentity
934
     */
935
    public static function assertSQLNotContains(
936
        $needleSQL,
937
        $haystackSQL,
938
        $message = '',
939
        $ignoreCase = false,
940
        $checkForObjectIdentity = true
941
    ) {
942
        $needleSQL = static::normaliseSQL($needleSQL);
943
        $haystackSQL = static::normaliseSQL($haystackSQL);
944
945
        static::assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
946
    }
947
948
    /**
949
     * Start test environment
950
     */
951
    public static function start()
952
    {
953
        if (static::is_running_test()) {
954
            return;
955
        }
956
957
        // Health check
958
        if (InjectorLoader::inst()->countManifests()) {
959
            throw new LogicException('SapphireTest::start() cannot be called within another application');
960
        }
961
        static::set_is_running_test(true);
962
963
        // Mock request
964
        $_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
965
        $request = CLIRequestBuilder::createFromEnvironment();
966
967
        // Test application
968
        $kernel = new TestKernel(BASE_PATH);
969
        $app = new HTTPApplication($kernel);
970
971
        // Custom application
972
        $app->execute($request, function (HTTPRequest $request) {
973
            // Start session and execute
974
            $request->getSession()->init($request);
975
976
            // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
977
            // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
978
            DataObject::reset();
979
980
            // Set dummy controller;
981
            $controller = Controller::create();
982
            $controller->setRequest($request);
983
            $controller->pushCurrent();
984
            $controller->doInit();
985
        }, true);
986
987
        // Register state
988
        static::$state = SapphireTestState::singleton();
989
        // Register temp DB holder
990
        static::tempDB();
991
    }
992
993
    /**
994
     * Reset the testing database's schema, but only if it is active
995
     * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
996
     * @param bool $forceCreate Force DB to be created if it doesn't exist
997
     */
998
    public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
999
    {
1000
        // Check if DB is active before reset
1001
        if (!static::$tempDB->isUsed()) {
1002
            if (!$forceCreate) {
1003
                return;
1004
            }
1005
            static::$tempDB->build();
1006
        }
1007
        $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
1008
        static::$tempDB->resetDBSchema((array)$extraDataObjects);
1009
    }
1010
1011
    /**
1012
     * A wrapper for automatically performing callbacks as a user with a specific permission
1013
     *
1014
     * @param string|array $permCode
1015
     * @param callable $callback
1016
     * @return mixed
1017
     */
1018
    public function actWithPermission($permCode, $callback)
1019
    {
1020
        return Member::actAs($this->createMemberWithPermission($permCode), $callback);
1021
    }
1022
1023
    /**
1024
     * Create Member and Group objects on demand with specific permission code
1025
     *
1026
     * @param string|array $permCode
1027
     * @return Member
1028
     */
1029
    protected function createMemberWithPermission($permCode)
1030
    {
1031
        if (is_array($permCode)) {
1032
            $permArray = $permCode;
1033
            $permCode = implode('.', $permCode);
1034
        } else {
1035
            $permArray = array($permCode);
1036
        }
1037
1038
        // Check cached member
1039
        if (isset($this->cache_generatedMembers[$permCode])) {
1040
            $member = $this->cache_generatedMembers[$permCode];
1041
        } else {
1042
            // Generate group with these permissions
1043
            $group = Group::create();
1044
            $group->Title = "$permCode group";
1045
            $group->write();
1046
1047
            // Create each individual permission
1048
            foreach ($permArray as $permArrayItem) {
1049
                $permission = Permission::create();
1050
                $permission->Code = $permArrayItem;
1051
                $permission->write();
1052
                $group->Permissions()->add($permission);
1053
            }
1054
1055
            $member = Member::get()->filter([
1056
                'Email' => "[email protected]",
1057
            ])->first();
1058
            if (!$member) {
0 ignored issues
show
introduced by
$member is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
1059
                $member = Member::create();
1060
            }
1061
1062
            $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...
1063
            $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...
1064
            $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...
1065
            $member->write();
1066
            $group->Members()->add($member);
1067
1068
            $this->cache_generatedMembers[$permCode] = $member;
1069
        }
1070
        return $member;
1071
    }
1072
1073
    /**
1074
     * Create a member and group with the given permission code, and log in with it.
1075
     * Returns the member ID.
1076
     *
1077
     * @param string|array $permCode Either a permission, or list of permissions
1078
     * @return int Member ID
1079
     */
1080
    public function logInWithPermission($permCode = 'ADMIN')
1081
    {
1082
        $member = $this->createMemberWithPermission($permCode);
1083
        $this->logInAs($member);
1084
        return $member->ID;
1085
    }
1086
1087
    /**
1088
     * Log in as the given member
1089
     *
1090
     * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1091
     */
1092
    public function logInAs($member)
1093
    {
1094
        if (is_numeric($member)) {
1095
            $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 integer|boolean, 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

1095
            $member = DataObject::get_by_id(Member::class, /** @scrutinizer ignore-type */ $member);
Loading history...
1096
        } elseif (!is_object($member)) {
1097
            $member = $this->objFromFixture(Member::class, $member);
1098
        }
1099
        Injector::inst()->get(IdentityStore::class)->logIn($member);
1100
    }
1101
1102
    /**
1103
     * Log out the current user
1104
     */
1105
    public function logOut()
1106
    {
1107
        /** @var IdentityStore $store */
1108
        $store = Injector::inst()->get(IdentityStore::class);
1109
        $store->logOut();
1110
    }
1111
1112
    /**
1113
     * Cache for logInWithPermission()
1114
     */
1115
    protected $cache_generatedMembers = array();
1116
1117
    /**
1118
     * Test against a theme.
1119
     *
1120
     * @param string $themeBaseDir themes directory
1121
     * @param string $theme Theme name
1122
     * @param callable $callback
1123
     * @throws Exception
1124
     */
1125
    protected function useTestTheme($themeBaseDir, $theme, $callback)
1126
    {
1127
        Config::nest();
1128
        if (strpos($themeBaseDir, BASE_PATH) === 0) {
1129
            $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1130
        }
1131
        SSViewer::config()->update('theme_enabled', true);
1132
        SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']);
1133
1134
        try {
1135
            $callback();
1136
        } finally {
1137
            Config::unnest();
1138
        }
1139
    }
1140
1141
    /**
1142
     * Get fixture paths for this test
1143
     *
1144
     * @return array List of paths
1145
     */
1146
    protected function getFixturePaths()
1147
    {
1148
        $fixtureFile = static::get_fixture_file();
1149
        if (empty($fixtureFile)) {
1150
            return [];
1151
        }
1152
1153
        $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile];
0 ignored issues
show
introduced by
The condition is_array($fixtureFile) is always false.
Loading history...
1154
1155
        return array_map(function ($fixtureFilePath) {
1156
            return $this->resolveFixturePath($fixtureFilePath);
1157
        }, $fixtureFiles);
1158
    }
1159
1160
    /**
1161
     * Return all extra objects to scaffold for this test
1162
     * @return array
1163
     */
1164
    public static function getExtraDataObjects()
1165
    {
1166
        return static::$extra_dataobjects;
1167
    }
1168
1169
    /**
1170
     * Get additional controller classes to register routes for
1171
     *
1172
     * @return array
1173
     */
1174
    public static function getExtraControllers()
1175
    {
1176
        return static::$extra_controllers;
1177
    }
1178
1179
    /**
1180
     * Map a fixture path to a physical file
1181
     *
1182
     * @param string $fixtureFilePath
1183
     * @return string
1184
     */
1185
    protected function resolveFixturePath($fixtureFilePath)
1186
    {
1187
        // support loading via composer name path.
1188
        if (strpos($fixtureFilePath, ':') !== false) {
1189
            return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath);
1190
        }
1191
        
1192
        // Support fixture paths relative to the test class, rather than relative to webroot
1193
        // String checking is faster than file_exists() calls.
1194
        $isRelativeToFile
1195
            = (strpos('/', $fixtureFilePath) === false)
1196
            || preg_match('/^(\.){1,2}/', $fixtureFilePath);
1197
1198
        if ($isRelativeToFile) {
1199
            $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1200
            if ($resolvedPath) {
1201
                return $resolvedPath;
1202
            }
1203
        }
1204
1205
        // Check if file exists relative to base dir
1206
        $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1207
        if ($resolvedPath) {
1208
            return $resolvedPath;
1209
        }
1210
1211
        return $fixtureFilePath;
1212
    }
1213
1214
    protected function setUpRoutes()
1215
    {
1216
        // Get overridden routes
1217
        $rules = $this->getExtraRoutes();
1218
1219
        // Add all other routes
1220
        foreach (Director::config()->uninherited('rules') as $route => $rule) {
1221
            if (!isset($rules[$route])) {
1222
                $rules[$route] = $rule;
1223
            }
1224
        }
1225
1226
        // Add default catch-all rule
1227
        $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1228
1229
        // Add controller-name auto-routing
1230
        Director::config()->set('rules', $rules);
1231
    }
1232
1233
    /**
1234
     * Get extra routes to merge into Director.rules
1235
     *
1236
     * @return array
1237
     */
1238
    protected function getExtraRoutes()
1239
    {
1240
        $rules = [];
1241
        foreach ($this->getExtraControllers() as $class) {
1242
            $controllerInst = Controller::singleton($class);
1243
            $link = Director::makeRelative($controllerInst->Link());
1244
            $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1245
            $rules[$route] = $class;
1246
        }
1247
        return $rules;
1248
    }
1249
}
1250