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

SapphireTest::expectWarning()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use Exception;
6
use LogicException;
7
use PHPUnit_Framework_Constraint_Not;
0 ignored issues
show
Bug introduced by
The type PHPUnit_Framework_Constraint_Not 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...
8
use PHPUnit_Framework_TestCase;
0 ignored issues
show
Bug introduced by
The type PHPUnit_Framework_TestCase 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...
9
use PHPUnit_Util_InvalidArgumentHelper;
0 ignored issues
show
Bug introduced by
The type PHPUnit_Util_InvalidArgumentHelper 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...
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\Core\Manifest\ModuleResourceLoader;
24
use SilverStripe\Dev\Constraint\SSListContains;
25
use SilverStripe\Dev\Constraint\SSListContainsOnly;
26
use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems;
27
use SilverStripe\Dev\State\FixtureTestState;
28
use SilverStripe\Dev\State\SapphireTestState;
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
42
if (!class_exists(PHPUnit_Framework_TestCase::class)) {
43
    return;
44
}
45
46
/**
47
 * This is for phpunit 5.7 / php <=7.2
48
 * TODO: deprecated?
49
 * TODO: upgrade guide
50
 *
51
 * Test case class for the Sapphire framework.
52
 * Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
53
 * to work with.
54
 *
55
 * This class should not be used anywhere outside of unit tests, as phpunit may not be installed
56
 * in production sites.
57
 */
58
class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
59
{
60
    // Implementation of expect exception functions in phpunit 9
61
62
    public function expectError(): void
63
    {
64
        $this->expectException(PHPUnit_Framework_Error::class);
0 ignored issues
show
Bug introduced by
The type SilverStripe\Dev\PHPUnit_Framework_Error 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...
65
    }
66
67
    public function expectErrorMessage(string $message): void
68
    {
69
        $this->expectExceptionMessage($message);
70
    }
71
72
    public function expectErrorMessageMatches(string $regularExpression): void
73
    {
74
        $this->expectExceptionMessageRegExp($regularExpression);
0 ignored issues
show
Bug introduced by
The method expectExceptionMessageRegExp() does not exist on SilverStripe\Dev\SapphireTest. Did you maybe mean expectExceptionMessage()? ( Ignorable by Annotation )

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

74
        $this->/** @scrutinizer ignore-call */ 
75
               expectExceptionMessageRegExp($regularExpression);

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

532
        /** @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...
533
        return $this;
534
    }
535
536
    /**
537
     * Get the ID of an object from the fixture.
538
     *
539
     * @param string $className The data class or table name, as specified in your fixture file.  Parent classes won't work
540
     * @param string $identifier The identifier string, as provided in your fixture file
541
     * @return int
542
     */
543
    protected function idFromFixture($className, $identifier)
544
    {
545
        /** @var FixtureTestState $state */
546
        $state = static::$state->getStateByName('fixtures');
547
        $id = $state->getFixtureFactory(static::class)->getId($className, $identifier);
548
549
        if (!$id) {
550
            throw new \InvalidArgumentException(sprintf(
551
                "Couldn't find object '%s' (class: %s)",
552
                $identifier,
553
                $className
554
            ));
555
        }
556
557
        return $id;
558
    }
559
560
    /**
561
     * Return all of the IDs in the fixture of a particular class name.
562
     * Will collate all IDs form all fixtures if multiple fixtures are provided.
563
     *
564
     * @param string $className The data class or table name, as specified in your fixture file
565
     * @return array A map of fixture-identifier => object-id
566
     */
567
    protected function allFixtureIDs($className)
568
    {
569
        /** @var FixtureTestState $state */
570
        $state = static::$state->getStateByName('fixtures');
571
        return $state->getFixtureFactory(static::class)->getIds($className);
572
    }
573
574
    /**
575
     * Get an object from the fixture.
576
     *
577
     * @param string $className The data class or table name, as specified in your fixture file. Parent classes won't work
578
     * @param string $identifier The identifier string, as provided in your fixture file
579
     *
580
     * @return DataObject
581
     */
582
    protected function objFromFixture($className, $identifier)
583
    {
584
        /** @var FixtureTestState $state */
585
        $state = static::$state->getStateByName('fixtures');
586
        $obj = $state->getFixtureFactory(static::class)->get($className, $identifier);
587
588
        if (!$obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
589
            throw new \InvalidArgumentException(sprintf(
590
                "Couldn't find object '%s' (class: %s)",
591
                $identifier,
592
                $className
593
            ));
594
        }
595
596
        return $obj;
597
    }
598
599
    /**
600
     * Load a YAML fixture file into the database.
601
     * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
602
     * Doesn't clear existing fixtures.
603
     * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
604
     * @deprecated 4.0.0:5.0.0
605
     *
606
     */
607
    public function loadFixture($fixtureFile)
608
    {
609
        Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
610
        $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
611
        $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

611
        $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...
612
    }
613
614
    /**
615
     * Clear all fixtures which were previously loaded through
616
     * {@link loadFixture()}
617
     */
618
    public function clearFixtures()
619
    {
620
        /** @var FixtureTestState $state */
621
        $state = static::$state->getStateByName('fixtures');
622
        $state->getFixtureFactory(static::class)->clear();
623
    }
624
625
    /**
626
     * Useful for writing unit tests without hardcoding folder structures.
627
     *
628
     * @return string Absolute path to current class.
629
     */
630
    protected function getCurrentAbsolutePath()
631
    {
632
        $filename = ClassLoader::inst()->getItemPath(static::class);
633
        if (!$filename) {
634
            throw new LogicException('getItemPath returned null for ' . static::class
635
                . '. Try adding flush=1 to the test run.');
636
        }
637
        return dirname($filename);
638
    }
639
640
    /**
641
     * @return string File path relative to webroot
642
     */
643
    protected function getCurrentRelativePath()
644
    {
645
        $base = Director::baseFolder();
646
        $path = $this->getCurrentAbsolutePath();
647
        if (substr($path, 0, strlen($base)) == $base) {
648
            $path = preg_replace('/^\/*/', '', substr($path, strlen($base)));
649
        }
650
        return $path;
651
    }
652
653
    /**
654
     * Setup  the test.
655
     * Always sets up in order:
656
     *  - Custom state helpers
657
     *  - Unnest
658
     *  - Reset php state
659
     *
660
     * User code should call parent::tearDown() after custom tear down code
661
     */
662
    protected function tearDown(): void
663
    {
664
        // Reset mocked datetime
665
        if (class_exists(DBDatetime::class)) {
666
            DBDatetime::clear_mock_now();
667
        }
668
669
        // Stop the redirection that might have been requested in the test.
670
        // Note: Ideally a clean Controller should be created for each test.
671
        // Now all tests executed in a batch share the same controller.
672
        if (class_exists(Controller::class)) {
673
            $controller = Controller::has_curr() ? Controller::curr() : null;
674
            if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) {
675
                $response->setStatusCode(200);
676
                $response->removeHeader('Location');
677
            }
678
        }
679
680
        // Call state helpers
681
        static::$state->tearDown($this);
682
    }
683
684
    public static function assertContains(
685
        $needle,
686
        $haystack,
687
        $message = '',
688
        $ignoreCase = false,
689
        $checkForObjectIdentity = true,
690
        $checkForNonObjectIdentity = false
691
    ) {
692
        if ($haystack instanceof DBField) {
693
            $haystack = (string)$haystack;
694
        }
695
        parent::assertContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
0 ignored issues
show
Unused Code introduced by
The call to PHPUnit\Framework\Assert::assertContains() has too many arguments starting with $ignoreCase. ( Ignorable by Annotation )

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

695
        parent::/** @scrutinizer ignore-call */ 
696
                assertContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
696
    }
697
698
    public static function assertNotContains(
699
        $needle,
700
        $haystack,
701
        $message = '',
702
        $ignoreCase = false,
703
        $checkForObjectIdentity = true,
704
        $checkForNonObjectIdentity = false
705
    ) {
706
        if ($haystack instanceof DBField) {
707
            $haystack = (string)$haystack;
708
        }
709
        parent::assertNotContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
0 ignored issues
show
Unused Code introduced by
The call to PHPUnit\Framework\Assert::assertNotContains() has too many arguments starting with $ignoreCase. ( Ignorable by Annotation )

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

709
        parent::/** @scrutinizer ignore-call */ 
710
                assertNotContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
710
    }
711
712
    /**
713
     * Clear the log of emails sent
714
     *
715
     * @return bool True if emails cleared
716
     */
717
    public function clearEmails()
718
    {
719
        /** @var Mailer $mailer */
720
        $mailer = Injector::inst()->get(Mailer::class);
721
        if ($mailer instanceof TestMailer) {
722
            $mailer->clearEmails();
723
            return true;
724
        }
725
        return false;
726
    }
727
728
    /**
729
     * Search for an email that was sent.
730
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
731
     * @param string $to
732
     * @param string $from
733
     * @param string $subject
734
     * @param string $content
735
     * @return array|null Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles',
736
     *               'HtmlContent'
737
     */
738
    public static function findEmail($to, $from = null, $subject = null, $content = null)
739
    {
740
        /** @var Mailer $mailer */
741
        $mailer = Injector::inst()->get(Mailer::class);
742
        if ($mailer instanceof TestMailer) {
743
            return $mailer->findEmail($to, $from, $subject, $content);
744
        }
745
        return null;
746
    }
747
748
    /**
749
     * Assert that the matching email was sent since the last call to clearEmails()
750
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
751
     *
752
     * @param string $to
753
     * @param string $from
754
     * @param string $subject
755
     * @param string $content
756
     */
757
    public static function assertEmailSent($to, $from = null, $subject = null, $content = null)
758
    {
759
        $found = (bool)static::findEmail($to, $from, $subject, $content);
760
761
        $infoParts = '';
762
        $withParts = [];
763
        if ($to) {
764
            $infoParts .= " to '$to'";
765
        }
766
        if ($from) {
767
            $infoParts .= " from '$from'";
768
        }
769
        if ($subject) {
770
            $withParts[] = "subject '$subject'";
771
        }
772
        if ($content) {
773
            $withParts[] = "content '$content'";
774
        }
775
        if ($withParts) {
776
            $infoParts .= ' with ' . implode(' and ', $withParts);
777
        }
778
779
        static::assertTrue(
780
            $found,
781
            "Failed asserting that an email was sent$infoParts."
782
        );
783
    }
784
785
786
    /**
787
     * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
788
     * pairs.  Each match must correspond to 1 distinct record.
789
     *
790
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
791
     * either pass a single pattern or an array of patterns.
792
     * @param SS_List $list The {@link SS_List} to test.
793
     * @param string $message
794
     *
795
     * Examples
796
     * --------
797
     * Check that $members includes an entry with Email = [email protected]:
798
     *      $this->assertListContains(['Email' => '[email protected]'], $members);
799
     *
800
     * Check that $members includes entries with Email = [email protected] and with
801
     * Email = [email protected]:
802
     *      $this->assertListContains([
803
     *         ['Email' => '[email protected]'],
804
     *         ['Email' => '[email protected]'],
805
     *      ], $members);
806
     */
807
    public static function assertListContains($matches, SS_List $list, $message = '')
808
    {
809
        if (!is_array($matches)) {
810
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
811
                1,
812
                'array'
813
            );
814
        }
815
816
        static::assertThat(
817
            $list,
818
            new SSListContains(
819
                $matches
820
            ),
821
            $message
822
        );
823
    }
824
825
    /**
826
     * @param $matches
827
     * @param $dataObjectSet
828
     * @deprecated 4.0.0:5.0.0 Use assertListContains() instead
829
     *
830
     */
831
    public function assertDOSContains($matches, $dataObjectSet)
832
    {
833
        Deprecation::notice('5.0', 'Use assertListContains() instead');
834
        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...
835
    }
836
837
    /**
838
     * Asserts that no items in a given list appear in the given dataobject list
839
     *
840
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
841
     * either pass a single pattern or an array of patterns.
842
     * @param SS_List $list The {@link SS_List} to test.
843
     * @param string $message
844
     *
845
     * Examples
846
     * --------
847
     * Check that $members doesn't have an entry with Email = [email protected]:
848
     *      $this->assertListNotContains(['Email' => '[email protected]'], $members);
849
     *
850
     * Check that $members doesn't have entries with Email = [email protected] and with
851
     * Email = [email protected]:
852
     *      $this->assertListNotContains([
853
     *          ['Email' => '[email protected]'],
854
     *          ['Email' => '[email protected]'],
855
     *      ], $members);
856
     */
857
    public static function assertListNotContains($matches, SS_List $list, $message = '')
858
    {
859
        if (!is_array($matches)) {
860
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
861
                1,
862
                'array'
863
            );
864
        }
865
866
        $constraint = new PHPUnit_Framework_Constraint_Not(
867
            new SSListContains(
868
                $matches
869
            )
870
        );
871
872
        static::assertThat(
873
            $list,
874
            $constraint,
875
            $message
876
        );
877
    }
878
879
    /**
880
     * @param $matches
881
     * @param $dataObjectSet
882
     * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead
883
     *
884
     */
885
    public static function assertNotDOSContains($matches, $dataObjectSet)
886
    {
887
        Deprecation::notice('5.0', 'Use assertListNotContains() instead');
888
        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...
889
    }
890
891
    /**
892
     * Assert that the given {@link SS_List} includes only DataObjects matching the given
893
     * key-value pairs.  Each match must correspond to 1 distinct record.
894
     *
895
     * Example
896
     * --------
897
     * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
898
     * matter:
899
     *     $this->assertListEquals([
900
     *        ['FirstName' =>'Sam', 'Surname' => 'Minnee'],
901
     *        ['FirstName' => 'Ingo', 'Surname' => 'Schommer'],
902
     *      ], $members);
903
     *
904
     * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
905
     * either pass a single pattern or an array of patterns.
906
     * @param mixed $list The {@link SS_List} to test.
907
     * @param string $message
908
     */
909
    public static function assertListEquals($matches, SS_List $list, $message = '')
910
    {
911
        if (!is_array($matches)) {
912
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
913
                1,
914
                'array'
915
            );
916
        }
917
918
        static::assertThat(
919
            $list,
920
            new SSListContainsOnly(
921
                $matches
922
            ),
923
            $message
924
        );
925
    }
926
927
    /**
928
     * @param $matches
929
     * @param SS_List $dataObjectSet
930
     * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead
931
     *
932
     */
933
    public function assertDOSEquals($matches, $dataObjectSet)
934
    {
935
        Deprecation::notice('5.0', 'Use assertListEquals() instead');
936
        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...
937
    }
938
939
940
    /**
941
     * Assert that the every record in the given {@link SS_List} matches the given key-value
942
     * pairs.
943
     *
944
     * Example
945
     * --------
946
     * Check that every entry in $members has a Status of 'Active':
947
     *     $this->assertListAllMatch(['Status' => 'Active'], $members);
948
     *
949
     * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
950
     * @param mixed $list The {@link SS_List} to test.
951
     * @param string $message
952
     */
953
    public static function assertListAllMatch($match, SS_List $list, $message = '')
954
    {
955
        if (!is_array($match)) {
956
            throw PHPUnit_Util_InvalidArgumentHelper::factory(
957
                1,
958
                'array'
959
            );
960
        }
961
962
        static::assertThat(
963
            $list,
964
            new SSListContainsOnlyMatchingItems(
965
                $match
966
            ),
967
            $message
968
        );
969
    }
970
971
    /**
972
     * @param $match
973
     * @param SS_List $dataObjectSet
974
     * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead
975
     *
976
     */
977
    public function assertDOSAllMatch($match, SS_List $dataObjectSet)
978
    {
979
        Deprecation::notice('5.0', 'Use assertListAllMatch() instead');
980
        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...
981
    }
982
983
    /**
984
     * Removes sequences of repeated whitespace characters from SQL queries
985
     * making them suitable for string comparison
986
     *
987
     * @param string $sql
988
     * @return string The cleaned and normalised SQL string
989
     */
990
    protected static function normaliseSQL($sql)
991
    {
992
        return trim(preg_replace('/\s+/m', ' ', $sql));
993
    }
994
995
    /**
996
     * Asserts that two SQL queries are equivalent
997
     *
998
     * @param string $expectedSQL
999
     * @param string $actualSQL
1000
     * @param string $message
1001
     * @param float|int $delta
1002
     * @param integer $maxDepth
1003
     * @param boolean $canonicalize
1004
     * @param boolean $ignoreCase
1005
     */
1006
    public static function assertSQLEquals(
1007
        $expectedSQL,
1008
        $actualSQL,
1009
        $message = '',
1010
        $delta = 0,
1011
        $maxDepth = 10,
1012
        $canonicalize = false,
1013
        $ignoreCase = false
1014
    ) {
1015
        // Normalise SQL queries to remove patterns of repeating whitespace
1016
        $expectedSQL = static::normaliseSQL($expectedSQL);
1017
        $actualSQL = static::normaliseSQL($actualSQL);
1018
1019
        static::assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
0 ignored issues
show
Unused Code introduced by
The call to PHPUnit\Framework\Assert::assertEquals() has too many arguments starting with $delta. ( Ignorable by Annotation )

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

1019
        static::/** @scrutinizer ignore-call */ 
1020
                assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1020
    }
1021
1022
    /**
1023
     * Asserts that a SQL query contains a SQL fragment
1024
     *
1025
     * @param string $needleSQL
1026
     * @param string $haystackSQL
1027
     * @param string $message
1028
     * @param boolean $ignoreCase
1029
     * @param boolean $checkForObjectIdentity
1030
     */
1031
    public static function assertSQLContains(
1032
        $needleSQL,
1033
        $haystackSQL,
1034
        $message = '',
1035
        $ignoreCase = false,
1036
        $checkForObjectIdentity = true
1037
    ) {
1038
        $needleSQL = static::normaliseSQL($needleSQL);
1039
        $haystackSQL = static::normaliseSQL($haystackSQL);
1040
1041
        static::assertContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1042
    }
1043
1044
    /**
1045
     * Asserts that a SQL query contains a SQL fragment
1046
     *
1047
     * @param string $needleSQL
1048
     * @param string $haystackSQL
1049
     * @param string $message
1050
     * @param boolean $ignoreCase
1051
     * @param boolean $checkForObjectIdentity
1052
     */
1053
    public static function assertSQLNotContains(
1054
        $needleSQL,
1055
        $haystackSQL,
1056
        $message = '',
1057
        $ignoreCase = false,
1058
        $checkForObjectIdentity = true
1059
    ) {
1060
        $needleSQL = static::normaliseSQL($needleSQL);
1061
        $haystackSQL = static::normaliseSQL($haystackSQL);
1062
1063
        static::assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1064
    }
1065
1066
    /**
1067
     * Start test environment
1068
     */
1069
    public static function start()
1070
    {
1071
        if (static::is_running_test()) {
1072
            return;
1073
        }
1074
1075
        // Health check
1076
        if (InjectorLoader::inst()->countManifests()) {
1077
            throw new LogicException('SapphireTest::start() cannot be called within another application');
1078
        }
1079
        static::set_is_running_test(true);
1080
1081
        // Test application
1082
        $kernel = new TestKernel(BASE_PATH);
1083
1084
        if (class_exists(HTTPApplication::class)) {
1085
            // Mock request
1086
            $_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
1087
            $request = CLIRequestBuilder::createFromEnvironment();
1088
1089
            $app = new HTTPApplication($kernel);
1090
            $flush = array_key_exists('flush', $request->getVars());
1091
1092
            // Custom application
1093
            $res = $app->execute($request, function (HTTPRequest $request) {
1094
                // Start session and execute
1095
                $request->getSession()->init($request);
1096
1097
                // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
1098
                // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
1099
                DataObject::reset();
1100
1101
                // Set dummy controller;
1102
                $controller = Controller::create();
1103
                $controller->setRequest($request);
1104
                $controller->pushCurrent();
1105
                $controller->doInit();
1106
            }, $flush);
1107
1108
            if ($res && $res->isError()) {
1109
                throw new LogicException($res->getBody());
1110
            }
1111
        } else {
1112
            // Allow flush from the command line in the absence of HTTPApplication's special sauce
1113
            $flush = false;
1114
            foreach ($_SERVER['argv'] as $arg) {
1115
                if (preg_match('/^(--)?flush(=1)?$/', $arg)) {
1116
                    $flush = true;
1117
                }
1118
            }
1119
            $kernel->boot($flush);
1120
        }
1121
1122
        // Register state
1123
        static::$state = SapphireTestState::singleton();
1124
        // Register temp DB holder
1125
        static::tempDB();
1126
    }
1127
1128
    /**
1129
     * Reset the testing database's schema, but only if it is active
1130
     * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
1131
     * @param bool $forceCreate Force DB to be created if it doesn't exist
1132
     */
1133
    public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
1134
    {
1135
        if (!static::$tempDB) {
1136
            return;
1137
        }
1138
1139
        // Check if DB is active before reset
1140
        if (!static::$tempDB->isUsed()) {
1141
            if (!$forceCreate) {
1142
                return;
1143
            }
1144
            static::$tempDB->build();
1145
        }
1146
        $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
1147
        static::$tempDB->resetDBSchema((array)$extraDataObjects);
1148
    }
1149
1150
    /**
1151
     * A wrapper for automatically performing callbacks as a user with a specific permission
1152
     *
1153
     * @param string|array $permCode
1154
     * @param callable $callback
1155
     * @return mixed
1156
     */
1157
    public function actWithPermission($permCode, $callback)
1158
    {
1159
        return Member::actAs($this->createMemberWithPermission($permCode), $callback);
1160
    }
1161
1162
    /**
1163
     * Create Member and Group objects on demand with specific permission code
1164
     *
1165
     * @param string|array $permCode
1166
     * @return Member
1167
     */
1168
    protected function createMemberWithPermission($permCode)
1169
    {
1170
        if (is_array($permCode)) {
1171
            $permArray = $permCode;
1172
            $permCode = implode('.', $permCode);
1173
        } else {
1174
            $permArray = [$permCode];
1175
        }
1176
1177
        // Check cached member
1178
        if (isset($this->cache_generatedMembers[$permCode])) {
1179
            $member = $this->cache_generatedMembers[$permCode];
1180
        } else {
1181
            // Generate group with these permissions
1182
            $group = Group::create();
1183
            $group->Title = "$permCode group";
1184
            $group->write();
1185
1186
            // Create each individual permission
1187
            foreach ($permArray as $permArrayItem) {
1188
                $permission = Permission::create();
1189
                $permission->Code = $permArrayItem;
1190
                $permission->write();
1191
                $group->Permissions()->add($permission);
1192
            }
1193
1194
            $member = Member::get()->filter([
1195
                'Email' => "[email protected]",
1196
            ])->first();
1197
            if (!$member) {
1198
                $member = Member::create();
1199
            }
1200
1201
            $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...
1202
            $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...
1203
            $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...
1204
            $member->write();
1205
            $group->Members()->add($member);
1206
1207
            $this->cache_generatedMembers[$permCode] = $member;
1208
        }
1209
        return $member;
1210
    }
1211
1212
    /**
1213
     * Create a member and group with the given permission code, and log in with it.
1214
     * Returns the member ID.
1215
     *
1216
     * @param string|array $permCode Either a permission, or list of permissions
1217
     * @return int Member ID
1218
     */
1219
    public function logInWithPermission($permCode = 'ADMIN')
1220
    {
1221
        $member = $this->createMemberWithPermission($permCode);
1222
        $this->logInAs($member);
1223
        return $member->ID;
1224
    }
1225
1226
    /**
1227
     * Log in as the given member
1228
     *
1229
     * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1230
     */
1231
    public function logInAs($member)
1232
    {
1233
        if (is_numeric($member)) {
1234
            $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

1234
            $member = DataObject::get_by_id(Member::class, /** @scrutinizer ignore-type */ $member);
Loading history...
1235
        } elseif (!is_object($member)) {
1236
            $member = $this->objFromFixture(Member::class, $member);
1237
        }
1238
        Injector::inst()->get(IdentityStore::class)->logIn($member);
1239
    }
1240
1241
    /**
1242
     * Log out the current user
1243
     */
1244
    public function logOut()
1245
    {
1246
        /** @var IdentityStore $store */
1247
        $store = Injector::inst()->get(IdentityStore::class);
1248
        $store->logOut();
1249
    }
1250
1251
    /**
1252
     * Cache for logInWithPermission()
1253
     */
1254
    protected $cache_generatedMembers = [];
1255
1256
    /**
1257
     * Test against a theme.
1258
     *
1259
     * @param string $themeBaseDir themes directory
1260
     * @param string $theme Theme name
1261
     * @param callable $callback
1262
     * @throws Exception
1263
     */
1264
    protected function useTestTheme($themeBaseDir, $theme, $callback)
1265
    {
1266
        Config::nest();
1267
        if (strpos($themeBaseDir, BASE_PATH) === 0) {
1268
            $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1269
        }
1270
        SSViewer::config()->update('theme_enabled', true);
1271
        SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']);
1272
1273
        try {
1274
            $callback();
1275
        } finally {
1276
            Config::unnest();
1277
        }
1278
    }
1279
1280
    /**
1281
     * Get fixture paths for this test
1282
     *
1283
     * @return array List of paths
1284
     */
1285
    protected function getFixturePaths()
1286
    {
1287
        $fixtureFile = static::get_fixture_file();
1288
        if (empty($fixtureFile)) {
1289
            return [];
1290
        }
1291
1292
        $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile];
0 ignored issues
show
introduced by
The condition is_array($fixtureFile) is always false.
Loading history...
1293
1294
        return array_map(function ($fixtureFilePath) {
1295
            return $this->resolveFixturePath($fixtureFilePath);
1296
        }, $fixtureFiles);
1297
    }
1298
1299
    /**
1300
     * Return all extra objects to scaffold for this test
1301
     * @return array
1302
     */
1303
    public static function getExtraDataObjects()
1304
    {
1305
        return static::$extra_dataobjects;
1306
    }
1307
1308
    /**
1309
     * Get additional controller classes to register routes for
1310
     *
1311
     * @return array
1312
     */
1313
    public static function getExtraControllers()
1314
    {
1315
        return static::$extra_controllers;
1316
    }
1317
1318
    /**
1319
     * Map a fixture path to a physical file
1320
     *
1321
     * @param string $fixtureFilePath
1322
     * @return string
1323
     */
1324
    protected function resolveFixturePath($fixtureFilePath)
1325
    {
1326
        // support loading via composer name path.
1327
        if (strpos($fixtureFilePath, ':') !== false) {
1328
            return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath);
1329
        }
1330
1331
        // Support fixture paths relative to the test class, rather than relative to webroot
1332
        // String checking is faster than file_exists() calls.
1333
        $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1334
        if ($resolvedPath) {
1335
            return $resolvedPath;
1336
        }
1337
1338
        // Check if file exists relative to base dir
1339
        $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1340
        if ($resolvedPath) {
1341
            return $resolvedPath;
1342
        }
1343
1344
        return $fixtureFilePath;
1345
    }
1346
1347
    protected function setUpRoutes()
1348
    {
1349
        if (!class_exists(Director::class)) {
1350
            return;
1351
        }
1352
1353
        // Get overridden routes
1354
        $rules = $this->getExtraRoutes();
1355
1356
        // Add all other routes
1357
        foreach (Director::config()->uninherited('rules') as $route => $rule) {
1358
            if (!isset($rules[$route])) {
1359
                $rules[$route] = $rule;
1360
            }
1361
        }
1362
1363
        // Add default catch-all rule
1364
        $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1365
1366
        // Add controller-name auto-routing
1367
        Director::config()->set('rules', $rules);
1368
    }
1369
1370
    /**
1371
     * Get extra routes to merge into Director.rules
1372
     *
1373
     * @return array
1374
     */
1375
    protected function getExtraRoutes()
1376
    {
1377
        $rules = [];
1378
        foreach ($this->getExtraControllers() as $class) {
1379
            $controllerInst = Controller::singleton($class);
1380
            $link = Director::makeRelative($controllerInst->Link());
1381
            $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1382
            $rules[$route] = $class;
1383
        }
1384
        return $rules;
1385
    }
1386
}
1387