1 | <?php |
||||||
2 | |||||||
3 | namespace SilverStripe\Dev; |
||||||
4 | |||||||
5 | use ArrayAccess; |
||||||
6 | use Exception; |
||||||
7 | use LogicException; |
||||||
8 | use PHPUnit\Framework\Constraint\LogicalNot; |
||||||
9 | use PHPUnit\Framework\ExpectationFailedException; |
||||||
10 | use PHPUnit\Framework\InvalidArgumentException; |
||||||
11 | use PHPUnit\Framework\TestCase; |
||||||
12 | use PHPUnit\Util\InvalidArgumentHelper; |
||||||
0 ignored issues
–
show
|
|||||||
13 | use SilverStripe\CMS\Controllers\RootURLController; |
||||||
0 ignored issues
–
show
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
14 | use SilverStripe\Control\CLIRequestBuilder; |
||||||
15 | use SilverStripe\Control\Controller; |
||||||
16 | use SilverStripe\Control\Cookie; |
||||||
17 | use SilverStripe\Control\Director; |
||||||
18 | use SilverStripe\Control\Email\Email; |
||||||
19 | use SilverStripe\Control\Email\Mailer; |
||||||
20 | use SilverStripe\Control\HTTPApplication; |
||||||
21 | use SilverStripe\Control\HTTPRequest; |
||||||
22 | use SilverStripe\Core\Config\Config; |
||||||
23 | use SilverStripe\Core\Injector\Injector; |
||||||
24 | use SilverStripe\Core\Injector\InjectorLoader; |
||||||
25 | use SilverStripe\Core\Manifest\ClassLoader; |
||||||
26 | use SilverStripe\Core\Manifest\ModuleResourceLoader; |
||||||
27 | use SilverStripe\Dev\Constraint\ArraySubset; |
||||||
28 | use SilverStripe\Dev\Constraint\SSListContains; |
||||||
29 | use SilverStripe\Dev\Constraint\SSListContainsOnly; |
||||||
30 | use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems; |
||||||
31 | use SilverStripe\Dev\State\FixtureTestState; |
||||||
32 | use SilverStripe\Dev\State\SapphireTestState; |
||||||
33 | use SilverStripe\i18n\i18n; |
||||||
34 | use SilverStripe\ORM\Connect\TempDatabase; |
||||||
35 | use SilverStripe\ORM\DataObject; |
||||||
36 | use SilverStripe\ORM\FieldType\DBDatetime; |
||||||
37 | use SilverStripe\ORM\FieldType\DBField; |
||||||
38 | use SilverStripe\ORM\SS_List; |
||||||
39 | use SilverStripe\Security\Group; |
||||||
40 | use SilverStripe\Security\IdentityStore; |
||||||
41 | use SilverStripe\Security\Member; |
||||||
42 | use SilverStripe\Security\Permission; |
||||||
43 | use SilverStripe\Security\Security; |
||||||
44 | use SilverStripe\View\SSViewer; |
||||||
45 | |||||||
46 | if (!class_exists(TestCase::class)) { |
||||||
47 | return; |
||||||
48 | } |
||||||
49 | |||||||
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 | abstract class SapphireTest extends TestCase implements TestOnly |
||||||
59 | { |
||||||
60 | /** |
||||||
61 | * Path to fixture data for this test run. |
||||||
62 | * If passed as an array, multiple fixture files will be loaded. |
||||||
63 | * Please note that you won't be able to refer with "=>" notation |
||||||
64 | * between the fixtures, they act independent of each other. |
||||||
65 | * |
||||||
66 | * @var string|array |
||||||
67 | */ |
||||||
68 | protected static $fixture_file = ''; |
||||||
69 | |||||||
70 | /** |
||||||
71 | * @deprecated 4.0..5.0 Use FixtureTestState instead |
||||||
72 | * @var FixtureFactory |
||||||
73 | */ |
||||||
74 | protected $fixtureFactory; |
||||||
75 | |||||||
76 | /** |
||||||
77 | * @var Boolean If set to TRUE, this will force a test database to be generated |
||||||
78 | * in {@link setUp()}. Note that this flag is overruled by the presence of a |
||||||
79 | * {@link $fixture_file}, which always forces a database build. |
||||||
80 | * |
||||||
81 | * @var bool |
||||||
82 | */ |
||||||
83 | protected $usesDatabase = null; |
||||||
84 | |||||||
85 | /** |
||||||
86 | * This test will cleanup its state via transactions. |
||||||
87 | * If set to false a full schema is forced between tests, but at a performance cost. |
||||||
88 | * |
||||||
89 | * @var bool |
||||||
90 | */ |
||||||
91 | protected $usesTransactions = true; |
||||||
92 | |||||||
93 | /** |
||||||
94 | * @var bool |
||||||
95 | */ |
||||||
96 | protected static $is_running_test = false; |
||||||
97 | |||||||
98 | /** |
||||||
99 | * By default, setUp() does not require default records. Pass |
||||||
100 | * class names in here, and the require/augment default records |
||||||
101 | * function will be called on them. |
||||||
102 | * |
||||||
103 | * @var array |
||||||
104 | */ |
||||||
105 | protected $requireDefaultRecordsFrom = array(); |
||||||
106 | |||||||
107 | /** |
||||||
108 | * A list of extensions that can't be applied during the execution of this run. If they are |
||||||
109 | * applied, they will be temporarily removed and a database migration called. |
||||||
110 | * |
||||||
111 | * The keys of the are the classes that the extensions can't be applied the extensions to, and |
||||||
112 | * the values are an array of illegal extensions on that class. |
||||||
113 | * |
||||||
114 | * Set a class to `*` to remove all extensions (unadvised) |
||||||
115 | * |
||||||
116 | * @var array |
||||||
117 | */ |
||||||
118 | protected static $illegal_extensions = []; |
||||||
119 | |||||||
120 | /** |
||||||
121 | * A list of extensions that must be applied during the execution of this run. If they are |
||||||
122 | * not applied, they will be temporarily added and a database migration called. |
||||||
123 | * |
||||||
124 | * The keys of the are the classes to apply the extensions to, and the values are an array |
||||||
125 | * of required extensions on that class. |
||||||
126 | * |
||||||
127 | * Example: |
||||||
128 | * <code> |
||||||
129 | * array("MyTreeDataObject" => array("Versioned", "Hierarchy")) |
||||||
130 | * </code> |
||||||
131 | * |
||||||
132 | * @var array |
||||||
133 | */ |
||||||
134 | protected static $required_extensions = []; |
||||||
135 | |||||||
136 | /** |
||||||
137 | * By default, the test database won't contain any DataObjects that have the interface TestOnly. |
||||||
138 | * This variable lets you define additional TestOnly DataObjects to set up for this test. |
||||||
139 | * Set it to an array of DataObject subclass names. |
||||||
140 | * |
||||||
141 | * @var array |
||||||
142 | */ |
||||||
143 | protected static $extra_dataobjects = []; |
||||||
144 | |||||||
145 | /** |
||||||
146 | * List of class names of {@see Controller} objects to register routes for |
||||||
147 | * Controllers must implement Link() method |
||||||
148 | * |
||||||
149 | * @var array |
||||||
150 | */ |
||||||
151 | protected static $extra_controllers = []; |
||||||
152 | |||||||
153 | /** |
||||||
154 | * We need to disabling backing up of globals to avoid overriding |
||||||
155 | * the few globals SilverStripe relies on, like $lang for the i18n subsystem. |
||||||
156 | * |
||||||
157 | * @see http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html |
||||||
158 | */ |
||||||
159 | protected $backupGlobals = false; |
||||||
160 | |||||||
161 | /** |
||||||
162 | * State management container for SapphireTest |
||||||
163 | * |
||||||
164 | * @var SapphireTestState |
||||||
165 | */ |
||||||
166 | protected static $state = null; |
||||||
167 | |||||||
168 | /** |
||||||
169 | * Temp database helper |
||||||
170 | * |
||||||
171 | * @var TempDatabase |
||||||
172 | */ |
||||||
173 | protected static $tempDB = null; |
||||||
174 | |||||||
175 | /** |
||||||
176 | * @return TempDatabase |
||||||
177 | */ |
||||||
178 | public static function tempDB() |
||||||
179 | { |
||||||
180 | if (!static::$tempDB) { |
||||||
181 | static::$tempDB = TempDatabase::create(); |
||||||
182 | } |
||||||
183 | return static::$tempDB; |
||||||
184 | } |
||||||
185 | |||||||
186 | /** |
||||||
187 | * Gets illegal extensions for this class |
||||||
188 | * |
||||||
189 | * @return array |
||||||
190 | */ |
||||||
191 | public static function getIllegalExtensions() : array |
||||||
192 | { |
||||||
193 | return static::$illegal_extensions; |
||||||
194 | } |
||||||
195 | |||||||
196 | /** |
||||||
197 | * Gets required extensions for this class |
||||||
198 | * |
||||||
199 | * @return array |
||||||
200 | */ |
||||||
201 | public static function getRequiredExtensions() : array |
||||||
202 | { |
||||||
203 | return static::$required_extensions; |
||||||
204 | } |
||||||
205 | |||||||
206 | /** |
||||||
207 | * Check if test bootstrapping has been performed. Must not be relied on |
||||||
208 | * outside of unit tests. |
||||||
209 | * |
||||||
210 | * @return bool |
||||||
211 | */ |
||||||
212 | protected static function is_running_test() : bool |
||||||
213 | { |
||||||
214 | return self::$is_running_test; |
||||||
215 | } |
||||||
216 | |||||||
217 | /** |
||||||
218 | * Set test running state |
||||||
219 | * |
||||||
220 | * @param bool $bool |
||||||
221 | */ |
||||||
222 | protected static function set_is_running_test(bool $bool) : void |
||||||
223 | { |
||||||
224 | self::$is_running_test = $bool; |
||||||
225 | } |
||||||
226 | |||||||
227 | /** |
||||||
228 | * @return string|string[] |
||||||
229 | */ |
||||||
230 | public static function get_fixture_file() |
||||||
231 | { |
||||||
232 | return static::$fixture_file; |
||||||
233 | } |
||||||
234 | |||||||
235 | /** |
||||||
236 | * @return bool |
||||||
237 | */ |
||||||
238 | public function getUsesDatabase() |
||||||
239 | { |
||||||
240 | return $this->usesDatabase; |
||||||
241 | } |
||||||
242 | |||||||
243 | /** |
||||||
244 | * @return bool |
||||||
245 | */ |
||||||
246 | public function getUsesTransactions() |
||||||
247 | { |
||||||
248 | return $this->usesTransactions; |
||||||
249 | } |
||||||
250 | |||||||
251 | /** |
||||||
252 | * @return array |
||||||
253 | */ |
||||||
254 | public function getRequireDefaultRecordsFrom() |
||||||
255 | { |
||||||
256 | return $this->requireDefaultRecordsFrom; |
||||||
257 | } |
||||||
258 | |||||||
259 | /** |
||||||
260 | * Setup the test. |
||||||
261 | * Always sets up in order: |
||||||
262 | * - Reset php state |
||||||
263 | * - Nest |
||||||
264 | * - Custom state helpers |
||||||
265 | * |
||||||
266 | * User code should call parent::setUp() before custom setup code |
||||||
267 | */ |
||||||
268 | protected function setUp() : void |
||||||
269 | { |
||||||
270 | if (!defined('FRAMEWORK_PATH')) { |
||||||
271 | trigger_error( |
||||||
272 | 'Missing constants, did you remember to include the test bootstrap in your phpunit.xml file?', |
||||||
273 | E_USER_WARNING |
||||||
274 | ); |
||||||
275 | } |
||||||
276 | |||||||
277 | // Call state helpers |
||||||
278 | static::$state->setUp($this); |
||||||
279 | |||||||
280 | // i18n needs to be set to the defaults or tests fail |
||||||
281 | i18n::set_locale(i18n::config()->uninherited('default_locale')); |
||||||
282 | |||||||
283 | // Set default timezone consistently to avoid NZ-specific dependencies |
||||||
284 | date_default_timezone_set('UTC'); |
||||||
285 | |||||||
286 | Member::set_password_validator(null); |
||||||
287 | Cookie::config()->update('report_errors', false); |
||||||
288 | if (class_exists(RootURLController::class)) { |
||||||
289 | RootURLController::reset(); |
||||||
290 | } |
||||||
291 | |||||||
292 | Security::clear_database_is_ready(); |
||||||
293 | |||||||
294 | // Set up test routes |
||||||
295 | $this->setUpRoutes(); |
||||||
296 | |||||||
297 | $fixtureFiles = $this->getFixturePaths(); |
||||||
298 | |||||||
299 | if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) { |
||||||
300 | // Assign fixture factory to deprecated prop in case old tests use it over the getter |
||||||
301 | /** @var FixtureTestState $fixtureState */ |
||||||
302 | $fixtureState = static::$state->getStateByName('fixtures'); |
||||||
303 | $this->fixtureFactory = $fixtureState->getFixtureFactory(static::class); |
||||||
0 ignored issues
–
show
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
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. ![]() It seems like
$fixtureState->getFixtureFactory(static::class) can also be of type false . However, the property $fixtureFactory is declared as type SilverStripe\Dev\FixtureFactory . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||||
304 | |||||||
305 | $this->logInWithPermission('ADMIN'); |
||||||
306 | } |
||||||
307 | |||||||
308 | // turn off template debugging |
||||||
309 | SSViewer::config()->update('source_file_comments', false); |
||||||
310 | |||||||
311 | // Set up the test mailer |
||||||
312 | Injector::inst()->registerService(new TestMailer(), Mailer::class); |
||||||
313 | Email::config()->remove('send_all_emails_to'); |
||||||
314 | Email::config()->remove('send_all_emails_from'); |
||||||
315 | Email::config()->remove('cc_all_emails_to'); |
||||||
316 | Email::config()->remove('bcc_all_emails_to'); |
||||||
317 | } |
||||||
318 | |||||||
319 | |||||||
320 | |||||||
321 | /** |
||||||
322 | * Helper method to determine if the current test should enable a test database |
||||||
323 | * |
||||||
324 | * @param array|string $fixtureFiles |
||||||
325 | * @return bool |
||||||
326 | */ |
||||||
327 | protected function shouldSetupDatabaseForCurrentTest($fixtureFiles) : bool |
||||||
328 | { |
||||||
329 | $databaseEnabledByDefault = $fixtureFiles || $this->usesDatabase; |
||||||
330 | |||||||
331 | return ($databaseEnabledByDefault && !$this->currentTestDisablesDatabase()) |
||||||
332 | || $this->currentTestEnablesDatabase(); |
||||||
333 | } |
||||||
334 | |||||||
335 | /** |
||||||
336 | * Helper method to check, if the current test uses the database. |
||||||
337 | * This can be switched on with the annotation "@useDatabase" |
||||||
338 | * |
||||||
339 | * @return bool |
||||||
340 | */ |
||||||
341 | protected function currentTestEnablesDatabase() : bool |
||||||
342 | { |
||||||
343 | $annotations = $this->getAnnotations(); |
||||||
344 | |||||||
345 | return array_key_exists('useDatabase', $annotations['method']) |
||||||
346 | && $annotations['method']['useDatabase'][0] !== 'false'; |
||||||
347 | } |
||||||
348 | |||||||
349 | /** |
||||||
350 | * Helper method to check, if the current test uses the database. |
||||||
351 | * This can be switched on with the annotation "@useDatabase false" |
||||||
352 | * |
||||||
353 | * @return bool |
||||||
354 | */ |
||||||
355 | protected function currentTestDisablesDatabase() : bool |
||||||
356 | { |
||||||
357 | $annotations = $this->getAnnotations(); |
||||||
358 | |||||||
359 | return array_key_exists('useDatabase', $annotations['method']) |
||||||
360 | && $annotations['method']['useDatabase'][0] === 'false'; |
||||||
361 | } |
||||||
362 | |||||||
363 | /** |
||||||
364 | * Called once per test case ({@link SapphireTest} subclass). |
||||||
365 | * This is different to {@link setUp()}, which gets called once |
||||||
366 | * per method. Useful to initialize expensive operations which |
||||||
367 | * don't change state for any called method inside the test, |
||||||
368 | * e.g. dynamically adding an extension. See {@link teardownAfterClass()} |
||||||
369 | * for tearing down the state again. |
||||||
370 | * |
||||||
371 | * Always sets up in order: |
||||||
372 | * - Reset php state |
||||||
373 | * - Nest |
||||||
374 | * - Custom state helpers |
||||||
375 | * |
||||||
376 | * User code should call parent::setUpBeforeClass() before custom setup code |
||||||
377 | * |
||||||
378 | * @throws Exception |
||||||
379 | */ |
||||||
380 | public static function setUpBeforeClass() : void |
||||||
381 | { |
||||||
382 | // Start tests |
||||||
383 | static::start(); |
||||||
384 | |||||||
385 | if (!static::$state) { |
||||||
386 | throw new Exception('SapphireTest failed to bootstrap!'); |
||||||
387 | } |
||||||
388 | |||||||
389 | // Call state helpers |
||||||
390 | static::$state->setUpOnce(static::class); |
||||||
391 | |||||||
392 | // Build DB if we have objects |
||||||
393 | if (static::getExtraDataObjects()) { |
||||||
394 | DataObject::reset(); |
||||||
395 | static::resetDBSchema(true, true); |
||||||
396 | } |
||||||
397 | } |
||||||
398 | |||||||
399 | /** |
||||||
400 | * tearDown method that's called once per test class rather once per test method. |
||||||
401 | * |
||||||
402 | * Always sets up in order: |
||||||
403 | * - Custom state helpers |
||||||
404 | * - Unnest |
||||||
405 | * - Reset php state |
||||||
406 | * |
||||||
407 | * User code should call parent::tearDownAfterClass() after custom tear down code |
||||||
408 | */ |
||||||
409 | public static function tearDownAfterClass() : void |
||||||
410 | { |
||||||
411 | // Call state helpers |
||||||
412 | static::$state->tearDownOnce(static::class); |
||||||
413 | |||||||
414 | // Reset DB schema |
||||||
415 | static::resetDBSchema(); |
||||||
416 | } |
||||||
417 | |||||||
418 | /** |
||||||
419 | * @deprecated 4.0.0:5.0.0 |
||||||
420 | * @return FixtureFactory|false |
||||||
421 | */ |
||||||
422 | public function getFixtureFactory() : FixtureFactory |
||||||
423 | { |
||||||
424 | Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead'); |
||||||
425 | /** @var FixtureTestState $state */ |
||||||
426 | $state = static::$state->getStateByName('fixtures'); |
||||||
427 | return $state->getFixtureFactory(static::class); |
||||||
428 | } |
||||||
429 | |||||||
430 | /** |
||||||
431 | * Sets a new fixture factory |
||||||
432 | * @deprecated 4.0.0:5.0.0 |
||||||
433 | * @param FixtureFactory $factory |
||||||
434 | * @return $this |
||||||
435 | */ |
||||||
436 | public function setFixtureFactory(FixtureFactory $factory) : SapphireTest |
||||||
437 | { |
||||||
438 | Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead'); |
||||||
439 | /** @var FixtureTestState $state */ |
||||||
440 | $state = static::$state->getStateByName('fixtures'); |
||||||
441 | $state->setFixtureFactory($factory, static::class); |
||||||
442 | $this->fixtureFactory = $factory; |
||||||
0 ignored issues
–
show
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
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. ![]() |
|||||||
443 | return $this; |
||||||
444 | } |
||||||
445 | |||||||
446 | /** |
||||||
447 | * Get the ID of an object from the fixture. |
||||||
448 | * |
||||||
449 | * @param string $className The data class or table name, as specified in your fixture file. |
||||||
450 | * Parent classes won't work |
||||||
451 | * @param string $identifier The identifier string, as provided in your fixture file |
||||||
452 | * @return int |
||||||
453 | */ |
||||||
454 | protected function idFromFixture(string $className, string $identifier) : int |
||||||
455 | { |
||||||
456 | /** @var FixtureTestState $state */ |
||||||
457 | $state = static::$state->getStateByName('fixtures'); |
||||||
458 | $id = $state->getFixtureFactory(static::class)->getId($className, $identifier); |
||||||
459 | |||||||
460 | if (!$id) { |
||||||
461 | user_error(sprintf( |
||||||
462 | "Couldn't find object '%s' (class: %s)", |
||||||
463 | $identifier, |
||||||
464 | $className |
||||||
465 | ), E_USER_ERROR); |
||||||
466 | } |
||||||
467 | |||||||
468 | return $id; |
||||||
469 | } |
||||||
470 | |||||||
471 | /** |
||||||
472 | * Return all of the IDs in the fixture of a particular class name. |
||||||
473 | * Will collate all IDs form all fixtures if multiple fixtures are provided. |
||||||
474 | * |
||||||
475 | * @param string $className The data class or table name, as specified in your fixture file |
||||||
476 | * @return array A map of fixture-identifier => object-id |
||||||
477 | */ |
||||||
478 | protected function allFixtureIDs(string $className) : array |
||||||
479 | { |
||||||
480 | /** @var FixtureTestState $state */ |
||||||
481 | $state = static::$state->getStateByName('fixtures'); |
||||||
482 | return $state->getFixtureFactory(static::class)->getIds($className); |
||||||
483 | } |
||||||
484 | |||||||
485 | /** |
||||||
486 | * Get an object from the fixture. |
||||||
487 | * |
||||||
488 | * @param string $className The data class or table name, as specified in your fixture file. |
||||||
489 | * Parent classes won't work |
||||||
490 | * @param string $identifier The identifier string, as provided in your fixture file |
||||||
491 | * |
||||||
492 | * @return DataObject |
||||||
493 | */ |
||||||
494 | protected function objFromFixture(string $className, string $identifier) : DataObject |
||||||
495 | { |
||||||
496 | /** @var FixtureTestState $state */ |
||||||
497 | $state = static::$state->getStateByName('fixtures'); |
||||||
498 | $obj = $state->getFixtureFactory(static::class)->get($className, $identifier); |
||||||
499 | |||||||
500 | if (!$obj) { |
||||||
0 ignored issues
–
show
|
|||||||
501 | user_error(sprintf( |
||||||
502 | "Couldn't find object '%s' (class: %s)", |
||||||
503 | $identifier, |
||||||
504 | $className |
||||||
505 | ), E_USER_ERROR); |
||||||
506 | } |
||||||
507 | |||||||
508 | return $obj; |
||||||
509 | } |
||||||
510 | |||||||
511 | /** |
||||||
512 | * Load a YAML fixture file into the database. |
||||||
513 | * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture. |
||||||
514 | * Doesn't clear existing fixtures. |
||||||
515 | * @deprecated 4.0.0:5.0.0 |
||||||
516 | * |
||||||
517 | * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir |
||||||
518 | */ |
||||||
519 | public function loadFixture(string $fixtureFile) : void |
||||||
520 | { |
||||||
521 | Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead'); |
||||||
522 | $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile); |
||||||
523 | $fixture->writeInto($this->getFixtureFactory()); |
||||||
0 ignored issues
–
show
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
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. ![]() |
|||||||
524 | } |
||||||
525 | |||||||
526 | /** |
||||||
527 | * Clear all fixtures which were previously loaded through |
||||||
528 | * {@link loadFixture()} |
||||||
529 | */ |
||||||
530 | public function clearFixtures() : void |
||||||
531 | { |
||||||
532 | /** @var FixtureTestState $state */ |
||||||
533 | $state = static::$state->getStateByName('fixtures'); |
||||||
534 | $state->getFixtureFactory(static::class)->clear(); |
||||||
535 | } |
||||||
536 | |||||||
537 | /** |
||||||
538 | * Useful for writing unit tests without hardcoding folder structures. |
||||||
539 | * |
||||||
540 | * @return string Absolute path to current class. |
||||||
541 | */ |
||||||
542 | protected function getCurrentAbsolutePath() : string |
||||||
543 | { |
||||||
544 | $filename = ClassLoader::inst()->getItemPath(static::class); |
||||||
545 | if (!$filename) { |
||||||
546 | throw new LogicException('getItemPath returned null for ' . static::class |
||||||
547 | . '. Try adding flush=1 to the test run.'); |
||||||
548 | } |
||||||
549 | return dirname($filename); |
||||||
550 | } |
||||||
551 | |||||||
552 | /** |
||||||
553 | * @return string File path relative to webroot |
||||||
554 | */ |
||||||
555 | protected function getCurrentRelativePath() : string |
||||||
556 | { |
||||||
557 | $base = Director::baseFolder(); |
||||||
558 | $path = $this->getCurrentAbsolutePath(); |
||||||
559 | if (substr($path, 0, strlen($base)) == $base) { |
||||||
560 | $path = preg_replace('/^\/*/', '', substr($path, strlen($base))); |
||||||
561 | } |
||||||
562 | return $path; |
||||||
563 | } |
||||||
564 | |||||||
565 | /** |
||||||
566 | * Setup the test. |
||||||
567 | * Always sets up in order: |
||||||
568 | * - Custom state helpers |
||||||
569 | * - Unnest |
||||||
570 | * - Reset php state |
||||||
571 | * |
||||||
572 | * User code should call parent::tearDown() after custom tear down code |
||||||
573 | */ |
||||||
574 | protected function tearDown() : void |
||||||
575 | { |
||||||
576 | // Reset mocked datetime |
||||||
577 | DBDatetime::clear_mock_now(); |
||||||
578 | |||||||
579 | // Stop the redirection that might have been requested in the test. |
||||||
580 | // Note: Ideally a clean Controller should be created for each test. |
||||||
581 | // Now all tests executed in a batch share the same controller. |
||||||
582 | $controller = Controller::has_curr() ? Controller::curr() : null; |
||||||
583 | if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) { |
||||||
584 | $response->setStatusCode(200); |
||||||
585 | $response->removeHeader('Location'); |
||||||
586 | } |
||||||
587 | |||||||
588 | // Call state helpers |
||||||
589 | static::$state->tearDown($this); |
||||||
590 | } |
||||||
591 | |||||||
592 | /** |
||||||
593 | * Asserts that an array has a specified subset. |
||||||
594 | * |
||||||
595 | * @param array|ArrayAccess $subset |
||||||
596 | * @param array|ArrayAccess $array |
||||||
597 | * |
||||||
598 | * @throws ExpectationFailedException |
||||||
599 | * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException |
||||||
600 | * @throws \PHPUnit\Framework\Exception |
||||||
601 | * |
||||||
602 | * @codeCoverageIgnore |
||||||
603 | */ |
||||||
604 | public static function assertArraySubset( |
||||||
605 | $subset, |
||||||
606 | $array, |
||||||
607 | bool $checkForObjectIdentity = false, |
||||||
608 | string $message = '' |
||||||
609 | ): void { |
||||||
610 | if (!(\is_array($subset) || $subset instanceof ArrayAccess)) { |
||||||
0 ignored issues
–
show
|
|||||||
611 | throw InvalidArgumentException::create( |
||||||
612 | 1, |
||||||
613 | 'array or ArrayAccess' |
||||||
614 | ); |
||||||
615 | } |
||||||
616 | |||||||
617 | if (!(\is_array($array) || $array instanceof ArrayAccess)) { |
||||||
0 ignored issues
–
show
|
|||||||
618 | throw InvalidArgumentException::create( |
||||||
619 | 2, |
||||||
620 | 'array or ArrayAccess' |
||||||
621 | ); |
||||||
622 | } |
||||||
623 | |||||||
624 | $constraint = new ArraySubset($subset, $checkForObjectIdentity); |
||||||
0 ignored issues
–
show
It seems like
$subset can also be of type ArrayAccess ; however, parameter $subset of SilverStripe\Dev\Constra...aySubset::__construct() does only seem to accept iterable , 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
![]() |
|||||||
625 | |||||||
626 | static::assertThat($array, $constraint, $message); |
||||||
627 | } |
||||||
628 | |||||||
629 | public static function assertContains( |
||||||
630 | $needle, |
||||||
631 | $haystack, |
||||||
632 | string $message = '', |
||||||
633 | bool $ignoreCase = false, |
||||||
634 | bool $checkForObjectIdentity = true, |
||||||
635 | bool $checkForNonObjectIdentity = false |
||||||
636 | ) : void { |
||||||
637 | if ($haystack instanceof DBField) { |
||||||
638 | $haystack = (string)$haystack; |
||||||
639 | } |
||||||
640 | parent::assertContains( |
||||||
641 | $needle, |
||||||
642 | $haystack, |
||||||
643 | $message, |
||||||
644 | $ignoreCase, |
||||||
645 | $checkForObjectIdentity, |
||||||
646 | $checkForNonObjectIdentity |
||||||
647 | ); |
||||||
648 | } |
||||||
649 | |||||||
650 | public static function assertNotContains( |
||||||
651 | $needle, |
||||||
652 | $haystack, |
||||||
653 | string $message = '', |
||||||
654 | bool $ignoreCase = false, |
||||||
655 | bool $checkForObjectIdentity = true, |
||||||
656 | bool $checkForNonObjectIdentity = false |
||||||
657 | ) : void { |
||||||
658 | if ($haystack instanceof DBField) { |
||||||
659 | $haystack = (string)$haystack; |
||||||
660 | } |
||||||
661 | parent::assertNotContains( |
||||||
662 | $needle, |
||||||
663 | $haystack, |
||||||
664 | $message, |
||||||
665 | $ignoreCase, |
||||||
666 | $checkForObjectIdentity, |
||||||
667 | $checkForNonObjectIdentity |
||||||
668 | ); |
||||||
669 | } |
||||||
670 | |||||||
671 | /** |
||||||
672 | * Clear the log of emails sent |
||||||
673 | * |
||||||
674 | * @return bool True if emails cleared |
||||||
675 | */ |
||||||
676 | public function clearEmails() : bool |
||||||
677 | { |
||||||
678 | /** @var Mailer $mailer */ |
||||||
679 | $mailer = Injector::inst()->get(Mailer::class); |
||||||
680 | if ($mailer instanceof TestMailer) { |
||||||
681 | $mailer->clearEmails(); |
||||||
682 | return true; |
||||||
683 | } |
||||||
684 | return false; |
||||||
685 | } |
||||||
686 | |||||||
687 | /** |
||||||
688 | * Search for an email that was sent. |
||||||
689 | * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression. |
||||||
690 | * @param string $to |
||||||
691 | * @param string $from |
||||||
692 | * @param string $subject |
||||||
693 | * @param string $content |
||||||
694 | * @return array Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles', |
||||||
695 | * 'HtmlContent' |
||||||
696 | */ |
||||||
697 | public static function findEmail($to, $from = null, $subject = null, $content = null) : array |
||||||
698 | { |
||||||
699 | /** @var Mailer $mailer */ |
||||||
700 | $mailer = Injector::inst()->get(Mailer::class); |
||||||
701 | if ($mailer instanceof TestMailer) { |
||||||
702 | return $mailer->findEmail($to, $from, $subject, $content); |
||||||
703 | } |
||||||
704 | return []; |
||||||
705 | } |
||||||
706 | |||||||
707 | /** |
||||||
708 | * Assert that the matching email was sent since the last call to clearEmails() |
||||||
709 | * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression. |
||||||
710 | * |
||||||
711 | * @param string $to |
||||||
712 | * @param string $from |
||||||
713 | * @param string $subject |
||||||
714 | * @param string $content |
||||||
715 | */ |
||||||
716 | public static function assertEmailSent($to, $from = null, $subject = null, $content = null) : void |
||||||
717 | { |
||||||
718 | $found = (bool)static::findEmail($to, $from, $subject, $content); |
||||||
719 | |||||||
720 | $infoParts = ''; |
||||||
721 | $withParts = array(); |
||||||
722 | if ($to) { |
||||||
723 | $infoParts .= " to '$to'"; |
||||||
724 | } |
||||||
725 | if ($from) { |
||||||
726 | $infoParts .= " from '$from'"; |
||||||
727 | } |
||||||
728 | if ($subject) { |
||||||
729 | $withParts[] = "subject '$subject'"; |
||||||
730 | } |
||||||
731 | if ($content) { |
||||||
732 | $withParts[] = "content '$content'"; |
||||||
733 | } |
||||||
734 | if ($withParts) { |
||||||
735 | $infoParts .= ' with ' . implode(' and ', $withParts); |
||||||
736 | } |
||||||
737 | |||||||
738 | static::assertTrue( |
||||||
739 | $found, |
||||||
740 | "Failed asserting that an email was sent$infoParts." |
||||||
741 | ); |
||||||
742 | } |
||||||
743 | |||||||
744 | |||||||
745 | /** |
||||||
746 | * Assert that the given {@link SS_List} includes DataObjects matching the given key-value |
||||||
747 | * pairs. Each match must correspond to 1 distinct record. |
||||||
748 | * |
||||||
749 | * @param SS_List|array $matches The patterns to match. Each pattern is a map of key-value pairs. You can |
||||||
750 | * either pass a single pattern or an array of patterns. |
||||||
751 | * @param SS_List $list The {@link SS_List} to test. |
||||||
752 | * @param string $message |
||||||
753 | * |
||||||
754 | * Examples |
||||||
755 | * -------- |
||||||
756 | * Check that $members includes an entry with Email = [email protected]: |
||||||
757 | * $this->assertListContains(['Email' => '[email protected]'], $members); |
||||||
758 | * |
||||||
759 | * Check that $members includes entries with Email = [email protected] and with |
||||||
760 | * Email = [email protected]: |
||||||
761 | * $this->assertListContains([ |
||||||
762 | * ['Email' => '[email protected]'], |
||||||
763 | * ['Email' => '[email protected]'], |
||||||
764 | * ], $members); |
||||||
765 | */ |
||||||
766 | public static function assertListContains($matches, SS_List $list, $message = '') : void |
||||||
767 | { |
||||||
768 | if (!is_array($matches)) { |
||||||
769 | throw InvalidArgumentHelper::factory( |
||||||
770 | 1, |
||||||
771 | 'array' |
||||||
772 | ); |
||||||
773 | } |
||||||
774 | |||||||
775 | static::assertThat( |
||||||
776 | $list, |
||||||
777 | new SSListContains( |
||||||
778 | $matches |
||||||
779 | ), |
||||||
780 | $message |
||||||
781 | ); |
||||||
782 | } |
||||||
783 | |||||||
784 | /** |
||||||
785 | * @deprecated 4.0.0:5.0.0 Use assertListContains() instead |
||||||
786 | * |
||||||
787 | * @param array $matches |
||||||
788 | * @param $dataObjectSet |
||||||
789 | */ |
||||||
790 | public function assertDOSContains($matches, $dataObjectSet) |
||||||
791 | { |
||||||
792 | Deprecation::notice('5.0', 'Use assertListContains() instead'); |
||||||
793 | static::assertListContains($matches, $dataObjectSet); |
||||||
794 | } |
||||||
795 | |||||||
796 | /** |
||||||
797 | * Asserts that no items in a given list appear in the given dataobject list |
||||||
798 | * |
||||||
799 | * @param SS_List|array $matches The patterns to match. Each pattern is a map of key-value pairs. You can |
||||||
800 | * either pass a single pattern or an array of patterns. |
||||||
801 | * @param SS_List $list The {@link SS_List} to test. |
||||||
802 | * @param string $message |
||||||
803 | * |
||||||
804 | * Examples |
||||||
805 | * -------- |
||||||
806 | * Check that $members doesn't have an entry with Email = [email protected]: |
||||||
807 | * $this->assertListNotContains(['Email' => '[email protected]'], $members); |
||||||
808 | * |
||||||
809 | * Check that $members doesn't have entries with Email = [email protected] and with |
||||||
810 | * Email = [email protected]: |
||||||
811 | * $this->assertListNotContains([ |
||||||
812 | * ['Email' => '[email protected]'], |
||||||
813 | * ['Email' => '[email protected]'], |
||||||
814 | * ], $members); |
||||||
815 | */ |
||||||
816 | public static function assertListNotContains($matches, SS_List $list, $message = '') : void |
||||||
817 | { |
||||||
818 | if (!is_array($matches)) { |
||||||
819 | throw InvalidArgumentHelper::factory( |
||||||
820 | 1, |
||||||
821 | 'array' |
||||||
822 | ); |
||||||
823 | } |
||||||
824 | |||||||
825 | $constraint = new LogicalNot( |
||||||
826 | new SSListContains( |
||||||
827 | $matches |
||||||
828 | ) |
||||||
829 | ); |
||||||
830 | |||||||
831 | static::assertThat( |
||||||
832 | $list, |
||||||
833 | $constraint, |
||||||
834 | $message |
||||||
835 | ); |
||||||
836 | } |
||||||
837 | |||||||
838 | /** |
||||||
839 | * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead |
||||||
840 | * |
||||||
841 | * @param $matches |
||||||
842 | * @param $dataObjectSet |
||||||
843 | */ |
||||||
844 | public static function assertNotDOSContains($matches, $dataObjectSet) |
||||||
845 | { |
||||||
846 | Deprecation::notice('5.0', 'Use assertListNotContains() instead'); |
||||||
847 | static::assertListNotContains($matches, $dataObjectSet); |
||||||
848 | } |
||||||
849 | |||||||
850 | /** |
||||||
851 | * Assert that the given {@link SS_List} includes only DataObjects matching the given |
||||||
852 | * key-value pairs. Each match must correspond to 1 distinct record. |
||||||
853 | * |
||||||
854 | * Example |
||||||
855 | * -------- |
||||||
856 | * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members. Order doesn't |
||||||
857 | * matter: |
||||||
858 | * $this->assertListEquals([ |
||||||
859 | * ['FirstName' =>'Sam', 'Surname' => 'Minnee'], |
||||||
860 | * ['FirstName' => 'Ingo', 'Surname' => 'Schommer'], |
||||||
861 | * ], $members); |
||||||
862 | * |
||||||
863 | * @param mixed $matches The patterns to match. Each pattern is a map of key-value pairs. You can |
||||||
864 | * either pass a single pattern or an array of patterns. |
||||||
865 | * @param mixed $list The {@link SS_List} to test. |
||||||
866 | * @param string $message |
||||||
867 | */ |
||||||
868 | public static function assertListEquals($matches, SS_List $list, $message = '') : void |
||||||
869 | { |
||||||
870 | if (!is_array($matches)) { |
||||||
871 | throw InvalidArgumentHelper::factory( |
||||||
872 | 1, |
||||||
873 | 'array' |
||||||
874 | ); |
||||||
875 | } |
||||||
876 | |||||||
877 | static::assertThat( |
||||||
878 | $list, |
||||||
879 | new SSListContainsOnly( |
||||||
880 | $matches |
||||||
881 | ), |
||||||
882 | $message |
||||||
883 | ); |
||||||
884 | } |
||||||
885 | |||||||
886 | /** |
||||||
887 | * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead |
||||||
888 | * |
||||||
889 | * @param $matches |
||||||
890 | * @param SS_List $dataObjectSet |
||||||
891 | */ |
||||||
892 | public function assertDOSEquals($matches, $dataObjectSet) |
||||||
893 | { |
||||||
894 | Deprecation::notice('5.0', 'Use assertListEquals() instead'); |
||||||
895 | static::assertListEquals($matches, $dataObjectSet); |
||||||
896 | } |
||||||
897 | |||||||
898 | |||||||
899 | /** |
||||||
900 | * Assert that the every record in the given {@link SS_List} matches the given key-value |
||||||
901 | * pairs. |
||||||
902 | * |
||||||
903 | * Example |
||||||
904 | * -------- |
||||||
905 | * Check that every entry in $members has a Status of 'Active': |
||||||
906 | * $this->assertListAllMatch(['Status' => 'Active'], $members); |
||||||
907 | * |
||||||
908 | * @param mixed $match The pattern to match. The pattern is a map of key-value pairs. |
||||||
909 | * @param mixed $list The {@link SS_List} to test. |
||||||
910 | * @param string $message |
||||||
911 | */ |
||||||
912 | public static function assertListAllMatch($match, SS_List $list, $message = '') : void |
||||||
913 | { |
||||||
914 | if (!is_array($match)) { |
||||||
915 | throw PHPUnit_Util_InvalidArgumentHelper::factory( |
||||||
0 ignored issues
–
show
The type
SilverStripe\Dev\PHPUnit...l_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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
916 | 1, |
||||||
917 | 'array' |
||||||
918 | ); |
||||||
919 | } |
||||||
920 | |||||||
921 | static::assertThat( |
||||||
922 | $list, |
||||||
923 | new SSListContainsOnlyMatchingItems( |
||||||
924 | $match |
||||||
925 | ), |
||||||
926 | $message |
||||||
927 | ); |
||||||
928 | } |
||||||
929 | |||||||
930 | /** |
||||||
931 | * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead |
||||||
932 | * |
||||||
933 | * @param $match |
||||||
934 | * @param SS_List $dataObjectSet |
||||||
935 | */ |
||||||
936 | public function assertDOSAllMatch($match, SS_List $dataObjectSet) |
||||||
937 | { |
||||||
938 | Deprecation::notice('5.0', 'Use assertListAllMatch() instead'); |
||||||
939 | static::assertListAllMatch($match, $dataObjectSet); |
||||||
940 | } |
||||||
941 | |||||||
942 | /** |
||||||
943 | * Removes sequences of repeated whitespace characters from SQL queries |
||||||
944 | * making them suitable for string comparison |
||||||
945 | * |
||||||
946 | * @param string $sql |
||||||
947 | * @return string The cleaned and normalised SQL string |
||||||
948 | */ |
||||||
949 | protected static function normaliseSQL($sql) : string |
||||||
950 | { |
||||||
951 | return trim(preg_replace('/\s+/m', ' ', $sql)); |
||||||
952 | } |
||||||
953 | |||||||
954 | /** |
||||||
955 | * Asserts that two SQL queries are equivalent |
||||||
956 | * |
||||||
957 | * @param string $expectedSQL |
||||||
958 | * @param string $actualSQL |
||||||
959 | * @param string $message |
||||||
960 | * @param float|int $delta |
||||||
961 | * @param integer $maxDepth |
||||||
962 | * @param boolean $canonicalize |
||||||
963 | * @param boolean $ignoreCase |
||||||
964 | */ |
||||||
965 | public static function assertSQLEquals( |
||||||
966 | $expectedSQL, |
||||||
967 | $actualSQL, |
||||||
968 | string $message = '', |
||||||
969 | int $delta = 0, |
||||||
970 | int $maxDepth = 10, |
||||||
971 | bool $canonicalize = false, |
||||||
972 | bool $ignoreCase = false |
||||||
973 | ) : void { |
||||||
974 | // Normalise SQL queries to remove patterns of repeating whitespace |
||||||
975 | $expectedSQL = static::normaliseSQL($expectedSQL); |
||||||
976 | $actualSQL = static::normaliseSQL($actualSQL); |
||||||
977 | |||||||
978 | static::assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); |
||||||
979 | } |
||||||
980 | |||||||
981 | /** |
||||||
982 | * Asserts that a SQL query contains a SQL fragment |
||||||
983 | * |
||||||
984 | * @param string $needleSQL |
||||||
985 | * @param string $haystackSQL |
||||||
986 | * @param string $message |
||||||
987 | * @param boolean $ignoreCase |
||||||
988 | * @param boolean $checkForObjectIdentity |
||||||
989 | */ |
||||||
990 | public static function assertSQLContains( |
||||||
991 | $needleSQL, |
||||||
992 | $haystackSQL, |
||||||
993 | string $message = '', |
||||||
994 | bool $ignoreCase = false, |
||||||
995 | bool $checkForObjectIdentity = true |
||||||
996 | ) : void { |
||||||
997 | $needleSQL = static::normaliseSQL($needleSQL); |
||||||
998 | $haystackSQL = static::normaliseSQL($haystackSQL); |
||||||
999 | |||||||
1000 | static::assertStringContainsString($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity); |
||||||
0 ignored issues
–
show
The call to
PHPUnit\Framework\Assert...tStringContainsString() 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
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. ![]() |
|||||||
1001 | } |
||||||
1002 | |||||||
1003 | /** |
||||||
1004 | * Asserts that a SQL query contains a SQL fragment |
||||||
1005 | * |
||||||
1006 | * @param string $needleSQL |
||||||
1007 | * @param string $haystackSQL |
||||||
1008 | * @param string $message |
||||||
1009 | * @param boolean $ignoreCase |
||||||
1010 | * @param boolean $checkForObjectIdentity |
||||||
1011 | */ |
||||||
1012 | public static function assertSQLNotContains( |
||||||
1013 | $needleSQL, |
||||||
1014 | $haystackSQL, |
||||||
1015 | string $message = '', |
||||||
1016 | bool $ignoreCase = false, |
||||||
1017 | bool $checkForObjectIdentity = true |
||||||
1018 | ) : void { |
||||||
1019 | $needleSQL = static::normaliseSQL($needleSQL); |
||||||
1020 | $haystackSQL = static::normaliseSQL($haystackSQL); |
||||||
1021 | |||||||
1022 | static::assertStringNotContainsString($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity); |
||||||
0 ignored issues
–
show
The call to
PHPUnit\Framework\Assert...ringNotContainsString() 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
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. ![]() |
|||||||
1023 | } |
||||||
1024 | |||||||
1025 | /** |
||||||
1026 | * Start test environment |
||||||
1027 | */ |
||||||
1028 | public static function start() : void |
||||||
1029 | { |
||||||
1030 | if (static::is_running_test()) { |
||||||
1031 | return; |
||||||
1032 | } |
||||||
1033 | |||||||
1034 | // Health check |
||||||
1035 | if (InjectorLoader::inst()->countManifests()) { |
||||||
1036 | throw new LogicException('SapphireTest::start() cannot be called within another application'); |
||||||
1037 | } |
||||||
1038 | static::set_is_running_test(true); |
||||||
1039 | |||||||
1040 | // Mock request |
||||||
1041 | $_SERVER['argv'] = ['vendor/bin/phpunit', '/']; |
||||||
1042 | $request = CLIRequestBuilder::createFromEnvironment(); |
||||||
1043 | |||||||
1044 | // Test application |
||||||
1045 | $kernel = new TestKernel(BASE_PATH); |
||||||
1046 | $app = new HTTPApplication($kernel); |
||||||
1047 | $flush = array_key_exists('flush', $request->getVars()); |
||||||
1048 | |||||||
1049 | // Custom application |
||||||
1050 | $app->execute($request, function (HTTPRequest $request) { |
||||||
1051 | // Start session and execute |
||||||
1052 | $request->getSession()->init($request); |
||||||
1053 | |||||||
1054 | // Invalidate classname spec since the test manifest will now pull out new subclasses for each |
||||||
1055 | // internal class (e.g. Member will now have various subclasses of DataObjects that implement TestOnly) |
||||||
1056 | DataObject::reset(); |
||||||
1057 | |||||||
1058 | // Set dummy controller; |
||||||
1059 | $controller = Controller::create(); |
||||||
1060 | $controller->setRequest($request); |
||||||
1061 | $controller->pushCurrent(); |
||||||
1062 | $controller->doInit(); |
||||||
1063 | }, $flush); |
||||||
1064 | |||||||
1065 | // Register state |
||||||
1066 | static::$state = SapphireTestState::singleton(); |
||||||
1067 | // Register temp DB holder |
||||||
1068 | static::tempDB(); |
||||||
1069 | } |
||||||
1070 | |||||||
1071 | /** |
||||||
1072 | * Reset the testing database's schema, but only if it is active |
||||||
1073 | * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included |
||||||
1074 | * @param bool $forceCreate Force DB to be created if it doesn't exist |
||||||
1075 | */ |
||||||
1076 | public static function resetDBSchema(bool $includeExtraDataObjects = false, bool $forceCreate = false) : void |
||||||
1077 | { |
||||||
1078 | // Check if DB is active before reset |
||||||
1079 | if (!static::$tempDB->isUsed()) { |
||||||
1080 | if (!$forceCreate) { |
||||||
1081 | return; |
||||||
1082 | } |
||||||
1083 | static::$tempDB->build(); |
||||||
1084 | } |
||||||
1085 | $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : []; |
||||||
1086 | static::$tempDB->resetDBSchema((array)$extraDataObjects); |
||||||
1087 | } |
||||||
1088 | |||||||
1089 | /** |
||||||
1090 | * A wrapper for automatically performing callbacks as a user with a specific permission |
||||||
1091 | * |
||||||
1092 | * @param string|array $permCode |
||||||
1093 | * @param callable $callback |
||||||
1094 | * @return mixed |
||||||
1095 | */ |
||||||
1096 | public function actWithPermission($permCode, callable $callback) |
||||||
1097 | { |
||||||
1098 | return Member::actAs($this->createMemberWithPermission($permCode), $callback); |
||||||
1099 | } |
||||||
1100 | |||||||
1101 | /** |
||||||
1102 | * Create Member and Group objects on demand with specific permission code |
||||||
1103 | * |
||||||
1104 | * @param string|array $permCode |
||||||
1105 | * @return Member |
||||||
1106 | */ |
||||||
1107 | protected function createMemberWithPermission($permCode) : Member |
||||||
1108 | { |
||||||
1109 | if (is_array($permCode)) { |
||||||
1110 | $permArray = $permCode; |
||||||
1111 | $permCode = implode('.', $permCode); |
||||||
1112 | } else { |
||||||
1113 | $permArray = array($permCode); |
||||||
1114 | } |
||||||
1115 | |||||||
1116 | // Check cached member |
||||||
1117 | if (isset($this->cache_generatedMembers[$permCode])) { |
||||||
1118 | $member = $this->cache_generatedMembers[$permCode]; |
||||||
1119 | } else { |
||||||
1120 | // Generate group with these permissions |
||||||
1121 | $group = Group::create(); |
||||||
1122 | $group->Title = "$permCode group"; |
||||||
1123 | $group->write(); |
||||||
1124 | |||||||
1125 | // Create each individual permission |
||||||
1126 | foreach ($permArray as $permArrayItem) { |
||||||
1127 | $permission = Permission::create(); |
||||||
1128 | $permission->Code = $permArrayItem; |
||||||
1129 | $permission->write(); |
||||||
1130 | $group->Permissions()->add($permission); |
||||||
1131 | } |
||||||
1132 | |||||||
1133 | $member = Member::get()->filter([ |
||||||
1134 | 'Email' => "[email protected]", |
||||||
1135 | ])->first(); |
||||||
1136 | if (!$member) { |
||||||
0 ignored issues
–
show
|
|||||||
1137 | $member = Member::create(); |
||||||
1138 | } |
||||||
1139 | |||||||
1140 | $member->FirstName = $permCode; |
||||||
0 ignored issues
–
show
The property
FirstName does not exist on SilverStripe\ORM\DataObject . Since you implemented __set , consider adding a @property annotation.
![]() |
|||||||
1141 | $member->Surname = 'User'; |
||||||
0 ignored issues
–
show
The property
Surname does not exist on SilverStripe\ORM\DataObject . Since you implemented __set , consider adding a @property annotation.
![]() |
|||||||
1142 | $member->Email = "[email protected]"; |
||||||
0 ignored issues
–
show
The property
Email does not exist on SilverStripe\ORM\DataObject . Since you implemented __set , consider adding a @property annotation.
![]() |
|||||||
1143 | $member->write(); |
||||||
1144 | $group->Members()->add($member); |
||||||
1145 | |||||||
1146 | $this->cache_generatedMembers[$permCode] = $member; |
||||||
1147 | } |
||||||
1148 | return $member; |
||||||
1149 | } |
||||||
1150 | |||||||
1151 | /** |
||||||
1152 | * Create a member and group with the given permission code, and log in with it. |
||||||
1153 | * Returns the member ID. |
||||||
1154 | * |
||||||
1155 | * @param string|array $permCode Either a permission, or list of permissions |
||||||
1156 | * @return int Member ID |
||||||
1157 | */ |
||||||
1158 | public function logInWithPermission($permCode = 'ADMIN') |
||||||
1159 | { |
||||||
1160 | $member = $this->createMemberWithPermission($permCode); |
||||||
1161 | $this->logInAs($member); |
||||||
1162 | return $member->ID; |
||||||
1163 | } |
||||||
1164 | |||||||
1165 | /** |
||||||
1166 | * Log in as the given member |
||||||
1167 | * |
||||||
1168 | * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in |
||||||
1169 | */ |
||||||
1170 | public function logInAs($member) : void |
||||||
1171 | { |
||||||
1172 | if (is_numeric($member)) { |
||||||
1173 | $member = DataObject::get_by_id(Member::class, $member); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1174 | } elseif (!is_object($member)) { |
||||||
1175 | $member = $this->objFromFixture(Member::class, $member); |
||||||
1176 | } |
||||||
1177 | Injector::inst()->get(IdentityStore::class)->logIn($member); |
||||||
1178 | } |
||||||
1179 | |||||||
1180 | /** |
||||||
1181 | * Log out the current user |
||||||
1182 | */ |
||||||
1183 | public function logOut() : void |
||||||
1184 | { |
||||||
1185 | /** @var IdentityStore $store */ |
||||||
1186 | $store = Injector::inst()->get(IdentityStore::class); |
||||||
1187 | $store->logOut(); |
||||||
1188 | } |
||||||
1189 | |||||||
1190 | /** |
||||||
1191 | * Cache for logInWithPermission() |
||||||
1192 | */ |
||||||
1193 | protected $cache_generatedMembers = []; |
||||||
1194 | |||||||
1195 | /** |
||||||
1196 | * Test against a theme. |
||||||
1197 | * |
||||||
1198 | * @param string $themeBaseDir themes directory |
||||||
1199 | * @param string $theme Theme name |
||||||
1200 | * @param callable $callback |
||||||
1201 | * @throws Exception |
||||||
1202 | */ |
||||||
1203 | protected function useTestTheme(string $themeBaseDir, string $theme, callable $callback) : void |
||||||
1204 | { |
||||||
1205 | Config::nest(); |
||||||
1206 | if (strpos($themeBaseDir, BASE_PATH) === 0) { |
||||||
1207 | $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH)); |
||||||
1208 | } |
||||||
1209 | SSViewer::config()->update('theme_enabled', true); |
||||||
1210 | SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']); |
||||||
1211 | |||||||
1212 | try { |
||||||
1213 | $callback(); |
||||||
1214 | } finally { |
||||||
1215 | Config::unnest(); |
||||||
1216 | } |
||||||
1217 | } |
||||||
1218 | |||||||
1219 | /** |
||||||
1220 | * Get fixture paths for this test |
||||||
1221 | * |
||||||
1222 | * @return array List of paths |
||||||
1223 | */ |
||||||
1224 | protected function getFixturePaths() : array |
||||||
1225 | { |
||||||
1226 | $fixtureFile = static::get_fixture_file(); |
||||||
1227 | if (empty($fixtureFile)) { |
||||||
1228 | return []; |
||||||
1229 | } |
||||||
1230 | |||||||
1231 | $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile]; |
||||||
0 ignored issues
–
show
|
|||||||
1232 | |||||||
1233 | return array_map(function ($fixtureFilePath) { |
||||||
1234 | return $this->resolveFixturePath($fixtureFilePath); |
||||||
1235 | }, $fixtureFiles); |
||||||
1236 | } |
||||||
1237 | |||||||
1238 | /** |
||||||
1239 | * Return all extra objects to scaffold for this test |
||||||
1240 | * @return array |
||||||
1241 | */ |
||||||
1242 | public static function getExtraDataObjects() |
||||||
1243 | { |
||||||
1244 | return static::$extra_dataobjects; |
||||||
1245 | } |
||||||
1246 | |||||||
1247 | /** |
||||||
1248 | * Get additional controller classes to register routes for |
||||||
1249 | * |
||||||
1250 | * @return array |
||||||
1251 | */ |
||||||
1252 | public static function getExtraControllers() : array |
||||||
1253 | { |
||||||
1254 | return static::$extra_controllers; |
||||||
1255 | } |
||||||
1256 | |||||||
1257 | /** |
||||||
1258 | * Map a fixture path to a physical file |
||||||
1259 | * |
||||||
1260 | * @param string $fixtureFilePath |
||||||
1261 | * @return string |
||||||
1262 | */ |
||||||
1263 | protected function resolveFixturePath(string $fixtureFilePath) : string |
||||||
1264 | { |
||||||
1265 | // support loading via composer name path. |
||||||
1266 | if (strpos($fixtureFilePath, ':') !== false) { |
||||||
1267 | return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath); |
||||||
1268 | } |
||||||
1269 | |||||||
1270 | // Support fixture paths relative to the test class, rather than relative to webroot |
||||||
1271 | // String checking is faster than file_exists() calls. |
||||||
1272 | $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath); |
||||||
1273 | if ($resolvedPath) { |
||||||
1274 | return $resolvedPath; |
||||||
1275 | } |
||||||
1276 | |||||||
1277 | // Check if file exists relative to base dir |
||||||
1278 | $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath); |
||||||
1279 | if ($resolvedPath) { |
||||||
1280 | return $resolvedPath; |
||||||
1281 | } |
||||||
1282 | |||||||
1283 | return $fixtureFilePath; |
||||||
1284 | } |
||||||
1285 | |||||||
1286 | protected function setUpRoutes() |
||||||
1287 | { |
||||||
1288 | // Get overridden routes |
||||||
1289 | $rules = $this->getExtraRoutes(); |
||||||
1290 | |||||||
1291 | // Add all other routes |
||||||
1292 | foreach (Director::config()->uninherited('rules') as $route => $rule) { |
||||||
1293 | if (!isset($rules[$route])) { |
||||||
1294 | $rules[$route] = $rule; |
||||||
1295 | } |
||||||
1296 | } |
||||||
1297 | |||||||
1298 | // Add default catch-all rule |
||||||
1299 | $rules['$Controller//$Action/$ID/$OtherID'] = '*'; |
||||||
1300 | |||||||
1301 | // Add controller-name auto-routing |
||||||
1302 | Director::config()->set('rules', $rules); |
||||||
1303 | } |
||||||
1304 | |||||||
1305 | /** |
||||||
1306 | * Get extra routes to merge into Director.rules |
||||||
1307 | * |
||||||
1308 | * @return array |
||||||
1309 | */ |
||||||
1310 | protected function getExtraRoutes() |
||||||
1311 | { |
||||||
1312 | $rules = []; |
||||||
1313 | foreach ($this->getExtraControllers() as $class) { |
||||||
1314 | $controllerInst = Controller::singleton($class); |
||||||
1315 | $link = Director::makeRelative($controllerInst->Link()); |
||||||
1316 | $route = rtrim($link, '/') . '//$Action/$ID/$OtherID'; |
||||||
1317 | $rules[$route] = $class; |
||||||
1318 | } |
||||||
1319 | return $rules; |
||||||
1320 | } |
||||||
1321 | } |
||||||
1322 |
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths