From 25511bfd34e338bd95f2b4d1721d2ca50d8a551d Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:25:50 -0600 Subject: [PATCH 001/100] Added storage/framework/cache/data folder required by 5.6. --- storage/framework/cache/.gitignore | 1 + storage/framework/cache/data/.gitignore | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 storage/framework/cache/data/.gitignore diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore index c96a04f00..869804c2a 100644 --- a/storage/framework/cache/.gitignore +++ b/storage/framework/cache/.gitignore @@ -1,2 +1,3 @@ * +!data/ !.gitignore \ No newline at end of file diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From 37db70ab8375cf0a2c09ba318fc3691c2bab363a Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:26:21 -0600 Subject: [PATCH 002/100] Config changes from 5.6 --- config/app.php | 17 +-------- config/hashing.php | 52 +++++++++++++++++++++++++ config/logging.php | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 config/hashing.php create mode 100644 config/logging.php diff --git a/config/app.php b/config/app.php index d427deb11..e0c727196 100644 --- a/config/app.php +++ b/config/app.php @@ -56,7 +56,7 @@ return [ | -------- STOP! -------- | Before you change this value, consider carefully if that is actually | what you want to do. It is HIGHLY recommended that this is always set - | to UTC (as your server & DB timezone should be as well) and instead you + | to UTC (as your server & DB timezone should be as well) and instead you | use cms.backendTimezone to set the default timezone used in the backend | to display dates & times. | @@ -105,21 +105,6 @@ return [ 'cipher' => 'AES-256-CBC', - /* - |-------------------------------------------------------------------------- - | Logging Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure the log settings for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Settings: "single", "daily", "syslog", "errorlog" - | - */ - - 'log' => 'single', - /* |-------------------------------------------------------------------------- | Autoloaded Service Providers diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 000000000..bc5da3a6e --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; \ No newline at end of file diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 000000000..372fc9e4b --- /dev/null +++ b/config/logging.php @@ -0,0 +1,94 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['daily'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => 'debug', + 'handler' => SyslogUdpHandler::class, + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + +]; \ No newline at end of file From 1e3edae81a4e67a17782c79de876855927060ab8 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:27:23 -0600 Subject: [PATCH 003/100] Default backend user password requirements changed for utf8mb4 DB type and minimum password length requirements --- modules/backend/models/User.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index f527f58ca..6399ff7ec 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -24,10 +24,10 @@ class User extends UserBase * Validation rules */ public $rules = [ - 'email' => 'required|between:6,255|email|unique:backend_users', - 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|between:4,255|confirmed', - 'password_confirmation' => 'required_with:password|between:4,255' + 'email' => 'required|between:6,191|email|unique:backend_users', + 'login' => 'required|between:2,191|unique:backend_users', + 'password' => 'required:create|between:8,191|confirmed', + 'password_confirmation' => 'required_with:password|between:8,191' ]; /** From 1aff1e0a1eda81169dadc1aeeffb59f538c1d959 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:33:26 -0600 Subject: [PATCH 004/100] Changed calls to the Cache to use DateTime instances instead of integers representing minutes as 5.8 changed integers into meaning seconds instead. --- modules/cms/classes/CmsCompoundObject.php | 3 ++- modules/cms/classes/CodeParser.php | 3 ++- modules/cms/classes/Router.php | 6 ++++-- modules/cms/classes/Theme.php | 3 ++- modules/cms/traits/UrlMaker.php | 3 ++- modules/system/classes/MediaLibrary.php | 3 ++- modules/system/classes/UpdateManager.php | 3 ++- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index 6b5346198..67c7cd35b 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -322,7 +322,8 @@ class CmsCompoundObject extends CmsObject self::$objectComponentPropertyMap = $objectComponentMap; - Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10)); + Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt); if (array_key_exists($componentName, $objectComponentMap[$objectCode])) { return $objectComponentMap[$objectCode][$componentName]; diff --git a/modules/cms/classes/CodeParser.php b/modules/cms/classes/CodeParser.php index 033832203..ce68fc3a2 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -222,7 +222,8 @@ class CodeParser $cached = $this->getCachedInfo() ?: []; $cached[$this->filePath] = $cacheItem; - Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440); + $expiresAt = now()->addMinutes(1440); + Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt); self::$cache[$this->filePath] = $result; } diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 002bfd686..d98cc142b 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -127,10 +127,11 @@ class Router : $fileName; $key = $this->getUrlListCacheKey(); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); Cache::put( $key, base64_encode(serialize($urlList)), - Config::get('cms.urlCacheTtl', 1) + $expiresAt ); } } @@ -251,7 +252,8 @@ class Router $this->urlMap = $map; if ($cacheable) { - Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1)); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); + Cache::put($key, base64_encode(serialize($map)), $expiresAt); } return false; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index 8fabc4bf5..de190f986 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -155,7 +155,8 @@ class Theme if (App::hasDatabase()) { try { try { - $dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () { + $expiresAt = now()->addMinutes(1440); + $dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () { return Parameter::applyKey(self::ACTIVE_KEY)->value('value'); }); } diff --git a/modules/cms/traits/UrlMaker.php b/modules/cms/traits/UrlMaker.php index 210922cfa..ebe0c050f 100644 --- a/modules/cms/traits/UrlMaker.php +++ b/modules/cms/traits/UrlMaker.php @@ -190,7 +190,8 @@ trait UrlMaker 'mtime' => @File::lastModified($filePath) ]; - Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440)); + Cache::put($key, serialize($cached), $expiresAt); return static::$urlPageName = $baseFileName; } diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 859fa056d..f07abe510 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -134,10 +134,11 @@ class MediaLibrary $folderContents = $this->scanFolderContents($fullFolderPath); $cached[$fullFolderPath] = $folderContents; + $expiresAt = now()->addMinutes(Config::get('cms.storage.media.ttl', 10)); Cache::put( $this->cacheKey, base64_encode(serialize($cached)), - Config::get('cms.storage.media.ttl', 10) + $expiresAt ); } diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 8d4b43419..1c3d4617d 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -703,7 +703,8 @@ class UpdateManager } $data = $this->requestServerData($type.'/popular'); - Cache::put($cacheKey, base64_encode(serialize($data)), 60); + $expiresAt = now()->addMinutes(60); + Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt); foreach ($data as $product) { $code = array_get($product, 'code', -1); From 627c0aa6431ad67c6575851b85630856eb5448d8 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:33:53 -0600 Subject: [PATCH 005/100] Replaced JSMin with JSqueeze --- modules/system/classes/CombineAssets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php index 5cd13c815..899768dc5 100644 --- a/modules/system/classes/CombineAssets.php +++ b/modules/system/classes/CombineAssets.php @@ -140,7 +140,7 @@ class CombineAssets * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \Assetic\Filter\JSMinFilter); + $this->registerFilter('js', new \Assetic\Filter\JSqueezeFilter); $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); } From 2fc515e2a5f93c3445af9fe38d21ccd033243155 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 02:34:28 -0600 Subject: [PATCH 006/100] Updated environment file generation command to match what laravel is calling the variable now --- modules/system/console/OctoberEnv.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index 62000a7aa..33d3db288 100644 --- a/modules/system/console/OctoberEnv.php +++ b/modules/system/console/OctoberEnv.php @@ -357,7 +357,7 @@ class OctoberEnv extends Command 'SESSION_DRIVER' => 'driver', ], 'queue' => [ - 'QUEUE_DRIVER' => 'default', + 'QUEUE_CONNECTION' => 'default', ], 'mail' => [ 'MAIL_DRIVER' => 'driver', From c7a026c2537d31b0d11bbae3fd44fe8712094e9b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 12 Jun 2019 10:22:20 -0600 Subject: [PATCH 007/100] Add support for PHPUnit 7.0 --- tests/PluginTestCase.php | 4 ++-- tests/unit/backend/traits/WidgetMakerTest.php | 2 +- tests/unit/cms/classes/CmsCompoundObjectTest.php | 2 +- tests/unit/cms/classes/CmsObjectQueryTest.php | 2 +- tests/unit/cms/classes/CodeParserTest.php | 2 +- tests/unit/cms/classes/ComponentManagerTest.php | 2 +- tests/unit/cms/classes/ControllerTest.php | 2 +- tests/unit/cms/classes/RouterTest.php | 2 +- tests/unit/cms/classes/ThemeTest.php | 2 +- tests/unit/plugins/database/AttachManyModelTest.php | 2 +- tests/unit/plugins/database/AttachOneModelTest.php | 2 +- tests/unit/plugins/database/BelongsToManyModelTest.php | 2 +- tests/unit/plugins/database/BelongsToModelTest.php | 2 +- tests/unit/plugins/database/DeferredBindingTest.php | 2 +- tests/unit/plugins/database/HasManyModelTest.php | 2 +- tests/unit/plugins/database/HasOneModelTest.php | 2 +- tests/unit/plugins/database/ModelTest.php | 2 +- tests/unit/plugins/database/MorphManyModelTest.php | 2 +- tests/unit/plugins/database/MorphOneModelTest.php | 2 +- tests/unit/plugins/database/MorphToModelTest.php | 2 +- tests/unit/plugins/database/NestedTreeModelTest.php | 2 +- tests/unit/plugins/database/NullableModelTest.php | 2 +- tests/unit/plugins/database/RevisionableModelTest.php | 2 +- tests/unit/plugins/database/SimpleTreeModelTest.php | 2 +- tests/unit/plugins/database/SluggableModelTest.php | 2 +- tests/unit/plugins/database/SoftDeleteModelTest.php | 2 +- tests/unit/plugins/database/ValidationModelTest.php | 2 +- tests/unit/system/classes/CombineAssetsTest.php | 2 +- tests/unit/system/classes/MarkupManagerTest.php | 2 +- tests/unit/system/classes/PluginManagerTest.php | 2 +- tests/unit/system/classes/VersionManagerTest.php | 2 +- tests/unit/system/traits/AssetMakerTest.php | 2 +- 32 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 9689c8790..06abaffc0 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -57,7 +57,7 @@ abstract class PluginTestCase extends Illuminate\Foundation\Testing\TestCase * Perform test case set up. * @return void */ - public function setUp() + public function setUp() : void { /* * Force reload of October singletons @@ -95,7 +95,7 @@ abstract class PluginTestCase extends Illuminate\Foundation\Testing\TestCase * Flush event listeners and collect garbage. * @return void */ - public function tearDown() + public function tearDown() : void { $this->flushModelEventListeners(); parent::tearDown(); diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 657384221..cf9453abf 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -28,7 +28,7 @@ class WidgetMakerTest extends TestCase * * @return void */ - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index 5d47553b3..58e9a5ef0 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -26,7 +26,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject class CmsCompoundObjectTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); Model::clearBootedModels(); diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index d43092332..4e19b55da 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Model; class CmsObjectQueryTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 95ddc9095..8eeacd3ca 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -10,7 +10,7 @@ use Cms\Classes\Controller; class CodeParserTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setup(); diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index a12255580..152367b8d 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -9,7 +9,7 @@ use Cms\Classes\ComponentManager; class ComponentManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 5ba899995..a0dbc5061 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -6,7 +6,7 @@ use October\Rain\Halcyon\Model; class ControllerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index db378394e..0a32a9acf 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -7,7 +7,7 @@ class RouterTest extends TestCase { protected static $theme = null; - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index e7410bbc3..b8594c496 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; class ThemeTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 79c03c443..9c316c032 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; class AttachManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index 52cb62fdf..d50debdb6 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e151d2304..e850b7752 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 6d87dbd3a..8fca6ff06 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index b97f4fa99..a0e897aa8 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Models\DeferredBinding; class DeferredBindingTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 64559dbc1..9bb165e39 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class HasManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index 7a5e30157..e6bd0f45e 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Phone; class HasOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 492b39505..4742b73b6 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; class ModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index e9b77aced..a7ba3041c 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class MorphManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index 4051e0b0c..e8c6f60fd 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\Meta; class MorphOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index 962f2d658..b0eb37ff7 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\EventLog; class MorphToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index b2f0ae019..49d46764c 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategoryNested; class NestedTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 0484409d4..9d6d61114 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\NullablePost; class NullableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index afee0c93d..c8fa19a54 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\RevisionablePost; class RevisionableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index fac6a48f3..31389dc0b 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategorySimple; class SimpleTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index f1f7aac71..22cad1837 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\SluggablePost; class SluggableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index 1e96b8ce4..686ce4306 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -10,7 +10,7 @@ use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; class SoftDeleteModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index ce1f4e382..4eba6d02c 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\ValidationPost; class ValidationModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 60e639c94..66fbd56a9 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -5,7 +5,7 @@ use System\Classes\CombineAssets; class CombineAssetsTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index 405c933d8..057d9501f 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\MarkupManager; class MarkupManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index 1271fc247..de82f3c7f 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\PluginManager; class PluginManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index b9934d3e5..5681a5a67 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\VersionManager; class VersionManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/traits/AssetMakerTest.php b/tests/unit/system/traits/AssetMakerTest.php index 352d66107..606df9562 100644 --- a/tests/unit/system/traits/AssetMakerTest.php +++ b/tests/unit/system/traits/AssetMakerTest.php @@ -10,7 +10,7 @@ class AssetMakerTest extends TestCase { private $stub; - public function setUp() + public function setUp() : void { $this->createApplication(); $this->stub = new AssetMakerStub(); From 74cc4d4666fca724642829818b61a09aba2bccb2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 16:28:20 +0800 Subject: [PATCH 008/100] Updated Composer dependencies for L6. Temporarily removed the October subsplits until they are supporting L6. --- composer.json | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index b638da9d7..eb1e73492 100644 --- a/composer.json +++ b/composer.json @@ -31,21 +31,19 @@ "source": "https://github.com/octobercms/october" }, "require": { - "php": ">=7.0.8", + "php": "^7.2", "ext-mbstring": "*", "ext-openssl": "*", - "october/rain": "~1.0", - "october/system": "~1.0", - "october/backend": "~1.0", - "october/cms": "~1.0", - "laravel/framework": "^5.5.40", + "october/rain": "dev-wip/laravel-5.9", + "laravel/framework": "^6.0", "wikimedia/composer-merge-plugin": "dev-master" }, "require-dev": { - "fzaninotto/faker": "~1.7", - "phpunit/phpunit": "~6.5", - "phpunit/phpunit-selenium": "~1.2", - "meyfa/phpunit-assert-gd": "1.1.0", + "fzaninotto/faker": "^1.9", + "phpunit/phpunit": "^8.0|^9.0", + "phpunit/phpunit-selenium": "dev-master", + "dms/phpunit-arraysubset-asserts": "^0.1.0", + "meyfa/phpunit-assert-gd": "^2.0", "squizlabs/php_codesniffer": "3.*", "jakub-onderka/php-parallel-lint": "^1.0" }, @@ -69,10 +67,7 @@ ] }, "config": { - "preferred-install": "dist", - "platform": { - "php": "7.0.8" - } + "preferred-install": "dist" }, "minimum-stability": "dev", "prefer-stable": true, From ae63b096f76682e2046063b6644e05c59e2dca2e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 16:33:06 +0800 Subject: [PATCH 009/100] Fix test case incompatibilities, clean up ignores --- .gitignore | 28 ++++++++++++------- phpunit.xml => phpunit.xml.dist | 20 ++++++------- tests/UiTestCase.php | 2 +- .../unit/backend/classes/AuthManagerTest.php | 4 +-- .../system/classes/AutoDatasourceTest.php | 4 +-- 5 files changed, 33 insertions(+), 25 deletions(-) rename phpunit.xml => phpunit.xml.dist (75%) diff --git a/.gitignore b/.gitignore index 4cd08cf9a..63a1b61d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,32 @@ -/bootstrap/compiled.php +# Composer ignores /vendor composer.phar -.DS_Store -.idea +composer.lock + +# Framework ignores .env .env.*.php .env.php +selenium.php +/bootstrap/compiled.php +.phpunit.result.cache + +# Hosting ignores php_errors.log nginx-error.log nginx-access.log nginx-ssl.access.log nginx-ssl.error.log -php-errors.log sftp-config.json .ftpconfig -selenium.php -composer.lock -package-lock.json -/node_modules + +# Editor ignores +nbproject +.idea +.vscode _ide_helper.php -# for netbeans -nbproject +# Other ignores +.DS_Store +package-lock.json +/node_modules diff --git a/phpunit.xml b/phpunit.xml.dist similarity index 75% rename from phpunit.xml rename to phpunit.xml.dist index 08cd19d58..580890228 100644 --- a/phpunit.xml +++ b/phpunit.xml.dist @@ -1,14 +1,14 @@ - diff --git a/tests/UiTestCase.php b/tests/UiTestCase.php index 66354672c..e95c3e9bf 100644 --- a/tests/UiTestCase.php +++ b/tests/UiTestCase.php @@ -1,6 +1,6 @@ createApplication(); @@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase ]); } - public function tearDown() + public function tearDown(): void { AuthManager::forgetInstance(); } diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 43410cc77..16e55dcb7 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -30,7 +30,7 @@ class AutoDatasourceTest extends PluginTestCase */ public $datasource; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -73,7 +73,7 @@ class AutoDatasourceTest extends PluginTestCase ]); } - public function tearDown() + public function tearDown(): void { foreach ($this->fixtures as $fixture) { $fixture->delete(); From 662b1c2e4505ce87559b4f13e06d67f62e972f9d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 16:35:01 +0800 Subject: [PATCH 010/100] Fix incompatible `where` method in CmsObjectCollection Signature for the `where` method changes in L6, so a wrapper has been put in place. --- modules/cms/classes/CmsObjectCollection.php | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index ffc7afc73..05e17e64a 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -1,5 +1,6 @@ applyWhereFilter($key, $operator, $value); + } + /** * Returns objects whose properties match the supplied value. * @param string $property @@ -42,7 +67,7 @@ class CmsObjectCollection extends CollectionBase * @param bool $strict * @return static */ - public function where($property, $value, $strict = true) + protected function applyWhereFilter($property, $value, $strict = true) { return $this->filter(function ($object) use ($property, $value, $strict) { From 2c529cf753b3e09a07e694aee3fc5ebf45b4407d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 17:27:38 +0800 Subject: [PATCH 011/100] Update unit tests to not rely on deprecated methods of testing --- .../backend/classes/NavigationManagerTest.php | 14 ++-- .../backend/helpers/BackendHelperTest.php | 4 +- .../cms/classes/CmsCompoundObjectTest.php | 28 ++++---- tests/unit/cms/classes/CmsObjectTest.php | 33 +++++----- tests/unit/cms/classes/CodeParserTest.php | 22 +++---- tests/unit/cms/classes/ControllerTest.php | 65 +++++++++---------- tests/unit/cms/classes/RouterTest.php | 4 +- tests/unit/cms/classes/ThemeTest.php | 9 ++- .../unit/system/classes/CombineAssetsTest.php | 4 +- .../unit/system/classes/MediaLibraryTest.php | 2 +- .../system/classes/VersionManagerTest.php | 4 +- 11 files changed, 89 insertions(+), 100 deletions(-) diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index 04ca2f183..f63645198 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -59,18 +59,18 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('posts', $items); $this->assertArrayHasKey('categories', $items); - $this->assertInternalType('object', $items['posts']); + $this->assertIsObject($items['posts']); $this->assertObjectHasAttribute('code', $items['posts']); $this->assertObjectHasAttribute('owner', $items['posts']); $this->assertEquals('posts', $items['posts']->code); $this->assertEquals('October.Tester', $items['posts']->owner); $this->assertObjectHasAttribute('permissions', $items['posts']); - $this->assertInternalType('array', $items['posts']->permissions); + $this->assertIsArray($items['posts']->permissions); $this->assertCount(1, $items['posts']->permissions); $this->assertObjectHasAttribute('order', $items['posts']); @@ -92,7 +92,7 @@ class NavigationManagerTest extends TestCase $items = $manager->listMainMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('OCTOBER.TESTER.PRINT', $items); $item = $items['OCTOBER.TESTER.PRINT']; @@ -143,10 +143,10 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('foo', $items); - $this->assertInternalType('object', $items['foo']); + $this->assertIsObject($items['foo']); $this->assertObjectHasAttribute('code', $items['foo']); $this->assertObjectHasAttribute('owner', $items['foo']); $this->assertObjectHasAttribute('order', $items['foo']); @@ -156,7 +156,7 @@ class NavigationManagerTest extends TestCase $this->assertEquals('October.Tester', $items['foo']->owner); $this->assertObjectHasAttribute('permissions', $items['foo']); - $this->assertInternalType('array', $items['foo']->permissions); + $this->assertIsArray($items['foo']->permissions); $this->assertCount(2, $items['foo']->permissions); $this->assertContains('october.tester.access_foo', $items['foo']->permissions); $this->assertContains('october.tester.access_bar', $items['foo']->permissions); diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index 61752a3c0..3766f5c8e 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -11,8 +11,8 @@ class BackendHelperTest extends TestCase $assets = $backendHelper->decompileAsset('tests/fixtures/backend/assets/compilation.js'); $this->assertCount(2, $assets); - $this->assertContains('file1.js', $assets[0]); - $this->assertContains('file2.js', $assets[1]); + $this->assertStringContainsString('file1.js', $assets[0]); + $this->assertStringContainsString('file2.js', $assets[1]); } public function testDecompileMissingFile() diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index c370dd290..ec82153f9 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase $theme = Theme::load('test'); $obj = TestCmsCompoundObject::load($theme, 'compound.htm'); - $this->assertContains("\$controller->data['something'] = 'some value'", $obj->code); + $this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('section', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -69,7 +69,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'component.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive', $obj->settings['components']); $this->assertArrayHasKey('posts-per-page', $obj->settings['components']['testArchive']); $this->assertEquals(10, $obj->settings['components']['testArchive']['posts-per-page']); @@ -82,7 +82,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'components.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive firstAlias', $obj->settings['components']); $this->assertArrayHasKey('October\Tester\Components\Post secondAlias', $obj->settings['components']); @@ -108,7 +108,7 @@ class CmsCompoundObjectTest extends TestCase $properties = $obj->getComponentProperties('October\Tester\Components\Post'); $emptyProperties = $obj->getComponentProperties('October\Tester\Components\Archive'); $notExistingProperties = $obj->getComponentProperties('This\Is\Not\Component'); - $this->assertInternalType('array', $properties); + $this->assertIsArray($properties); $this->assertArrayHasKey('show-featured', $properties); $this->assertTrue((bool)$properties['show-featured']); $this->assertEquals('true', $properties['show-featured']); @@ -148,18 +148,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -173,18 +173,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); } @@ -280,14 +280,14 @@ class CmsCompoundObjectTest extends TestCase $obj = TestParsedCmsCompoundObject::load($theme, 'viewbag.htm'); $this->assertNull($obj->code); $this->assertEquals('

Chop Suey!

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('viewBag', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['viewBag']); + $this->assertIsArray($obj->settings['components']['viewBag']); $this->assertArrayHasKey('title', $obj->settings['components']['viewBag']); $this->assertEquals('Toxicity', $obj->settings['components']['viewBag']['title']); diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 95d4f50d9..2c65761b4 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -148,12 +148,11 @@ class CmsObjectTest extends TestCase $this->assertNull($obj->something); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNameSymbol() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -164,12 +163,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNamePath() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -180,12 +178,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileSlash() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -196,12 +193,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage The File Name field is required - */ public function testFillEmptyFileName() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('The File Name field is required'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -266,11 +262,12 @@ class CmsObjectTest extends TestCase /** * @depends testRename - * @expectedException \October\Rain\Exception\ApplicationException - * @expectedExceptionMessage already exists */ public function testRenameToExistingFile() { + $this->expectException(\October\Rain\Exception\ApplicationException::class); + $this->expectExceptionMessageMatches('/already\sexists/'); + $theme = Theme::load('apitest'); $srcFilePath = $theme->getPath().'/testobjects/anotherobj.htm'; diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index ac3af4ca1..10d1b7d23 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -41,7 +41,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -78,7 +78,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -91,7 +91,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -101,7 +101,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -116,7 +116,7 @@ class CodeParserTest extends TestCase $property->setValue($parser, []); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('parser', $info['source']); $this->assertFileExists($info['filePath']); } @@ -131,7 +131,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -157,7 +157,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -191,7 +191,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -220,7 +220,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -255,7 +255,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -284,7 +284,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 53b3c911e..da16e317d 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -69,7 +69,7 @@ class ControllerTest extends TestCase $response = $controller->run('/some-page-that-doesnt-exist'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

Page not found

', $content); } @@ -83,16 +83,15 @@ class ControllerTest extends TestCase $response = $controller->run('/'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

My Webpage

', trim($content)); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage is not found - */ public function testLayoutNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-layout'); @@ -146,12 +145,11 @@ class ControllerTest extends TestCase $this->assertEquals("
LAYOUT CONTENT

This page is a subdirectory

", $response); } - /** - * @expectedException \Twig\Error\RuntimeError - * @expectedExceptionMessage is not found - */ public function testPartialNotFound() { + $this->expectException(\Twig\Error\RuntimeError::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-partial')->getContent(); @@ -193,12 +191,11 @@ class ControllerTest extends TestCase return $requestMock; } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage AJAX handler 'onNoHandler' was not found. - */ public function testAjaxHandlerNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('AJAX handler \'onNoHandler\' was not found.'); + Request::swap($this->configAjaxRequestMock('onNoHandler', '')); $theme = Theme::load('test'); @@ -206,12 +203,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid AJAX handler name: delete. - */ public function testAjaxInvalidHandlerName() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid AJAX handler name: delete.'); + Request::swap($this->configAjaxRequestMock('delete')); $theme = Theme::load('test'); @@ -219,12 +215,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid partial name: p:artial. - */ public function testAjaxInvalidPartial() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid partial name: p:artial.'); + Request::swap($this->configAjaxRequestMock('onTest', 'p:artial')); $theme = Theme::load('test'); @@ -232,12 +227,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage The partial 'partial' is not found. - */ public function testAjaxPartialNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('The partial \'partial\' is not found.'); + Request::swap($this->configAjaxRequestMock('onTest', 'partial')); $theme = Theme::load('test'); @@ -255,7 +249,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -272,7 +266,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -289,7 +283,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(2, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -303,7 +297,7 @@ class ControllerTest extends TestCase $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-component')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -331,7 +325,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-components')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('firstAlias', $page->components); $this->assertArrayHasKey('secondAlias', $page->components); @@ -363,19 +357,18 @@ ESC; $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); $this->assertEquals('page', $content['ajax-result']); } - /** - * @expectedException October\Rain\Exception\SystemException - * @expectedExceptionMessage is not registered for the component - */ public function testComponentClassNotFound() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessageMatches('/is\snot\sregistered\sfor\sthe\scomponent/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-component-class')->getContent(); diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index 5dd9e57db..f4eeda27a 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -43,7 +43,7 @@ class RouterTest extends TestCase $this->assertFalse($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); /* @@ -52,7 +52,7 @@ class RouterTest extends TestCase $value = $method->invoke($router); $this->assertTrue($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); } diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 85802af7b..07a15f2db 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -44,7 +44,7 @@ class ThemeTest extends TestCase $pageCollection = $theme->listPages(); $pages = array_values($pageCollection->all()); - $this->assertInternalType('array', $pages); + $this->assertIsArray($pages); $expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages'); $this->assertCount($expectedPageNum, $pages); @@ -63,12 +63,11 @@ class ThemeTest extends TestCase $this->assertEquals('test', $activeTheme->getDirName()); } - /** - * @expectedException \October\Rain\Exception\SystemException - * @expectedExceptionMessage The active theme is not set. - */ public function testNoActiveTheme() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessage('The active theme is not set.'); + Config::set('cms.activeTheme', null); Theme::getActiveTheme(); } diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 0e1860d89..5c5d313bd 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -24,10 +24,10 @@ class CombineAssetsTest extends TestCase * Supported file extensions should exist */ $jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions'); - $this->assertInternalType('array', $jsExt); + $this->assertIsArray($jsExt); $cssExt = self::getProtectedProperty($combiner, 'cssExtensions'); - $this->assertInternalType('array', $cssExt); + $this->assertIsArray($cssExt); /* * Check service methods diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 24bab1567..5d8082ba5 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -60,6 +60,6 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine public function testValidPathsOnValidatePath($path) { $result = MediaLibrary::validatePath($path); - $this->assertInternalType('string', $result); + $this->assertIsString($result); } } diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index 5681a5a67..ba163d18c 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -100,8 +100,8 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]); - $this->assertInternalType('array', $comments); - $this->assertInternalType('array', $scripts); + $this->assertIsArray($comments); + $this->assertIsArray($scripts); $this->assertEquals($expectedComments, $comments); $this->assertEquals($expectedScripts, $scripts); From a4c34d1dea497cfe6fdacb51dbe9d20b21f8711a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:26:21 +0800 Subject: [PATCH 012/100] Fix more exception tests --- tests/unit/plugins/database/ModelTest.php | 7 +++---- tests/unit/plugins/database/SimpleTreeModelTest.php | 7 +++---- tests/unit/plugins/database/ValidationModelTest.php | 5 ++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 4742b73b6..16bf07c1d 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -23,12 +23,11 @@ class ModelTest extends PluginTestCase $this->assertEquals(1, $post->id); } - /** - * @expectedException \Illuminate\Database\Eloquent\MassAssignmentException - * @expectedExceptionMessage title - */ public function testGuardedAttribute() { + $this->expectException(\Illuminate\Database\Eloquent\MassAssignmentException::class); + $this->expectExceptionMessageMatches('/title/'); + Post::create(['title' => 'Hi!', 'slug' => 'authenticity']); } } diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index 31389dc0b..c22b11d16 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -165,12 +165,11 @@ class SimpleTreeModelTest extends PluginTestCase ], $array); } - /** - * @expectedException \Exception - * @expectedExceptionMessage Column mismatch in listsNested method - */ public function testListsNestedUnknownColumn() { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Column mismatch in listsNested method'); + CategorySimple::listsNested('custom_name', 'id'); } diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index 4eba6d02c..f5ddb55b7 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -13,11 +13,10 @@ class ValidationModelTest extends PluginTestCase $this->runPluginRefreshCommand('Database.Tester'); } - /** - * @expectedException October\Rain\Database\ModelException - */ public function testUniqueTableValidation() { + $this->expectException(\October\Rain\Database\ModelException::class); + $post = ValidationPost::create([ 'title' => 'This is a new post', 'slug' => 'post-1', From 4302f8d4f5aa19dcb8896322cd73eee281196b00 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:26:46 +0800 Subject: [PATCH 013/100] Change module seeders to only temporary unguard attributes --- modules/backend/database/seeds/DatabaseSeeder.php | 6 +++--- modules/system/database/seeds/DatabaseSeeder.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php index 7be5f9885..78c561441 100644 --- a/modules/backend/database/seeds/DatabaseSeeder.php +++ b/modules/backend/database/seeds/DatabaseSeeder.php @@ -12,8 +12,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + Eloquent::unguarded(function () { + $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + }); } } diff --git a/modules/system/database/seeds/DatabaseSeeder.php b/modules/system/database/seeds/DatabaseSeeder.php index f2f924b13..7dec41339 100644 --- a/modules/system/database/seeds/DatabaseSeeder.php +++ b/modules/system/database/seeds/DatabaseSeeder.php @@ -13,8 +13,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + Eloquent::unguarded(function () { + $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + }); } } From 8279bc63e4363cd01b69176bf42c86dcc28909dd Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:27:12 +0800 Subject: [PATCH 014/100] Seed admin password that passes our current validation rules --- modules/backend/database/seeds/SeedSetupAdmin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backend/database/seeds/SeedSetupAdmin.php b/modules/backend/database/seeds/SeedSetupAdmin.php index e161a41d8..a58015120 100644 --- a/modules/backend/database/seeds/SeedSetupAdmin.php +++ b/modules/backend/database/seeds/SeedSetupAdmin.php @@ -9,7 +9,7 @@ class SeedSetupAdmin extends Seeder { public static $email = 'admin@domain.tld'; public static $login = 'admin'; - public static $password = 'admin'; + public static $password = 'admin1234'; public static $firstName = 'Admin'; public static $lastName = 'Person'; From 9be2decce38c06a5d97e6cb9386b18ef758eb388 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:41:28 +0800 Subject: [PATCH 015/100] Fix guarding for fixtures now that models aren't unguarded by seeders --- tests/unit/system/classes/AutoDatasourceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 16e55dcb7..14dc6f54b 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Datasource\FileDatasource; class CmsThemeTemplateFixture extends Model { - protected $fillable = ['*']; + protected $guarded = []; public $timestamps = false; From d6fc30626cd6496477c093dfa6572ff8e80b405b Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:41:43 +0800 Subject: [PATCH 016/100] Un-mark skipped test - seems to be working --- tests/unit/plugins/database/HasManyModelTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 9bb165e39..9a1def095 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -63,8 +63,6 @@ class HasManyModelTest extends PluginTestCase public function testGetRelationValue() { - $this->markTestSkipped('Marked as \'skipped\' for further investigation'); - Model::unguard(); $author = Author::create(['name' => 'Stevie']); $post1 = Post::create(['title' => "First post", 'author_id' => $author->id]); From 852ed1afb8265e74511f1b227fc1c7d77ee55bf1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:56:24 +0800 Subject: [PATCH 017/100] Remove deprecated `getNotes` method from UpdateManager --- modules/system/classes/UpdateManager.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index a70f71937..87ea60403 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -348,11 +348,9 @@ class UpdateManager * Rollback modules */ while (true) { - $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); - - foreach ($this->migrator->getNotes() as $note) { - $this->note($note); - } + $rolledBack = $this->migrator + ->setOutput($this->notesOutput) + ->rollback($paths, ['pretend' => false]); if (count($rolledBack) == 0) { break; @@ -405,13 +403,11 @@ class UpdateManager */ public function migrateModule($module) { - $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); - $this->note($module); - foreach ($this->migrator->getNotes() as $note) { - $this->note(' - '.$note); - } + $this->migrator + ->setOutput($this->notesOutput) + ->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); return $this; } From 078eb9191c01437ca32bd97a3cc8f3293a720829 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 22:58:13 +0800 Subject: [PATCH 018/100] Ensure October helpers are loaded before Laravel helpers --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 072b795da..8b70b1a62 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,4 +41,4 @@ jobs: - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . - ./vendor/bin/phpunit + ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php From 56785bcd3cbd88ab709815e1427a86b7de55621e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 23:15:02 +0800 Subject: [PATCH 019/100] Fix missing output when UpdateManager is run within the Backend --- modules/system/classes/UpdateManager.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 87ea60403..8fe43604d 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -348,9 +348,11 @@ class UpdateManager * Rollback modules */ while (true) { - $rolledBack = $this->migrator - ->setOutput($this->notesOutput) - ->rollback($paths, ['pretend' => false]); + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } + + $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); if (count($rolledBack) == 0) { break; @@ -405,9 +407,11 @@ class UpdateManager { $this->note($module); - $this->migrator - ->setOutput($this->notesOutput) - ->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } + + $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); return $this; } From f9d7c79de5f00bb9cd3b1c2efc25fc9929c6eb23 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 23:16:50 +0800 Subject: [PATCH 020/100] Link Input alias to October's Input facade --- modules/system/aliases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/aliases.php b/modules/system/aliases.php index bcb2c78b2..01a04ec96 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,7 +16,7 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, + 'Input' => October\Rain\Support\Facades\Input::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, From cf67e8359845f52e6bb9df542057503ad9d6dcb9 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 19 Jan 2020 23:23:27 +0800 Subject: [PATCH 021/100] Make slug route param optional for CMS module. This allows the home page to load. --- modules/cms/routes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms/routes.php b/modules/cms/routes.php index 76f35c4b3..1aaf5ec5d 100644 --- a/modules/cms/routes.php +++ b/modules/cms/routes.php @@ -22,7 +22,7 @@ App::before(function ($request) { * The CMS module intercepts all URLs that were not * handled by the back-end modules. */ - Route::any('{slug}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); + Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); /** * @event cms.route From e81101c875af1a59a9cc9b2b3557b535f4c2689c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 11:51:08 +0800 Subject: [PATCH 022/100] Use updated branch name for library --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eb1e73492..3fa2cbf32 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "php": "^7.2", "ext-mbstring": "*", "ext-openssl": "*", - "october/rain": "dev-wip/laravel-5.9", + "october/rain": "dev-wip/laravel-6", "laravel/framework": "^6.0", "wikimedia/composer-merge-plugin": "dev-master" }, From a7904d62881f9a59c0c37bf49cb103605e75ed33 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 12:33:30 +0800 Subject: [PATCH 023/100] Drop PHP 7.1 tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b70b1a62..aaaf69350 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 6 matrix: - phpVersions: ['7.1', '7.2', '7.3', '7.4'] + phpVersions: ['7.2', '7.3', '7.4'] fail-fast: false name: PHP ${{ matrix.phpVersions }} steps: From ad271163627e45d3641315a73dce6073aa794e30 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 12:37:03 +0800 Subject: [PATCH 024/100] Drop code reset and library replacement in automated tests, for now --- .github/workflows/tests.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aaaf69350..29713edeb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,14 +26,6 @@ jobs: extension-csv: mbstring, intl, gd, xml, sqlite - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload - name: Run post-update Composer scripts run: | php artisan october:util set build From b2d2d34c102c245d338c6dcb9a7eddeface5d7a7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 12:40:10 +0800 Subject: [PATCH 025/100] Fix code quality errors --- config/hashing.php | 2 +- config/logging.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/hashing.php b/config/hashing.php index bc5da3a6e..842577087 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -49,4 +49,4 @@ return [ 'time' => 2, ], -]; \ No newline at end of file +]; diff --git a/config/logging.php b/config/logging.php index 372fc9e4b..d09cd7d29 100644 --- a/config/logging.php +++ b/config/logging.php @@ -91,4 +91,4 @@ return [ ], ], -]; \ No newline at end of file +]; From 9ecad139c4c200dfd94a3c96b537b2d93a99e97d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 13:59:29 +0800 Subject: [PATCH 026/100] Comment tweak for CmsObjectCollection::where() --- modules/cms/classes/CmsObjectCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index 05e17e64a..c6a5ad52e 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -39,7 +39,7 @@ class CmsObjectCollection extends CollectionBase /** * Returns objects whose properties match the supplied value. * - * This is a wrapper for our custom `filterWhere` method, as the signature of this method changed in Laravel 6.0. + * This is a wrapper for our custom `applyWhereFilter` method, as the signature of this method changed in Laravel 6. * * @param string $key * @param mixed $operator From e099b7bdeea9f9619196cb288da9603aae016ea7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 14:33:14 +0800 Subject: [PATCH 027/100] Move autoloading to index to make it the same as Laravel 6. Implements work done in https://github.com/octobercms/october/pull/4280. Credit to @Samuell1 --- artisan | 28 +++++++++++++++++-- bootstrap/autoload.php | 54 ------------------------------------ index.php | 47 +++++++++++++++++++++++++------ tests/bootstrap.php | 6 ---- tests/functional/phpunit.xml | 4 +-- 5 files changed, 66 insertions(+), 73 deletions(-) delete mode 100644 bootstrap/autoload.php diff --git a/artisan b/artisan index 961e94d0b..1d451b277 100644 --- a/artisan +++ b/artisan @@ -1,6 +1,28 @@ #!/usr/bin/env php make('Illuminate\Contracts\Console\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +70,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); \ No newline at end of file +exit($status); diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php deleted file mode 100644 index b980622d7..000000000 --- a/bootstrap/autoload.php +++ /dev/null @@ -1,54 +0,0 @@ -make('Illuminate\Contracts\Http\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e7e53fd1a..013490740 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,10 +1,4 @@ ./
-
\ No newline at end of file + From 60d6825c0d118822be3e0c807b9e7360d1d08ee7 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 15:11:38 +0800 Subject: [PATCH 028/100] Partially revert and restore bootstrap/autoload.php As discussed with @daftspunk, we now have to hard-code some overrides to ensure our helpers are loaded first. To keep the code DRY and self-explanatory, we do need to keep the autoload.php file. --- artisan | 28 +++++++++------------------- bootstrap/autoload.php | 35 +++++++++++++++++++++++++++++++++++ index.php | 30 ++++-------------------------- tests/functional/phpunit.xml | 2 +- 4 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 bootstrap/autoload.php diff --git a/artisan b/artisan index 1d451b277..df3993e90 100644 --- a/artisan +++ b/artisan @@ -5,38 +5,28 @@ define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- -| Register Core Helpers +| Bootstrap dependencies |-------------------------------------------------------------------------- | -| We cannot rely on Composer's load order when calculating the weight of -| each package. This line ensures that the core global helpers are -| always given priority one status. +| We use a custom bootstrap autoloader to load some October dependencies +| first, before bringing in all dependencies from Composer. | */ -$helperPath = __DIR__.'/vendor/october/rain/src/Support/helpers.php'; - -if (!file_exists($helperPath)) { - echo 'Missing vendor files, try running "composer install" or use the Wizard installer.'.PHP_EOL; - exit(1); -} - -require $helperPath; +require __DIR__.'/bootstrap/autoload.php'; /* |-------------------------------------------------------------------------- -| Register The Auto Loader +| Turn On The Lights |-------------------------------------------------------------------------- | -| Composer provides a convenient, automatically generated class loader -| for our application. We just need to utilize it! We'll require it -| into the script here so that we do not have to worry about the -| loading of any our classes "manually". Feels great to relax. +| We need to illuminate PHP development, so let us turn on the lights. +| This bootstraps the framework and gets it ready for use, then it +| will load up this application so that we can run it and send +| the responses back to the browser and delight our users. | */ -require __DIR__.'/vendor/autoload.php'; - $app = require_once __DIR__.'/bootstrap/app.php'; /* diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php new file mode 100644 index 000000000..9a131647a --- /dev/null +++ b/bootstrap/autoload.php @@ -0,0 +1,35 @@ + Date: Mon, 20 Jan 2020 16:08:15 +0800 Subject: [PATCH 029/100] Add Composer scripts --- composer.json | 9 ++++++ config/app.php | 6 ++-- config/cache.php | 2 +- config/cms.php | 10 +++--- config/database.php | 42 ++++++++++++------------- config/mail.php | 12 +++---- config/queue.php | 2 +- config/session.php | 2 +- storage/framework/cache/data/.gitignore | 2 -- themes/demo/pages/404.htm | 15 ++++----- 10 files changed, 55 insertions(+), 47 deletions(-) delete mode 100644 storage/framework/cache/data/.gitignore diff --git a/composer.json b/composer.json index 3fa2cbf32..0f630b0e0 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,15 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" + ], + "test": [ + "phpunit --stop-on-failure --prepend ./vendor/october/rain/src/Support/helpers.php" + ], + "lint": [ + "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." + ], + "sniff": [ + "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { diff --git a/config/app.php b/config/app.php index e0c727196..838123183 100644 --- a/config/app.php +++ b/config/app.php @@ -16,7 +16,7 @@ return [ | */ - 'debug' => true, + 'debug' => env('APP_DEBUG', true), /* |-------------------------------------------------------------------------- @@ -41,7 +41,7 @@ return [ | */ - 'url' => 'http://localhost', + 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- @@ -101,7 +101,7 @@ return [ | */ - 'key' => 'CHANGE_ME!!!!!!!!!!!!!!!!!!!!!!!', + 'key' => env('APP_KEY', ''), 'cipher' => 'AES-256-CBC', diff --git a/config/cache.php b/config/cache.php index 251771724..ba95b1801 100644 --- a/config/cache.php +++ b/config/cache.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => 'file', + 'default' => env('CACHE_DRIVER', 'file'), /* |-------------------------------------------------------------------------- diff --git a/config/cms.php b/config/cms.php index 4c838a026..b5b5eb135 100644 --- a/config/cms.php +++ b/config/cms.php @@ -156,7 +156,7 @@ return [ | */ - 'enableRoutesCache' => false, + 'enableRoutesCache' => env('ROUTES_CACHE', false), /* |-------------------------------------------------------------------------- @@ -196,7 +196,7 @@ return [ | */ - 'enableAssetCache' => false, + 'enableAssetCache' => env('ASSET_CACHE', false), /* |-------------------------------------------------------------------------- @@ -250,7 +250,7 @@ return [ | */ - 'databaseTemplates' => false, + 'databaseTemplates' => env('DATABASE_TEMPLATES', false), /* |-------------------------------------------------------------------------- @@ -343,7 +343,7 @@ return [ | */ - 'linkPolicy' => 'detect', + 'linkPolicy' => env('LINK_POLICY', 'detect'), /* |-------------------------------------------------------------------------- @@ -379,7 +379,7 @@ return [ | */ - 'enableCsrfProtection' => true, + 'enableCsrfProtection' => env('ENABLE_CSRF', true), /* |-------------------------------------------------------------------------- diff --git a/config/database.php b/config/database.php index 70f1420c6..8087772e9 100644 --- a/config/database.php +++ b/config/database.php @@ -26,7 +26,7 @@ return [ | */ - 'default' => 'mysql', + 'default' => env('DB_CONNECTION', 'mysql'), /* |-------------------------------------------------------------------------- @@ -48,18 +48,18 @@ return [ 'sqlite' => [ 'driver' => 'sqlite', - 'database' => 'storage/database.sqlite', + 'database' => env('DB_DATABASE', 'storage/database.sqlite'), 'prefix' => '', ], 'mysql' => [ 'driver' => 'mysql', 'engine' => 'InnoDB', - 'host' => 'localhost', - 'port' => 3306, - 'database' => 'database', - 'username' => 'root', - 'password' => '', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', @@ -68,11 +68,11 @@ return [ 'pgsql' => [ 'driver' => 'pgsql', - 'host' => 'localhost', - 'port' => 5432, - 'database' => 'database', - 'username' => 'root', - 'password' => '', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', @@ -80,11 +80,11 @@ return [ 'sqlsrv' => [ 'driver' => 'sqlsrv', - 'host' => 'localhost', - 'port' => 1433, - 'database' => 'database', - 'username' => 'root', - 'password' => '', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), 'prefix' => '', ], @@ -119,9 +119,9 @@ return [ 'cluster' => false, 'default' => [ - 'host' => '127.0.0.1', - 'password' => null, - 'port' => 6379, + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', ''), + 'port' => env('REDIS_PORT', 6379), 'database' => 0, ], @@ -142,5 +142,5 @@ return [ | */ - 'useConfigForTesting' => false, + 'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false), ]; diff --git a/config/mail.php b/config/mail.php index 7c2332d2f..2823eac3a 100644 --- a/config/mail.php +++ b/config/mail.php @@ -16,7 +16,7 @@ return [ | */ - 'driver' => 'smtp', + 'driver' => env('MAIL_DRIVER', 'smtp'), /* |-------------------------------------------------------------------------- @@ -29,7 +29,7 @@ return [ | */ - 'host' => 'smtp.mailgun.org', + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), /* |-------------------------------------------------------------------------- @@ -42,7 +42,7 @@ return [ | */ - 'port' => 587, + 'port' => env('MAIL_PORT', 587), /* |-------------------------------------------------------------------------- @@ -68,7 +68,7 @@ return [ | */ - 'encryption' => 'tls', + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), /* |-------------------------------------------------------------------------- @@ -81,7 +81,7 @@ return [ | */ - 'username' => null, + 'username' => env('MAIL_USERNAME', ''), /* |-------------------------------------------------------------------------- @@ -94,7 +94,7 @@ return [ | */ - 'password' => null, + 'password' => env('MAIL_PASSWORD', ''), /* |-------------------------------------------------------------------------- diff --git a/config/queue.php b/config/queue.php index de27ab7f1..e14dfac78 100644 --- a/config/queue.php +++ b/config/queue.php @@ -16,7 +16,7 @@ return [ | */ - 'default' => 'sync', + 'default' => env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index ab762f221..194747a76 100644 --- a/config/session.php +++ b/config/session.php @@ -16,7 +16,7 @@ return [ | */ - 'driver' => 'file', + 'driver' => env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore deleted file mode 100644 index c96a04f00..000000000 --- a/storage/framework/cache/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/themes/demo/pages/404.htm b/themes/demo/pages/404.htm index 984fb0e77..56246825d 100644 --- a/themes/demo/pages/404.htm +++ b/themes/demo/pages/404.htm @@ -1,10 +1,11 @@ title = "Page not found (404)" -url = "/404" +url = "/404/:slug" layout = "default" +is_hidden = 0 == -
-
-

Page not found

-

We're sorry, but the page you requested cannot be found.

-
-
\ No newline at end of file +<div class="jumbotron"> + <div class="container"> + <h1>Page not found</h1> + <p>We're sorry, but the page you requested cannot be found.</p> + </div> +</div> \ No newline at end of file From 86351cec7fd318b79c1a6c0813e4028b7587431a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 16:14:00 +0800 Subject: [PATCH 030/100] Revert "Add Composer scripts" This reverts commit a1cfc2aa58c2fa88c186a4306381fdcf1cd4f269. --- composer.json | 9 ------ config/app.php | 6 ++-- config/cache.php | 2 +- config/cms.php | 10 +++--- config/database.php | 42 ++++++++++++------------- config/mail.php | 12 +++---- config/queue.php | 2 +- config/session.php | 2 +- storage/framework/cache/data/.gitignore | 2 ++ themes/demo/pages/404.htm | 15 +++++---- 10 files changed, 47 insertions(+), 55 deletions(-) create mode 100644 storage/framework/cache/data/.gitignore diff --git a/composer.json b/composer.json index 0f630b0e0..3fa2cbf32 100644 --- a/composer.json +++ b/composer.json @@ -64,15 +64,6 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" - ], - "test": [ - "phpunit --stop-on-failure --prepend ./vendor/october/rain/src/Support/helpers.php" - ], - "lint": [ - "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." - ], - "sniff": [ - "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { diff --git a/config/app.php b/config/app.php index 838123183..e0c727196 100644 --- a/config/app.php +++ b/config/app.php @@ -16,7 +16,7 @@ return [ | */ - 'debug' => env('APP_DEBUG', true), + 'debug' => true, /* |-------------------------------------------------------------------------- @@ -41,7 +41,7 @@ return [ | */ - 'url' => env('APP_URL', 'http://localhost'), + 'url' => 'http://localhost', /* |-------------------------------------------------------------------------- @@ -101,7 +101,7 @@ return [ | */ - 'key' => env('APP_KEY', ''), + 'key' => 'CHANGE_ME!!!!!!!!!!!!!!!!!!!!!!!', 'cipher' => 'AES-256-CBC', diff --git a/config/cache.php b/config/cache.php index ba95b1801..251771724 100644 --- a/config/cache.php +++ b/config/cache.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => env('CACHE_DRIVER', 'file'), + 'default' => 'file', /* |-------------------------------------------------------------------------- diff --git a/config/cms.php b/config/cms.php index b5b5eb135..4c838a026 100644 --- a/config/cms.php +++ b/config/cms.php @@ -156,7 +156,7 @@ return [ | */ - 'enableRoutesCache' => env('ROUTES_CACHE', false), + 'enableRoutesCache' => false, /* |-------------------------------------------------------------------------- @@ -196,7 +196,7 @@ return [ | */ - 'enableAssetCache' => env('ASSET_CACHE', false), + 'enableAssetCache' => false, /* |-------------------------------------------------------------------------- @@ -250,7 +250,7 @@ return [ | */ - 'databaseTemplates' => env('DATABASE_TEMPLATES', false), + 'databaseTemplates' => false, /* |-------------------------------------------------------------------------- @@ -343,7 +343,7 @@ return [ | */ - 'linkPolicy' => env('LINK_POLICY', 'detect'), + 'linkPolicy' => 'detect', /* |-------------------------------------------------------------------------- @@ -379,7 +379,7 @@ return [ | */ - 'enableCsrfProtection' => env('ENABLE_CSRF', true), + 'enableCsrfProtection' => true, /* |-------------------------------------------------------------------------- diff --git a/config/database.php b/config/database.php index 8087772e9..70f1420c6 100644 --- a/config/database.php +++ b/config/database.php @@ -26,7 +26,7 @@ return [ | */ - 'default' => env('DB_CONNECTION', 'mysql'), + 'default' => 'mysql', /* |-------------------------------------------------------------------------- @@ -48,18 +48,18 @@ return [ 'sqlite' => [ 'driver' => 'sqlite', - 'database' => env('DB_DATABASE', 'storage/database.sqlite'), + 'database' => 'storage/database.sqlite', 'prefix' => '', ], 'mysql' => [ 'driver' => 'mysql', 'engine' => 'InnoDB', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 3306), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), + 'host' => 'localhost', + 'port' => 3306, + 'database' => 'database', + 'username' => 'root', + 'password' => '', 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', @@ -68,11 +68,11 @@ return [ 'pgsql' => [ 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 5432), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), + 'host' => 'localhost', + 'port' => 5432, + 'database' => 'database', + 'username' => 'root', + 'password' => '', 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', @@ -80,11 +80,11 @@ return [ 'sqlsrv' => [ 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 5432), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), + 'host' => 'localhost', + 'port' => 1433, + 'database' => 'database', + 'username' => 'root', + 'password' => '', 'prefix' => '', ], @@ -119,9 +119,9 @@ return [ 'cluster' => false, 'default' => [ - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', ''), - 'port' => env('REDIS_PORT', 6379), + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, 'database' => 0, ], @@ -142,5 +142,5 @@ return [ | */ - 'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false), + 'useConfigForTesting' => false, ]; diff --git a/config/mail.php b/config/mail.php index 2823eac3a..7c2332d2f 100644 --- a/config/mail.php +++ b/config/mail.php @@ -16,7 +16,7 @@ return [ | */ - 'driver' => env('MAIL_DRIVER', 'smtp'), + 'driver' => 'smtp', /* |-------------------------------------------------------------------------- @@ -29,7 +29,7 @@ return [ | */ - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'host' => 'smtp.mailgun.org', /* |-------------------------------------------------------------------------- @@ -42,7 +42,7 @@ return [ | */ - 'port' => env('MAIL_PORT', 587), + 'port' => 587, /* |-------------------------------------------------------------------------- @@ -68,7 +68,7 @@ return [ | */ - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'encryption' => 'tls', /* |-------------------------------------------------------------------------- @@ -81,7 +81,7 @@ return [ | */ - 'username' => env('MAIL_USERNAME', ''), + 'username' => null, /* |-------------------------------------------------------------------------- @@ -94,7 +94,7 @@ return [ | */ - 'password' => env('MAIL_PASSWORD', ''), + 'password' => null, /* |-------------------------------------------------------------------------- diff --git a/config/queue.php b/config/queue.php index e14dfac78..de27ab7f1 100644 --- a/config/queue.php +++ b/config/queue.php @@ -16,7 +16,7 @@ return [ | */ - 'default' => env('QUEUE_CONNECTION', 'sync'), + 'default' => 'sync', /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index 194747a76..ab762f221 100644 --- a/config/session.php +++ b/config/session.php @@ -16,7 +16,7 @@ return [ | */ - 'driver' => env('SESSION_DRIVER', 'file'), + 'driver' => 'file', /* |-------------------------------------------------------------------------- diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/themes/demo/pages/404.htm b/themes/demo/pages/404.htm index 56246825d..984fb0e77 100644 --- a/themes/demo/pages/404.htm +++ b/themes/demo/pages/404.htm @@ -1,11 +1,10 @@ title = "Page not found (404)" -url = "/404/:slug" +url = "/404" layout = "default" -is_hidden = 0 == -<div class="jumbotron"> - <div class="container"> - <h1>Page not found</h1> - <p>We're sorry, but the page you requested cannot be found.</p> - </div> -</div> \ No newline at end of file +
+
+

Page not found

+

We're sorry, but the page you requested cannot be found.

+
+
\ No newline at end of file From 0a6372033572d752d541138f33431c45566bd4b3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 16:15:20 +0800 Subject: [PATCH 031/100] Add JUST the Composer scripts --- composer.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/composer.json b/composer.json index 3fa2cbf32..0f630b0e0 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,15 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" + ], + "test": [ + "phpunit --stop-on-failure --prepend ./vendor/october/rain/src/Support/helpers.php" + ], + "lint": [ + "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." + ], + "sniff": [ + "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { From 11b7111413a297e2ebb07902be4c66d514c33635 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 16:25:50 +0800 Subject: [PATCH 032/100] Use old signature for `where` method in CmsObjectCollection. Refs: https://github.com/octobercms/october/pull/4893#discussion_r368408407 --- modules/cms/classes/CmsObjectCollection.php | 35 +++++++++------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index c6a5ad52e..2105789ac 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -39,38 +39,31 @@ class CmsObjectCollection extends CollectionBase /** * Returns objects whose properties match the supplied value. * - * This is a wrapper for our custom `applyWhereFilter` method, as the signature of this method changed in Laravel 6. + * Note that this deviates from Laravel 6's Illuminate\Support\Traits\EnumeratesValues::where() method signature, + * which uses ($key, $operator = null, $value = null) as parameters and that this class extends. * - * @param string $key - * @param mixed $operator - * @param mixed $value + * To ensure backwards compatibility with our current Halcyon functionality, this method retains the original + * parameters and functions the same way as before, with handling for the $value and $strict parameters to ensure + * they match the previously expected formats. This means that you cannot use operators for "where" queries on + * CMS object collections. + * + * @param string $property + * @param string $value + * @param bool $strict * @return static */ - public function where($key, $operator = null, $value = null) + public function where($property, $value = null, $strict = null) { - if (empty($operator) || !is_string($operator)) { + if (empty($value) || !is_string($value)) { throw new ApplicationException('You must provide a string value to compare with when executing a "where" ' . 'query for CMS object collections.'); } - if (!isset($value) || !is_bool($value)) { - $value = true; + if (!isset($strict) || !is_bool($strict)) { + $strict = true; } - return $this->applyWhereFilter($key, $operator, $value); - } - - /** - * Returns objects whose properties match the supplied value. - * @param string $property - * @param string $value - * @param bool $strict - * @return static - */ - protected function applyWhereFilter($property, $value, $strict = true) - { return $this->filter(function ($object) use ($property, $value, $strict) { - if (!array_key_exists($property, $object->settings)) { return false; } From 4716084f14cf4bd1353b4a1cabea8cfbdbf917c1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 16:39:23 +0800 Subject: [PATCH 033/100] Change User rules to use min for character lengths --- modules/backend/models/User.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index a0121e5c7..3decb8066 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -24,10 +24,10 @@ class User extends UserBase * Validation rules */ public $rules = [ - 'email' => 'required|between:6,191|email|unique:backend_users', - 'login' => 'required|between:2,191|unique:backend_users', - 'password' => 'required:create|between:8,191|confirmed', - 'password_confirmation' => 'required_with:password|between:8,191' + 'email' => 'required|min:6|email|unique:backend_users', + 'login' => 'required|min:2|unique:backend_users', + 'password' => 'required:create|min:8|confirmed', + 'password_confirmation' => 'required_with:password|min:8' ]; /** From 12208e526fda7f5e2ba09723ed3869dc46c147ba Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jan 2020 16:48:09 +0800 Subject: [PATCH 034/100] Adjust User rules to limit lengths of emails and logins --- modules/backend/models/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 3decb8066..8efaf6bb3 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -24,8 +24,8 @@ class User extends UserBase * Validation rules */ public $rules = [ - 'email' => 'required|min:6|email|unique:backend_users', - 'login' => 'required|min:2|unique:backend_users', + 'email' => 'required|between:6,255|email|unique:backend_users', + 'login' => 'required|between:2,255|unique:backend_users', 'password' => 'required:create|min:8|confirmed', 'password_confirmation' => 'required_with:password|min:8' ]; From 12f5c1794e2b0a0bac71e3954449857d9ccdc6f2 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 21 Jan 2020 08:29:48 +0800 Subject: [PATCH 035/100] Use default October log path for system logs --- config/logging.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/logging.php b/config/logging.php index d09cd7d29..329b7a934 100644 --- a/config/logging.php +++ b/config/logging.php @@ -42,13 +42,13 @@ return [ 'single' => [ 'driver' => 'single', - 'path' => storage_path('logs/laravel.log'), + 'path' => storage_path('logs/system.log'), 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', - 'path' => storage_path('logs/laravel.log'), + 'path' => storage_path('logs/system.log'), 'level' => 'debug', 'days' => 14, ], From c7b85fcd6e429c68906dd49a7052c99058c2d6ee Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 21 Jan 2020 08:44:10 +0800 Subject: [PATCH 036/100] Default to using Bootstrap 3 pagination. L5.6 introduced Bootstrap 4 validation. While it is likely our pagination is all custom and is using the Storm UI, just to be safe, we'll make sure pagination is using Bootstrap 3 classes in all circumstances to retain current functionality. This should have no effect on simple pagination, which is overriden with our custom template on the next line. --- modules/system/ServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index d01a01d0d..f87c136ae 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -94,6 +94,7 @@ class ServiceProvider extends ModuleServiceProvider } } + Paginator::useBootstrapThree(); Paginator::defaultSimpleView('system::pagination.simple-default'); /* From 0c2dc29f22f13bfb398c1baa0d00510cf19b005f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 21 Jan 2020 08:50:44 +0800 Subject: [PATCH 037/100] Add Discord URL --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 0f630b0e0..3e0c939e5 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", "irc": "irc://irc.freenode.net/october", + "chat": "https://discord.gg/gEKgwSZ", "source": "https://github.com/octobercms/october" }, "require": { From e33032441b12d50bb37d0bfee81e8000f7c61aa3 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 21 Jan 2020 09:03:49 +0800 Subject: [PATCH 038/100] Drop monkey-patch on mock objects generator. Reverts https://github.com/octobercms/october/commit/72f85ebc558a8e6fe514f6496f5393e535bac41b. No longer needed with PHPUnit 8. --- tests/bootstrap.php | 11 - tests/resources/patches/php-generator-7.php | 1185 ------------------- 2 files changed, 1196 deletions(-) delete mode 100644 tests/resources/patches/php-generator-7.php diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 013490740..3de4e0dc9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -13,14 +13,3 @@ $loader->addDirectories([ 'modules', 'plugins' ]); - -/* - * Monkey patch PHPUnit\Framework\MockObject\Generator to avoid - * "Function ReflectionType::__toString() is deprecated" warnings - */ -$generatorPatchPath = __DIR__ . '/resources/patches/php-generator-7.php'; -$generatorSourcePath = __DIR__ . '/../vendor/phpunit/phpunit-mock-objects/src/Generator.php'; - -if (file_exists($generatorSourcePath)) { - file_put_contents($generatorSourcePath, file_get_contents($generatorPatchPath)); -} diff --git a/tests/resources/patches/php-generator-7.php b/tests/resources/patches/php-generator-7.php deleted file mode 100644 index 677680d69..000000000 --- a/tests/resources/patches/php-generator-7.php +++ /dev/null @@ -1,1185 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - * Patched with: https://github.com/sebastianbergmann/phpunit/pull/3765/files - */ -namespace PHPUnit\Framework\MockObject; - -use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; -use Doctrine\Instantiator\Instantiator; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\Exception; -use PHPUnit\Util\InvalidArgumentHelper; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; -use SoapClient; -use Text_Template; -use Traversable; - -/** - * Mock Object Code Generator - */ -class Generator -{ - /** - * @var array - */ - private static $cache = []; - - /** - * @var Text_Template[] - */ - private static $templates = []; - - /** - * @var array - */ - private $blacklistedMethodNames = [ - '__CLASS__' => true, - '__DIR__' => true, - '__FILE__' => true, - '__FUNCTION__' => true, - '__LINE__' => true, - '__METHOD__' => true, - '__NAMESPACE__' => true, - '__TRAIT__' => true, - '__clone' => true, - '__halt_compiler' => true, - ]; - - /** - * Returns a mock object for the specified class. - * - * @param string|string[] $type - * @param array $methods - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * @param bool $allowMockingUnknownTypes - * - * @return MockObject - * - * @throws Exception - * @throws RuntimeException - * @throws \PHPUnit\Framework\Exception - * @throws \ReflectionException - */ - public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true) - { - if (!\is_array($type) && !\is_string($type)) { - throw InvalidArgumentHelper::factory(1, 'array or string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(4, 'string'); - } - - if (!\is_array($methods) && null !== $methods) { - throw InvalidArgumentHelper::factory(2, 'array', $methods); - } - - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - - if (\is_array($type)) { - $type = \array_unique( - \array_map( - function ($type) { - if ($type === 'Traversable' || - $type === '\\Traversable' || - $type === '\\Iterator') { - return 'Iterator'; - } - - return $type; - }, - $type - ) - ); - } - - if (!$allowMockingUnknownTypes) { - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\class_exists($_type, $callAutoload) && - !\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $_type - ) - ); - } - } - } else { - if (!\class_exists($type, $callAutoload) && - !\interface_exists($type, $callAutoload) - ) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $type - ) - ); - } - } - } - - if (null !== $methods) { - foreach ($methods as $method) { - if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock method with invalid name "%s"', - $method - ) - ); - } - } - - if ($methods !== \array_unique($methods)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', - \implode(', ', $methods), - \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods)))) - ) - ); - } - } - - if ($mockClassName !== '' && \class_exists($mockClassName, false)) { - $reflect = new ReflectionClass($mockClassName); - - if (!$reflect->implementsInterface(MockObject::class)) { - throw new RuntimeException( - \sprintf( - 'Class "%s" already exists.', - $mockClassName - ) - ); - } - } - - if ($callOriginalConstructor === false && $callOriginalMethods === true) { - throw new RuntimeException( - 'Proxying to original methods requires invoking the original constructor' - ); - } - - $mock = $this->generate( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - return $this->getObject( - $mock['code'], - $mock['mockClassName'], - $type, - $callOriginalConstructor, - $callAutoload, - $arguments, - $callOriginalMethods, - $proxyTarget - ); - } - - /** - * @param string $code - * @param string $className - * @param array|string $type - * @param bool $callOriginalConstructor - * @param bool $callAutoload - * @param array $arguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - */ - private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null) - { - $this->evalClass($code, $className); - - if ($callOriginalConstructor && - \is_string($type) && - !\interface_exists($type, $callAutoload)) { - if (\count($arguments) === 0) { - $object = new $className; - } else { - $class = new ReflectionClass($className); - $object = $class->newInstanceArgs($arguments); - } - } else { - try { - $instantiator = new Instantiator; - $object = $instantiator->instantiate($className); - } catch (InstantiatorException $exception) { - throw new RuntimeException($exception->getMessage()); - } - } - - if ($callOriginalMethods) { - if (!\is_object($proxyTarget)) { - if (\count($arguments) === 0) { - $proxyTarget = new $type; - } else { - $class = new ReflectionClass($type); - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - - $object->__phpunit_setOriginalObject($proxyTarget); - } - - return $object; - } - - /** - * @param string $code - * @param string $className - */ - private function evalClass($code, $className) - { - if (!\class_exists($className, false)) { - eval($code); - } - } - - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods to mock can be specified with - * the last parameter - * - * @param string $originalClassName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($originalClassName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (\class_exists($originalClassName, $callAutoload) || - \interface_exists($originalClassName, $callAutoload)) { - $reflector = new ReflectionClass($originalClassName); - $methods = $mockedMethods; - - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !\in_array($method->getName(), $methods)) { - $methods[] = $method->getName(); - } - } - - if (empty($methods)) { - $methods = null; - } - - return $this->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments - ); - } - - throw new RuntimeException( - \sprintf('Class "%s" does not exist.', $originalClassName) - ); - } - - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @param string $traitName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - '', - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => 'abstract ', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - $this->evalClass( - $classTemplate->render(), - $className['className'] - ); - - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - } - - /** - * Returns an object for the specified trait. - * - * @param string $traitName - * @param array $arguments - * @param string $traitClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * - * @return object - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($traitClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - $traitClassName, - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => '', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - return $this->getObject($classTemplate->render(), $className['className']); - } - - /** - * @param array|string $type - * @param array $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \ReflectionException - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) - { - if (\is_array($type)) { - \sort($type); - } - - if ($mockClassName === '') { - $key = \md5( - \is_array($type) ? \implode('_', $type) : $type . - \serialize($methods) . - \serialize($callOriginalClone) . - \serialize($cloneArguments) . - \serialize($callOriginalMethods) - ); - - if (isset(self::$cache[$key])) { - return self::$cache[$key]; - } - } - - $mock = $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - if (isset($key)) { - self::$cache[$key] = $mock; - } - - return $mock; - } - - /** - * @param string $wsdlFile - * @param string $className - * @param array $methods - * @param array $options - * - * @return string - * - * @throws RuntimeException - */ - public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = []) - { - if (!\extension_loaded('soap')) { - throw new RuntimeException( - 'The SOAP extension is required to generate a mock object from WSDL.' - ); - } - - $options = \array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); - $client = new SoapClient($wsdlFile, $options); - $_methods = \array_unique($client->__getFunctions()); - unset($client); - - \sort($_methods); - - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - - foreach ($_methods as $method) { - $nameStart = \strpos($method, ' ') + 1; - $nameEnd = \strpos($method, '('); - $name = \substr($method, $nameStart, $nameEnd - $nameStart); - - if (empty($methods) || \in_array($name, $methods)) { - $args = \explode( - ',', - \substr( - $method, - $nameEnd + 1, - \strpos($method, ')') - $nameEnd - 1 - ) - ); - - foreach (\range(0, \count($args) - 1) as $i) { - $args[$i] = \substr($args[$i], \strpos($args[$i], '$')); - } - - $methodTemplate->setVar( - [ - 'method_name' => $name, - 'arguments' => \implode(', ', $args) - ] - ); - - $methodsBuffer .= $methodTemplate->render(); - } - } - - $optionsBuffer = 'array('; - - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - - $optionsBuffer .= ')'; - - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - - if (\strpos($className, '\\') !== false) { - $parts = \explode('\\', $className); - $className = \array_pop($parts); - $namespace = 'namespace ' . \implode('\\', $parts) . ';' . "\n\n"; - } - - $classTemplate->setVar( - [ - 'namespace' => $namespace, - 'class_name' => $className, - 'wsdl' => $wsdlFile, - 'options' => $optionsBuffer, - 'methods' => $methodsBuffer - ] - ); - - return $classTemplate->render(); - } - - /** - * @param array|string $type - * @param array|null $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \InvalidArgumentException - * @throws \ReflectionException - * @throws RuntimeException - */ - private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) - { - $methodReflections = []; - $classTemplate = $this->getTemplate('mocked_class.tpl'); - - $additionalInterfaces = []; - $cloneTemplate = ''; - $isClass = false; - $isInterface = false; - $isMultipleInterfaces = false; - - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Interface "%s" does not exist.', - $_type - ) - ); - } - - $isMultipleInterfaces = true; - - $additionalInterfaces[] = $_type; - $typeClass = new ReflectionClass($this->generateClassName( - $_type, - $mockClassName, - 'Mock_' - )['fullClassName'] - ); - - foreach ($this->getClassMethods($_type) as $method) { - if (\in_array($method, $methods)) { - throw new RuntimeException( - \sprintf( - 'Duplicate method "%s" not allowed.', - $method - ) - ); - } - - $methodReflections[$method] = $typeClass->getMethod($method); - $methods[] = $method; - } - } - } - - $mockClassName = $this->generateClassName( - $type, - $mockClassName, - 'Mock_' - ); - - if (\class_exists($mockClassName['fullClassName'], $callAutoload)) { - $isClass = true; - } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) { - $isInterface = true; - } - - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; - - if (!empty($mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $mockClassName['namespaceName'] . - " {\n\n" . $prologue . "}\n\n" . - "namespace {\n\n"; - - $epilogue = "\n\n}"; - } - - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } else { - $class = new ReflectionClass($mockClassName['fullClassName']); - - if ($class->isFinal()) { - throw new RuntimeException( - \sprintf( - 'Class "%s" is declared "final" and cannot be mocked.', - $mockClassName['fullClassName'] - ) - ); - } - - if ($class->hasMethod('__clone')) { - $cloneMethod = $class->getMethod('__clone'); - - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $cloneTemplate = $this->getTemplate('unmocked_clone.tpl'); - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - - if (\is_object($cloneTemplate)) { - $cloneTemplate = $cloneTemplate->render(); - } - - if (\is_array($methods) && empty($methods) && - ($isClass || $isInterface)) { - $methods = $this->getClassMethods($mockClassName['fullClassName']); - } - - if (!\is_array($methods)) { - $methods = []; - } - - $mockedMethods = ''; - $configurable = []; - - foreach ($methods as $methodName) { - if ($methodName !== '__construct' && $methodName !== '__clone') { - $configurable[] = \strtolower($methodName); - } - } - - if (isset($class)) { - // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && - !$class->implementsInterface(Iterator::class) && - !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - $methods = \array_merge($methods, $this->getClassMethods(Iterator::class)); - } - - foreach ($methods as $methodName) { - try { - $method = $class->getMethod($methodName); - - if ($this->canMockMethod($method)) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $method, - $cloneArguments, - $callOriginalMethods - ); - } - } catch (ReflectionException $e) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - } elseif ($isMultipleInterfaces) { - foreach ($methods as $methodName) { - if ($this->canMockMethod($methodReflections[$methodName])) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $methodReflections[$methodName], - $cloneArguments, - $callOriginalMethods - ); - } - } - } else { - foreach ($methods as $methodName) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - - $method = ''; - - if (!\in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) { - $methodTemplate = $this->getTemplate('mocked_class_method.tpl'); - - $method = $methodTemplate->render(); - } - - $classTemplate->setVar( - [ - 'prologue' => $prologue ?? '', - 'epilogue' => $epilogue ?? '', - 'class_declaration' => $this->generateMockClassDeclaration( - $mockClassName, - $isInterface, - $additionalInterfaces - ), - 'clone' => $cloneTemplate, - 'mock_class_name' => $mockClassName['className'], - 'mocked_methods' => $mockedMethods, - 'method' => $method, - 'configurable' => '[' . \implode(', ', \array_map(function ($m) { - return '\'' . $m . '\''; - }, $configurable)) . ']' - ] - ); - - return [ - 'code' => $classTemplate->render(), - 'mockClassName' => $mockClassName['className'] - ]; - } - - /** - * @param array|string $type - * @param string $className - * @param string $prefix - * - * @return array - */ - private function generateClassName($type, $className, $prefix) - { - if (\is_array($type)) { - $type = \implode('_', $type); - } - - if ($type[0] === '\\') { - $type = \substr($type, 1); - } - - $classNameParts = \explode('\\', $type); - - if (\count($classNameParts) > 1) { - $type = \array_pop($classNameParts); - $namespaceName = \implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; - } else { - $namespaceName = ''; - $fullClassName = $type; - } - - if ($className === '') { - do { - $className = $prefix . $type . '_' . - \substr(\md5(\mt_rand()), 0, 8); - } while (\class_exists($className, false)); - } - - return [ - 'className' => $className, - 'originalClassName' => $type, - 'fullClassName' => $fullClassName, - 'namespaceName' => $namespaceName - ]; - } - - /** - * @param array $mockClassName - * @param bool $isInterface - * @param array $additionalInterfaces - * - * @return string - */ - private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = []) - { - $buffer = 'class '; - - $additionalInterfaces[] = MockObject::class; - $interfaces = \implode(', ', $additionalInterfaces); - - if ($isInterface) { - $buffer .= \sprintf( - '%s implements %s', - $mockClassName['className'], - $interfaces - ); - - if (!\in_array($mockClassName['originalClassName'], $additionalInterfaces)) { - $buffer .= ', '; - - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - - $buffer .= $mockClassName['originalClassName']; - } - } else { - $buffer .= \sprintf( - '%s extends %s%s implements %s', - $mockClassName['className'], - !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', - $mockClassName['originalClassName'], - $interfaces - ); - } - - return $buffer; - } - - /** - * @param ReflectionMethod $method - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return string - * - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods) - { - if ($method->isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - - if ($method->isStatic()) { - $modifier .= ' static'; - } - - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - - if ($method->hasReturnType()) { - $returnType = $method->getReturnType()->getName(); - } else { - $returnType = ''; - } - - if (\preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) { - $deprecation = \trim(\preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = false; - } - - return $this->generateMockedMethodDefinition( - $method->getDeclaringClass()->getName(), - $method->getName(), - $cloneArguments, - $modifier, - $this->getMethodParameters($method), - $this->getMethodParameters($method, true), - $returnType, - $reference, - $callOriginalMethods, - $method->isStatic(), - $deprecation, - $method->hasReturnType() && PHP_VERSION_ID >= 70100 && $method->getReturnType()->allowsNull() - ); - } - - /** - * @param string $className - * @param string $methodName - * @param bool $cloneArguments - * @param string $modifier - * @param string $argumentsForDeclaration - * @param string $argumentsForCall - * @param string $returnType - * @param string $reference - * @param bool $callOriginalMethods - * @param bool $static - * @param bool|string $deprecation - * @param bool $allowsReturnNull - * - * @return string - * - * @throws \InvalidArgumentException - */ - private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false) - { - if ($static) { - $templateFile = 'mocked_static_method.tpl'; - } else { - if ($returnType === 'void') { - $templateFile = \sprintf( - '%s_method_void.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } else { - $templateFile = \sprintf( - '%s_method.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } - } - - // Mocked interfaces returning 'self' must explicitly declare the - // interface name as the return type. See - // https://bugs.php.net/bug.php?id=70722 - if ($returnType === 'self') { - $returnType = $className; - } - - if (false !== $deprecation) { - $deprecation = "The $className::$methodName method is deprecated ($deprecation)."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - - $deprecationTemplate->setVar( - [ - 'deprecation' => \var_export($deprecation, true), - ] - ); - - $deprecation = $deprecationTemplate->render(); - } - - $template = $this->getTemplate($templateFile); - - $template->setVar( - [ - 'arguments_decl' => $argumentsForDeclaration, - 'arguments_call' => $argumentsForCall, - 'return_delim' => $returnType ? ': ' : '', - 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType, - 'arguments_count' => !empty($argumentsForCall) ? \substr_count($argumentsForCall, ',') + 1 : 0, - 'class_name' => $className, - 'method_name' => $methodName, - 'modifier' => $modifier, - 'reference' => $reference, - 'clone_arguments' => $cloneArguments ? 'true' : 'false', - 'deprecation' => $deprecation - ] - ); - - return $template->render(); - } - - /** - * @param ReflectionMethod $method - * - * @return bool - * - * @throws \ReflectionException - */ - private function canMockMethod(ReflectionMethod $method) - { - return !($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())); - } - - /** - * Returns whether a method name is blacklisted - * - * @param string $name - * - * @return bool - */ - private function isMethodNameBlacklisted($name) - { - return isset($this->blacklistedMethodNames[$name]); - } - - /** - * Returns the parameters of a function or method. - * - * @param ReflectionMethod $method - * @param bool $forCall - * - * @return string - * - * @throws RuntimeException - */ - private function getMethodParameters(ReflectionMethod $method, $forCall = false) - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - if ($parameter->isVariadic()) { - if ($forCall) { - continue; - } - - $name = '...' . $name; - } - - $nullable = ''; - $default = ''; - $reference = ''; - $typeDeclaration = ''; - - if (!$forCall) { - if (PHP_VERSION_ID >= 70100 && $parameter->hasType() && $parameter->allowsNull()) { - $nullable = '?'; - } - - if ($parameter->hasType() && $parameter->getType()->getName() !== 'self') { - $typeDeclaration = $parameter->getType()->getName() . ' '; - } elseif ($parameter->isArray()) { - $typeDeclaration = 'array '; - } elseif ($parameter->isCallable()) { - $typeDeclaration = 'callable '; - } else { - try { - $class = $parameter->getClass(); - } catch (ReflectionException $e) { - throw new RuntimeException( - \sprintf( - 'Cannot mock %s::%s() because a class or ' . - 'interface used in the signature is not loaded', - $method->getDeclaringClass()->getName(), - $method->getName() - ), - 0, - $e - ); - } - - if ($class !== null) { - $typeDeclaration = $class->getName() . ' '; - } - } - - if (!$parameter->isVariadic()) { - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValueConstantName(); - - if ($value === null) { - $value = \var_export($parameter->getDefaultValue(), true); - } elseif (!\defined($value)) { - $rootValue = \preg_replace('/^.*\\\\/', '', $value); - $value = \defined($rootValue) ? $rootValue : $value; - } - - $default = ' = ' . $value; - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - } - } - - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - - $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; - } - - return \implode(', ', $parameters); - } - - /** - * @param string $className - * - * @return array - * - * @throws \ReflectionException - */ - public function getClassMethods($className) - { - $class = new ReflectionClass($className); - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } - - /** - * @param string $template - * - * @return Text_Template - * - * @throws \InvalidArgumentException - */ - private function getTemplate($template) - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - self::$templates[$filename] = new Text_Template($filename); - } - - return self::$templates[$filename]; - } -} From 26944a5f68ae34723d9ecfdeac8be4bd799950d4 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 22 Jan 2020 18:16:36 +0800 Subject: [PATCH 039/100] Add simple unit tests for HasOneThrough and HasManyThrough relations --- .../plugins/database/tester/models/Author.php | 1 + .../database/tester/models/Country.php | 35 ++++++++++++ .../plugins/database/tester/models/User.php | 13 +++++ .../tester/updates/create_authors_table.php | 1 + .../tester/updates/create_countries_table.php | 23 ++++++++ .../database/tester/updates/version.yaml | 1 + .../database/HasManyThroughModelTest.php | 54 +++++++++++++++++++ .../database/HasOneThroughModelTest.php | 39 ++++++++++++++ 8 files changed, 167 insertions(+) create mode 100644 tests/fixtures/plugins/database/tester/models/Country.php create mode 100644 tests/fixtures/plugins/database/tester/updates/create_countries_table.php create mode 100644 tests/unit/plugins/database/HasManyThroughModelTest.php create mode 100644 tests/unit/plugins/database/HasOneThroughModelTest.php diff --git a/tests/fixtures/plugins/database/tester/models/Author.php b/tests/fixtures/plugins/database/tester/models/Author.php index 05e9a039f..2d9656fee 100644 --- a/tests/fixtures/plugins/database/tester/models/Author.php +++ b/tests/fixtures/plugins/database/tester/models/Author.php @@ -20,6 +20,7 @@ class Author extends Model */ public $belongsTo = [ 'user' => ['Database\Tester\Models\User', 'delete' => true], + 'country' => ['Database\Tester\Models\Country'], 'user_soft' => ['Database\Tester\Models\SoftDeleteUser', 'key' => 'user_id', 'softDelete' => true], ]; diff --git a/tests/fixtures/plugins/database/tester/models/Country.php b/tests/fixtures/plugins/database/tester/models/Country.php new file mode 100644 index 000000000..e7bb583c5 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/models/Country.php @@ -0,0 +1,35 @@ + [ + 'Database\Tester\Models\User', + ], + ]; + + public $hasManyThrough = [ + 'posts' => [ + 'Database\Tester\Models\Post', + 'through' => 'Database\Tester\Models\Author', + ] + ]; +} + +class SoftDeleteCountry extends Country +{ + use \October\Rain\Database\Traits\SoftDelete; +} diff --git a/tests/fixtures/plugins/database/tester/models/User.php b/tests/fixtures/plugins/database/tester/models/User.php index 867992c2c..130120e9c 100644 --- a/tests/fixtures/plugins/database/tester/models/User.php +++ b/tests/fixtures/plugins/database/tester/models/User.php @@ -17,6 +17,19 @@ class User extends Model /** * @var array Relations */ + public $hasOne = [ + 'author' => [ + 'Database\Tester\Models\Author', + ] + ]; + + public $hasOneThrough = [ + 'phone' => [ + 'Database\Tester\Models\Phone', + 'through' => 'Database\Tester\Models\Author', + ], + ]; + public $attachOne = [ 'avatar' => 'System\Models\File' ]; diff --git a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php index 6466e4e13..d6277e525 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php @@ -11,6 +11,7 @@ class CreateAuthorsTable extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->integer('user_id')->unsigned()->index()->nullable(); + $table->integer('country_id')->unsigned()->index()->nullable(); $table->string('name')->nullable(); $table->string('email')->nullable(); $table->softDeletes(); diff --git a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php new file mode 100644 index 000000000..7fc85ae54 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php @@ -0,0 +1,23 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('database_tester_countries'); + } +} diff --git a/tests/fixtures/plugins/database/tester/updates/version.yaml b/tests/fixtures/plugins/database/tester/updates/version.yaml index 613fc5337..590bb7892 100644 --- a/tests/fixtures/plugins/database/tester/updates/version.yaml +++ b/tests/fixtures/plugins/database/tester/updates/version.yaml @@ -9,3 +9,4 @@ - create_users_table.php - create_event_log_table.php - create_meta_table.php + - create_countries_table.php diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php new file mode 100644 index 000000000..85ac947f2 --- /dev/null +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -0,0 +1,54 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $country = Country::create(['name' => 'Australia']); + $author1 = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $author2 = Author::create(['name' => 'Louie', 'email' => 'louie@email.tld']); + $post1 = Post::create(['title' => "First post", 'description' => "Yay!!"]); + $post2 = Post::create(['title' => "Second post", 'description' => "Woohoo!!"]); + $post3 = Post::create(['title' => "Third post", 'description' => "Yipiee!!"]); + $post4 = Post::make(['title' => "Fourth post", 'description' => "Hooray!!"]); + Model::reguard(); + + // Set data + $author1->country = $country; + $author2->country = $country; + + $author1->posts = new Collection([$post1, $post2]); + $author2->posts = new Collection([$post3, $post4]); + + $author1->save(); + $author2->save(); + + $country = Country::with([ + 'posts' + ])->find($country->id); + + $this->assertEquals([ + $post1->id, + $post2->id, + $post3->id, + $post4->id + ], $country->posts->pluck('id')->toArray()); + } +} diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php new file mode 100644 index 000000000..91be83b60 --- /dev/null +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -0,0 +1,39 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $phone = Phone::create(['number' => '08 1234 5678']); + $author = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $user = User::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + Model::reguard(); + + // Set data + $author->phone = $phone; + $author->user = $user; + $author->save(); + + $user = User::with([ + 'phone' + ])->find($user->id); + + $this->assertEquals($phone->id, $user->phone->id); + } +} From 6aeb079f8bcd2255d5e48935e135a2ec808660aa Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 23 Jan 2020 12:28:17 +0800 Subject: [PATCH 040/100] Add support for Postmark mail transport configuration --- config/mail.php | 2 +- config/services.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/config/mail.php b/config/mail.php index 7c2332d2f..4c471d568 100644 --- a/config/mail.php +++ b/config/mail.php @@ -12,7 +12,7 @@ return [ | your application here. By default, Laravel is setup for SMTP mail. | | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | "postmark", "sparkpost", "log", "array" | */ diff --git a/config/services.php b/config/services.php index c2d453065..d53643b03 100644 --- a/config/services.php +++ b/config/services.php @@ -24,6 +24,10 @@ return [ 'secret' => '', ], + 'postmark' => [ + 'token' => '', + ], + 'ses' => [ 'key' => '', 'secret' => '', From 5f89a6cd4eca603308383689976b8af1236f9b44 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 27 Jan 2020 12:06:19 -0600 Subject: [PATCH 041/100] Update composer file to use new subsplit repos --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3e0c939e5..35fa6f05a 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,11 @@ "php": "^7.2", "ext-mbstring": "*", "ext-openssl": "*", - "october/rain": "dev-wip/laravel-6", - "laravel/framework": "^6.0", + "october/rain": "dev-wip/laravel-6 as 1.0", + "october/system": "dev-wip/laravel-6", + "october/backend": "dev-wip/laravel-6", + "october/cms": "dev-wip/laravel-6", + "laravel/framework": "~6.0", "wikimedia/composer-merge-plugin": "dev-master" }, "require-dev": { From 65c3a88179b30df2db1777bea25813f234065cc5 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 3 Feb 2020 12:21:04 +0800 Subject: [PATCH 042/100] [Laravel 6] Add support for Laravel Dusk tests (#4919) --- .env.dusk | 38 +++ .github/workflows/code-quality-pr.yaml | 23 +- .github/workflows/code-quality-push.yaml | 23 +- .github/workflows/frontend-tests.yaml | 24 -- .github/workflows/matchers/phpcs-matcher.json | 23 ++ .github/workflows/tests.yml | 81 ++++- composer.json | 18 +- config/dusk/app.php | 30 ++ config/dusk/cms.php | 27 ++ config/dusk/database.php | 146 +++++++++ modules/system/ServiceProvider.php | 9 + modules/system/console/Dusk.php | 89 ++++++ modules/system/console/DuskFails.php | 31 ++ phpunit.dusk.xml.dist | 34 ++ tests/Browser/Backend/AuthTest.php | 41 +++ tests/Browser/Backend/Cms/TemplateTest.php | 300 ++++++++++++++++++ tests/Browser/Pages/Backend/Cms.php | 32 ++ tests/Browser/Pages/Backend/Dashboard.php | 33 ++ .../Browser/Pages/Backend/ForgotPassword.php | 48 +++ tests/Browser/Pages/Backend/Login.php | 49 +++ tests/Browser/Pages/BackendPage.php | 21 ++ tests/Browser/Pages/Page.php | 16 + tests/Browser/console/.gitignore | 2 + tests/Browser/screenshots/.gitignore | 2 + tests/BrowserTestCase.php | 147 +++++++++ tests/Concerns/CreatesApplication.php | 98 ++++++ .../InteractsWithAuthentication.php | 4 +- tests/Concerns/RunsMigrations.php | 14 + tests/Concerns/TestsPlugins.php | 108 +++++++ tests/PluginTestCase.php | 205 ++---------- tests/README.md | 277 ++++++++++++---- tests/TestCase.php | 12 +- tests/UiTestCase.php | 111 ------- tests/bootstrap.php | 2 +- tests/fixtures/backend/models/UserFixture.php | 2 +- tests/functional/backend/AuthTest.php | 90 ------ tests/functional/cms/TemplateTest.php | 143 --------- tests/functional/phpunit.xml | 18 -- .../unit/backend/classes/AuthManagerTest.php | 2 +- .../backend/classes/NavigationManagerTest.php | 2 +- .../backend/classes/WidgetManagerTest.php | 2 +- .../backend/helpers/BackendHelperTest.php | 2 +- tests/unit/backend/models/ExportModelTest.php | 2 +- tests/unit/backend/models/ImportModelTest.php | 2 +- tests/unit/backend/traits/WidgetMakerTest.php | 2 +- tests/unit/backend/widgets/FilterTest.php | 11 +- tests/unit/backend/widgets/FormTest.php | 12 +- tests/unit/backend/widgets/ListsTest.php | 11 +- .../cms/classes/CmsCompoundObjectTest.php | 2 +- tests/unit/cms/classes/CmsExceptionTest.php | 2 +- tests/unit/cms/classes/CmsObjectQueryTest.php | 2 +- tests/unit/cms/classes/CmsObjectTest.php | 2 +- tests/unit/cms/classes/CodeParserTest.php | 2 +- .../unit/cms/classes/ComponentManagerTest.php | 2 +- tests/unit/cms/classes/ContentTest.php | 2 +- tests/unit/cms/classes/ControllerTest.php | 2 +- tests/unit/cms/classes/PartialStackTest.php | 2 +- tests/unit/cms/classes/RouterTest.php | 2 +- tests/unit/cms/classes/ThemeTest.php | 2 +- tests/unit/cms/helpers/FileTest.php | 2 +- .../plugins/backend/ImportModelDbTest.php | 2 +- .../plugins/database/AttachManyModelTest.php | 2 +- .../plugins/database/AttachOneModelTest.php | 2 +- .../database/BelongsToManyModelTest.php | 2 +- .../plugins/database/BelongsToModelTest.php | 2 +- .../plugins/database/DeferredBindingTest.php | 2 +- .../plugins/database/HasManyModelTest.php | 2 +- .../database/HasManyThroughModelTest.php | 2 +- .../unit/plugins/database/HasOneModelTest.php | 2 +- .../database/HasOneThroughModelTest.php | 2 +- tests/unit/plugins/database/ModelTest.php | 2 +- .../plugins/database/MorphManyModelTest.php | 2 +- .../plugins/database/MorphOneModelTest.php | 2 +- .../plugins/database/MorphToModelTest.php | 2 +- .../plugins/database/NestedTreeModelTest.php | 2 +- .../plugins/database/NullableModelTest.php | 2 +- .../database/RevisionableModelTest.php | 2 +- .../plugins/database/SimpleTreeModelTest.php | 2 +- .../plugins/database/SluggableModelTest.php | 2 +- .../plugins/database/SoftDeleteModelTest.php | 2 +- .../plugins/database/ValidationModelTest.php | 2 +- .../system/classes/AutoDatasourceTest.php | 2 +- .../unit/system/classes/CombineAssetsTest.php | 2 +- tests/unit/system/classes/CoreLangTest.php | 2 +- .../unit/system/classes/MarkupManagerTest.php | 2 +- .../unit/system/classes/MediaLibraryTest.php | 2 +- .../unit/system/classes/PluginManagerTest.php | 2 +- .../system/classes/UpdatesControllerTest.php | 2 +- .../system/classes/VersionManagerTest.php | 2 +- tests/unit/system/traits/AssetMakerTest.php | 2 +- 90 files changed, 1755 insertions(+), 748 deletions(-) create mode 100644 .env.dusk delete mode 100644 .github/workflows/frontend-tests.yaml create mode 100644 .github/workflows/matchers/phpcs-matcher.json create mode 100644 config/dusk/app.php create mode 100644 config/dusk/cms.php create mode 100644 config/dusk/database.php create mode 100644 modules/system/console/Dusk.php create mode 100644 modules/system/console/DuskFails.php create mode 100644 phpunit.dusk.xml.dist create mode 100644 tests/Browser/Backend/AuthTest.php create mode 100644 tests/Browser/Backend/Cms/TemplateTest.php create mode 100644 tests/Browser/Pages/Backend/Cms.php create mode 100644 tests/Browser/Pages/Backend/Dashboard.php create mode 100644 tests/Browser/Pages/Backend/ForgotPassword.php create mode 100644 tests/Browser/Pages/Backend/Login.php create mode 100644 tests/Browser/Pages/BackendPage.php create mode 100644 tests/Browser/Pages/Page.php create mode 100644 tests/Browser/console/.gitignore create mode 100644 tests/Browser/screenshots/.gitignore create mode 100644 tests/BrowserTestCase.php create mode 100644 tests/Concerns/CreatesApplication.php rename tests/{concerns => Concerns}/InteractsWithAuthentication.php (98%) create mode 100644 tests/Concerns/RunsMigrations.php create mode 100644 tests/Concerns/TestsPlugins.php delete mode 100644 tests/UiTestCase.php delete mode 100644 tests/functional/backend/AuthTest.php delete mode 100644 tests/functional/cms/TemplateTest.php delete mode 100644 tests/functional/phpunit.xml diff --git a/.env.dusk b/.env.dusk new file mode 100644 index 000000000..4ba3c8e1b --- /dev/null +++ b/.env.dusk @@ -0,0 +1,38 @@ +APP_ENV=dusk +APP_DEBUG=true +APP_URL=http://127.0.0.1:8000 +APP_KEY=base64:R2w4QUhGcWxobnpjcHhlSkd0MHpMTjVxZTVuZ1BkaUM= + +DB_CONNECTION=sqlite +DB_HOST= +DB_PORT= +DB_DATABASE=storage/dusk.sqlite +DB_USERNAME= +DB_PASSWORD= +REDIS_HOST= +REDIS_PASSWORD= +REDIS_PORT= +DB_USE_CONFIG_FOR_TESTING=false + +CACHE_DRIVER=file + +SESSION_DRIVER=file + +QUEUE_CONNECTION=sync + +MAIL_DRIVER=array +MAIL_HOST= +MAIL_PORT= +MAIL_ENCRYPTION=tls +MAIL_USERNAME= +MAIL_PASSWORD= + +ROUTES_CACHE=false +ASSET_CACHE=false +DATABASE_TEMPLATES=false +LINK_POLICY=detect +ENABLE_CSRF=false + +# Uncomment the following to use custom authentication credentials for tests +#DUSK_ADMIN_USER= +#DUSK_ADMIN_PASS= diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index 65a0f4646..af05451d1 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,25 +6,18 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index 3de3f6a97..ff209d0b5 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,23 +9,16 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks - run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml deleted file mode 100644 index 9d0068a0a..000000000 --- a/.github/workflows/frontend-tests.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - - develop - pull_request: - -jobs: - frontendTests: - runs-on: ubuntu-latest - name: JavaScript - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install Node - uses: actions/setup-node@v1 - with: - node-version: 8 - - name: Install Node dependencies - run: npm install - - name: Run tests - run: npm run test diff --git a/.github/workflows/matchers/phpcs-matcher.json b/.github/workflows/matchers/phpcs-matcher.json new file mode 100644 index 000000000..5c80b26d2 --- /dev/null +++ b/.github/workflows/matchers/phpcs-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] + } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29713edeb..3c5e7b922 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,6 +8,20 @@ on: pull_request: jobs: + frontendTests: + runs-on: ubuntu-latest + name: JavaScript + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install Node + uses: actions/setup-node@v1 + with: + node-version: 8 + - name: Install Node dependencies + run: npm install + - name: Run tests + run: npm run test phpUnitTests: runs-on: ubuntu-latest strategy: @@ -15,22 +29,79 @@ jobs: matrix: phpVersions: ['7.2', '7.3', '7.4'] fail-fast: false - name: PHP ${{ matrix.phpVersions }} + name: Unit Tests / PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@master + uses: shivammathur/setup-php@v1 with: php-version: ${{ matrix.phpVersions }} - extension-csv: mbstring, intl, gd, xml, sqlite + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules run: | - php artisan october:util set build - php artisan package:discover + git reset --hard HEAD + composer dumpautoload - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php + duskTests: + runs-on: ubuntu-latest + name: Browser Tests + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install PHP + uses: shivammathur/setup-php@v1 + with: + php-version: '7.3' + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matcher for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --no-suggest --no-scripts + - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules + run: | + git reset --hard HEAD + composer dumpautoload + - name: Install Chrome driver + run: php artisan dusk:chrome-driver + - name: Start Chrome driver + run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 & + - name: Run Laravel Server + run: php artisan serve > /dev/null 2>&1 & + - name: Run Dusk Tests + run: php artisan dusk + - name: Upload Dusk Screenshots + uses: actions/upload-artifact@v1 + if: failure() + with: + name: dusk-screenshots + path: ./tests/Browser/screenshots diff --git a/composer.json b/composer.json index 35fa6f05a..22a0fac82 100644 --- a/composer.json +++ b/composer.json @@ -49,16 +49,13 @@ "dms/phpunit-arraysubset-asserts": "^0.1.0", "meyfa/phpunit-assert-gd": "^2.0", "squizlabs/php_codesniffer": "3.*", - "jakub-onderka/php-parallel-lint": "^1.0" + "jakub-onderka/php-parallel-lint": "^1.0", + "laravel/dusk": "^5.8" }, "autoload-dev": { - "classmap": [ - "tests/concerns/InteractsWithAuthentication.php", - "tests/fixtures/backend/models/UserFixture.php", - "tests/TestCase.php", - "tests/UiTestCase.php", - "tests/PluginTestCase.php" - ] + "psr-4": { + "October\\Core\\Tests\\": "tests/" + } }, "scripts": { "post-create-project-cmd": [ @@ -92,6 +89,11 @@ "recurse": true, "replace": false, "merge-dev": false + }, + "laravel": { + "dont-discover": [ + "laravel/dusk" + ] } } } diff --git a/config/dusk/app.php b/config/dusk/app.php new file mode 100644 index 000000000..79438c741 --- /dev/null +++ b/config/dusk/app.php @@ -0,0 +1,30 @@ + env('APP_URL', 'http://127.0.0.1:8000'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY', 'Gl8AHFqlhnzcpxeJGt0zLN5qe5ngPdiC'), +]; diff --git a/config/dusk/cms.php b/config/dusk/cms.php new file mode 100644 index 000000000..1b0ad7107 --- /dev/null +++ b/config/dusk/cms.php @@ -0,0 +1,27 @@ + 'demo', + + /* + |-------------------------------------------------------------------------- + | Cross Site Request Forgery (CSRF) Protection + |-------------------------------------------------------------------------- + | + | If the CSRF protection is enabled, all "postback" & AJAX requests are + | checked for a valid security token. + | + */ + + 'enableCsrfProtection' => false, +]; diff --git a/config/dusk/database.php b/config/dusk/database.php new file mode 100644 index 000000000..072d9b35b --- /dev/null +++ b/config/dusk/database.php @@ -0,0 +1,146 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => env('DB_DATABASE', 'storage/dusk.sqlite'), + 'prefix' => '', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'engine' => 'InnoDB', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'varcharmax' => 191, + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 5432), + 'database' => env('DB_DATABASE', 'database'), + 'username' => env('DB_USERNAME', ''), + 'password' => env('DB_PASSWORD', ''), + 'prefix' => '', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk have not actually be run in the databases. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'cluster' => false, + + 'default' => [ + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', ''), + 'port' => env('REDIS_PORT', 6379), + 'database' => 0, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Use DB configuration for testing + |-------------------------------------------------------------------------- + | + | When running plugin tests OctoberCMS by default uses SQLite in memory. + | You can override this behavior by setting `useConfigForTesting` to true. + | + | After that OctoberCMS will take DB parameters from the config. + | If file `/config/testing/database.php` exists, config will be read from it, + | but remember that when not specified it will use parameters specified in + | `/config/database.php`. + | + */ + + 'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false), +]; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 1695adc54..40ea3d649 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -261,6 +261,15 @@ class ServiceProvider extends ModuleServiceProvider $this->registerConsoleCommand('theme.list', 'System\Console\ThemeList'); $this->registerConsoleCommand('theme.use', 'System\Console\ThemeUse'); $this->registerConsoleCommand('theme.sync', 'System\Console\ThemeSync'); + + if (!App::isProduction() && class_exists('Laravel\Dusk\Dusk')) { + $this->registerConsoleCommand('dusk', 'System\Console\Dusk'); + $this->registerConsoleCommand('dusk.fails', 'System\Console\DuskFails'); + + $this->commands([ + \Laravel\Dusk\Console\ChromeDriverCommand::class, + ]); + } } /* diff --git a/modules/system/console/Dusk.php b/modules/system/console/Dusk.php new file mode 100644 index 000000000..5891037b5 --- /dev/null +++ b/modules/system/console/Dusk.php @@ -0,0 +1,89 @@ +duskFile()))) { + if (!file_exists(base_path('.env'))) { + $this->stubEnvironment(); + } elseif (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) { + $this->backupEnvironment(); + } + $this->refreshEnvironment(); + } + + $this->writeConfiguration(); + + $this->setupSignalHandler(); + } + + /** + * Restore the original environment. + * + * @return void + */ + protected function teardownDuskEnviroment() + { + $this->removeConfiguration(); + + if ( + file_exists(base_path($this->duskFile())) + && ( + file_exists(base_path('.env.backup')) + || file_exists(base_path('.env.blank')) + ) + ) { + $this->restoreEnvironment(); + } + } + + + /** + * Stub a current environment file. + * + * @return void + */ + protected function stubEnvironment() + { + touch(base_path('.env.blank')); + + copy(base_path($this->duskFile()), base_path('.env')); + } + + /** + * Backup the current environment file. + * + * @return void + */ + protected function backupEnvironment() + { + copy(base_path('.env'), base_path('.env.backup')); + + copy(base_path($this->duskFile()), base_path('.env')); + } + + /** + * Restore the backed-up environment file. + * + * @return void + */ + protected function restoreEnvironment() + { + if (file_exists(base_path('.env.blank'))) { + unlink(base_path('.env')); + unlink(base_path('.env.blank')); + } else { + copy(base_path('.env.backup'), base_path('.env')); + + unlink(base_path('.env.backup')); + } + } +} diff --git a/modules/system/console/DuskFails.php b/modules/system/console/DuskFails.php new file mode 100644 index 000000000..7cbbd3b71 --- /dev/null +++ b/modules/system/console/DuskFails.php @@ -0,0 +1,31 @@ + + + + + ./tests/Browser/Backend + + + + + + ./modules/ + + + ./modules/backend/routes.php + ./modules/cms/routes.php + ./modules/system/routes.php + + ./modules/backend/database + ./modules/cms/database + ./modules/system/database + + + + diff --git a/tests/Browser/Backend/AuthTest.php b/tests/Browser/Backend/AuthTest.php new file mode 100644 index 000000000..6d1b0514a --- /dev/null +++ b/tests/Browser/Backend/AuthTest.php @@ -0,0 +1,41 @@ +browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->click('@accountMenu') + ->clickLink('Sign out'); + + $browser + ->on(new Login); + }); + } + + public function testPasswordReset() + { + $this->browse(function (Browser $browser) { + $browser + ->visit(new Login) + ->pause(500) + ->click('@forgotPasswordLink'); + + $browser + ->on(new ForgotPassword) + ->type('@loginField', 'admin') + ->click('@submitButton'); + + $browser + ->on(new Login) + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Message sent to your email address'); + }); + } +} diff --git a/tests/Browser/Backend/Cms/TemplateTest.php b/tests/Browser/Backend/Cms/TemplateTest.php new file mode 100644 index 000000000..43fcb5039 --- /dev/null +++ b/tests/Browser/Backend/Cms/TemplateTest.php @@ -0,0 +1,300 @@ +browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + // Add a new page + $browser + ->click('form[data-template-type="page"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New page', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="settings[title]"]', 'Functional Test Page') + ->pause(100) + + // Check that slug values are working + ->assertInputValue('input[name="settings[url]"]', '/functional-test-page') + ->assertInputValue('input[name="fileName"]', 'functional-test-page') + + ->clear('input[name="settings[url]"]') + ->type('input[name="settings[url]"]', '/xxx/functional/test/page') + ->clear('input[name="fileName"]') + ->type('input[name="fileName"]', 'xxx_functional_test_page.htm') + + // Check that slug values have not been re-added after manual entry + ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); + + // Save the new page + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'Functional Test Page', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="page-"][data-tab-id$="-xxx_functional_test_page.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the page + $browser + ->click('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="settings[title]"]', 'Functional Test Page') + ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); + + // Delete the page + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"]'); + }); + } + + public function testPartialTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="partials"] a'); + + // Add a new partial + $browser + ->click('form[data-template-type="partial"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New partial', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_partial') + ->type('input[name="settings[description]"]', 'Test Partial'); + + // Save the new partial + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_partial', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="partial-"][data-tab-id$="-xxx_functional_test_partial.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_partial.htm') + ->assertInputValue('input[name="settings[description]"]', 'Test Partial'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"]'); + }); + } + + public function testLayoutTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="layouts"] a'); + + // Add a new layout + $browser + ->click('form[data-template-type="layout"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertEquals('New layout', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_layout') + ->type('input[name="settings[description]"]', 'Test Layout'); + + // Save the new layout + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_layout', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="layout-"][data-tab-id$="-xxx_functional_test_layout.htm"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_layout.htm') + ->assertInputValue('input[name="settings[description]"]', 'Test Layout'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"]'); + }); + } + + public function testContentTemplates() + { + $this->browse(function (Browser $browser) { + $browser + ->signInToBackend() + ->visit(new Cms) + ->pause(200); + + // Fix side panel, if necessary + if ($browser->hasClass('', 'side-panel-not-fixed')) { + $browser + ->mouseover('@sideNav > li[data-menu-item="pages"]') + ->waitFor('@sidePanel') + ->mouseover('@sidePanel') + ->waitFor('@sidePanelFixButton') + ->click('@sidePanelFixButton'); + } + + $browser + ->click('@sideNav > li[data-menu-item="content"] a'); + + // Add a new content file + $browser + ->click('form[data-template-type="content"] button[data-control="create-template"]') + ->waitFor('#cms-master-tabs .tab-content .tab-pane'); + + $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); + + $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); + $this->assertStringContainsString('content', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); + + $browser + ->type('input[name="fileName"]', 'xxx_functional_test_content.txt'); + + // Save the new content file + $browser + ->click('a[data-request="onSave"]') + ->waitFor('.flash-message') + ->assertSeeIn('.flash-message', 'Template saved.'); + + $this->assertEquals( + 'xxx_functional_test_content.txt', + $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') + ); + + // Close the tab + $browser + ->click('li[data-tab-id^="content-"][data-tab-id$="-xxx_functional_test_content.txt"] span.tab-close') + ->pause(100) + ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); + + // Re-open the partial + $browser + ->click('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"] a') + ->waitFor('#cms-master-tabs .tab-content .tab-pane') + + // Check that saved details are still there + ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_content.txt'); + + // Delete the partial + $browser + ->click('button[data-request="onDelete"]') + ->waitFor('.sweet-alert.showSweetAlert.visible') + ->pause(300) + ->click('.sweet-alert.showSweetAlert.visible button.confirm') + ->waitUntilMissing('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"]'); + }); + } +} diff --git a/tests/Browser/Pages/Backend/Cms.php b/tests/Browser/Pages/Backend/Cms.php new file mode 100644 index 000000000..e088330c1 --- /dev/null +++ b/tests/Browser/Pages/Backend/Cms.php @@ -0,0 +1,32 @@ +assertTitleContains('CMS |') + ->assertPresent('@mainMenu') + ->assertPresent('@sideNav') + ->assertPresent('@accountMenu'); + } +} diff --git a/tests/Browser/Pages/Backend/Dashboard.php b/tests/Browser/Pages/Backend/Dashboard.php new file mode 100644 index 000000000..1fb910d20 --- /dev/null +++ b/tests/Browser/Pages/Backend/Dashboard.php @@ -0,0 +1,33 @@ +assertTitleContains('Dashboard |') + ->assertPresent('@mainMenu') + ->assertPresent('@accountMenu') + ->waitFor('.report-widget') + ->assertSee('Welcome'); + } +} diff --git a/tests/Browser/Pages/Backend/ForgotPassword.php b/tests/Browser/Pages/Backend/ForgotPassword.php new file mode 100644 index 000000000..97f02bb1e --- /dev/null +++ b/tests/Browser/Pages/Backend/ForgotPassword.php @@ -0,0 +1,48 @@ +assertTitle('Administration Area') + ->assertPresent('@loginField') + ->assertMissing('input[name="password"]') + ->assertPresent('@submitButton') + ->assertPresent('@cancelLink') + ->assertSeeIn('@submitButton', 'Restore'); + } + + /** + * Get the global element shortcuts for the site. + * + * @return array + */ + public function elements() + { + return [ + '@loginField' => 'input[name="login"]', + '@submitButton' => 'button[type="submit"]', + '@cancelLink' => 'p.forgot-password > a', + ]; + } +} diff --git a/tests/Browser/Pages/Backend/Login.php b/tests/Browser/Pages/Backend/Login.php new file mode 100644 index 000000000..d62672aa0 --- /dev/null +++ b/tests/Browser/Pages/Backend/Login.php @@ -0,0 +1,49 @@ +assertTitle('Administration Area') + ->assertPresent('@loginField') + ->assertPresent('@passwordField') + ->assertPresent('@submitButton') + ->assertPresent('@forgotPasswordLink') + ->assertSeeIn('@submitButton', 'Login'); + } + + /** + * Get the global element shortcuts for the site. + * + * @return array + */ + public function elements() + { + return [ + '@loginField' => 'input[name="login"]', + '@passwordField' => 'input[name="password"]', + '@submitButton' => 'button[type="submit"]', + '@forgotPasswordLink' => 'p.forgot-password > a', + ]; + } +} diff --git a/tests/Browser/Pages/BackendPage.php b/tests/Browser/Pages/BackendPage.php new file mode 100644 index 000000000..b57d6cee5 --- /dev/null +++ b/tests/Browser/Pages/BackendPage.php @@ -0,0 +1,21 @@ + '#layout-mainmenu', + '@accountMenu' => '#layout-mainmenu .mainmenu-account > a', + + '@sideNav' => '#layout-sidenav > ul', + '@sidePanel' => '#layout-side-panel', + '@sidePanelFixButton' => '#layout-side-panel a.fix-button', + ]; + } +} diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php new file mode 100644 index 000000000..30bfd0632 --- /dev/null +++ b/tests/Browser/Pages/Page.php @@ -0,0 +1,16 @@ +addArguments([ + '--disable-gpu', + '--headless', + '--window-size=1920,1080', + ]); + + return RemoteWebDriver::create( + 'http://localhost:9515', + DesiredCapabilities::chrome()->setCapability( + ChromeOptions::CAPABILITY, + $options + ) + ); + } + + public function setUp(): void + { + $this->resetManagers(); + + parent::setUp(); + + // Ensure system is up to date + if ($this->usingTestDatabase) { + $this->runOctoberUpCommand(); + } + + // Detect a plugin and autoload it, if necessary + $this->detectPlugin(); + + // Disable mailer + \Mail::pretend(); + + Browser::$baseUrl = $this->baseUrl(); + Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots'); + Browser::$storeConsoleLogAt = base_path('tests/Browser/console'); + Browser::$userResolver = function () { + return $this->user(); + }; + + $this->setupMacros(); + } + + public function tearDown(): void + { + if ($this->usingTestDatabase && isset($this->testDatabasePath)) { + unlink($this->testDatabasePath); + } + + parent::tearDown(); + } + + /** + * Defines October macros for use in browser tests + * + * @return void + */ + protected function setupMacros() + { + /** + * Signs the user into the backend + */ + Browser::macro('signInToBackend', function (string $username = null, string $password = null) { + $username = $username ?? env('DUSK_ADMIN_USER', 'admin'); + $password = $password ?? env('DUSK_ADMIN_PASS', 'admin1234'); + + $this + ->visit(new Login) + ->pause(500) + ->type('@loginField', $username) + ->type('@passwordField', $password) + ->click('@submitButton'); + + $this-> + on(new Dashboard); + + return $this; + }); + + + Browser::macro('hasClass', function (string $selector, string $class) { + $classes = preg_split('/\s+/', $this->attribute($selector, 'class'), -1, PREG_SPLIT_NO_EMPTY); + + if (empty($classes)) { + return false; + } + + return in_array($class, $classes); + }); + } + + /** + * Similar to the native getConfirmation() function + */ + protected function getSweetConfirmation($expectedText = null, $clickOk = true) + { + $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); + + if ($expectedText) { + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); + } + + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); + + if ($clickOk) { + $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); + } + } +} diff --git a/tests/Concerns/CreatesApplication.php b/tests/Concerns/CreatesApplication.php new file mode 100644 index 000000000..e9a35fc75 --- /dev/null +++ b/tests/Concerns/CreatesApplication.php @@ -0,0 +1,98 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + $app['cache']->setDefaultDriver('array'); + $app->setLocale('en'); + + $app->singleton('auth', function ($app) { + $app['auth.loaded'] = true; + + return AuthManager::instance(); + }); + + // Use test database configuration, unless overriden + $dbConnection = Config::get('database.default', 'sqlite'); + $dbConnections = [ + $dbConnection => Config::get('database.connections.' . $dbConnection, [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]) + ]; + + if (env('APP_ENV') === 'testing' && !Config::get('database.useConfigForTesting', false)) { + $this->usingTestDatabase = true; + + $dbConnection = 'sqlite'; + $dbConnections = [ + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ], + ]; + } elseif (env('APP_ENV') === 'dusk' && !Config::get('database.useConfigForTesting', false)) { + $this->usingTestDatabase = true; + + $dbConnection = 'sqlite'; + $dbConnections = [ + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => 'storage/dusk.sqlite', + 'prefix' => '', + ], + ]; + + // Ensure a fresh copy of the SQLite database is made + $this->testDatabasePath = base_path('storage/dusk.sqlite'); + + if (file_exists($this->testDatabasePath)) { + unlink($this->testDatabasePath); + } + + touch($this->testDatabasePath); + } + + $app['config']->set('database.default', $dbConnection); + $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); + + /** + * Prevent mail from being sent out + */ + $app['config']->set('mail.driver', 'array'); + + /** + * Modify the plugin path away from the test context + */ + $app->setPluginsPath(realpath(base_path() . Config::get('cms.pluginsPath'))); + + return $app; + } +} diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/Concerns/InteractsWithAuthentication.php similarity index 98% rename from tests/concerns/InteractsWithAuthentication.php rename to tests/Concerns/InteractsWithAuthentication.php index a7950f230..bde15cdef 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/Concerns/InteractsWithAuthentication.php @@ -1,6 +1,4 @@ -pluginTestCaseLoadedPlugins = []; + $pluginCode = $this->guessPluginCodeFromTest(); + + if ($pluginCode !== false) { + $this->runPluginRefreshCommand($pluginCode, false); + } + } + + /** + * Locates the plugin code based on the test file location. + * + * @return string|bool + */ + protected function guessPluginCodeFromTest() + { + $reflect = new \ReflectionClass($this); + $path = $reflect->getFilename(); + $basePath = $this->app->pluginsPath(); + + $result = false; + + if (strpos($path, $basePath) === 0) { + $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); + $result = implode('.', array_slice(explode('/', $result), 0, 2)); + } + + return $result; + } + + /** + * Runs a refresh command on a plugin. + * + * Since the test environment has loaded all the test plugins + * natively, this method will ensure the desired plugin is + * loaded in the system before proceeding to migrate it. + * + * @return void + */ + protected function runPluginRefreshCommand($code, $throwException = true): void + { + if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { + if (!$throwException) { + return; + } + throw new \Exception(sprintf('Invalid plugin code: "%s"', $code)); + } + + $manager = PluginManager::instance(); + $plugin = $manager->findByIdentifier($code); + + // First time seeing this plugin, load it up + if (!$plugin) { + $namespace = '\\'.str_replace('.', '\\', strtolower($code)); + $path = array_get($manager->getPluginNamespaces(), $namespace); + + if (!$path) { + if (!$throwException) { + return; + } + throw new \Exception(sprintf('Unable to find plugin with code: "%s"', $code)); + } + + $plugin = $manager->loadPlugin($namespace, $path); + } + + // Spin over dependencies and refresh them too + $this->pluginTestCaseLoadedPlugins[$code] = $plugin; + + if (!empty($plugin->require)) { + foreach ((array) $plugin->require as $dependency) { + if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { + continue; + } + + $this->runPluginRefreshCommand($dependency); + } + } + + // Execute the command + \Artisan::call('plugin:refresh', ['name' => $code]); + } +} diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 728d5d430..82f09618a 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -1,67 +1,17 @@ -make('Illuminate\Contracts\Console\Kernel')->bootstrap(); - - $app['cache']->setDefaultDriver('array'); - $app->setLocale('en'); - - $app->singleton('auth', function ($app) { - $app['auth.loaded'] = true; - - return AuthManager::instance(); - }); - - /* - * Store database in memory by default, if not specified otherwise - */ - $dbConnection = 'sqlite'; - - $dbConnections = []; - $dbConnections['sqlite'] = [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '' - ]; - - if (env('APP_ENV') === 'testing' && Config::get('database.useConfigForTesting', false)) { - $dbConnection = Config::get('database.default', 'sqlite'); - - $dbConnections[$dbConnection] = Config::get('database.connections' . $dbConnection, $dbConnections['sqlite']); - } - - $app['config']->set('database.default', $dbConnection); - $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); - - /* - * Modify the plugin path away from the test context - */ - $app->setPluginsPath(realpath(base_path().Config::get('cms.pluginsPath'))); - - return $app; - } + use RunsMigrations; + use TestsPlugins; /** * Perform test case set up. @@ -69,36 +19,21 @@ abstract class PluginTestCase extends TestCase */ public function setUp() : void { - /* - * Force reload of October singletons - */ - PluginManager::forgetInstance(); - UpdateManager::forgetInstance(); + $this->resetManagers(); - /* - * Create application instance - */ + // Create application parent::setUp(); - /* - * Ensure system is up to date - */ - $this->runOctoberUpCommand(); - - /* - * Detect plugin from test and autoload it - */ - $this->pluginTestCaseLoadedPlugins = []; - $pluginCode = $this->guessPluginCodeFromTest(); - - if ($pluginCode !== false) { - $this->runPluginRefreshCommand($pluginCode, false); + // Ensure system is up to date + if ($this->usingTestDatabase) { + $this->runOctoberUpCommand(); } - /* - * Disable mailer - */ - Mail::pretend(); + // Detect a plugin and autoload it, if necessary + $this->detectPlugin(); + + // Disable mailer + \Mail::pretend(); } /** @@ -108,88 +43,8 @@ abstract class PluginTestCase extends TestCase public function tearDown() : void { $this->flushModelEventListeners(); + parent::tearDown(); - unset($this->app); - } - - /** - * Migrate database using october:up command. - * @return void - */ - protected function runOctoberUpCommand() - { - Artisan::call('october:up'); - } - - /** - * Since the test environment has loaded all the test plugins - * natively, this method will ensure the desired plugin is - * loaded in the system before proceeding to migrate it. - * @return void - */ - protected function runPluginRefreshCommand($code, $throwException = true) - { - if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { - if (!$throwException) { - return; - } - throw new Exception(sprintf('Invalid plugin code: "%s"', $code)); - } - - $manager = PluginManager::instance(); - $plugin = $manager->findByIdentifier($code); - - /* - * First time seeing this plugin, load it up - */ - if (!$plugin) { - $namespace = '\\'.str_replace('.', '\\', strtolower($code)); - $path = array_get($manager->getPluginNamespaces(), $namespace); - - if (!$path) { - if (!$throwException) { - return; - } - throw new Exception(sprintf('Unable to find plugin with code: "%s"', $code)); - } - - $plugin = $manager->loadPlugin($namespace, $path); - } - - /* - * Spin over dependencies and refresh them too - */ - $this->pluginTestCaseLoadedPlugins[$code] = $plugin; - - if (!empty($plugin->require)) { - foreach ((array) $plugin->require as $dependency) { - if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { - continue; - } - - $this->runPluginRefreshCommand($dependency); - } - } - - /* - * Execute the command - */ - Artisan::call('plugin:refresh', ['name' => $code]); - } - - /** - * Returns a plugin object from its code, useful for registering events, etc. - * @return PluginBase - */ - protected function getPluginObject($code = null) - { - if ($code === null) { - $code = $this->guessPluginCodeFromTest(); - } - - if (isset($this->pluginTestCaseLoadedPlugins[$code])) { - return $this->pluginTestCaseLoadedPlugins[$code]; - } } /** @@ -205,7 +60,7 @@ abstract class PluginTestCase extends TestCase continue; } - $reflectClass = new ReflectionClass($class); + $reflectClass = new \ReflectionClass($class); if ( !$reflectClass->isInstantiable() || !$reflectClass->isSubclassOf('October\Rain\Database\Model') || @@ -219,24 +74,4 @@ abstract class PluginTestCase extends TestCase ActiveRecord::flushEventListeners(); } - - /** - * Locates the plugin code based on the test file location. - * @return string|bool - */ - protected function guessPluginCodeFromTest() - { - $reflect = new ReflectionClass($this); - $path = $reflect->getFilename(); - $basePath = $this->app->pluginsPath(); - - $result = false; - - if (strpos($path, $basePath) === 0) { - $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); - $result = implode('.', array_slice(explode('/', $result), 0, 2)); - } - - return $result; - } } diff --git a/tests/README.md b/tests/README.md index a9e7b78b5..9960ba0c6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,26 +1,143 @@ -# Plugin testing +# Testing -Plugin unit tests can be performed by running `phpunit` in the base plugin directory. +October CMS has a suite of tools available for running automated tests on your October instance and plugins. To run tests, you must ensure that you have PHPUnit installed and can run the `phpunit` command from a command-line interface. -### Creating plugin tests +--- -Plugins can be tested by creating a file called `phpunit.xml` in the base directory with the following content, for example, in a file **/plugins/acme/blog/phpunit.xml**: +- [System Tests](#system-tests) + - [Unit Tests](#unit-tests) + - [Using a Custom Database Engine](#custom-database-engine) + - [Browser Tests](#browser-tests) + - [Testing Environment for Browser Tests](#testing-environment) + - [JavaScript Tests](#javascript-tests) +- [Creating Tests for Plugins](#creating-plugin-tests) + - [Unit Tests](#plugin-unit-tests) + - [Browser Tests](#plugin-browser-tests) +--- + + +## System Tests + +The system tests cover the tests that analyse the core functionality of October CMS. To run these tests, we recommend that you use Git to checkout a copy of the development version of October CMS and use Composer to install all necessary dependencies. + +You can do this on command-line by simply running the following: + +```bash +git checkout git@github.com:octobercms/october.git +cd october +composer install +``` + + +### Unit Tests + +The unit tests in October CMS can be found in the **tests/unit** folder and are executed through PHPUnit. You can run the tests by running the following command in the root folder of the October CMS installation: + +```bash +./vendor/bin/phpunit +``` + +This will run tests for both October CMS and the Rain library. The Rain library tests can be found in the **vendor/october/rain/tests** folder. + +Note that unit tests run in a special environment called `testing`. You may configure this environment by adding or modifying the configuration files in the **config/testing/** directory. + + +#### Using a Custom Database Engine + +By default, OctoberCMS uses SQLite stored in memory for the `testing` environment. If you wish to override this with your own database configuration, set the `useConfigForTesting` config to `true` in your `/config/database.php` file. + +When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. + +You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. + + +### Browser Tests + +Browsers tests are a more flexible type of automated test that run directly in a web browser. October CMS leverages the [Laravel Dusk](https://laravel.com/docs/6.x/dusk) framework to run these tests, which in turn uses Google Chrome and a ChromeDriver install to run the tests. + +Running the browser tests will require Google Chrome to be installed on your machine. Once this is done, you may prepare your installation for running Browser tests by running the following in your project root folder: + +```bash +php artisan dusk:chrome-driver +``` + +> **Note:** It is possible to use other browsers, or a standalone Selenium server, if you wish. Please see the [Laravel Dusk documentation](https://laravel.com/docs/6.x/dusk#using-other-browsers) for more information. + +Once installed, you may run the browsers test by simply running the following command in the root folder of the October CMS installation: + +```bash +php artisan dusk +``` + +If you have previously run the browser tests and want to re-run only the tests that failed, you may use this shortcut to run just the failed tests: + +```bash +php artisan dusk:fails +``` + +Note that your October CMS installation must be web-accessible in order to run the browser tests. Please review the next section on setting up the testing environment. + + +#### Testing Environment for Browser Tests + +The Browser tests in October CMS are, by default, set up to run within a special testing environment called `dusk`. This is configured to run October CMS via the inbuilt PHP web server, using an SQLite database to store the database temporarily. + +You may start this web server before running the browser tests simply by running the following: + +```bash +php artisan serve +``` + +This environment is configured in two places: the **.env.dusk** file available in the project root, and within the **config/dusk/** folder. You may modify either of these configuration files in order to configure the testing environment to your requirements. + +When the browser tests are started, the **.env** file in your project (if any) is subtituted with **.env.dusk** for the duration of the tests. Once the tests end, the **.env** file is restored to its original content. + +> **Note:** The system browser tests will need to be authenticated with a superuser-level user to run correctly. If you are using the default environment, this will happen automatically.

If you are custom settings however, you may need to provide authentication information to Dusk in order for it to run. You can specify the environment variables `DUSK_ADMIN_USER` and `DUSK_ADMIN_PASS` to use specific authentication credentials during testing. These are available in the `.env.dusk` file. + + +### JavaScript Tests + +In addition to the PHP-based tests above, we also have a suite of unit tests for our JavaScript libraries and functions. These run on an NodeJS-based environment, so you will need to [download and install](https://nodejs.org/en/download/) NodeJS and NPM in order to install the tools required for these tests. + +Once installed, you may install the tools by running the following in the browser root: + +```bash +npm install +``` + +Then, you may run the following command to run the JavaScript tests: + +```bash +npm run test +``` + + +## Creating Tests for Plugins + +October CMS has made it easy for plugin developers to create unit and browser tests for their plugins. + +Please read the sections below in order to configure your plugin for your required types of testing. + + +### Unit Tests + +To allow unit testing in your plugin, you must first create a file called `phpunit.xml` in the plugin base directory with the following content - for example, in a file **/plugins/acme/blog/phpunit.xml**: - - ./tests + ./tests/unit @@ -30,29 +147,53 @@ Plugins can be tested by creating a file called `phpunit.xml` in the base direct -Then a **tests/** directory can be created to contain the test classes. The file structure should mimic the base directory with classes having a `Test` suffix. Using a namespace for the class is also recommended. +Then you may create a **tests/unit/** directory to contain the unit test classes. - 'Hi!']); - $this->assertEquals(1, $post->id); - } + $post = Post::create(['title' => 'Hi!']); + $this->assertEquals(1, $post->id); } +} +``` -The test class should extend the base class `PluginTestCase` and this is a special class that will set up the October database stored in memory, as part of the `setUp` method. It will also refresh the plugin being tested, along with any of the defined dependencies in the plugin registration file. This is the equivalent of running the following before each test: +The `October\Core\Tests\PluginTestCase` class takes care of ensuring that you have a clean database for each test, in order to run each test in isolation. If you need to run some code before, or after each test, you may overwrite the `setUp` and `tearDown` methods in your class. It is important, however, that you allow the parent methods to run too. - php artisan october:up - php artisan plugin:refresh Acme.Blog - [php artisan plugin:refresh , ...] +```php +public function setUp(): void +{ + parent::setUp(); -> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests. Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. + // Load necessary model fixture + $this->postFixture = PostFixture::create(['title' => 'Test Post']); +} + +public function tearDown(): void +{ + // Remove model fixtur + unset($this->postFixture); + + parent::tearDown(); +} +``` + +> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests.

Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. use System\Classes\PluginManager; @@ -84,51 +225,51 @@ The test class should extend the base class `PluginTestCase` and this is a speci } } -#### Changing database engine for plugins tests +To run the unit tests for your plugin, simply go to the base folder for your plugin, and run the following command: -By default OctoberCMS uses SQLite stored in memory for the plugin testing environment. If you want to override the default behavior set the `useConfigForTesting` config to `true` in your `/config/database.php` file. When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. +```bash +../../../vendor/bin/phpunit +``` -You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. +This will execute PHPUnit in the context of your plugin. -## System testing + +### Browsers Tests -To perform unit testing on the core October files, you should download a development copy using composer or cloning the git repo. This will ensure you have the `tests/` directory. +Browser tests for plugins can be set up in much the same way as unit tests, with a small number of differences. -### Unit tests +Browsers tests require their own PHPUnit XML configuration file. You should create a `phpunit.dusk.xml` file in your project base directory with the following content: -Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. + + + + + ./tests/browser + + + + + + + + -### Functional tests +Browser tests should be separate from unit tests - you can instead store browser tests within the **tests/browser/** directory. -Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: +Browser test files follow the same rules as unit test files, however, instead of extending the `October\Core\Tests\PluginTestCase` class, they should instead extend the `October\Core\Tests\BrowserTestCase` class. -- Active theme is `demo` -- Language preference is `en` +To run the plugin browser tests, you must run the following command within the root folder of your *October CMS install*, not the plugin: -#### Selenium set up - -1. Download latest Java SE from http://java.sun.com/ and install -1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). -1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. -1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. - -#### Selenium configuration - -Create a new file `selenium.php` in the root directory, add the following content: - - getMethod($name); $method->setAccessible(true); return $method->invokeArgs($object, $params); @@ -34,7 +34,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase public static function getProtectedProperty($object, $name) { $className = get_class($object); - $class = new ReflectionClass($className); + $class = new \ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->getValue($object); @@ -43,7 +43,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase public static function setProtectedProperty($object, $name, $value) { $className = get_class($object); - $class = new ReflectionClass($className); + $class = new \ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->setValue($object, $value); diff --git a/tests/UiTestCase.php b/tests/UiTestCase.php deleted file mode 100644 index e95c3e9bf..000000000 --- a/tests/UiTestCase.php +++ /dev/null @@ -1,111 +0,0 @@ -markTestSkipped('Selenium skipped'); - } - - if (defined('TEST_SELENIUM_HOST')) { - $this->setHost(TEST_SELENIUM_HOST); - } - if (defined('TEST_SELENIUM_PORT')) { - $this->setPort(TEST_SELENIUM_PORT); - } - if (defined('TEST_SELENIUM_BROWSER')) { - $this->setBrowser(TEST_SELENIUM_BROWSER); - } - $this->setBrowserUrl(TEST_SELENIUM_URL); - } - - // - // OctoberCMS Helpers - // - - protected function signInToBackend() - { - $this->open('backend'); - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } - - // - // Selenium helpers - // - - protected function waitForElementPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if ($this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } - - protected function waitForElementNotPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if (!$this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3de4e0dc9..6c9464ba3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -11,5 +11,5 @@ $loader = new October\Rain\Support\ClassLoader( $loader->register(); $loader->addDirectories([ 'modules', - 'plugins' + 'plugins', ]); diff --git a/tests/fixtures/backend/models/UserFixture.php b/tests/fixtures/backend/models/UserFixture.php index a444cd597..acad32ce1 100644 --- a/tests/fixtures/backend/models/UserFixture.php +++ b/tests/fixtures/backend/models/UserFixture.php @@ -1,6 +1,6 @@ open('backend'); - - $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("name=login")); - $this->assertTrue($this->isElementPresent("name=password")); - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Login"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - /* - * Sign in - */ - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Dashboard | October CMS'); - $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->verifyText('css='.$cssLogoutLink, "Sign out"); - - /* - * Log out - */ - $this->click('css='.$cssLogoutLink); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } - - public function testPasswordReset() - { - $this->open('backend'); - - try { - $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->click('link=exact:Forgot your password?'); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Restore"); - $this->assertTrue($this->isElementPresent("link=Cancel")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->type("name=login", TEST_SELENIUM_USER); - sleep(1); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); - $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } -} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php deleted file mode 100644 index 5ff2a0a9e..000000000 --- a/tests/functional/cms/TemplateTest.php +++ /dev/null @@ -1,143 +0,0 @@ -signInToBackend(); - $this->open('cms'); - $this->waitForPageToLoad("30000"); - - // Fix the sidebar - $this->click("xpath=(//a[@class='fix-button'])[1]"); - - /* - * Page - */ - - // Create a new page - $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=settings[title]"); - - // Populate page details - $this->type('name=settings[title]', 'Functional Test Page'); - $this->type('name=settings[url]', '/xxx/functional/test/page'); - $this->type('name=fileName', 'xxx_functional_test_page'); - - // Save the new page - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->waitForElementPresent("name=settings[title]"); - sleep(1); - - // Delete the page - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this page?'); - // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); - $this->waitForElementNotPresent("name=settings[title]"); - - /* - * Partial - */ - - // Click partials menu item - $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); - - // Create a new partial - $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate partial details - $this->type('name=fileName', 'xxx_functional_test_partial'); - $this->type('name=settings[description]', 'Test partial'); - - // Save the new partial - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the partial - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this partial?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Layout - */ - - // Click layouts menu item - $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); - - // Create a new layout - $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate layout details - $this->type('name=fileName', 'xxx_functional_test_layout'); - $this->type('name=settings[description]', 'Test layout'); - - // Save the new layout - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the layout - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this layout?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Content - */ - - // Click contents menu item - $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); - - // Create a new content - $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate content details - $this->type('name=fileName', 'xxx_functional_test_content.txt'); - - // Save the new content - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the content - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this content file?'); - $this->waitForElementNotPresent("name=fileName"); - } -} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml deleted file mode 100644 index cb4586424..000000000 --- a/tests/functional/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ./ - - - diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index 65dd5e735..d06ed53a6 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -2,7 +2,7 @@ use Backend\Classes\AuthManager; use October\Rain\Exception\SystemException; -class AuthManagerTest extends TestCase +class AuthManagerTest extends \October\Core\Tests\TestCase { public function setUp(): void { diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index f63645198..9c4a99a7c 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\NavigationManager; -class NavigationManagerTest extends TestCase +class NavigationManagerTest extends \October\Core\Tests\TestCase { public function testRegisterMenuItems() { diff --git a/tests/unit/backend/classes/WidgetManagerTest.php b/tests/unit/backend/classes/WidgetManagerTest.php index 819997385..c46a75731 100644 --- a/tests/unit/backend/classes/WidgetManagerTest.php +++ b/tests/unit/backend/classes/WidgetManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\WidgetManager; -class WidgetManagerTest extends TestCase +class WidgetManagerTest extends \October\Core\Tests\TestCase { public function testListFormWidgets() { diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index 3766f5c8e..e33127eea 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -3,7 +3,7 @@ use Backend\Helpers\Backend; use Backend\Helpers\Exception\DecompileException; -class BackendHelperTest extends TestCase +class BackendHelperTest extends \October\Core\Tests\TestCase { public function testDecompileAssets() { diff --git a/tests/unit/backend/models/ExportModelTest.php b/tests/unit/backend/models/ExportModelTest.php index 4fd55b6f0..75fc8dba6 100644 --- a/tests/unit/backend/models/ExportModelTest.php +++ b/tests/unit/backend/models/ExportModelTest.php @@ -25,7 +25,7 @@ class ExampleExportModel extends ExportModel } } -class ExportModelTest extends TestCase +class ExportModelTest extends \October\Core\Tests\TestCase { // diff --git a/tests/unit/backend/models/ImportModelTest.php b/tests/unit/backend/models/ImportModelTest.php index 1f803bd06..c390c40f2 100644 --- a/tests/unit/backend/models/ImportModelTest.php +++ b/tests/unit/backend/models/ImportModelTest.php @@ -16,7 +16,7 @@ class ExampleImportModel extends ImportModel } } -class ImportModelTest extends TestCase +class ImportModelTest extends \October\Core\Tests\TestCase { // diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 9a290b509..0aa7ff8da 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -12,7 +12,7 @@ class ExampleTraitClass } } -class WidgetMakerTest extends TestCase +class WidgetMakerTest extends \October\Core\Tests\TestCase { /** * The object under test. diff --git a/tests/unit/backend/widgets/FilterTest.php b/tests/unit/backend/widgets/FilterTest.php index 05d0f0d78..66f04e62e 100644 --- a/tests/unit/backend/widgets/FilterTest.php +++ b/tests/unit/backend/widgets/FilterTest.php @@ -2,10 +2,17 @@ use Backend\Widgets\Filter; use Backend\Models\User; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; -class FilterTest extends PluginTestCase +class FilterTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + public function testRestrictedScopeWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/FormTest.php b/tests/unit/backend/widgets/FormTest.php index d373a1d86..81f49917a 100644 --- a/tests/unit/backend/widgets/FormTest.php +++ b/tests/unit/backend/widgets/FormTest.php @@ -2,15 +2,23 @@ use Backend\Widgets\Form; use Illuminate\Database\Eloquent\Model; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; class FormTestModel extends Model { } -class FormTest extends PluginTestCase +class FormTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + + public function testRestrictedFieldWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/ListsTest.php b/tests/unit/backend/widgets/ListsTest.php index e74aa877d..e8ad8cf03 100644 --- a/tests/unit/backend/widgets/ListsTest.php +++ b/tests/unit/backend/widgets/ListsTest.php @@ -3,10 +3,17 @@ use Backend\Models\User; use Backend\Widgets\Lists; use October\Rain\Exception\ApplicationException; -use October\Tests\Fixtures\Backend\Models\UserFixture; +use October\Core\Tests\Fixtures\Backend\Models\UserFixture; -class ListsTest extends PluginTestCase +class ListsTest extends \October\Core\Tests\PluginTestCase { + public function setUp() : void + { + parent::setUp(); + + include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; + } + public function testRestrictedColumnWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index ec82153f9..66bc7d849 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -28,7 +28,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject } } -class CmsCompoundObjectTest extends TestCase +class CmsCompoundObjectTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsExceptionTest.php b/tests/unit/cms/classes/CmsExceptionTest.php index 6e270fb7a..1a100e1fe 100644 --- a/tests/unit/cms/classes/CmsExceptionTest.php +++ b/tests/unit/cms/classes/CmsExceptionTest.php @@ -9,7 +9,7 @@ use Cms\Classes\CmsException; use Cms\Classes\CodeParser; use October\Rain\Exception\SystemException; -class CmsExceptionTest extends TestCase +class CmsExceptionTest extends \October\Core\Tests\TestCase { // // Tests diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index 4e19b55da..fa7cd2110 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -5,7 +5,7 @@ use Cms\Classes\Theme; use Cms\Classes\Layout; use October\Rain\Halcyon\Model; -class CmsObjectQueryTest extends TestCase +class CmsObjectQueryTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 2c65761b4..9a008ac71 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -13,7 +13,7 @@ class TestTemporaryCmsObject extends CmsObject protected $dirName = 'temporary'; } -class CmsObjectTest extends TestCase +class CmsObjectTest extends \October\Core\Tests\TestCase { public function testLoad() { diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 10d1b7d23..13e8024ee 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -8,7 +8,7 @@ use Cms\Classes\Layout; use Cms\Classes\CodeParser; use Cms\Classes\Controller; -class CodeParserTest extends TestCase +class CodeParserTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index 152367b8d..6a067e5f3 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -7,7 +7,7 @@ use Cms\Classes\Controller; use Cms\Classes\CodeParser; use Cms\Classes\ComponentManager; -class ComponentManagerTest extends TestCase +class ComponentManagerTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ContentTest.php b/tests/unit/cms/classes/ContentTest.php index 790501c6f..0178d41ae 100644 --- a/tests/unit/cms/classes/ContentTest.php +++ b/tests/unit/cms/classes/ContentTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use Cms\Classes\Content; -class ContentTest extends TestCase +class ContentTest extends \October\Core\Tests\TestCase { public function testMarkdownContent() diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index da16e317d..d7f1f40a4 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; use Cms\Classes\Controller; use October\Rain\Halcyon\Model; -class ControllerTest extends TestCase +class ControllerTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/PartialStackTest.php b/tests/unit/cms/classes/PartialStackTest.php index f9371a3b6..14404ffde 100644 --- a/tests/unit/cms/classes/PartialStackTest.php +++ b/tests/unit/cms/classes/PartialStackTest.php @@ -2,7 +2,7 @@ use Cms\Classes\PartialStack; -class PartialStackTest extends TestCase +class PartialStackTest extends \October\Core\Tests\TestCase { public function testStackPartials() diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index f4eeda27a..c5887e929 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Router; use Cms\Classes\Theme; -class RouterTest extends TestCase +class RouterTest extends \October\Core\Tests\TestCase { protected static $theme = null; diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 07a15f2db..69d461862 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -2,7 +2,7 @@ use Cms\Classes\Theme; -class ThemeTest extends TestCase +class ThemeTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/cms/helpers/FileTest.php b/tests/unit/cms/helpers/FileTest.php index dde19f0c3..23c253d6b 100644 --- a/tests/unit/cms/helpers/FileTest.php +++ b/tests/unit/cms/helpers/FileTest.php @@ -2,7 +2,7 @@ use Cms\Helpers\File as FileHelper; -class FileTest extends TestCase +class FileTest extends \October\Core\Tests\TestCase { public function testValidateName() { diff --git a/tests/unit/plugins/backend/ImportModelDbTest.php b/tests/unit/plugins/backend/ImportModelDbTest.php index 61a1d8ea9..7ca7c6cba 100644 --- a/tests/unit/plugins/backend/ImportModelDbTest.php +++ b/tests/unit/plugins/backend/ImportModelDbTest.php @@ -13,7 +13,7 @@ class ExampleDbImportModel extends ImportModel } } -class ImportModelDbTest extends PluginTestCase +class ImportModelDbTest extends \October\Core\Tests\PluginTestCase { public function testGetImportFilePath() { diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 9c316c032..5aed54acd 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -3,7 +3,7 @@ use System\Models\File as FileModel; use Database\Tester\Models\User; -class AttachManyModelTest extends PluginTestCase +class AttachManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index d50debdb6..da7af5f33 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; use Database\Tester\Models\SoftDeleteUser; use Symfony\Component\HttpFoundation\File\UploadedFile; -class AttachOneModelTest extends PluginTestCase +class AttachOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e850b7752..8d7a3314d 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Role; use Database\Tester\Models\Author; -class BelongsToManyModelTest extends PluginTestCase +class BelongsToManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 8fca6ff06..12ee089f4 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; -class BelongsToModelTest extends PluginTestCase +class BelongsToModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index e36460255..b84898a30 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use October\Rain\Database\Models\DeferredBinding; -class DeferredBindingTest extends PluginTestCase +class DeferredBindingTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 9a1def095..93f81914d 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyModelTest extends PluginTestCase +class HasManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php index 85ac947f2..8fde8f1fa 100644 --- a/tests/unit/plugins/database/HasManyThroughModelTest.php +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Country; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyThroughModelTest extends PluginTestCase +class HasManyThroughModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index e6bd0f45e..5e0920630 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; -class HasOneModelTest extends PluginTestCase +class HasOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php index 91be83b60..220dc5ec6 100644 --- a/tests/unit/plugins/database/HasOneThroughModelTest.php +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; use Database\Tester\Models\User; -class HasOneThroughModelTest extends PluginTestCase +class HasOneThroughModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 16bf07c1d..ed2a4286f 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\Post; -class ModelTest extends PluginTestCase +class ModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index a7ba3041c..8ffb67ecb 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; use October\Rain\Database\Collection; -class MorphManyModelTest extends PluginTestCase +class MorphManyModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index e8c6f60fd..130ad1a90 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use Database\Tester\Models\Meta; -class MorphOneModelTest extends PluginTestCase +class MorphOneModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index b0eb37ff7..cedf23017 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; -class MorphToModelTest extends PluginTestCase +class MorphToModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index 49d46764c..2a05621a3 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategoryNested; -class NestedTreeModelTest extends PluginTestCase +class NestedTreeModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 9d6d61114..568a54c0d 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\NullablePost; -class NullableModelTest extends PluginTestCase +class NullableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index c8fa19a54..3d9af946f 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\RevisionablePost; -class RevisionableModelTest extends PluginTestCase +class RevisionableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index c22b11d16..c1b0e3ad7 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategorySimple; -class SimpleTreeModelTest extends PluginTestCase +class SimpleTreeModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index 22cad1837..5f8a0de0e 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\SluggablePost; -class SluggableModelTest extends PluginTestCase +class SluggableModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index fc5212433..399f21dda 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -8,7 +8,7 @@ use Database\Tester\Models\UserWithSoftAuthor; use Database\Tester\Models\UserWithAuthorAndSoftDelete; use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; -class SoftDeleteModelTest extends PluginTestCase +class SoftDeleteModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index f5ddb55b7..94aa60926 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\ValidationPost; -class ValidationModelTest extends PluginTestCase +class ValidationModelTest extends \October\Core\Tests\PluginTestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 14dc6f54b..f75dd70fb 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -14,7 +14,7 @@ class CmsThemeTemplateFixture extends Model public $table = 'cms_theme_templates'; } -class AutoDatasourceTest extends PluginTestCase +class AutoDatasourceTest extends \October\Core\Tests\PluginTestCase { /** * Array of model fixtures. diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 5c5d313bd..77173cf3a 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use System\Classes\CombineAssets; -class CombineAssetsTest extends TestCase +class CombineAssetsTest extends \October\Core\Tests\TestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/CoreLangTest.php b/tests/unit/system/classes/CoreLangTest.php index 54899a812..5a5c5fdf3 100644 --- a/tests/unit/system/classes/CoreLangTest.php +++ b/tests/unit/system/classes/CoreLangTest.php @@ -2,7 +2,7 @@ use System\Classes\PluginManager; -class CoreLangTest extends TestCase +class CoreLangTest extends \October\Core\Tests\TestCase { public function testValidationTranslator() { diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index f4ee00a32..4c8eaa3e2 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -2,7 +2,7 @@ use System\Classes\MarkupManager; -class MarkupManagerTest extends TestCase +class MarkupManagerTest extends \October\Core\Tests\TestCase { public function setUp() : void diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 5d8082ba5..a326bbdf0 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -2,7 +2,7 @@ use System\Classes\MediaLibrary; -class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine +class MediaLibraryTest extends \October\Core\Tests\TestCase // @codingStandardsIgnoreLine { public function invalidPathsProvider() { diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index e41d860f5..c5c5102f8 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -1,7 +1,7 @@ Date: Mon, 3 Feb 2020 12:24:24 +0800 Subject: [PATCH 043/100] Tweak code block in README --- tests/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/README.md b/tests/README.md index 9960ba0c6..fb8a002c5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -196,6 +196,7 @@ public function tearDown(): void > **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests.

Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. use System\Classes\PluginManager; + use October\Core\Tests\PluginTestCase; class BaseTestCase extends PluginTestCase { From 6b114bf3b26fa45d0cc61f635a14b600a7e3d776 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 3 Feb 2020 12:44:45 +0800 Subject: [PATCH 044/100] Drop PHPUnit Selenium requirement --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 22a0fac82..21cb2afcb 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,6 @@ "require-dev": { "fzaninotto/faker": "^1.9", "phpunit/phpunit": "^8.0|^9.0", - "phpunit/phpunit-selenium": "dev-master", "dms/phpunit-arraysubset-asserts": "^0.1.0", "meyfa/phpunit-assert-gd": "^2.0", "squizlabs/php_codesniffer": "3.*", From 5e4916148f44a5373ca53452a6e1cf41028989d8 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 7 Feb 2020 16:59:39 +0800 Subject: [PATCH 045/100] Revert Laravel Dusk changes (#4919) The Browser tests will be made into a RainLab plugin. (https://github.com/rainlab/dusk-plugin) --- .env.dusk | 38 --- modules/system/ServiceProvider.php | 9 - modules/system/console/Dusk.php | 89 ------ modules/system/console/DuskFails.php | 31 -- phpunit.dusk.xml.dist | 34 -- tests/Browser/Backend/AuthTest.php | 41 --- tests/Browser/Backend/Cms/TemplateTest.php | 300 ------------------ tests/Browser/Pages/Backend/Cms.php | 32 -- tests/Browser/Pages/Backend/Dashboard.php | 33 -- .../Browser/Pages/Backend/ForgotPassword.php | 48 --- tests/Browser/Pages/Backend/Login.php | 49 --- tests/Browser/Pages/BackendPage.php | 21 -- tests/Browser/Pages/Page.php | 16 - tests/Browser/console/.gitignore | 2 - tests/Browser/screenshots/.gitignore | 2 - tests/BrowserTestCase.php | 147 --------- tests/Concerns/CreatesApplication.php | 98 ------ tests/Concerns/RunsMigrations.php | 14 - tests/Concerns/TestsPlugins.php | 108 ------- tests/PluginTestCase.php | 205 ++++++++++-- tests/README.md | 278 ++++------------ tests/TestCase.php | 12 +- tests/UiTestCase.php | 111 +++++++ tests/bootstrap.php | 2 +- .../InteractsWithAuthentication.php | 4 +- tests/fixtures/backend/models/UserFixture.php | 2 +- tests/functional/backend/AuthTest.php | 90 ++++++ tests/functional/cms/TemplateTest.php | 143 +++++++++ tests/functional/phpunit.xml | 18 ++ .../unit/backend/classes/AuthManagerTest.php | 2 +- .../backend/classes/NavigationManagerTest.php | 2 +- .../backend/classes/WidgetManagerTest.php | 2 +- .../backend/helpers/BackendHelperTest.php | 2 +- tests/unit/backend/models/ExportModelTest.php | 2 +- tests/unit/backend/models/ImportModelTest.php | 2 +- tests/unit/backend/traits/WidgetMakerTest.php | 2 +- tests/unit/backend/widgets/FilterTest.php | 11 +- tests/unit/backend/widgets/FormTest.php | 12 +- tests/unit/backend/widgets/ListsTest.php | 11 +- .../cms/classes/CmsCompoundObjectTest.php | 2 +- tests/unit/cms/classes/CmsExceptionTest.php | 2 +- tests/unit/cms/classes/CmsObjectQueryTest.php | 2 +- tests/unit/cms/classes/CmsObjectTest.php | 2 +- tests/unit/cms/classes/CodeParserTest.php | 2 +- .../unit/cms/classes/ComponentManagerTest.php | 2 +- tests/unit/cms/classes/ContentTest.php | 2 +- tests/unit/cms/classes/ControllerTest.php | 2 +- tests/unit/cms/classes/PartialStackTest.php | 2 +- tests/unit/cms/classes/RouterTest.php | 2 +- tests/unit/cms/classes/ThemeTest.php | 2 +- tests/unit/cms/helpers/FileTest.php | 2 +- .../plugins/backend/ImportModelDbTest.php | 2 +- .../plugins/database/AttachManyModelTest.php | 2 +- .../plugins/database/AttachOneModelTest.php | 2 +- .../database/BelongsToManyModelTest.php | 2 +- .../plugins/database/BelongsToModelTest.php | 2 +- .../plugins/database/DeferredBindingTest.php | 2 +- .../plugins/database/HasManyModelTest.php | 2 +- .../database/HasManyThroughModelTest.php | 2 +- .../unit/plugins/database/HasOneModelTest.php | 2 +- .../database/HasOneThroughModelTest.php | 2 +- tests/unit/plugins/database/ModelTest.php | 2 +- .../plugins/database/MorphManyModelTest.php | 2 +- .../plugins/database/MorphOneModelTest.php | 2 +- .../plugins/database/MorphToModelTest.php | 2 +- .../plugins/database/NestedTreeModelTest.php | 2 +- .../plugins/database/NullableModelTest.php | 2 +- .../database/RevisionableModelTest.php | 2 +- .../plugins/database/SimpleTreeModelTest.php | 2 +- .../plugins/database/SluggableModelTest.php | 2 +- .../plugins/database/SoftDeleteModelTest.php | 2 +- .../plugins/database/ValidationModelTest.php | 2 +- .../system/classes/AutoDatasourceTest.php | 2 +- .../unit/system/classes/CombineAssetsTest.php | 2 +- tests/unit/system/classes/CoreLangTest.php | 2 +- .../unit/system/classes/MarkupManagerTest.php | 2 +- .../unit/system/classes/MediaLibraryTest.php | 2 +- .../unit/system/classes/PluginManagerTest.php | 2 +- .../system/classes/UpdatesControllerTest.php | 2 +- .../system/classes/VersionManagerTest.php | 2 +- tests/unit/system/traits/AssetMakerTest.php | 2 +- 81 files changed, 681 insertions(+), 1428 deletions(-) delete mode 100644 .env.dusk delete mode 100644 modules/system/console/Dusk.php delete mode 100644 modules/system/console/DuskFails.php delete mode 100644 phpunit.dusk.xml.dist delete mode 100644 tests/Browser/Backend/AuthTest.php delete mode 100644 tests/Browser/Backend/Cms/TemplateTest.php delete mode 100644 tests/Browser/Pages/Backend/Cms.php delete mode 100644 tests/Browser/Pages/Backend/Dashboard.php delete mode 100644 tests/Browser/Pages/Backend/ForgotPassword.php delete mode 100644 tests/Browser/Pages/Backend/Login.php delete mode 100644 tests/Browser/Pages/BackendPage.php delete mode 100644 tests/Browser/Pages/Page.php delete mode 100644 tests/Browser/console/.gitignore delete mode 100644 tests/Browser/screenshots/.gitignore delete mode 100644 tests/BrowserTestCase.php delete mode 100644 tests/Concerns/CreatesApplication.php delete mode 100644 tests/Concerns/RunsMigrations.php delete mode 100644 tests/Concerns/TestsPlugins.php create mode 100644 tests/UiTestCase.php rename tests/{Concerns => concerns}/InteractsWithAuthentication.php (98%) create mode 100644 tests/functional/backend/AuthTest.php create mode 100644 tests/functional/cms/TemplateTest.php create mode 100644 tests/functional/phpunit.xml diff --git a/.env.dusk b/.env.dusk deleted file mode 100644 index 4ba3c8e1b..000000000 --- a/.env.dusk +++ /dev/null @@ -1,38 +0,0 @@ -APP_ENV=dusk -APP_DEBUG=true -APP_URL=http://127.0.0.1:8000 -APP_KEY=base64:R2w4QUhGcWxobnpjcHhlSkd0MHpMTjVxZTVuZ1BkaUM= - -DB_CONNECTION=sqlite -DB_HOST= -DB_PORT= -DB_DATABASE=storage/dusk.sqlite -DB_USERNAME= -DB_PASSWORD= -REDIS_HOST= -REDIS_PASSWORD= -REDIS_PORT= -DB_USE_CONFIG_FOR_TESTING=false - -CACHE_DRIVER=file - -SESSION_DRIVER=file - -QUEUE_CONNECTION=sync - -MAIL_DRIVER=array -MAIL_HOST= -MAIL_PORT= -MAIL_ENCRYPTION=tls -MAIL_USERNAME= -MAIL_PASSWORD= - -ROUTES_CACHE=false -ASSET_CACHE=false -DATABASE_TEMPLATES=false -LINK_POLICY=detect -ENABLE_CSRF=false - -# Uncomment the following to use custom authentication credentials for tests -#DUSK_ADMIN_USER= -#DUSK_ADMIN_PASS= diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 40ea3d649..1695adc54 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -261,15 +261,6 @@ class ServiceProvider extends ModuleServiceProvider $this->registerConsoleCommand('theme.list', 'System\Console\ThemeList'); $this->registerConsoleCommand('theme.use', 'System\Console\ThemeUse'); $this->registerConsoleCommand('theme.sync', 'System\Console\ThemeSync'); - - if (!App::isProduction() && class_exists('Laravel\Dusk\Dusk')) { - $this->registerConsoleCommand('dusk', 'System\Console\Dusk'); - $this->registerConsoleCommand('dusk.fails', 'System\Console\DuskFails'); - - $this->commands([ - \Laravel\Dusk\Console\ChromeDriverCommand::class, - ]); - } } /* diff --git a/modules/system/console/Dusk.php b/modules/system/console/Dusk.php deleted file mode 100644 index 5891037b5..000000000 --- a/modules/system/console/Dusk.php +++ /dev/null @@ -1,89 +0,0 @@ -duskFile()))) { - if (!file_exists(base_path('.env'))) { - $this->stubEnvironment(); - } elseif (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) { - $this->backupEnvironment(); - } - $this->refreshEnvironment(); - } - - $this->writeConfiguration(); - - $this->setupSignalHandler(); - } - - /** - * Restore the original environment. - * - * @return void - */ - protected function teardownDuskEnviroment() - { - $this->removeConfiguration(); - - if ( - file_exists(base_path($this->duskFile())) - && ( - file_exists(base_path('.env.backup')) - || file_exists(base_path('.env.blank')) - ) - ) { - $this->restoreEnvironment(); - } - } - - - /** - * Stub a current environment file. - * - * @return void - */ - protected function stubEnvironment() - { - touch(base_path('.env.blank')); - - copy(base_path($this->duskFile()), base_path('.env')); - } - - /** - * Backup the current environment file. - * - * @return void - */ - protected function backupEnvironment() - { - copy(base_path('.env'), base_path('.env.backup')); - - copy(base_path($this->duskFile()), base_path('.env')); - } - - /** - * Restore the backed-up environment file. - * - * @return void - */ - protected function restoreEnvironment() - { - if (file_exists(base_path('.env.blank'))) { - unlink(base_path('.env')); - unlink(base_path('.env.blank')); - } else { - copy(base_path('.env.backup'), base_path('.env')); - - unlink(base_path('.env.backup')); - } - } -} diff --git a/modules/system/console/DuskFails.php b/modules/system/console/DuskFails.php deleted file mode 100644 index 7cbbd3b71..000000000 --- a/modules/system/console/DuskFails.php +++ /dev/null @@ -1,31 +0,0 @@ - - - - - ./tests/Browser/Backend - - - - - - ./modules/ - - - ./modules/backend/routes.php - ./modules/cms/routes.php - ./modules/system/routes.php - - ./modules/backend/database - ./modules/cms/database - ./modules/system/database - - - - diff --git a/tests/Browser/Backend/AuthTest.php b/tests/Browser/Backend/AuthTest.php deleted file mode 100644 index 6d1b0514a..000000000 --- a/tests/Browser/Backend/AuthTest.php +++ /dev/null @@ -1,41 +0,0 @@ -browse(function (Browser $browser) { - $browser - ->signInToBackend() - ->click('@accountMenu') - ->clickLink('Sign out'); - - $browser - ->on(new Login); - }); - } - - public function testPasswordReset() - { - $this->browse(function (Browser $browser) { - $browser - ->visit(new Login) - ->pause(500) - ->click('@forgotPasswordLink'); - - $browser - ->on(new ForgotPassword) - ->type('@loginField', 'admin') - ->click('@submitButton'); - - $browser - ->on(new Login) - ->waitFor('.flash-message') - ->assertSeeIn('.flash-message', 'Message sent to your email address'); - }); - } -} diff --git a/tests/Browser/Backend/Cms/TemplateTest.php b/tests/Browser/Backend/Cms/TemplateTest.php deleted file mode 100644 index 43fcb5039..000000000 --- a/tests/Browser/Backend/Cms/TemplateTest.php +++ /dev/null @@ -1,300 +0,0 @@ -browse(function (Browser $browser) { - $browser - ->signInToBackend() - ->visit(new Cms) - ->pause(200); - - // Fix side panel, if necessary - if ($browser->hasClass('', 'side-panel-not-fixed')) { - $browser - ->mouseover('@sideNav > li[data-menu-item="pages"]') - ->waitFor('@sidePanel') - ->mouseover('@sidePanel') - ->waitFor('@sidePanelFixButton') - ->click('@sidePanelFixButton'); - } - - // Add a new page - $browser - ->click('form[data-template-type="page"] button[data-control="create-template"]') - ->waitFor('#cms-master-tabs .tab-content .tab-pane'); - - $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); - - $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); - $this->assertEquals('New page', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); - - $browser - ->type('input[name="settings[title]"]', 'Functional Test Page') - ->pause(100) - - // Check that slug values are working - ->assertInputValue('input[name="settings[url]"]', '/functional-test-page') - ->assertInputValue('input[name="fileName"]', 'functional-test-page') - - ->clear('input[name="settings[url]"]') - ->type('input[name="settings[url]"]', '/xxx/functional/test/page') - ->clear('input[name="fileName"]') - ->type('input[name="fileName"]', 'xxx_functional_test_page.htm') - - // Check that slug values have not been re-added after manual entry - ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') - ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); - - // Save the new page - $browser - ->click('a[data-request="onSave"]') - ->waitFor('.flash-message') - ->assertSeeIn('.flash-message', 'Template saved.'); - - $this->assertEquals( - 'Functional Test Page', - $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') - ); - - // Close the tab - $browser - ->click('li[data-tab-id^="page-"][data-tab-id$="-xxx_functional_test_page.htm"] span.tab-close') - ->pause(100) - ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); - - // Re-open the page - $browser - ->click('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"] a') - ->waitFor('#cms-master-tabs .tab-content .tab-pane') - - // Check that saved details are still there - ->assertInputValue('input[name="settings[title]"]', 'Functional Test Page') - ->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page') - ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm'); - - // Delete the page - $browser - ->click('button[data-request="onDelete"]') - ->waitFor('.sweet-alert.showSweetAlert.visible') - ->pause(300) - ->click('.sweet-alert.showSweetAlert.visible button.confirm') - ->waitUntilMissing('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"]'); - }); - } - - public function testPartialTemplates() - { - $this->browse(function (Browser $browser) { - $browser - ->signInToBackend() - ->visit(new Cms) - ->pause(200); - - // Fix side panel, if necessary - if ($browser->hasClass('', 'side-panel-not-fixed')) { - $browser - ->mouseover('@sideNav > li[data-menu-item="pages"]') - ->waitFor('@sidePanel') - ->mouseover('@sidePanel') - ->waitFor('@sidePanelFixButton') - ->click('@sidePanelFixButton'); - } - - $browser - ->click('@sideNav > li[data-menu-item="partials"] a'); - - // Add a new partial - $browser - ->click('form[data-template-type="partial"] button[data-control="create-template"]') - ->waitFor('#cms-master-tabs .tab-content .tab-pane'); - - $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); - - $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); - $this->assertEquals('New partial', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); - - $browser - ->type('input[name="fileName"]', 'xxx_functional_test_partial') - ->type('input[name="settings[description]"]', 'Test Partial'); - - // Save the new partial - $browser - ->click('a[data-request="onSave"]') - ->waitFor('.flash-message') - ->assertSeeIn('.flash-message', 'Template saved.'); - - $this->assertEquals( - 'xxx_functional_test_partial', - $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') - ); - - // Close the tab - $browser - ->click('li[data-tab-id^="partial-"][data-tab-id$="-xxx_functional_test_partial.htm"] span.tab-close') - ->pause(100) - ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); - - // Re-open the partial - $browser - ->click('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"] a') - ->waitFor('#cms-master-tabs .tab-content .tab-pane') - - // Check that saved details are still there - ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_partial.htm') - ->assertInputValue('input[name="settings[description]"]', 'Test Partial'); - - // Delete the partial - $browser - ->click('button[data-request="onDelete"]') - ->waitFor('.sweet-alert.showSweetAlert.visible') - ->pause(300) - ->click('.sweet-alert.showSweetAlert.visible button.confirm') - ->waitUntilMissing('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"]'); - }); - } - - public function testLayoutTemplates() - { - $this->browse(function (Browser $browser) { - $browser - ->signInToBackend() - ->visit(new Cms) - ->pause(200); - - // Fix side panel, if necessary - if ($browser->hasClass('', 'side-panel-not-fixed')) { - $browser - ->mouseover('@sideNav > li[data-menu-item="pages"]') - ->waitFor('@sidePanel') - ->mouseover('@sidePanel') - ->waitFor('@sidePanelFixButton') - ->click('@sidePanelFixButton'); - } - - $browser - ->click('@sideNav > li[data-menu-item="layouts"] a'); - - // Add a new layout - $browser - ->click('form[data-template-type="layout"] button[data-control="create-template"]') - ->waitFor('#cms-master-tabs .tab-content .tab-pane'); - - $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); - - $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); - $this->assertEquals('New layout', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); - - $browser - ->type('input[name="fileName"]', 'xxx_functional_test_layout') - ->type('input[name="settings[description]"]', 'Test Layout'); - - // Save the new layout - $browser - ->click('a[data-request="onSave"]') - ->waitFor('.flash-message') - ->assertSeeIn('.flash-message', 'Template saved.'); - - $this->assertEquals( - 'xxx_functional_test_layout', - $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') - ); - - // Close the tab - $browser - ->click('li[data-tab-id^="layout-"][data-tab-id$="-xxx_functional_test_layout.htm"] span.tab-close') - ->pause(100) - ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); - - // Re-open the partial - $browser - ->click('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"] a') - ->waitFor('#cms-master-tabs .tab-content .tab-pane') - - // Check that saved details are still there - ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_layout.htm') - ->assertInputValue('input[name="settings[description]"]', 'Test Layout'); - - // Delete the partial - $browser - ->click('button[data-request="onDelete"]') - ->waitFor('.sweet-alert.showSweetAlert.visible') - ->pause(300) - ->click('.sweet-alert.showSweetAlert.visible button.confirm') - ->waitUntilMissing('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"]'); - }); - } - - public function testContentTemplates() - { - $this->browse(function (Browser $browser) { - $browser - ->signInToBackend() - ->visit(new Cms) - ->pause(200); - - // Fix side panel, if necessary - if ($browser->hasClass('', 'side-panel-not-fixed')) { - $browser - ->mouseover('@sideNav > li[data-menu-item="pages"]') - ->waitFor('@sidePanel') - ->mouseover('@sidePanel') - ->waitFor('@sidePanelFixButton') - ->click('@sidePanelFixButton'); - } - - $browser - ->click('@sideNav > li[data-menu-item="content"] a'); - - // Add a new content file - $browser - ->click('form[data-template-type="content"] button[data-control="create-template"]') - ->waitFor('#cms-master-tabs .tab-content .tab-pane'); - - $tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id'); - - $browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]'); - $this->assertStringContainsString('content', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]')); - - $browser - ->type('input[name="fileName"]', 'xxx_functional_test_content.txt'); - - // Save the new content file - $browser - ->click('a[data-request="onSave"]') - ->waitFor('.flash-message') - ->assertSeeIn('.flash-message', 'Template saved.'); - - $this->assertEquals( - 'xxx_functional_test_content.txt', - $browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title') - ); - - // Close the tab - $browser - ->click('li[data-tab-id^="content-"][data-tab-id$="-xxx_functional_test_content.txt"] span.tab-close') - ->pause(100) - ->assertMissing('#cms-master-tabs .tab-content .tab-pane'); - - // Re-open the partial - $browser - ->click('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"] a') - ->waitFor('#cms-master-tabs .tab-content .tab-pane') - - // Check that saved details are still there - ->assertInputValue('input[name="fileName"]', 'xxx_functional_test_content.txt'); - - // Delete the partial - $browser - ->click('button[data-request="onDelete"]') - ->waitFor('.sweet-alert.showSweetAlert.visible') - ->pause(300) - ->click('.sweet-alert.showSweetAlert.visible button.confirm') - ->waitUntilMissing('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"]'); - }); - } -} diff --git a/tests/Browser/Pages/Backend/Cms.php b/tests/Browser/Pages/Backend/Cms.php deleted file mode 100644 index e088330c1..000000000 --- a/tests/Browser/Pages/Backend/Cms.php +++ /dev/null @@ -1,32 +0,0 @@ -assertTitleContains('CMS |') - ->assertPresent('@mainMenu') - ->assertPresent('@sideNav') - ->assertPresent('@accountMenu'); - } -} diff --git a/tests/Browser/Pages/Backend/Dashboard.php b/tests/Browser/Pages/Backend/Dashboard.php deleted file mode 100644 index 1fb910d20..000000000 --- a/tests/Browser/Pages/Backend/Dashboard.php +++ /dev/null @@ -1,33 +0,0 @@ -assertTitleContains('Dashboard |') - ->assertPresent('@mainMenu') - ->assertPresent('@accountMenu') - ->waitFor('.report-widget') - ->assertSee('Welcome'); - } -} diff --git a/tests/Browser/Pages/Backend/ForgotPassword.php b/tests/Browser/Pages/Backend/ForgotPassword.php deleted file mode 100644 index 97f02bb1e..000000000 --- a/tests/Browser/Pages/Backend/ForgotPassword.php +++ /dev/null @@ -1,48 +0,0 @@ -assertTitle('Administration Area') - ->assertPresent('@loginField') - ->assertMissing('input[name="password"]') - ->assertPresent('@submitButton') - ->assertPresent('@cancelLink') - ->assertSeeIn('@submitButton', 'Restore'); - } - - /** - * Get the global element shortcuts for the site. - * - * @return array - */ - public function elements() - { - return [ - '@loginField' => 'input[name="login"]', - '@submitButton' => 'button[type="submit"]', - '@cancelLink' => 'p.forgot-password > a', - ]; - } -} diff --git a/tests/Browser/Pages/Backend/Login.php b/tests/Browser/Pages/Backend/Login.php deleted file mode 100644 index d62672aa0..000000000 --- a/tests/Browser/Pages/Backend/Login.php +++ /dev/null @@ -1,49 +0,0 @@ -assertTitle('Administration Area') - ->assertPresent('@loginField') - ->assertPresent('@passwordField') - ->assertPresent('@submitButton') - ->assertPresent('@forgotPasswordLink') - ->assertSeeIn('@submitButton', 'Login'); - } - - /** - * Get the global element shortcuts for the site. - * - * @return array - */ - public function elements() - { - return [ - '@loginField' => 'input[name="login"]', - '@passwordField' => 'input[name="password"]', - '@submitButton' => 'button[type="submit"]', - '@forgotPasswordLink' => 'p.forgot-password > a', - ]; - } -} diff --git a/tests/Browser/Pages/BackendPage.php b/tests/Browser/Pages/BackendPage.php deleted file mode 100644 index b57d6cee5..000000000 --- a/tests/Browser/Pages/BackendPage.php +++ /dev/null @@ -1,21 +0,0 @@ - '#layout-mainmenu', - '@accountMenu' => '#layout-mainmenu .mainmenu-account > a', - - '@sideNav' => '#layout-sidenav > ul', - '@sidePanel' => '#layout-side-panel', - '@sidePanelFixButton' => '#layout-side-panel a.fix-button', - ]; - } -} diff --git a/tests/Browser/Pages/Page.php b/tests/Browser/Pages/Page.php deleted file mode 100644 index 30bfd0632..000000000 --- a/tests/Browser/Pages/Page.php +++ /dev/null @@ -1,16 +0,0 @@ -addArguments([ - '--disable-gpu', - '--headless', - '--window-size=1920,1080', - ]); - - return RemoteWebDriver::create( - 'http://localhost:9515', - DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, - $options - ) - ); - } - - public function setUp(): void - { - $this->resetManagers(); - - parent::setUp(); - - // Ensure system is up to date - if ($this->usingTestDatabase) { - $this->runOctoberUpCommand(); - } - - // Detect a plugin and autoload it, if necessary - $this->detectPlugin(); - - // Disable mailer - \Mail::pretend(); - - Browser::$baseUrl = $this->baseUrl(); - Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots'); - Browser::$storeConsoleLogAt = base_path('tests/Browser/console'); - Browser::$userResolver = function () { - return $this->user(); - }; - - $this->setupMacros(); - } - - public function tearDown(): void - { - if ($this->usingTestDatabase && isset($this->testDatabasePath)) { - unlink($this->testDatabasePath); - } - - parent::tearDown(); - } - - /** - * Defines October macros for use in browser tests - * - * @return void - */ - protected function setupMacros() - { - /** - * Signs the user into the backend - */ - Browser::macro('signInToBackend', function (string $username = null, string $password = null) { - $username = $username ?? env('DUSK_ADMIN_USER', 'admin'); - $password = $password ?? env('DUSK_ADMIN_PASS', 'admin1234'); - - $this - ->visit(new Login) - ->pause(500) - ->type('@loginField', $username) - ->type('@passwordField', $password) - ->click('@submitButton'); - - $this-> - on(new Dashboard); - - return $this; - }); - - - Browser::macro('hasClass', function (string $selector, string $class) { - $classes = preg_split('/\s+/', $this->attribute($selector, 'class'), -1, PREG_SPLIT_NO_EMPTY); - - if (empty($classes)) { - return false; - } - - return in_array($class, $classes); - }); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } -} diff --git a/tests/Concerns/CreatesApplication.php b/tests/Concerns/CreatesApplication.php deleted file mode 100644 index e9a35fc75..000000000 --- a/tests/Concerns/CreatesApplication.php +++ /dev/null @@ -1,98 +0,0 @@ -make('Illuminate\Contracts\Console\Kernel')->bootstrap(); - $app['cache']->setDefaultDriver('array'); - $app->setLocale('en'); - - $app->singleton('auth', function ($app) { - $app['auth.loaded'] = true; - - return AuthManager::instance(); - }); - - // Use test database configuration, unless overriden - $dbConnection = Config::get('database.default', 'sqlite'); - $dbConnections = [ - $dbConnection => Config::get('database.connections.' . $dbConnection, [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ]) - ]; - - if (env('APP_ENV') === 'testing' && !Config::get('database.useConfigForTesting', false)) { - $this->usingTestDatabase = true; - - $dbConnection = 'sqlite'; - $dbConnections = [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ], - ]; - } elseif (env('APP_ENV') === 'dusk' && !Config::get('database.useConfigForTesting', false)) { - $this->usingTestDatabase = true; - - $dbConnection = 'sqlite'; - $dbConnections = [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => 'storage/dusk.sqlite', - 'prefix' => '', - ], - ]; - - // Ensure a fresh copy of the SQLite database is made - $this->testDatabasePath = base_path('storage/dusk.sqlite'); - - if (file_exists($this->testDatabasePath)) { - unlink($this->testDatabasePath); - } - - touch($this->testDatabasePath); - } - - $app['config']->set('database.default', $dbConnection); - $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); - - /** - * Prevent mail from being sent out - */ - $app['config']->set('mail.driver', 'array'); - - /** - * Modify the plugin path away from the test context - */ - $app->setPluginsPath(realpath(base_path() . Config::get('cms.pluginsPath'))); - - return $app; - } -} diff --git a/tests/Concerns/RunsMigrations.php b/tests/Concerns/RunsMigrations.php deleted file mode 100644 index a9b6cd41a..000000000 --- a/tests/Concerns/RunsMigrations.php +++ /dev/null @@ -1,14 +0,0 @@ -pluginTestCaseLoadedPlugins = []; - $pluginCode = $this->guessPluginCodeFromTest(); - - if ($pluginCode !== false) { - $this->runPluginRefreshCommand($pluginCode, false); - } - } - - /** - * Locates the plugin code based on the test file location. - * - * @return string|bool - */ - protected function guessPluginCodeFromTest() - { - $reflect = new \ReflectionClass($this); - $path = $reflect->getFilename(); - $basePath = $this->app->pluginsPath(); - - $result = false; - - if (strpos($path, $basePath) === 0) { - $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); - $result = implode('.', array_slice(explode('/', $result), 0, 2)); - } - - return $result; - } - - /** - * Runs a refresh command on a plugin. - * - * Since the test environment has loaded all the test plugins - * natively, this method will ensure the desired plugin is - * loaded in the system before proceeding to migrate it. - * - * @return void - */ - protected function runPluginRefreshCommand($code, $throwException = true): void - { - if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { - if (!$throwException) { - return; - } - throw new \Exception(sprintf('Invalid plugin code: "%s"', $code)); - } - - $manager = PluginManager::instance(); - $plugin = $manager->findByIdentifier($code); - - // First time seeing this plugin, load it up - if (!$plugin) { - $namespace = '\\'.str_replace('.', '\\', strtolower($code)); - $path = array_get($manager->getPluginNamespaces(), $namespace); - - if (!$path) { - if (!$throwException) { - return; - } - throw new \Exception(sprintf('Unable to find plugin with code: "%s"', $code)); - } - - $plugin = $manager->loadPlugin($namespace, $path); - } - - // Spin over dependencies and refresh them too - $this->pluginTestCaseLoadedPlugins[$code] = $plugin; - - if (!empty($plugin->require)) { - foreach ((array) $plugin->require as $dependency) { - if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { - continue; - } - - $this->runPluginRefreshCommand($dependency); - } - } - - // Execute the command - \Artisan::call('plugin:refresh', ['name' => $code]); - } -} diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 82f09618a..728d5d430 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -1,17 +1,67 @@ -make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + + $app['cache']->setDefaultDriver('array'); + $app->setLocale('en'); + + $app->singleton('auth', function ($app) { + $app['auth.loaded'] = true; + + return AuthManager::instance(); + }); + + /* + * Store database in memory by default, if not specified otherwise + */ + $dbConnection = 'sqlite'; + + $dbConnections = []; + $dbConnections['sqlite'] = [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '' + ]; + + if (env('APP_ENV') === 'testing' && Config::get('database.useConfigForTesting', false)) { + $dbConnection = Config::get('database.default', 'sqlite'); + + $dbConnections[$dbConnection] = Config::get('database.connections' . $dbConnection, $dbConnections['sqlite']); + } + + $app['config']->set('database.default', $dbConnection); + $app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]); + + /* + * Modify the plugin path away from the test context + */ + $app->setPluginsPath(realpath(base_path().Config::get('cms.pluginsPath'))); + + return $app; + } /** * Perform test case set up. @@ -19,21 +69,36 @@ abstract class PluginTestCase extends TestCase */ public function setUp() : void { - $this->resetManagers(); + /* + * Force reload of October singletons + */ + PluginManager::forgetInstance(); + UpdateManager::forgetInstance(); - // Create application + /* + * Create application instance + */ parent::setUp(); - // Ensure system is up to date - if ($this->usingTestDatabase) { - $this->runOctoberUpCommand(); + /* + * Ensure system is up to date + */ + $this->runOctoberUpCommand(); + + /* + * Detect plugin from test and autoload it + */ + $this->pluginTestCaseLoadedPlugins = []; + $pluginCode = $this->guessPluginCodeFromTest(); + + if ($pluginCode !== false) { + $this->runPluginRefreshCommand($pluginCode, false); } - // Detect a plugin and autoload it, if necessary - $this->detectPlugin(); - - // Disable mailer - \Mail::pretend(); + /* + * Disable mailer + */ + Mail::pretend(); } /** @@ -43,8 +108,88 @@ abstract class PluginTestCase extends TestCase public function tearDown() : void { $this->flushModelEventListeners(); - parent::tearDown(); + unset($this->app); + } + + /** + * Migrate database using october:up command. + * @return void + */ + protected function runOctoberUpCommand() + { + Artisan::call('october:up'); + } + + /** + * Since the test environment has loaded all the test plugins + * natively, this method will ensure the desired plugin is + * loaded in the system before proceeding to migrate it. + * @return void + */ + protected function runPluginRefreshCommand($code, $throwException = true) + { + if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) { + if (!$throwException) { + return; + } + throw new Exception(sprintf('Invalid plugin code: "%s"', $code)); + } + + $manager = PluginManager::instance(); + $plugin = $manager->findByIdentifier($code); + + /* + * First time seeing this plugin, load it up + */ + if (!$plugin) { + $namespace = '\\'.str_replace('.', '\\', strtolower($code)); + $path = array_get($manager->getPluginNamespaces(), $namespace); + + if (!$path) { + if (!$throwException) { + return; + } + throw new Exception(sprintf('Unable to find plugin with code: "%s"', $code)); + } + + $plugin = $manager->loadPlugin($namespace, $path); + } + + /* + * Spin over dependencies and refresh them too + */ + $this->pluginTestCaseLoadedPlugins[$code] = $plugin; + + if (!empty($plugin->require)) { + foreach ((array) $plugin->require as $dependency) { + if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) { + continue; + } + + $this->runPluginRefreshCommand($dependency); + } + } + + /* + * Execute the command + */ + Artisan::call('plugin:refresh', ['name' => $code]); + } + + /** + * Returns a plugin object from its code, useful for registering events, etc. + * @return PluginBase + */ + protected function getPluginObject($code = null) + { + if ($code === null) { + $code = $this->guessPluginCodeFromTest(); + } + + if (isset($this->pluginTestCaseLoadedPlugins[$code])) { + return $this->pluginTestCaseLoadedPlugins[$code]; + } } /** @@ -60,7 +205,7 @@ abstract class PluginTestCase extends TestCase continue; } - $reflectClass = new \ReflectionClass($class); + $reflectClass = new ReflectionClass($class); if ( !$reflectClass->isInstantiable() || !$reflectClass->isSubclassOf('October\Rain\Database\Model') || @@ -74,4 +219,24 @@ abstract class PluginTestCase extends TestCase ActiveRecord::flushEventListeners(); } + + /** + * Locates the plugin code based on the test file location. + * @return string|bool + */ + protected function guessPluginCodeFromTest() + { + $reflect = new ReflectionClass($this); + $path = $reflect->getFilename(); + $basePath = $this->app->pluginsPath(); + + $result = false; + + if (strpos($path, $basePath) === 0) { + $result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/'); + $result = implode('.', array_slice(explode('/', $result), 0, 2)); + } + + return $result; + } } diff --git a/tests/README.md b/tests/README.md index fb8a002c5..a9e7b78b5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,143 +1,26 @@ -# Testing +# Plugin testing -October CMS has a suite of tools available for running automated tests on your October instance and plugins. To run tests, you must ensure that you have PHPUnit installed and can run the `phpunit` command from a command-line interface. +Plugin unit tests can be performed by running `phpunit` in the base plugin directory. ---- +### Creating plugin tests -- [System Tests](#system-tests) - - [Unit Tests](#unit-tests) - - [Using a Custom Database Engine](#custom-database-engine) - - [Browser Tests](#browser-tests) - - [Testing Environment for Browser Tests](#testing-environment) - - [JavaScript Tests](#javascript-tests) -- [Creating Tests for Plugins](#creating-plugin-tests) - - [Unit Tests](#plugin-unit-tests) - - [Browser Tests](#plugin-browser-tests) ---- - - -## System Tests - -The system tests cover the tests that analyse the core functionality of October CMS. To run these tests, we recommend that you use Git to checkout a copy of the development version of October CMS and use Composer to install all necessary dependencies. - -You can do this on command-line by simply running the following: - -```bash -git checkout git@github.com:octobercms/october.git -cd october -composer install -``` - - -### Unit Tests - -The unit tests in October CMS can be found in the **tests/unit** folder and are executed through PHPUnit. You can run the tests by running the following command in the root folder of the October CMS installation: - -```bash -./vendor/bin/phpunit -``` - -This will run tests for both October CMS and the Rain library. The Rain library tests can be found in the **vendor/october/rain/tests** folder. - -Note that unit tests run in a special environment called `testing`. You may configure this environment by adding or modifying the configuration files in the **config/testing/** directory. - - -#### Using a Custom Database Engine - -By default, OctoberCMS uses SQLite stored in memory for the `testing` environment. If you wish to override this with your own database configuration, set the `useConfigForTesting` config to `true` in your `/config/database.php` file. - -When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. - -You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. - - -### Browser Tests - -Browsers tests are a more flexible type of automated test that run directly in a web browser. October CMS leverages the [Laravel Dusk](https://laravel.com/docs/6.x/dusk) framework to run these tests, which in turn uses Google Chrome and a ChromeDriver install to run the tests. - -Running the browser tests will require Google Chrome to be installed on your machine. Once this is done, you may prepare your installation for running Browser tests by running the following in your project root folder: - -```bash -php artisan dusk:chrome-driver -``` - -> **Note:** It is possible to use other browsers, or a standalone Selenium server, if you wish. Please see the [Laravel Dusk documentation](https://laravel.com/docs/6.x/dusk#using-other-browsers) for more information. - -Once installed, you may run the browsers test by simply running the following command in the root folder of the October CMS installation: - -```bash -php artisan dusk -``` - -If you have previously run the browser tests and want to re-run only the tests that failed, you may use this shortcut to run just the failed tests: - -```bash -php artisan dusk:fails -``` - -Note that your October CMS installation must be web-accessible in order to run the browser tests. Please review the next section on setting up the testing environment. - - -#### Testing Environment for Browser Tests - -The Browser tests in October CMS are, by default, set up to run within a special testing environment called `dusk`. This is configured to run October CMS via the inbuilt PHP web server, using an SQLite database to store the database temporarily. - -You may start this web server before running the browser tests simply by running the following: - -```bash -php artisan serve -``` - -This environment is configured in two places: the **.env.dusk** file available in the project root, and within the **config/dusk/** folder. You may modify either of these configuration files in order to configure the testing environment to your requirements. - -When the browser tests are started, the **.env** file in your project (if any) is subtituted with **.env.dusk** for the duration of the tests. Once the tests end, the **.env** file is restored to its original content. - -> **Note:** The system browser tests will need to be authenticated with a superuser-level user to run correctly. If you are using the default environment, this will happen automatically.

If you are custom settings however, you may need to provide authentication information to Dusk in order for it to run. You can specify the environment variables `DUSK_ADMIN_USER` and `DUSK_ADMIN_PASS` to use specific authentication credentials during testing. These are available in the `.env.dusk` file. - - -### JavaScript Tests - -In addition to the PHP-based tests above, we also have a suite of unit tests for our JavaScript libraries and functions. These run on an NodeJS-based environment, so you will need to [download and install](https://nodejs.org/en/download/) NodeJS and NPM in order to install the tools required for these tests. - -Once installed, you may install the tools by running the following in the browser root: - -```bash -npm install -``` - -Then, you may run the following command to run the JavaScript tests: - -```bash -npm run test -``` - - -## Creating Tests for Plugins - -October CMS has made it easy for plugin developers to create unit and browser tests for their plugins. - -Please read the sections below in order to configure your plugin for your required types of testing. - - -### Unit Tests - -To allow unit testing in your plugin, you must first create a file called `phpunit.xml` in the plugin base directory with the following content - for example, in a file **/plugins/acme/blog/phpunit.xml**: +Plugins can be tested by creating a file called `phpunit.xml` in the base directory with the following content, for example, in a file **/plugins/acme/blog/phpunit.xml**: - - ./tests/unit + ./tests @@ -147,56 +30,31 @@ To allow unit testing in your plugin, you must first create a file called `phpun -Then you may create a **tests/unit/** directory to contain the unit test classes. +Then a **tests/** directory can be created to contain the test classes. The file structure should mimic the base directory with classes having a `Test` suffix. Using a namespace for the class is also recommended. -Each unit test class file must match the following guidelines: + 'Hi!']); - $this->assertEquals(1, $post->id); + public function testCreateFirstPost() + { + $post = Post::create(['title' => 'Hi!']); + $this->assertEquals(1, $post->id); + } } -} -``` -The `October\Core\Tests\PluginTestCase` class takes care of ensuring that you have a clean database for each test, in order to run each test in isolation. If you need to run some code before, or after each test, you may overwrite the `setUp` and `tearDown` methods in your class. It is important, however, that you allow the parent methods to run too. +The test class should extend the base class `PluginTestCase` and this is a special class that will set up the October database stored in memory, as part of the `setUp` method. It will also refresh the plugin being tested, along with any of the defined dependencies in the plugin registration file. This is the equivalent of running the following before each test: -```php -public function setUp(): void -{ - parent::setUp(); + php artisan october:up + php artisan plugin:refresh Acme.Blog + [php artisan plugin:refresh , ...] - // Load necessary model fixture - $this->postFixture = PostFixture::create(['title' => 'Test Post']); -} - -public function tearDown(): void -{ - // Remove model fixtur - unset($this->postFixture); - - parent::tearDown(); -} -``` - -> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests.

Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. +> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests. Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation. use System\Classes\PluginManager; - use October\Core\Tests\PluginTestCase; class BaseTestCase extends PluginTestCase { @@ -226,51 +84,51 @@ public function tearDown(): void } } -To run the unit tests for your plugin, simply go to the base folder for your plugin, and run the following command: +#### Changing database engine for plugins tests -```bash -../../../vendor/bin/phpunit -``` +By default OctoberCMS uses SQLite stored in memory for the plugin testing environment. If you want to override the default behavior set the `useConfigForTesting` config to `true` in your `/config/database.php` file. When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`. -This will execute PHPUnit in the context of your plugin. +You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken. - -### Browsers Tests +## System testing -Browser tests for plugins can be set up in much the same way as unit tests, with a small number of differences. +To perform unit testing on the core October files, you should download a development copy using composer or cloning the git repo. This will ensure you have the `tests/` directory. -Browsers tests require their own PHPUnit XML configuration file. You should create a `phpunit.dusk.xml` file in your project base directory with the following content: +### Unit tests - - - - - ./tests/browser - - - - - - - - +Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. -Browser tests should be separate from unit tests - you can instead store browser tests within the **tests/browser/** directory. +### Functional tests -Browser test files follow the same rules as unit test files, however, instead of extending the `October\Core\Tests\PluginTestCase` class, they should instead extend the `October\Core\Tests\BrowserTestCase` class. +Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: -To run the plugin browser tests, you must run the following command within the root folder of your *October CMS install*, not the plugin: +- Active theme is `demo` +- Language preference is `en` -```bash -php artisan dusk -c plugins/acme/test/phpunit.dusk.xml -``` +#### Selenium set up + +1. Download latest Java SE from http://java.sun.com/ and install +1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). +1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. +1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. + +#### Selenium configuration + +Create a new file `selenium.php` in the root directory, add the following content: + + getMethod($name); $method->setAccessible(true); return $method->invokeArgs($object, $params); @@ -34,7 +34,7 @@ abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase public static function getProtectedProperty($object, $name) { $className = get_class($object); - $class = new \ReflectionClass($className); + $class = new ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->getValue($object); @@ -43,7 +43,7 @@ abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase public static function setProtectedProperty($object, $name, $value) { $className = get_class($object); - $class = new \ReflectionClass($className); + $class = new ReflectionClass($className); $property = $class->getProperty($name); $property->setAccessible(true); return $property->setValue($object, $value); diff --git a/tests/UiTestCase.php b/tests/UiTestCase.php new file mode 100644 index 000000000..e95c3e9bf --- /dev/null +++ b/tests/UiTestCase.php @@ -0,0 +1,111 @@ +markTestSkipped('Selenium skipped'); + } + + if (defined('TEST_SELENIUM_HOST')) { + $this->setHost(TEST_SELENIUM_HOST); + } + if (defined('TEST_SELENIUM_PORT')) { + $this->setPort(TEST_SELENIUM_PORT); + } + if (defined('TEST_SELENIUM_BROWSER')) { + $this->setBrowser(TEST_SELENIUM_BROWSER); + } + $this->setBrowserUrl(TEST_SELENIUM_URL); + } + + // + // OctoberCMS Helpers + // + + protected function signInToBackend() + { + $this->open('backend'); + $this->type("name=login", TEST_SELENIUM_USER); + $this->type("name=password", TEST_SELENIUM_PASS); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + } + + /** + * Similar to the native getConfirmation() function + */ + protected function getSweetConfirmation($expectedText = null, $clickOk = true) + { + $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); + + if ($expectedText) { + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); + } + + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); + + if ($clickOk) { + $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); + } + } + + // + // Selenium helpers + // + + protected function waitForElementPresent($target, $timeout = 60) + { + $second = 0; + + while (true) { + if ($second >= $timeout) { + $this->fail('timeout'); + } + + try { + if ($this->isElementPresent($target)) { + break; + } + } + catch (Exception $e) { + } + + sleep(1); + ++$second; + } + } + + protected function waitForElementNotPresent($target, $timeout = 60) + { + $second = 0; + + while (true) { + if ($second >= $timeout) { + $this->fail('timeout'); + } + + try { + if (!$this->isElementPresent($target)) { + break; + } + } + catch (Exception $e) { + } + + sleep(1); + ++$second; + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6c9464ba3..3de4e0dc9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -11,5 +11,5 @@ $loader = new October\Rain\Support\ClassLoader( $loader->register(); $loader->addDirectories([ 'modules', - 'plugins', + 'plugins' ]); diff --git a/tests/Concerns/InteractsWithAuthentication.php b/tests/concerns/InteractsWithAuthentication.php similarity index 98% rename from tests/Concerns/InteractsWithAuthentication.php rename to tests/concerns/InteractsWithAuthentication.php index bde15cdef..a7950f230 100644 --- a/tests/Concerns/InteractsWithAuthentication.php +++ b/tests/concerns/InteractsWithAuthentication.php @@ -1,4 +1,6 @@ -open('backend'); + + $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; + + try { + $this->assertTitle('Administration Area'); + $this->assertTrue($this->isElementPresent("name=login")); + $this->assertTrue($this->isElementPresent("name=password")); + $this->assertTrue($this->isElementPresent("//button[@type='submit']")); + $this->verifyText("//button[@type='submit']", "Login"); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + /* + * Sign in + */ + $this->type("name=login", TEST_SELENIUM_USER); + $this->type("name=password", TEST_SELENIUM_PASS); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Dashboard | October CMS'); + $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->verifyText('css='.$cssLogoutLink, "Sign out"); + + /* + * Log out + */ + $this->click('css='.$cssLogoutLink); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Administration Area'); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + } + + public function testPasswordReset() + { + $this->open('backend'); + + try { + $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->click('link=exact:Forgot your password?'); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTrue($this->isElementPresent("//button[@type='submit']")); + $this->verifyText("//button[@type='submit']", "Restore"); + $this->assertTrue($this->isElementPresent("link=Cancel")); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->type("name=login", TEST_SELENIUM_USER); + sleep(1); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Administration Area'); + $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); + $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + } +} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php new file mode 100644 index 000000000..5ff2a0a9e --- /dev/null +++ b/tests/functional/cms/TemplateTest.php @@ -0,0 +1,143 @@ +signInToBackend(); + $this->open('cms'); + $this->waitForPageToLoad("30000"); + + // Fix the sidebar + $this->click("xpath=(//a[@class='fix-button'])[1]"); + + /* + * Page + */ + + // Create a new page + $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=settings[title]"); + + // Populate page details + $this->type('name=settings[title]', 'Functional Test Page'); + $this->type('name=settings[url]', '/xxx/functional/test/page'); + $this->type('name=fileName', 'xxx_functional_test_page'); + + // Save the new page + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); + $this->waitForElementPresent("name=settings[title]"); + sleep(1); + + // Delete the page + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this page?'); + // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); + $this->waitForElementNotPresent("name=settings[title]"); + + /* + * Partial + */ + + // Click partials menu item + $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); + + // Create a new partial + $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate partial details + $this->type('name=fileName', 'xxx_functional_test_partial'); + $this->type('name=settings[description]', 'Test partial'); + + // Save the new partial + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the partial + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this partial?'); + $this->waitForElementNotPresent("name=fileName"); + + /* + * Layout + */ + + // Click layouts menu item + $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); + + // Create a new layout + $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate layout details + $this->type('name=fileName', 'xxx_functional_test_layout'); + $this->type('name=settings[description]', 'Test layout'); + + // Save the new layout + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the layout + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this layout?'); + $this->waitForElementNotPresent("name=fileName"); + + /* + * Content + */ + + // Click contents menu item + $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); + + // Create a new content + $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate content details + $this->type('name=fileName', 'xxx_functional_test_content.txt'); + + // Save the new content + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the content + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this content file?'); + $this->waitForElementNotPresent("name=fileName"); + } +} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml new file mode 100644 index 000000000..cb4586424 --- /dev/null +++ b/tests/functional/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./ + + + diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index d06ed53a6..65dd5e735 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -2,7 +2,7 @@ use Backend\Classes\AuthManager; use October\Rain\Exception\SystemException; -class AuthManagerTest extends \October\Core\Tests\TestCase +class AuthManagerTest extends TestCase { public function setUp(): void { diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index 9c4a99a7c..f63645198 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\NavigationManager; -class NavigationManagerTest extends \October\Core\Tests\TestCase +class NavigationManagerTest extends TestCase { public function testRegisterMenuItems() { diff --git a/tests/unit/backend/classes/WidgetManagerTest.php b/tests/unit/backend/classes/WidgetManagerTest.php index c46a75731..819997385 100644 --- a/tests/unit/backend/classes/WidgetManagerTest.php +++ b/tests/unit/backend/classes/WidgetManagerTest.php @@ -3,7 +3,7 @@ use Backend\Classes\Controller; use Backend\Classes\WidgetManager; -class WidgetManagerTest extends \October\Core\Tests\TestCase +class WidgetManagerTest extends TestCase { public function testListFormWidgets() { diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index e33127eea..3766f5c8e 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -3,7 +3,7 @@ use Backend\Helpers\Backend; use Backend\Helpers\Exception\DecompileException; -class BackendHelperTest extends \October\Core\Tests\TestCase +class BackendHelperTest extends TestCase { public function testDecompileAssets() { diff --git a/tests/unit/backend/models/ExportModelTest.php b/tests/unit/backend/models/ExportModelTest.php index 75fc8dba6..4fd55b6f0 100644 --- a/tests/unit/backend/models/ExportModelTest.php +++ b/tests/unit/backend/models/ExportModelTest.php @@ -25,7 +25,7 @@ class ExampleExportModel extends ExportModel } } -class ExportModelTest extends \October\Core\Tests\TestCase +class ExportModelTest extends TestCase { // diff --git a/tests/unit/backend/models/ImportModelTest.php b/tests/unit/backend/models/ImportModelTest.php index c390c40f2..1f803bd06 100644 --- a/tests/unit/backend/models/ImportModelTest.php +++ b/tests/unit/backend/models/ImportModelTest.php @@ -16,7 +16,7 @@ class ExampleImportModel extends ImportModel } } -class ImportModelTest extends \October\Core\Tests\TestCase +class ImportModelTest extends TestCase { // diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 0aa7ff8da..9a290b509 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -12,7 +12,7 @@ class ExampleTraitClass } } -class WidgetMakerTest extends \October\Core\Tests\TestCase +class WidgetMakerTest extends TestCase { /** * The object under test. diff --git a/tests/unit/backend/widgets/FilterTest.php b/tests/unit/backend/widgets/FilterTest.php index 66f04e62e..05d0f0d78 100644 --- a/tests/unit/backend/widgets/FilterTest.php +++ b/tests/unit/backend/widgets/FilterTest.php @@ -2,17 +2,10 @@ use Backend\Widgets\Filter; use Backend\Models\User; -use October\Core\Tests\Fixtures\Backend\Models\UserFixture; +use October\Tests\Fixtures\Backend\Models\UserFixture; -class FilterTest extends \October\Core\Tests\PluginTestCase +class FilterTest extends PluginTestCase { - public function setUp() : void - { - parent::setUp(); - - include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; - } - public function testRestrictedScopeWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/FormTest.php b/tests/unit/backend/widgets/FormTest.php index 81f49917a..d373a1d86 100644 --- a/tests/unit/backend/widgets/FormTest.php +++ b/tests/unit/backend/widgets/FormTest.php @@ -2,23 +2,15 @@ use Backend\Widgets\Form; use Illuminate\Database\Eloquent\Model; -use October\Core\Tests\Fixtures\Backend\Models\UserFixture; +use October\Tests\Fixtures\Backend\Models\UserFixture; class FormTestModel extends Model { } -class FormTest extends \October\Core\Tests\PluginTestCase +class FormTest extends PluginTestCase { - public function setUp() : void - { - parent::setUp(); - - include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; - } - - public function testRestrictedFieldWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/backend/widgets/ListsTest.php b/tests/unit/backend/widgets/ListsTest.php index e8ad8cf03..e74aa877d 100644 --- a/tests/unit/backend/widgets/ListsTest.php +++ b/tests/unit/backend/widgets/ListsTest.php @@ -3,17 +3,10 @@ use Backend\Models\User; use Backend\Widgets\Lists; use October\Rain\Exception\ApplicationException; -use October\Core\Tests\Fixtures\Backend\Models\UserFixture; +use October\Tests\Fixtures\Backend\Models\UserFixture; -class ListsTest extends \October\Core\Tests\PluginTestCase +class ListsTest extends PluginTestCase { - public function setUp() : void - { - parent::setUp(); - - include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php'; - } - public function testRestrictedColumnWithUserWithNoPermissions() { $user = new UserFixture; diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index 66bc7d849..ec82153f9 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -28,7 +28,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject } } -class CmsCompoundObjectTest extends \October\Core\Tests\TestCase +class CmsCompoundObjectTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsExceptionTest.php b/tests/unit/cms/classes/CmsExceptionTest.php index 1a100e1fe..6e270fb7a 100644 --- a/tests/unit/cms/classes/CmsExceptionTest.php +++ b/tests/unit/cms/classes/CmsExceptionTest.php @@ -9,7 +9,7 @@ use Cms\Classes\CmsException; use Cms\Classes\CodeParser; use October\Rain\Exception\SystemException; -class CmsExceptionTest extends \October\Core\Tests\TestCase +class CmsExceptionTest extends TestCase { // // Tests diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index fa7cd2110..4e19b55da 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -5,7 +5,7 @@ use Cms\Classes\Theme; use Cms\Classes\Layout; use October\Rain\Halcyon\Model; -class CmsObjectQueryTest extends \October\Core\Tests\TestCase +class CmsObjectQueryTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 9a008ac71..2c65761b4 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -13,7 +13,7 @@ class TestTemporaryCmsObject extends CmsObject protected $dirName = 'temporary'; } -class CmsObjectTest extends \October\Core\Tests\TestCase +class CmsObjectTest extends TestCase { public function testLoad() { diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 13e8024ee..10d1b7d23 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -8,7 +8,7 @@ use Cms\Classes\Layout; use Cms\Classes\CodeParser; use Cms\Classes\Controller; -class CodeParserTest extends \October\Core\Tests\TestCase +class CodeParserTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index 6a067e5f3..152367b8d 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -7,7 +7,7 @@ use Cms\Classes\Controller; use Cms\Classes\CodeParser; use Cms\Classes\ComponentManager; -class ComponentManagerTest extends \October\Core\Tests\TestCase +class ComponentManagerTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/ContentTest.php b/tests/unit/cms/classes/ContentTest.php index 0178d41ae..790501c6f 100644 --- a/tests/unit/cms/classes/ContentTest.php +++ b/tests/unit/cms/classes/ContentTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use Cms\Classes\Content; -class ContentTest extends \October\Core\Tests\TestCase +class ContentTest extends TestCase { public function testMarkdownContent() diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index d7f1f40a4..da16e317d 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; use Cms\Classes\Controller; use October\Rain\Halcyon\Model; -class ControllerTest extends \October\Core\Tests\TestCase +class ControllerTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/classes/PartialStackTest.php b/tests/unit/cms/classes/PartialStackTest.php index 14404ffde..f9371a3b6 100644 --- a/tests/unit/cms/classes/PartialStackTest.php +++ b/tests/unit/cms/classes/PartialStackTest.php @@ -2,7 +2,7 @@ use Cms\Classes\PartialStack; -class PartialStackTest extends \October\Core\Tests\TestCase +class PartialStackTest extends TestCase { public function testStackPartials() diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index c5887e929..f4eeda27a 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Router; use Cms\Classes\Theme; -class RouterTest extends \October\Core\Tests\TestCase +class RouterTest extends TestCase { protected static $theme = null; diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 69d461862..07a15f2db 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -2,7 +2,7 @@ use Cms\Classes\Theme; -class ThemeTest extends \October\Core\Tests\TestCase +class ThemeTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/cms/helpers/FileTest.php b/tests/unit/cms/helpers/FileTest.php index 23c253d6b..dde19f0c3 100644 --- a/tests/unit/cms/helpers/FileTest.php +++ b/tests/unit/cms/helpers/FileTest.php @@ -2,7 +2,7 @@ use Cms\Helpers\File as FileHelper; -class FileTest extends \October\Core\Tests\TestCase +class FileTest extends TestCase { public function testValidateName() { diff --git a/tests/unit/plugins/backend/ImportModelDbTest.php b/tests/unit/plugins/backend/ImportModelDbTest.php index 7ca7c6cba..61a1d8ea9 100644 --- a/tests/unit/plugins/backend/ImportModelDbTest.php +++ b/tests/unit/plugins/backend/ImportModelDbTest.php @@ -13,7 +13,7 @@ class ExampleDbImportModel extends ImportModel } } -class ImportModelDbTest extends \October\Core\Tests\PluginTestCase +class ImportModelDbTest extends PluginTestCase { public function testGetImportFilePath() { diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 5aed54acd..9c316c032 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -3,7 +3,7 @@ use System\Models\File as FileModel; use Database\Tester\Models\User; -class AttachManyModelTest extends \October\Core\Tests\PluginTestCase +class AttachManyModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index da7af5f33..d50debdb6 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; use Database\Tester\Models\SoftDeleteUser; use Symfony\Component\HttpFoundation\File\UploadedFile; -class AttachOneModelTest extends \October\Core\Tests\PluginTestCase +class AttachOneModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index 8d7a3314d..e850b7752 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Role; use Database\Tester\Models\Author; -class BelongsToManyModelTest extends \October\Core\Tests\PluginTestCase +class BelongsToManyModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 12ee089f4..8fca6ff06 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; -class BelongsToModelTest extends \October\Core\Tests\PluginTestCase +class BelongsToModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index b84898a30..e36460255 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use October\Rain\Database\Models\DeferredBinding; -class DeferredBindingTest extends \October\Core\Tests\PluginTestCase +class DeferredBindingTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 93f81914d..9a1def095 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyModelTest extends \October\Core\Tests\PluginTestCase +class HasManyModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php index 8fde8f1fa..85ac947f2 100644 --- a/tests/unit/plugins/database/HasManyThroughModelTest.php +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Country; use Database\Tester\Models\Post; use October\Rain\Database\Collection; -class HasManyThroughModelTest extends \October\Core\Tests\PluginTestCase +class HasManyThroughModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index 5e0920630..e6bd0f45e 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -3,7 +3,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; -class HasOneModelTest extends \October\Core\Tests\PluginTestCase +class HasOneModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php index 220dc5ec6..91be83b60 100644 --- a/tests/unit/plugins/database/HasOneThroughModelTest.php +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Phone; use Database\Tester\Models\User; -class HasOneThroughModelTest extends \October\Core\Tests\PluginTestCase +class HasOneThroughModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index ed2a4286f..16bf07c1d 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\Post; -class ModelTest extends \October\Core\Tests\PluginTestCase +class ModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index 8ffb67ecb..a7ba3041c 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; use October\Rain\Database\Collection; -class MorphManyModelTest extends \October\Core\Tests\PluginTestCase +class MorphManyModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index 130ad1a90..e8c6f60fd 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Author; use Database\Tester\Models\Post; use Database\Tester\Models\Meta; -class MorphOneModelTest extends \October\Core\Tests\PluginTestCase +class MorphOneModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index cedf23017..b0eb37ff7 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; use Database\Tester\Models\Author; use Database\Tester\Models\EventLog; -class MorphToModelTest extends \October\Core\Tests\PluginTestCase +class MorphToModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index 2a05621a3..49d46764c 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategoryNested; -class NestedTreeModelTest extends \October\Core\Tests\PluginTestCase +class NestedTreeModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 568a54c0d..9d6d61114 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\NullablePost; -class NullableModelTest extends \October\Core\Tests\PluginTestCase +class NullableModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index 3d9af946f..c8fa19a54 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\RevisionablePost; -class RevisionableModelTest extends \October\Core\Tests\PluginTestCase +class RevisionableModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index c1b0e3ad7..c22b11d16 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Database\Tester\Models\CategorySimple; -class SimpleTreeModelTest extends \October\Core\Tests\PluginTestCase +class SimpleTreeModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index 5f8a0de0e..22cad1837 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\SluggablePost; -class SluggableModelTest extends \October\Core\Tests\PluginTestCase +class SluggableModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index 399f21dda..fc5212433 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -8,7 +8,7 @@ use Database\Tester\Models\UserWithSoftAuthor; use Database\Tester\Models\UserWithAuthorAndSoftDelete; use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; -class SoftDeleteModelTest extends \October\Core\Tests\PluginTestCase +class SoftDeleteModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index 94aa60926..f5ddb55b7 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -2,7 +2,7 @@ use Database\Tester\Models\ValidationPost; -class ValidationModelTest extends \October\Core\Tests\PluginTestCase +class ValidationModelTest extends PluginTestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index f75dd70fb..14dc6f54b 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -14,7 +14,7 @@ class CmsThemeTemplateFixture extends Model public $table = 'cms_theme_templates'; } -class AutoDatasourceTest extends \October\Core\Tests\PluginTestCase +class AutoDatasourceTest extends PluginTestCase { /** * Array of model fixtures. diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 77173cf3a..5c5d313bd 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -3,7 +3,7 @@ use Cms\Classes\Theme; use System\Classes\CombineAssets; -class CombineAssetsTest extends \October\Core\Tests\TestCase +class CombineAssetsTest extends TestCase { public function setUp() : void { diff --git a/tests/unit/system/classes/CoreLangTest.php b/tests/unit/system/classes/CoreLangTest.php index 5a5c5fdf3..54899a812 100644 --- a/tests/unit/system/classes/CoreLangTest.php +++ b/tests/unit/system/classes/CoreLangTest.php @@ -2,7 +2,7 @@ use System\Classes\PluginManager; -class CoreLangTest extends \October\Core\Tests\TestCase +class CoreLangTest extends TestCase { public function testValidationTranslator() { diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index 4c8eaa3e2..f4ee00a32 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -2,7 +2,7 @@ use System\Classes\MarkupManager; -class MarkupManagerTest extends \October\Core\Tests\TestCase +class MarkupManagerTest extends TestCase { public function setUp() : void diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index a326bbdf0..5d8082ba5 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -2,7 +2,7 @@ use System\Classes\MediaLibrary; -class MediaLibraryTest extends \October\Core\Tests\TestCase // @codingStandardsIgnoreLine +class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine { public function invalidPathsProvider() { diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index c5c5102f8..e41d860f5 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -1,7 +1,7 @@ Date: Thu, 13 Feb 2020 15:39:42 +0800 Subject: [PATCH 046/100] Fix autoload map for tests --- composer.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 21cb2afcb..733e5ed86 100644 --- a/composer.json +++ b/composer.json @@ -52,9 +52,13 @@ "laravel/dusk": "^5.8" }, "autoload-dev": { - "psr-4": { - "October\\Core\\Tests\\": "tests/" - } + "classmap": [ + "tests/concerns/InteractsWithAuthentication.php", + "tests/fixtures/backend/models/UserFixture.php", + "tests/TestCase.php", + "tests/UiTestCase.php", + "tests/PluginTestCase.php" + ] }, "scripts": { "post-create-project-cmd": [ From 28eafd9afc9a69c278365677f657644e7d4b718c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 13 Feb 2020 16:24:55 +0800 Subject: [PATCH 047/100] Install RainLab Dusk plugin for browser tests --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3c5e7b922..410e55292 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,6 +91,12 @@ jobs: run: | git reset --hard HEAD composer dumpautoload + - name: Install Dusk plugin + run: | + curl -fLs https://github.com/rainlab/dusk-plugin/archive/master.zip -o rainlab-dusk.zip + mkdir -p plugins/rainlab + unzip rainlab-dusk.zip -d plugins/rainlab + mv plugins/rainlab/dusk-plugin-master plugins/rainlab/dusk - name: Install Chrome driver run: php artisan dusk:chrome-driver - name: Start Chrome driver From cb061fe7fe3df296dfdfda4bc2e63eca0b5c61ce Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 13 Feb 2020 16:49:03 +0800 Subject: [PATCH 048/100] Remove Dusk from dev dependencies --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 733e5ed86..394c21239 100644 --- a/composer.json +++ b/composer.json @@ -48,8 +48,7 @@ "dms/phpunit-arraysubset-asserts": "^0.1.0", "meyfa/phpunit-assert-gd": "^2.0", "squizlabs/php_codesniffer": "3.*", - "jakub-onderka/php-parallel-lint": "^1.0", - "laravel/dusk": "^5.8" + "jakub-onderka/php-parallel-lint": "^1.0" }, "autoload-dev": { "classmap": [ From b78909d914a1283040fdd49fbd5234147f0139be Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 13 Feb 2020 16:52:58 +0800 Subject: [PATCH 049/100] Install Dusk plugin before getting Composer deps --- .github/workflows/tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 410e55292..61217a48a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,6 +83,12 @@ jobs: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- + - name: Install Dusk plugin + run: | + curl -fLs https://github.com/rainlab/dusk-plugin/archive/master.zip -o rainlab-dusk.zip + mkdir -p plugins/rainlab + unzip rainlab-dusk.zip -d plugins/rainlab + mv plugins/rainlab/dusk-plugin-master plugins/rainlab/dusk - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - name: Run post-update Composer scripts @@ -91,12 +97,6 @@ jobs: run: | git reset --hard HEAD composer dumpautoload - - name: Install Dusk plugin - run: | - curl -fLs https://github.com/rainlab/dusk-plugin/archive/master.zip -o rainlab-dusk.zip - mkdir -p plugins/rainlab - unzip rainlab-dusk.zip -d plugins/rainlab - mv plugins/rainlab/dusk-plugin-master plugins/rainlab/dusk - name: Install Chrome driver run: php artisan dusk:chrome-driver - name: Start Chrome driver From 1f30cf58099fc80ed0e14d1de276ed05dca8cfef Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 13 Feb 2020 16:56:08 +0800 Subject: [PATCH 050/100] Enable Composer scripts for browser tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 61217a48a..3d6239c2c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -90,7 +90,7 @@ jobs: unzip rainlab-dusk.zip -d plugins/rainlab mv plugins/rainlab/dusk-plugin-master plugins/rainlab/dusk - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest --no-scripts + run: composer install --no-interaction --no-progress --no-suggest - name: Run post-update Composer scripts run: php artisan package:discover - name: Reset October modules From 1ac3c7194e103ebd77672a775cc63f6f01e7dbcc Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 13 Feb 2020 17:04:04 +0800 Subject: [PATCH 051/100] Install Chrome 78 driver --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3d6239c2c..1f0c443c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,8 +97,8 @@ jobs: run: | git reset --hard HEAD composer dumpautoload - - name: Install Chrome driver - run: php artisan dusk:chrome-driver + - name: Install Chrome 78 driver + run: php artisan dusk:chrome-driver 78 - name: Start Chrome driver run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 & - name: Run Laravel Server From a5db9e325741bb435ceb1b0230d676bcde644369 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 26 Feb 2020 16:56:40 +0800 Subject: [PATCH 052/100] Add new supported filesystems to config --- config/filesystems.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filesystems.php b/config/filesystems.php index 4d843013c..36d9a0dfa 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -11,7 +11,7 @@ return [ | by the framework. A "local" driver, as well as a variety of cloud | based drivers are available for your choosing. Just store away! | - | Supported: "local", "s3", "rackspace" + | Supported: "local", "ftp", "sftp", "s3", "rackspace" | */ From 9911c6b5817088a99ac65d9f2ac76346402e1d47 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 26 Feb 2020 17:10:51 +0800 Subject: [PATCH 053/100] Add new validation type lang strings. --- modules/system/lang/en/validation.php | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index edc036dd0..ad6561c9e 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -32,6 +32,7 @@ return [ 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', @@ -39,9 +40,22 @@ return [ 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', @@ -50,6 +64,18 @@ return [ 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', @@ -65,6 +91,7 @@ return [ 'array' => 'The :attribute must have at least :min items.', ], 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', @@ -82,11 +109,13 @@ return [ 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- From cf87f1466aebdbf8a1bb43a6a65ad83eb40b090d Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 26 Feb 2020 17:17:16 +0800 Subject: [PATCH 054/100] Use latest Chrome driver. We'll have to assume that GitHub Actions' Ubuntu image is continually updated with the latest Chrome, although there's every likelihood that these tests will sometimes fail if the versions aren't in sync. If it becomes too much of a problem, remove these tests. --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f0c443c8..3d6239c2c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,8 +97,8 @@ jobs: run: | git reset --hard HEAD composer dumpautoload - - name: Install Chrome 78 driver - run: php artisan dusk:chrome-driver 78 + - name: Install Chrome driver + run: php artisan dusk:chrome-driver - name: Start Chrome driver run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 & - name: Run Laravel Server From 3591f38cd4baa9dc6350083906cdcb42887abd6f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 27 Feb 2020 16:39:25 +0800 Subject: [PATCH 055/100] Add `predis` client as default in config --- config/database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/config/database.php b/config/database.php index 70f1420c6..a06bc7586 100644 --- a/config/database.php +++ b/config/database.php @@ -116,6 +116,7 @@ return [ 'redis' => [ + 'client' => 'predis', 'cluster' => false, 'default' => [ From a2966da395bb98bca5ff1d105e12d6ed9d233c35 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 27 Feb 2020 16:57:32 +0800 Subject: [PATCH 056/100] Use October provider for Redis service --- modules/system/providers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system/providers.php b/modules/system/providers.php index 7b63edb43..79e1d7c48 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -15,7 +15,6 @@ return [ Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, @@ -36,5 +35,6 @@ return [ October\Rain\Flash\FlashServiceProvider::class, October\Rain\Mail\MailServiceProvider::class, October\Rain\Argon\ArgonServiceProvider::class, + October\Rain\Redis\RedisServiceProvider::class, ]; From 7b00768f3a0c6682d453eacdb3f5bf2db1de5e48 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 19:58:11 +1100 Subject: [PATCH 057/100] Remove local notes support --- modules/system/classes/UpdateManager.php | 50 ++++------------------- modules/system/classes/VersionManager.php | 38 ++--------------- 2 files changed, 12 insertions(+), 76 deletions(-) diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 8fe43604d..2a2803d1c 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -29,11 +29,6 @@ class UpdateManager { use \October\Rain\Support\Traits\Singleton; - /** - * @var array The notes for the current operation. - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -347,11 +342,11 @@ class UpdateManager /* * Rollback modules */ - while (true) { - if (isset($this->notesOutput)) { - $this->migrator->setOutput($this->notesOutput); - } + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } + while (true) { $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); if (count($rolledBack) == 0) { @@ -405,12 +400,12 @@ class UpdateManager */ public function migrateModule($module) { - $this->note($module); - if (isset($this->notesOutput)) { $this->migrator->setOutput($this->notesOutput); } + $this->note($module); + $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); return $this; @@ -520,13 +515,9 @@ class UpdateManager $this->note($name); - $this->versionManager->resetNotes()->setNotesOutput($this->notesOutput); + $this->versionManager->setNotesOutput($this->notesOutput); - if ($this->versionManager->updatePlugin($plugin) !== false) { - foreach ($this->versionManager->getNotes() as $note) { - $this->note($note); - } - } + $this->versionManager->updatePlugin($plugin); return $this; } @@ -797,31 +788,6 @@ class UpdateManager if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); } - else { - $this->notes[] = $message; - } - - return $this; - } - - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; return $this; } diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index 55e8636fe..fac34d1b2 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -29,12 +29,6 @@ class VersionManager const HISTORY_TYPE_COMMENT = 'comment'; const HISTORY_TYPE_SCRIPT = 'script'; - /** - * The notes for the current operation. - * @var array - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -412,6 +406,7 @@ class VersionManager * Execute the database PHP script */ $updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script; + $this->updater->packDown($updateFile); Db::table('system_plugin_history') @@ -481,31 +476,6 @@ class VersionManager if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); } - else { - $this->notes[] = $message; - } - - return $this; - } - - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; return $this; } @@ -523,8 +493,7 @@ class VersionManager } /** - * @param $details - * + * Extract script and comments from version details * @return array */ protected function extractScriptsAndComments($details) @@ -539,7 +508,8 @@ class VersionManager $scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) { return preg_match($fileNamePattern, $detail); })); - } else { + } + else { $comments = (array)$details; $scripts = []; } From f7ef665af3477cb281a078017e50ca06c3ad2b84 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 19:58:44 +1100 Subject: [PATCH 058/100] Set default logging to single, remove dusk config --- config/dusk/app.php | 30 -------- config/dusk/cms.php | 27 -------- config/dusk/database.php | 146 --------------------------------------- config/logging.php | 11 ++- 4 files changed, 4 insertions(+), 210 deletions(-) delete mode 100644 config/dusk/app.php delete mode 100644 config/dusk/cms.php delete mode 100644 config/dusk/database.php diff --git a/config/dusk/app.php b/config/dusk/app.php deleted file mode 100644 index 79438c741..000000000 --- a/config/dusk/app.php +++ /dev/null @@ -1,30 +0,0 @@ - env('APP_URL', 'http://127.0.0.1:8000'), - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - | This key is used by the Illuminate encrypter service and should be set - | to a random, 32 character string, otherwise these encrypted strings - | will not be safe. Please do this before deploying an application! - | - */ - - 'key' => env('APP_KEY', 'Gl8AHFqlhnzcpxeJGt0zLN5qe5ngPdiC'), -]; diff --git a/config/dusk/cms.php b/config/dusk/cms.php deleted file mode 100644 index 1b0ad7107..000000000 --- a/config/dusk/cms.php +++ /dev/null @@ -1,27 +0,0 @@ - 'demo', - - /* - |-------------------------------------------------------------------------- - | Cross Site Request Forgery (CSRF) Protection - |-------------------------------------------------------------------------- - | - | If the CSRF protection is enabled, all "postback" & AJAX requests are - | checked for a valid security token. - | - */ - - 'enableCsrfProtection' => false, -]; diff --git a/config/dusk/database.php b/config/dusk/database.php deleted file mode 100644 index 072d9b35b..000000000 --- a/config/dusk/database.php +++ /dev/null @@ -1,146 +0,0 @@ - PDO::FETCH_CLASS, - - /* - |-------------------------------------------------------------------------- - | Default Database Connection Name - |-------------------------------------------------------------------------- - | - | Here you may specify which of the database connections below you wish - | to use as your default connection for all database work. Of course - | you may use many connections at once using the Database library. - | - */ - - 'default' => env('DB_CONNECTION', 'sqlite'), - - /* - |-------------------------------------------------------------------------- - | Database Connections - |-------------------------------------------------------------------------- - | - | Here are each of the database connections setup for your application. - | Of course, examples of configuring each database platform that is - | supported by Laravel is shown below to make development simple. - | - | - | All database work in Laravel is done through the PHP PDO facilities - | so make sure you have the driver for your particular database of - | choice installed on your machine before you begin development. - | - */ - - 'connections' => [ - - 'sqlite' => [ - 'driver' => 'sqlite', - 'database' => env('DB_DATABASE', 'storage/dusk.sqlite'), - 'prefix' => '', - ], - - 'mysql' => [ - 'driver' => 'mysql', - 'engine' => 'InnoDB', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 3306), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => '', - 'varcharmax' => 191, - ], - - 'pgsql' => [ - 'driver' => 'pgsql', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 5432), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'schema' => 'public', - ], - - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', 5432), - 'database' => env('DB_DATABASE', 'database'), - 'username' => env('DB_USERNAME', ''), - 'password' => env('DB_PASSWORD', ''), - 'prefix' => '', - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Migration Repository Table - |-------------------------------------------------------------------------- - | - | This table keeps track of all the migrations that have already run for - | your application. Using this information, we can determine which of - | the migrations on disk have not actually be run in the databases. - | - */ - - 'migrations' => 'migrations', - - /* - |-------------------------------------------------------------------------- - | Redis Databases - |-------------------------------------------------------------------------- - | - | Redis is an open source, fast, and advanced key-value store that also - | provides a richer set of commands than a typical key-value systems - | such as APC or Memcached. Laravel makes it easy to dig right in. - | - */ - - 'redis' => [ - - 'cluster' => false, - - 'default' => [ - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'password' => env('REDIS_PASSWORD', ''), - 'port' => env('REDIS_PORT', 6379), - 'database' => 0, - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Use DB configuration for testing - |-------------------------------------------------------------------------- - | - | When running plugin tests OctoberCMS by default uses SQLite in memory. - | You can override this behavior by setting `useConfigForTesting` to true. - | - | After that OctoberCMS will take DB parameters from the config. - | If file `/config/testing/database.php` exists, config will be read from it, - | but remember that when not specified it will use parameters specified in - | `/config/database.php`. - | - */ - - 'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false), -]; diff --git a/config/logging.php b/config/logging.php index 329b7a934..900d48123 100644 --- a/config/logging.php +++ b/config/logging.php @@ -1,8 +1,5 @@ env('LOG_CHANNEL', 'stack'), + 'default' => env('LOG_CHANNEL', 'single'), /* |-------------------------------------------------------------------------- @@ -56,7 +53,7 @@ return [ 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => 'Laravel Log', + 'username' => 'October CMS Log', 'emoji' => ':boom:', 'level' => 'critical', ], @@ -64,7 +61,7 @@ return [ 'papertrail' => [ 'driver' => 'monolog', 'level' => 'debug', - 'handler' => SyslogUdpHandler::class, + 'handler' => \Monolog\Handler\SyslogUdpHandler::class, 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), @@ -73,7 +70,7 @@ return [ 'stderr' => [ 'driver' => 'monolog', - 'handler' => StreamHandler::class, + 'handler' => \Monolog\Handler\StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', From 6e607361dc200ff488bd3044e058e85d6a826c52 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 19:59:12 +1100 Subject: [PATCH 059/100] Restore default password setting --- modules/backend/database/seeds/SeedSetupAdmin.php | 2 +- modules/backend/models/User.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/backend/database/seeds/SeedSetupAdmin.php b/modules/backend/database/seeds/SeedSetupAdmin.php index a58015120..e161a41d8 100644 --- a/modules/backend/database/seeds/SeedSetupAdmin.php +++ b/modules/backend/database/seeds/SeedSetupAdmin.php @@ -9,7 +9,7 @@ class SeedSetupAdmin extends Seeder { public static $email = 'admin@domain.tld'; public static $login = 'admin'; - public static $password = 'admin1234'; + public static $password = 'admin'; public static $firstName = 'Admin'; public static $lastName = 'Person'; diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 8efaf6bb3..f44c97a71 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -26,8 +26,8 @@ class User extends UserBase public $rules = [ 'email' => 'required|between:6,255|email|unique:backend_users', 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|min:8|confirmed', - 'password_confirmation' => 'required_with:password|min:8' + 'password' => 'required:create|min:2|confirmed', + 'password_confirmation' => 'required_with:password|min:2' ]; /** From 9d94271977739f468fd6a4e06233245a4d93efe7 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 19:59:47 +1100 Subject: [PATCH 060/100] Remove selenium tests --- tests/README.md | 39 +------ tests/UiTestCase.php | 111 -------------------- tests/functional/backend/AuthTest.php | 90 ---------------- tests/functional/cms/TemplateTest.php | 143 -------------------------- tests/functional/phpunit.xml | 18 ---- 5 files changed, 2 insertions(+), 399 deletions(-) delete mode 100644 tests/UiTestCase.php delete mode 100644 tests/functional/backend/AuthTest.php delete mode 100644 tests/functional/cms/TemplateTest.php delete mode 100644 tests/functional/phpunit.xml diff --git a/tests/README.md b/tests/README.md index a9e7b78b5..1828e6cfb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -58,7 +58,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci class BaseTestCase extends PluginTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); @@ -72,7 +72,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci $pluginManager->bootAll(true); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); @@ -97,38 +97,3 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. - -### Functional tests - -Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: - -- Active theme is `demo` -- Language preference is `en` - -#### Selenium set up - -1. Download latest Java SE from http://java.sun.com/ and install -1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). -1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. -1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. - -#### Selenium configuration - -Create a new file `selenium.php` in the root directory, add the following content: - - markTestSkipped('Selenium skipped'); - } - - if (defined('TEST_SELENIUM_HOST')) { - $this->setHost(TEST_SELENIUM_HOST); - } - if (defined('TEST_SELENIUM_PORT')) { - $this->setPort(TEST_SELENIUM_PORT); - } - if (defined('TEST_SELENIUM_BROWSER')) { - $this->setBrowser(TEST_SELENIUM_BROWSER); - } - $this->setBrowserUrl(TEST_SELENIUM_URL); - } - - // - // OctoberCMS Helpers - // - - protected function signInToBackend() - { - $this->open('backend'); - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } - - // - // Selenium helpers - // - - protected function waitForElementPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if ($this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } - - protected function waitForElementNotPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if (!$this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } -} diff --git a/tests/functional/backend/AuthTest.php b/tests/functional/backend/AuthTest.php deleted file mode 100644 index 4c1fe925d..000000000 --- a/tests/functional/backend/AuthTest.php +++ /dev/null @@ -1,90 +0,0 @@ -open('backend'); - - $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("name=login")); - $this->assertTrue($this->isElementPresent("name=password")); - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Login"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - /* - * Sign in - */ - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Dashboard | October CMS'); - $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->verifyText('css='.$cssLogoutLink, "Sign out"); - - /* - * Log out - */ - $this->click('css='.$cssLogoutLink); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } - - public function testPasswordReset() - { - $this->open('backend'); - - try { - $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->click('link=exact:Forgot your password?'); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Restore"); - $this->assertTrue($this->isElementPresent("link=Cancel")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->type("name=login", TEST_SELENIUM_USER); - sleep(1); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); - $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } -} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php deleted file mode 100644 index 5ff2a0a9e..000000000 --- a/tests/functional/cms/TemplateTest.php +++ /dev/null @@ -1,143 +0,0 @@ -signInToBackend(); - $this->open('cms'); - $this->waitForPageToLoad("30000"); - - // Fix the sidebar - $this->click("xpath=(//a[@class='fix-button'])[1]"); - - /* - * Page - */ - - // Create a new page - $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=settings[title]"); - - // Populate page details - $this->type('name=settings[title]', 'Functional Test Page'); - $this->type('name=settings[url]', '/xxx/functional/test/page'); - $this->type('name=fileName', 'xxx_functional_test_page'); - - // Save the new page - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->waitForElementPresent("name=settings[title]"); - sleep(1); - - // Delete the page - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this page?'); - // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); - $this->waitForElementNotPresent("name=settings[title]"); - - /* - * Partial - */ - - // Click partials menu item - $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); - - // Create a new partial - $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate partial details - $this->type('name=fileName', 'xxx_functional_test_partial'); - $this->type('name=settings[description]', 'Test partial'); - - // Save the new partial - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the partial - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this partial?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Layout - */ - - // Click layouts menu item - $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); - - // Create a new layout - $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate layout details - $this->type('name=fileName', 'xxx_functional_test_layout'); - $this->type('name=settings[description]', 'Test layout'); - - // Save the new layout - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the layout - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this layout?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Content - */ - - // Click contents menu item - $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); - - // Create a new content - $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate content details - $this->type('name=fileName', 'xxx_functional_test_content.txt'); - - // Save the new content - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the content - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this content file?'); - $this->waitForElementNotPresent("name=fileName"); - } -} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml deleted file mode 100644 index cb4586424..000000000 --- a/tests/functional/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ./ - - - From babf8f05a73f76aef55587b5ad94254b6bcaac67 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 20:01:10 +1100 Subject: [PATCH 061/100] Restore autoloader and bootstrapper There was no real need to change this from what it was --- artisan | 26 +++++++------------------- bootstrap/autoload.php | 2 ++ index.php | 21 ++++++--------------- phpunit.xml.dist => phpunit.xml | 19 +++++++++---------- tests/bootstrap.php | 6 ++++++ 5 files changed, 30 insertions(+), 44 deletions(-) rename phpunit.xml.dist => phpunit.xml (76%) diff --git a/artisan b/artisan index df3993e90..961e94d0b 100644 --- a/artisan +++ b/artisan @@ -1,32 +1,20 @@ #!/usr/bin/env php make(Illuminate\Contracts\Console\Kernel::class); +$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -60,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); +exit($status); \ No newline at end of file diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index 9a131647a..6533c54ca 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -1,5 +1,7 @@ - diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 3de4e0dc9..5ed3e1296 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,10 @@ Date: Thu, 27 Feb 2020 20:02:52 +1100 Subject: [PATCH 062/100] Update composer to suit latest changes --- composer.json | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 394c21239..126e27142 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,6 @@ { "name": "Luke Towers", "email": "octobercms@luketowers.ca", - "homepage": "https://luketowers.ca", "role": "Maintainer" } ], @@ -27,12 +26,10 @@ "issues": "https://github.com/octobercms/october/issues", "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", - "irc": "irc://irc.freenode.net/october", - "chat": "https://discord.gg/gEKgwSZ", "source": "https://github.com/octobercms/october" }, "require": { - "php": "^7.2", + "php": ">=7.2", "ext-mbstring": "*", "ext-openssl": "*", "october/rain": "dev-wip/laravel-6 as 1.0", @@ -43,19 +40,18 @@ "wikimedia/composer-merge-plugin": "dev-master" }, "require-dev": { - "fzaninotto/faker": "^1.9", "phpunit/phpunit": "^8.0|^9.0", - "dms/phpunit-arraysubset-asserts": "^0.1.0", - "meyfa/phpunit-assert-gd": "^2.0", + "fzaninotto/faker": "~1.9", "squizlabs/php_codesniffer": "3.*", - "jakub-onderka/php-parallel-lint": "^1.0" + "jakub-onderka/php-parallel-lint": "^1.0", + "meyfa/phpunit-assert-gd": "^2.0.0", + "dms/phpunit-arraysubset-asserts": "^0.1.0" }, "autoload-dev": { "classmap": [ "tests/concerns/InteractsWithAuthentication.php", "tests/fixtures/backend/models/UserFixture.php", "tests/TestCase.php", - "tests/UiTestCase.php", "tests/PluginTestCase.php" ] }, @@ -69,7 +65,7 @@ "php artisan package:discover" ], "test": [ - "phpunit --stop-on-failure --prepend ./vendor/october/rain/src/Support/helpers.php" + "phpunit --stop-on-failure" ], "lint": [ "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." @@ -79,7 +75,10 @@ ] }, "config": { - "preferred-install": "dist" + "preferred-install": "dist", + "platform": { + "php": "7.2" + } }, "minimum-stability": "dev", "prefer-stable": true, From 73b551cf0481811b07b8c0af4f0138b097afcf90 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 20:03:19 +1100 Subject: [PATCH 063/100] Switch to October Rain assetic --- modules/backend/classes/FormField.php | 4 ++-- modules/backend/routes.php | 2 +- modules/system/aliases.php | 2 +- modules/system/classes/CombineAssets.php | 24 ++++++++++++------------ modules/system/console/OctoberUpdate.php | 1 - 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/modules/backend/classes/FormField.php b/modules/backend/classes/FormField.php index 5f52b5c23..02080a9ca 100644 --- a/modules/backend/classes/FormField.php +++ b/modules/backend/classes/FormField.php @@ -124,7 +124,7 @@ class FormField /** * @var string Specifies a comment to accompany the field */ - public $comment; + public $comment = ''; /** * @var string Specifies the comment position. @@ -139,7 +139,7 @@ class FormField /** * @var string Specifies a message to display when there is no value supplied (placeholder). */ - public $placeholder; + public $placeholder = ''; /** * @var array Contains a list of attributes specified in the field configuration. diff --git a/modules/backend/routes.php b/modules/backend/routes.php index 81904802f..0268f7efc 100644 --- a/modules/backend/routes.php +++ b/modules/backend/routes.php @@ -25,7 +25,7 @@ App::before(function ($request) { 'middleware' => ['web'], 'prefix' => Config::get('cms.backendUri', 'backend') ], function () { - Route::any('{slug}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); + Route::any('{slug?}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); }) ; diff --git a/modules/system/aliases.php b/modules/system/aliases.php index 01a04ec96..314dd0992 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,7 +16,6 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => October\Rain\Support\Facades\Input::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -42,6 +41,7 @@ return [ 'Config' => October\Rain\Support\Facades\Config::class, 'Seeder' => October\Rain\Database\Updates\Seeder::class, 'Flash' => October\Rain\Support\Facades\Flash::class, + 'Input' => October\Rain\Support\Facades\Input::class, 'Form' => October\Rain\Support\Facades\Form::class, 'Html' => October\Rain\Support\Facades\Html::class, 'Http' => October\Rain\Support\Facades\Http::class, diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php index b85826d84..17ec7c2bd 100644 --- a/modules/system/classes/CombineAssets.php +++ b/modules/system/classes/CombineAssets.php @@ -10,11 +10,11 @@ use Route; use Config; use Request; use Response; -use Assetic\Asset\FileAsset; -use Assetic\Asset\AssetCache; -use Assetic\Asset\AssetCollection; -use Assetic\Factory\AssetFactory; -use October\Rain\Parse\Assetic\FilesystemCache; +use October\Rain\Assetic\Asset\FileAsset; +use October\Rain\Assetic\Asset\AssetCache; +use October\Rain\Assetic\Asset\AssetCollection; +use October\Rain\Assetic\Cache\FilesystemCache; +use October\Rain\Assetic\Factory\AssetFactory; use System\Helpers\Cache as CacheHelper; use ApplicationException; use DateTime; @@ -126,22 +126,22 @@ class CombineAssets /* * Register JavaScript filters */ - $this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter); /* * Register CSS filters */ - $this->registerFilter('css', new \Assetic\Filter\CssImportFilter); - $this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter); - $this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler); - $this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler); + $this->registerFilter('css', new \October\Rain\Assetic\Filter\CssImportFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\CssRewriteFilter); + $this->registerFilter('less', new \October\Rain\Assetic\Filter\LessCompiler); + $this->registerFilter('scss', new \October\Rain\Assetic\Filter\ScssCompiler); /* * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \Assetic\Filter\JSqueezeFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify); } /* diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index b826fe305..200dcd221 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -17,7 +17,6 @@ use Symfony\Component\Console\Input\InputOption; */ class OctoberUpdate extends Command { - /** * The console command name. */ From c6e61206ed5698987b2ec2548d67a106e518bbe4 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 27 Feb 2020 17:40:56 +0800 Subject: [PATCH 064/100] Add roles and updated PHP version to module Composer requirements --- modules/backend/composer.json | 8 +++++--- modules/cms/composer.json | 8 +++++--- modules/system/composer.json | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/backend/composer.json b/modules/backend/composer.json index 15cfd2a3f..e5f1223ae 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,7 +24,7 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", "october/rain": "~1.0" }, diff --git a/modules/cms/composer.json b/modules/cms/composer.json index 49e2d944b..f2041bcde 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,7 +24,7 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", "october/rain": "~1.0" }, diff --git a/modules/system/composer.json b/modules/system/composer.json index 5bebae7be..01bfb7e11 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,7 +24,7 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", "october/rain": "~1.0" }, From 48a8bc3c16cc5f0db56f256ea8b3208c1354aecf Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 27 Feb 2020 21:08:43 +1100 Subject: [PATCH 065/100] Minor --- artisan | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artisan b/artisan index 961e94d0b..df630d0d6 100644 --- a/artisan +++ b/artisan @@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); \ No newline at end of file +exit($status); From 0c982c270481ee2810a92780951f37c3af1b140c Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 16 Mar 2020 17:19:30 +0800 Subject: [PATCH 066/100] Use correct folder for getting screenshot artifacts from browser tests --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3d6239c2c..0a5987cea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -110,4 +110,4 @@ jobs: if: failure() with: name: dusk-screenshots - path: ./tests/Browser/screenshots + path: ./storage/dusk/screenshots From 0b10c0e837907d3a6a5c66e62bf0d1fd9a9a5b6d Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 20 Mar 2020 16:49:55 -0600 Subject: [PATCH 067/100] Remove storage/framework/cache/data directory. See https://github.com/octoberrain/meta/commit/18d7724287a668a6f9540bf95e858396a622223a for more information. --- storage/framework/cache/.gitignore | 1 - storage/framework/cache/data/.gitignore | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 storage/framework/cache/data/.gitignore diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore index 869804c2a..c96a04f00 100644 --- a/storage/framework/cache/.gitignore +++ b/storage/framework/cache/.gitignore @@ -1,3 +1,2 @@ * -!data/ !.gitignore \ No newline at end of file diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore deleted file mode 100644 index c96a04f00..000000000 --- a/storage/framework/cache/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file From f98c71aa14dd2e418a0d1a3ba57d5ae92e625222 Mon Sep 17 00:00:00 2001 From: datune Date: Tue, 24 Mar 2020 18:51:27 +0100 Subject: [PATCH 068/100] Replace Illuminate Dumper with Symfony Dumper (#4998) See https://github.com/laravel/framework/pull/25087 --- modules/cms/twig/DebugExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php index 242eb23a5..f974bb8e2 100644 --- a/modules/cms/twig/DebugExtension.php +++ b/modules/cms/twig/DebugExtension.php @@ -8,7 +8,7 @@ use Cms\Classes\Controller; use Cms\Classes\ComponentBase; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; -use Illuminate\Support\Debug\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Cloner\VarCloner; use October\Rain\Database\Model; From 82238ba3a40b66cb67f91a1bdc06a579a62c7722 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 27 Mar 2020 12:20:31 -0600 Subject: [PATCH 069/100] Fix for Laravel 6 upgrade --- tests/unit/system/console/OctoberEnvTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/system/console/OctoberEnvTest.php b/tests/unit/system/console/OctoberEnvTest.php index d05f43794..77530b002 100644 --- a/tests/unit/system/console/OctoberEnvTest.php +++ b/tests/unit/system/console/OctoberEnvTest.php @@ -13,7 +13,7 @@ class OctoberEnvTest extends TestCase /** @var string Stores the original config path from the app container */ public static $origConfigPath; - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -52,7 +52,7 @@ class OctoberEnvTest extends TestCase } } - protected function tearDown() + protected function tearDown(): void { $this->tearDownConfigFixtures(); $this->restoreEnvFile(); From 64ba17ee7517be6a1adc3a437aa7e8ac02d3af0a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 29 Mar 2020 16:20:51 +0800 Subject: [PATCH 070/100] Drop assertContains calls in OctoberEnv test --- tests/unit/system/console/OctoberEnvTest.php | 57 ++++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/tests/unit/system/console/OctoberEnvTest.php b/tests/unit/system/console/OctoberEnvTest.php index c075fe857..184c3e68d 100644 --- a/tests/unit/system/console/OctoberEnvTest.php +++ b/tests/unit/system/console/OctoberEnvTest.php @@ -30,56 +30,29 @@ class OctoberEnvTest extends TestCase // Check environment file $envFile = file_get_contents(base_path('.env')); - // Forward compatible assertions - // @TODO: Use only `assertStringContainsString` after L6 upgrade - - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('APP_DEBUG=true', $envFile); - $this->assertStringContainsString('APP_URL=https://localhost', $envFile); - $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); - $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); - $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); - $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertStringContainsString('DB_PORT=3306', $envFile); - } else { - $this->assertContains('APP_DEBUG=true', $envFile); - $this->assertContains('APP_URL=https://localhost', $envFile); - $this->assertContains('DB_CONNECTION=mysql', $envFile); - $this->assertContains('DB_DATABASE="data#base"', $envFile); - $this->assertContains('DB_USERNAME="teal\'c"', $envFile); - $this->assertContains('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertContains('DB_PORT=3306', $envFile); - } + $this->assertStringContainsString('APP_DEBUG=true', $envFile); + $this->assertStringContainsString('APP_URL=https://localhost', $envFile); + $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); + $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); + $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); + $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); + $this->assertStringContainsString('DB_PORT=3306', $envFile); // Check app.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/app.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } else { - $this->assertContains('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertContains('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } + $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); + $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); // Check database.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/database.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } else { - $this->assertContains('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertContains('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertContains('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertContains('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } + $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); + $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); + // Both the following configurations had values in the original config, they should be stripped out once + // the .env file is generated. + $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); + $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); } protected function tearDown(): void From 822e6d29add0384d74ffa188c7a2a339b480e4ff Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 29 Mar 2020 16:33:12 +0800 Subject: [PATCH 071/100] Drop browser tests from test suite As much as I'd like to keep them, they're just too flaky right now. --- .github/workflows/tests.yml | 50 ------------------------------------- 1 file changed, 50 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0a5987cea..a27246d97 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,53 +61,3 @@ jobs: run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php - duskTests: - runs-on: ubuntu-latest - name: Browser Tests - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@v1 - with: - php-version: '7.3' - extensions: mbstring, intl, gd, xml, sqlite - - name: Setup problem matcher for PHPUnit - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Set Composer cache - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache Composer dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install Dusk plugin - run: | - curl -fLs https://github.com/rainlab/dusk-plugin/archive/master.zip -o rainlab-dusk.zip - mkdir -p plugins/rainlab - unzip rainlab-dusk.zip -d plugins/rainlab - mv plugins/rainlab/dusk-plugin-master plugins/rainlab/dusk - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Run post-update Composer scripts - run: php artisan package:discover - - name: Reset October modules - run: | - git reset --hard HEAD - composer dumpautoload - - name: Install Chrome driver - run: php artisan dusk:chrome-driver - - name: Start Chrome driver - run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 & - - name: Run Laravel Server - run: php artisan serve > /dev/null 2>&1 & - - name: Run Dusk Tests - run: php artisan dusk - - name: Upload Dusk Screenshots - uses: actions/upload-artifact@v1 - if: failure() - with: - name: dusk-screenshots - path: ./storage/dusk/screenshots From 17fd8b5d9fec8554f0243408a18f15e97f70b809 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 29 Mar 2020 16:41:37 +0800 Subject: [PATCH 072/100] Add info about RainLab Dusk for functional testing --- tests/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/README.md b/tests/README.md index 1828e6cfb..2932c2b67 100644 --- a/tests/README.md +++ b/tests/README.md @@ -97,3 +97,9 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. + +### Functional tests + +Functional tests can be performed by installing the [RainLab Dusk](https://octobercms.com/plugin/rainlab-dusk) in your October CMS installation. The RainLab Dusk plugin is powered by Laravel Dusk, a comprehensive testing suite for the Laravel framework that is designed to test interactions with a fully operational October CMS instance through a virtual browser. + +For information on installing and setting up your October CMS install to run functional tests, please review the [README](https://github.com/rainlab/dusk-plugin/blob/master/README.md) for the plugin. From e51579134abfcea8dfa614c615a4d082db682297 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 29 Mar 2020 17:07:19 +0800 Subject: [PATCH 073/100] Fix PHPUnit location --- tests/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/README.md b/tests/README.md index 2932c2b67..325d5f64e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Plugin unit tests can be performed by running `phpunit` in the base plugin directory. +Plugin unit tests can be performed by running `vendor/bin/phpunit` in the base plugin directory. ### Creating plugin tests @@ -96,7 +96,7 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests -Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. +Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation. ### Functional tests From d18f78bb3d8be4f4a39264da9a11d9a0fd720922 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 29 Mar 2020 08:56:59 -0600 Subject: [PATCH 074/100] Minor path fix --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 325d5f64e..0e985d79b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Plugin unit tests can be performed by running `vendor/bin/phpunit` in the base plugin directory. +Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`. ### Creating plugin tests From 832809841f9b8f99734b863c6e3e305b394c82b1 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 31 Mar 2020 09:57:36 -0600 Subject: [PATCH 075/100] Switch to using league/csv >= 9.1 --- modules/backend/behaviors/ImportExportController.php | 9 ++------- modules/backend/models/ExportModel.php | 5 ++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index c146050b6..b0278ccdc 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -11,7 +11,7 @@ use Backend\Behaviors\ImportExportController\TranscodeFilter; use Illuminate\Database\Eloquent\MassAssignmentException; use League\Csv\Reader as CsvReader; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; use Exception; @@ -624,9 +624,7 @@ class ImportExportController extends ControllerBehavior $csv->setDelimiter($options['delimiter']); $csv->setEnclosure($options['enclosure']); $csv->setEscape($options['escape']); - - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers @@ -662,9 +660,6 @@ class ImportExportController extends ControllerBehavior $record[] = $value; } - // Temporary until upgrading to league/csv >= 9.1.0 - $record = $formatter($record); - $csv->insertOne($record); } diff --git a/modules/backend/models/ExportModel.php b/modules/backend/models/ExportModel.php index 1a03a813b..dfd03c5ef 100644 --- a/modules/backend/models/ExportModel.php +++ b/modules/backend/models/ExportModel.php @@ -5,7 +5,7 @@ use Lang; use Model; use Response; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; @@ -112,8 +112,7 @@ abstract class ExportModel extends Model $csv->setEscape($options['escape']); } - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers From 8c7d50a6388aec4ef8068371c66501c4476f7201 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Tue, 31 Mar 2020 10:09:11 -0600 Subject: [PATCH 076/100] Remove leftover code --- modules/backend/models/ExportModel.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/backend/models/ExportModel.php b/modules/backend/models/ExportModel.php index dfd03c5ef..e6a767e80 100644 --- a/modules/backend/models/ExportModel.php +++ b/modules/backend/models/ExportModel.php @@ -127,10 +127,6 @@ abstract class ExportModel extends Model */ foreach ($results as $result) { $data = $this->matchDataToColumns($result, $columns); - - // Temporary until upgrading to league/csv >= 9.1.0 - $data = $formatter($data); - $csv->insertOne($data); } From 3720af4c02fae89815bd982f2ef6fc920dd8e1d1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Apr 2020 11:37:30 +0800 Subject: [PATCH 077/100] Make ImportModel compatible with League/CSV v9 --- modules/backend/models/ImportModel.php | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index 12f937a16..f0574a29e 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -5,6 +5,7 @@ use Str; use Lang; use Model; use League\Csv\Reader as CsvReader; +use League\Csv\Statement as CsvStatement; /** * Model used for importing data @@ -108,11 +109,6 @@ abstract class ImportModel extends Model */ $reader = CsvReader::createFromPath($filePath, 'r'); - // Filter out empty rows - $reader->addFilter(function (array $row) { - return count($row) > 1 || reset($row) !== null; - }); - if ($options['delimiter'] !== null) { $reader->setDelimiter($options['delimiter']); } @@ -125,15 +121,11 @@ abstract class ImportModel extends Model $reader->setEscape($options['escape']); } - if ($options['firstRowTitles']) { - $reader->setOffset(1); - } - if ( $options['encoding'] !== null && $reader->isActiveStreamFilter() ) { - $reader->appendStreamFilter(sprintf( + $reader->addStreamFilter(sprintf( '%s%s:%s', TranscodeFilter::FILTER_NAME, strtolower($options['encoding']), @@ -141,8 +133,19 @@ abstract class ImportModel extends Model )); } + // Create reader statement + $stmt = (new CsvStatement) + ->where(function (array $row) { + // Filter out empty rows + return count($row) > 1 || reset($row) !== null; + }); + + if ($options['firstRowTitles']) { + $stmt = $stmt->offset(1); + } + $result = []; - $contents = $reader->fetch(); + $contents = $stmt->process($reader); foreach ($contents as $row) { $result[] = $this->processImportRow($row, $matches); } From 28e8758648dabfffb279f9e11c358823c4684f35 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 17 Apr 2020 11:45:48 +0800 Subject: [PATCH 078/100] Further compat fixes --- modules/backend/models/ImportModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index f0574a29e..09c1bf426 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -123,7 +123,7 @@ abstract class ImportModel extends Model if ( $options['encoding'] !== null && - $reader->isActiveStreamFilter() + $reader->supportsStreamFilter() ) { $reader->addStreamFilter(sprintf( '%s%s:%s', From c3a5780d37996f14fefacd7ba5af4703f53b14db Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 19 May 2020 12:47:49 +0800 Subject: [PATCH 079/100] Update providers and alias to use October Validator service --- modules/system/aliases.php | 2 +- modules/system/providers.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/system/aliases.php b/modules/system/aliases.php index 314dd0992..622ec2c1f 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -29,7 +29,6 @@ return [ 'Storage' => Illuminate\Support\Facades\Storage::class, 'Url' => Illuminate\Support\Facades\URL::class, // Preferred 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, /* @@ -52,6 +51,7 @@ return [ 'Twig' => October\Rain\Support\Facades\Twig::class, 'DbDongle' => October\Rain\Support\Facades\DbDongle::class, 'Schema' => October\Rain\Support\Facades\Schema::class, + 'Validator' => October\Rain\Support\Facades\Validator::class, 'Cms' => Cms\Facades\Cms::class, 'Backend' => Backend\Facades\Backend::class, 'BackendMenu' => Backend\Facades\BackendMenu::class, diff --git a/modules/system/providers.php b/modules/system/providers.php index 79e1d7c48..96951f747 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -16,7 +16,6 @@ return [ Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, @@ -36,5 +35,6 @@ return [ October\Rain\Mail\MailServiceProvider::class, October\Rain\Argon\ArgonServiceProvider::class, October\Rain\Redis\RedisServiceProvider::class, + October\Rain\Validation\ValidationServiceProvider::class, ]; From 3241cf7d6465ec6ae0d1f307bff79b9530530d51 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 19 May 2020 12:57:10 +0800 Subject: [PATCH 080/100] Fix deprecated PHPUnit calls in ControllerTest --- tests/unit/cms/classes/ControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 614159ad7..1496055a3 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -388,7 +388,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -414,7 +414,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class-alias')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('someAlias', $page->components); $component = $page->components['someAlias']; From ec1d8fe3155e4faaffee5954b483028b51448417 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 27 May 2020 13:25:36 -0600 Subject: [PATCH 081/100] Fix Media Library test for L6 --- tests/unit/system/classes/MediaLibraryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 701d23401..54c4a6a3e 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -4,7 +4,7 @@ use System\Classes\MediaLibrary; class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine { - protected function tearDown() + protected function tearDown() : void { $this->removeMedia(); parent::tearDown(); From 0317ecec3c4969f649685820c3615a380f0f3e92 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 23 Jun 2020 10:40:36 +0800 Subject: [PATCH 082/100] Add app.loadDiscoveredPackages config item Refs: https://github.com/octobercms/library/pull/492 --- config/app.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/config/app.php b/config/app.php index 6d6b8dee2..d0926ae7b 100644 --- a/config/app.php +++ b/config/app.php @@ -129,6 +129,26 @@ return [ 'System\ServiceProvider', ]), + /* + |-------------------------------------------------------------------------- + | Load automatically discovered packages + |-------------------------------------------------------------------------- + | + | By default, October CMS disables the loading of discovered packages + | through Laravel's package discovery service, in order to allow packages + | used by plugins to be disabled if the plugin itself is disabled. + | + | Set this to `true` to enable automatic loading of these packages. This + | will result in packages being loaded, even if the plugin using them is + | disabled. + | + | Please note that packages defined in `app.providers` will still be loaded + | even if discovery is disabled. + | + */ + + 'loadDiscoveredPackages' => false, + /* |-------------------------------------------------------------------------- | Class Aliases From 83137dafb87c3b9fc4e92fc60741a1e90b6df6cf Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 23 Jun 2020 11:04:25 +0800 Subject: [PATCH 083/100] Remove Dusk "don't discover" flag Will be fixed instead by https://github.com/octobercms/library/pull/492 when it is merged. --- composer.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/composer.json b/composer.json index c9115050f..64a151edd 100644 --- a/composer.json +++ b/composer.json @@ -90,11 +90,6 @@ "recurse": true, "replace": false, "merge-dev": false - }, - "laravel": { - "dont-discover": [ - "laravel/dusk" - ] } } } From e7b6917ad53d41d06d2bc7e2dbc2c31ce3b3a128 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 22 Jun 2020 21:58:08 -0600 Subject: [PATCH 084/100] Update config/app.php --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index d0926ae7b..56e4959ab 100644 --- a/config/app.php +++ b/config/app.php @@ -140,7 +140,7 @@ return [ | | Set this to `true` to enable automatic loading of these packages. This | will result in packages being loaded, even if the plugin using them is - | disabled. + | disabled. This is NOT RECOMMENDED. | | Please note that packages defined in `app.providers` will still be loaded | even if discovery is disabled. From b5dcc42ed2741640cc31dce074d20c4e7005596f Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 1 Jul 2020 10:52:55 +0800 Subject: [PATCH 085/100] Namespace the authentication in tests under 'backend.auth' Prevents conflicts with unit tests that might use another auth system, ie. Passport. Hat tip to @LukeTowers for pointing out my shame. --- tests/PluginTestCase.php | 2 +- tests/concerns/InteractsWithAuthentication.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 728d5d430..1d4a66f00 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -28,7 +28,7 @@ abstract class PluginTestCase extends TestCase $app['cache']->setDefaultDriver('array'); $app->setLocale('en'); - $app->singleton('auth', function ($app) { + $app->singleton('backend.auth', function ($app) { $app['auth.loaded'] = true; return AuthManager::instance(); diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/concerns/InteractsWithAuthentication.php index a7950f230..5f5c3c8c7 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/concerns/InteractsWithAuthentication.php @@ -29,7 +29,7 @@ trait InteractsWithAuthentication */ public function be(UserContract $user, $driver = null) { - $this->app['auth']->setUser($user); + $this->app['backend.auth']->setUser($user); } /** @@ -66,7 +66,7 @@ trait InteractsWithAuthentication */ protected function isAuthenticated($guard = null) { - return $this->app->make('auth')->guard($guard)->check(); + return $this->app->make('backend.auth')->guard($guard)->check(); } /** @@ -78,7 +78,7 @@ trait InteractsWithAuthentication */ public function assertAuthenticatedAs($user, $guard = null) { - $expected = $this->app->make('auth')->guard($guard)->user(); + $expected = $this->app->make('backend.auth')->guard($guard)->user(); $this->assertNotNull($expected, 'The current user is not authenticated.'); @@ -140,7 +140,7 @@ trait InteractsWithAuthentication */ protected function hasCredentials(array $credentials, $guard = null) { - $provider = $this->app->make('auth')->guard($guard)->getProvider(); + $provider = $this->app->make('backend.auth')->guard($guard)->getProvider(); $user = $provider->retrieveByCredentials($credentials); From 4950edc1964cf375a50eca67bb4046ffbf0afb28 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 8 Jul 2020 16:26:38 +0800 Subject: [PATCH 086/100] Add sensitive field input (#5201) A field widget that allows for entering of sensitive information that can be revealed at the user's request - ie. API keys, secrets. When a sensitive field that has been previously populated is loaded again, a placeholder is used instead of the real value, until the user opts to reveal the value. The real value is loaded via AJAX. Credit to @tomaszstrojny for the original implementation. Replaces #5062. Fixes #5061, #1850, perhaps #1061. Co-authored-by: Tomasz Strojny Co-authored-by: Luke Towers --- modules/backend/ServiceProvider.php | 2 + modules/backend/formwidgets/Sensitive.php | 117 +++++++++++ .../sensitive/assets/css/sensitive.css | 2 + .../sensitive/assets/js/sensitive.js | 192 ++++++++++++++++++ .../sensitive/assets/less/sensitive.less | 10 + .../sensitive/partials/_sensitive.htm | 41 ++++ modules/system/models/mailsetting/fields.yaml | 5 + 7 files changed, 369 insertions(+) create mode 100644 modules/backend/formwidgets/Sensitive.php create mode 100644 modules/backend/formwidgets/sensitive/assets/css/sensitive.css create mode 100644 modules/backend/formwidgets/sensitive/assets/js/sensitive.js create mode 100644 modules/backend/formwidgets/sensitive/assets/less/sensitive.less create mode 100644 modules/backend/formwidgets/sensitive/partials/_sensitive.htm diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index b0a5026f8..4c3825dd0 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -80,6 +80,7 @@ class ServiceProvider extends ModuleServiceProvider $combiner->registerBundle('~/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less'); $combiner->registerBundle('~/modules/backend/formwidgets/permissioneditor/assets/less/permissioneditor.less'); $combiner->registerBundle('~/modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less'); + $combiner->registerBundle('~/modules/backend/formwidgets/sensitive/assets/less/sensitive.less'); /* * Rich Editor is protected by DRM @@ -199,6 +200,7 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist'); $manager->registerFormWidget('Backend\FormWidgets\MediaFinder', 'mediafinder'); $manager->registerFormWidget('Backend\FormWidgets\NestedForm', 'nestedform'); + $manager->registerFormWidget('Backend\FormWidgets\Sensitive', 'sensitive'); }); } diff --git a/modules/backend/formwidgets/Sensitive.php b/modules/backend/formwidgets/Sensitive.php new file mode 100644 index 000000000..a28a8d60b --- /dev/null +++ b/modules/backend/formwidgets/Sensitive.php @@ -0,0 +1,117 @@ +fillFromConfig([ + 'readOnly', + 'disabled', + 'allowCopy', + 'hiddenPlaceholder', + 'hideOnTabChange', + ]); + + if ($this->formField->disabled || $this->formField->readOnly) { + $this->previewMode = true; + } + } + + /** + * @inheritDoc + */ + public function render() + { + $this->prepareVars(); + + return $this->makePartial('sensitive'); + } + + /** + * Prepares the view data for the widget partial. + */ + public function prepareVars() + { + $this->vars['readOnly'] = $this->readOnly; + $this->vars['disabled'] = $this->disabled; + $this->vars['hasValue'] = !empty($this->getLoadValue()); + $this->vars['allowCopy'] = $this->allowCopy; + $this->vars['hiddenPlaceholder'] = $this->hiddenPlaceholder; + $this->vars['hideOnTabChange'] = $this->hideOnTabChange; + } + + /** + * Reveals the value of a hidden, unmodified sensitive field. + * + * @return array + */ + public function onShowValue() + { + return [ + 'value' => $this->getLoadValue() + ]; + } + + /** + * @inheritDoc + */ + public function getSaveValue($value) + { + if ($value === $this->hiddenPlaceholder) { + $value = $this->getLoadValue(); + } + + return $value; + } + + /** + * @inheritDoc + */ + protected function loadAssets() + { + $this->addCss('css/sensitive.css', 'core'); + $this->addJs('js/sensitive.js', 'core'); + } +} diff --git a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css new file mode 100644 index 000000000..c8ac9378a --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css @@ -0,0 +1,2 @@ +div[data-control="sensitive"] a[data-toggle], +div[data-control="sensitive"] a[data-copy] {box-shadow:none;border:1px solid #d1d6d9;border-left:0} \ No newline at end of file diff --git a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js new file mode 100644 index 000000000..69251304c --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js @@ -0,0 +1,192 @@ +/* + * Sensitive field widget plugin. + * + * Data attributes: + * - data-control="sensitive" - enables the plugin on an element + * + * JavaScript API: + * $('div#someElement').sensitive({...}) + */ ++function ($) { "use strict"; + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var Sensitive = function(element, options) { + this.$el = $(element) + this.options = options + this.clean = Boolean(this.$el.data('clean')) + this.hidden = true + + this.$input = this.$el.find('[data-input]').first() + this.$toggle = this.$el.find('[data-toggle]').first() + this.$icon = this.$el.find('[data-icon]').first() + this.$loader = this.$el.find('[data-loader]').first() + this.$copy = this.$el.find('[data-copy]').first() + + $.oc.foundation.controlUtils.markDisposable(element) + Base.call(this) + this.init() + } + + Sensitive.DEFAULTS = { + readOnly: false, + disabled: false, + eventHandler: null, + hideOnTabChange: false, + } + + Sensitive.prototype = Object.create(BaseProto) + Sensitive.prototype.constructor = Sensitive + + Sensitive.prototype.init = function() { + this.$input.on('keydown', this.proxy(this.onInput)) + this.$toggle.on('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + // Watch for tab change or minimise + document.addEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.on('click', this.proxy(this.onCopy)) + } + } + + Sensitive.prototype.dispose = function () { + this.$input.off('keydown', this.proxy(this.onInput)) + this.$toggle.off('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + document.removeEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.off('click', this.proxy(this.onCopy)) + } + + this.$input = this.$toggle = this.$icon = this.$loader = null + this.$el = null + + BaseProto.dispose.call(this) + } + + Sensitive.prototype.onInput = function() { + if (this.clean) { + this.clean = false + this.$input.val('') + } + + return true + } + + Sensitive.prototype.onToggle = function() { + if (this.$input.val() !== '' && this.clean) { + this.reveal() + } else { + this.toggleVisibility() + } + + return true + } + + Sensitive.prototype.onTabChange = function() { + if (document.hidden && !this.hidden) { + this.toggleVisibility() + } + } + + Sensitive.prototype.onCopy = function() { + var that = this, + deferred = $.Deferred(), + isHidden = this.hidden + + deferred.then(function () { + if (that.hidden) { + that.toggleVisibility() + } + + that.$input.focus() + that.$input.select() + + try { + document.execCommand('copy') + } catch (err) { + } + + that.$input.blur() + if (isHidden) { + that.toggleVisibility() + } + }) + + if (this.$input.val() !== '' && this.clean) { + this.reveal(deferred) + } else { + deferred.resolve() + } + } + + Sensitive.prototype.toggleVisibility = function() { + if (this.hidden) { + this.$input.attr('type', 'text') + } else { + this.$input.attr('type', 'password') + } + + this.$icon.toggleClass('icon-eye icon-eye-slash') + + this.hidden = !this.hidden + } + + Sensitive.prototype.reveal = function(deferred) { + var that = this + this.$icon.css({ + visibility: 'hidden' + }) + this.$loader.removeClass('hide') + + this.$input.request(this.options.eventHandler, { + success: function (data) { + that.$input.val(data.value) + that.clean = false + + that.$icon.css({ + visibility: 'visible' + }) + that.$loader.addClass('hide') + + that.toggleVisibility() + + if (deferred) { + deferred.resolve() + } + } + }) + } + + var old = $.fn.sensitive + + $.fn.sensitive = function (option) { + var args = Array.prototype.slice.call(arguments, 1), result + this.each(function () { + var $this = $(this) + var data = $this.data('oc.sensitive') + var options = $.extend({}, Sensitive.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.sensitive', (data = new Sensitive(this, options))) + if (typeof option == 'string') result = data[option].apply(data, args) + if (typeof result != 'undefined') return false + }) + + return result ? result : this + } + + $.fn.sensitive.noConflict = function () { + $.fn.sensitive = old + return this + } + + $(document).render(function () { + $('[data-control="sensitive"]').sensitive() + }); + +}(window.jQuery); diff --git a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less new file mode 100644 index 000000000..5717658f2 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less @@ -0,0 +1,10 @@ +@import "../../../../assets/less/core/boot.less"; + +div[data-control="sensitive"] { + a[data-toggle], + a[data-copy] { + box-shadow: none; + border: 1px solid @input-group-addon-border-color; + border-left: 0; + } +} diff --git a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm new file mode 100644 index 000000000..b913070d7 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm @@ -0,0 +1,41 @@ +
data-hide-on-tab-change="true" +> +
+
+ previewMode): ?>disabled="disabled" + autocomplete="off" + data-input + /> + + + + + + + + +
+
+ +
+
+
diff --git a/modules/system/models/mailsetting/fields.yaml b/modules/system/models/mailsetting/fields.yaml index c2457ec38..d49851cba 100644 --- a/modules/system/models/mailsetting/fields.yaml +++ b/modules/system/models/mailsetting/fields.yaml @@ -79,6 +79,7 @@ tabs: smtp_password: label: system::lang.mail.smtp_password tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -107,6 +108,7 @@ tabs: label: system::lang.mail.mailgun_secret commentAbove: system::lang.mail.mailgun_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -116,6 +118,7 @@ tabs: label: system::lang.mail.mandrill_secret commentAbove: system::lang.mail.mandrill_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -135,6 +138,7 @@ tabs: label: system::lang.mail.ses_secret commentAbove: system::lang.mail.ses_secret_comment tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -154,6 +158,7 @@ tabs: sparkpost_secret: label: system::lang.mail.sparkpost_secret commentAbove: system::lang.mail.sparkpost_secret_comment + type: sensitive tab: system::lang.mail.general trigger: action: show From bb3d0185905aaaa9b10e11a605d7c1a8072ee404 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 10 Jul 2020 15:56:11 +0800 Subject: [PATCH 087/100] Add fallback alias for Illuminate\Support\Facades\Input This will redirect any instances of the now-removed Input facade from Laravel to the Rain library's Input facade. --- modules/system/aliases.php | 6 ++++++ tests/unit/system/AliasesTest.php | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/unit/system/AliasesTest.php diff --git a/modules/system/aliases.php b/modules/system/aliases.php index 622ec2c1f..fd32fa476 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -60,4 +60,10 @@ return [ 'SystemException' => October\Rain\Exception\SystemException::class, 'ApplicationException' => October\Rain\Exception\ApplicationException::class, 'ValidationException' => October\Rain\Exception\ValidationException::class, + + /* + * Fallback aliases + */ + // Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility. + 'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class, ]; diff --git a/tests/unit/system/AliasesTest.php b/tests/unit/system/AliasesTest.php new file mode 100644 index 000000000..c0a64fbd2 --- /dev/null +++ b/tests/unit/system/AliasesTest.php @@ -0,0 +1,13 @@ +assertTrue(class_exists('Illuminate\Support\Facades\Input')); + $this->assertInstanceOf( + \October\Rain\Support\Facades\Input::class, + new \Illuminate\Support\Facades\Input() + ); + } +} From 4fb4e318f15836ca1918c60d3f831c65780172ff Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Tue, 14 Jul 2020 12:25:35 +0800 Subject: [PATCH 088/100] Add "develop.allowDeepSymlinks" configuration option. Refs: https://github.com/octobercms/library/pull/491 --- config/develop.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/config/develop.php b/config/develop.php index cd4aee7d7..2c1cbc642 100644 --- a/config/develop.php +++ b/config/develop.php @@ -20,5 +20,27 @@ return [ */ 'decompileBackendAssets' => false, + + /* + |-------------------------------------------------------------------------- + | Allow deep-level symlinks + |-------------------------------------------------------------------------- + | + | October CMS, by default, will allow symlinks within the first level of + | subdirectories. When this feature is enabled, the system will allow + | symlinks to be used at any directory level. This can be useful for + | symlinking individual plugins or themes. + | + | Please note that this has a negative effect on performance. This feature + | abides by "cms.restrictBaseDir" - if enabled, symlinks cannot point to + | resources outside of the root folder. + | + | true - allow symlinks at any level + | + | false - only allow symlinks at the first level of subdirectories (default) + | + */ + + 'allowDeepSymlinks' => false, ]; From 8fd1ddf7aa4a48545de160a26ba7b858c7f35238 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Wed, 15 Jul 2020 14:04:01 +0800 Subject: [PATCH 089/100] Provide an accessor for the form widget of the Settings controller (#5212) Co-authored-by: Luke Towers --- modules/system/controllers/Settings.php | 53 ++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/modules/system/controllers/Settings.php b/modules/system/controllers/Settings.php index 7ba29dea7..719dff20e 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -2,6 +2,8 @@ use Lang; use Flash; +use Config; +use Request; use Backend; use BackendMenu; use System\Classes\SettingsManager; @@ -139,6 +141,22 @@ class Settings extends Controller return $this->formWidget->render($options); } + /** + * Returns the form widget used by this behavior. + * + * @return \Backend\Widgets\Form + */ + public function formGetWidget() + { + if (is_null($this->formWidget)) { + $item = $this->findSettingItem(); + $model = $this->createModel($item); + $this->initWidgets($model); + } + + return $this->formWidget; + } + /** * Prepare the widgets used by this action * Model $model @@ -169,10 +187,22 @@ class Settings extends Controller } /** - * Locates a setting item for a module or plugin + * Locates a setting item for a module or plugin. + * + * If none of the parameters are provided, they will be auto-guessed from the URL. + * + * @param string|null $author + * @param string|null $plugin + * @param string|null $code + * + * @return array */ - protected function findSettingItem($author, $plugin, $code) + protected function findSettingItem($author = null, $plugin = null, $code = null) { + if (is_null($author) || is_null($plugin)) { + [$author, $plugin, $code] = $this->guessSettingItem(); + } + $manager = SettingsManager::instance(); $moduleOwner = $author; @@ -187,4 +217,23 @@ class Settings extends Controller return $item; } + + /** + * Guesses the requested setting item from the current URL segments provided by the Request object. + * + * @return array + */ + protected function guessSettingItem() + { + $segments = Request::segments(); + + if (!empty(Config::get('cms.backendUri', 'backend'))) { + array_splice($segments, 0, 4); + } else { + array_splice($segments, 0, 3); + } + + // Ensure there's at least 3 segments + return array_pad($segments, 3, null); + } } From 18714ae9f0b8efce630a26633aaf35917ee7e8af Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 19 Jul 2020 01:42:58 -0600 Subject: [PATCH 090/100] Add L6 as a dep to the modules to prevent people with older Laravel versions from accidentally pulling in the L6 update --- composer.json | 4 ++-- modules/backend/composer.json | 3 ++- modules/cms/composer.json | 3 ++- modules/system/composer.json | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 64a151edd..06edcd9a6 100644 --- a/composer.json +++ b/composer.json @@ -19,10 +19,12 @@ { "name": "Luke Towers", "email": "octobercms@luketowers.ca", + "homepage": "https://luketowers.ca", "role": "Maintainer" } ], "support": { + "paid": "https://octobercms.com/premium-support", "issues": "https://github.com/octobercms/october/issues", "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", @@ -30,8 +32,6 @@ }, "require": { "php": ">=7.2", - "ext-mbstring": "*", - "ext-openssl": "*", "october/rain": "dev-wip/laravel-6 as 1.0", "october/system": "dev-wip/laravel-6", "october/backend": "dev-wip/laravel-6", diff --git a/modules/backend/composer.json b/modules/backend/composer.json index e5f1223ae..5d4231af2 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -26,7 +26,8 @@ "require": { "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/cms/composer.json b/modules/cms/composer.json index f2041bcde..9e4cfa70e 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -26,7 +26,8 @@ "require": { "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/system/composer.json b/modules/system/composer.json index 01bfb7e11..81d59a4dc 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -26,7 +26,8 @@ "require": { "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { From 6ea06b3d8e561f5040f0f2cb559bd87ed465d40d Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 19 Jul 2020 02:07:09 -0600 Subject: [PATCH 091/100] restore original password min length value --- modules/backend/models/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 8b9a5a985..46259792b 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -27,8 +27,8 @@ class User extends UserBase public $rules = [ 'email' => 'required|between:6,255|email|unique:backend_users', 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|min:2|confirmed', - 'password_confirmation' => 'required_with:password|min:2' + 'password' => 'required:create|min:4|confirmed', + 'password_confirmation' => 'required_with:password|min:4' ]; /** From 0dee0e54b100a22cdd36647161e951af04effcce Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 20 Jul 2020 10:40:50 +0800 Subject: [PATCH 092/100] Alias Illuminate\Support\Debug\HtmlDumper to Symfony's HtmlDumper --- modules/system/aliases.php | 2 ++ tests/unit/system/AliasesTest.php | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/modules/system/aliases.php b/modules/system/aliases.php index fd32fa476..d6364cd13 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -66,4 +66,6 @@ return [ */ // Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility. 'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class, + // Illuminate's HtmlDumper was "dumped" in Laravel 6 - we'll route this to Symfony's HtmlDumper as Laravel have done. + 'Illuminate\Support\Debug\HtmlDumper' => Symfony\Component\VarDumper\Dumper\HtmlDumper::class, ]; diff --git a/tests/unit/system/AliasesTest.php b/tests/unit/system/AliasesTest.php index c0a64fbd2..8d389cdcc 100644 --- a/tests/unit/system/AliasesTest.php +++ b/tests/unit/system/AliasesTest.php @@ -10,4 +10,13 @@ class AliasesTest extends PluginTestCase new \Illuminate\Support\Facades\Input() ); } + + public function testHtmlDumperAlias() + { + $this->assertTrue(class_exists('Illuminate\Support\Debug\HtmlDumper')); + $this->assertInstanceOf( + \Symfony\Component\VarDumper\Dumper\HtmlDumper::class, + new \Illuminate\Support\Debug\HtmlDumper() + ); + } } From 3051018aff10179c2188696d23a17bea997e1e71 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 3 Aug 2020 23:11:30 +0800 Subject: [PATCH 093/100] Update content of script fixture files --- tests/fixtures/themes/test/assets/js/script1.js | 1 + tests/fixtures/themes/test/assets/js/script2.js | 1 + tests/fixtures/themes/test/assets/js/subdir/script1.js | 1 + 3 files changed, 3 insertions(+) create mode 100644 tests/fixtures/themes/test/assets/js/subdir/script1.js diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index e69de29bb..b8bbe4e13 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -0,0 +1 @@ +console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index e69de29bb..54e00dea3 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -0,0 +1 @@ +console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js new file mode 100644 index 000000000..bc27e099c --- /dev/null +++ b/tests/fixtures/themes/test/assets/js/subdir/script1.js @@ -0,0 +1 @@ +console.log('subdir/script1.js'); From 40f9d7e40c3508c888ba149a9607f7841eaf82de Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Mon, 3 Aug 2020 23:17:10 +0800 Subject: [PATCH 094/100] Revert "Update content of script fixture files" This reverts commit 3051018aff10179c2188696d23a17bea997e1e71. --- tests/fixtures/themes/test/assets/js/script1.js | 1 - tests/fixtures/themes/test/assets/js/script2.js | 1 - tests/fixtures/themes/test/assets/js/subdir/script1.js | 1 - 3 files changed, 3 deletions(-) delete mode 100644 tests/fixtures/themes/test/assets/js/subdir/script1.js diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index b8bbe4e13..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -1 +0,0 @@ -console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index 54e00dea3..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -1 +0,0 @@ -console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js deleted file mode 100644 index bc27e099c..000000000 --- a/tests/fixtures/themes/test/assets/js/subdir/script1.js +++ /dev/null @@ -1 +0,0 @@ -console.log('subdir/script1.js'); From 50e61df10ed8f83a947b08471bbe9e1a24f92120 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Thu, 6 Aug 2020 02:00:22 +0800 Subject: [PATCH 095/100] Fix path validation for CmsObjects & Assets (#5229) Instead of using a temporary file to allow checks with `realpath()` to succeed, use the new `resolve_path()` helper added in https://github.com/octobercms/library/commit/a9a629b0123c38608c48181704577f25798e77f7 to replace the `realpath()` usage and allow for path resolution of files that don't exist yet. This will allow new asset files to still be saved correctly, whilst still preventing paths outside of the assets directory to be used. Replaces https://github.com/octobercms/october/commit/b1954e92586f33a345068d56b957d7e366a5ba71. --- modules/cms/classes/Asset.php | 17 +-- modules/cms/classes/CmsObject.php | 11 +- .../fixtures/themes/test/assets/js/script1.js | 1 + .../fixtures/themes/test/assets/js/script2.js | 1 + .../themes/test/assets/js/subdir/script1.js | 1 + tests/unit/cms/classes/AssetTest.php | 119 ++++++++++++++++++ tests/unit/cms/classes/CodeParserTest.php | 1 + 7 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 tests/fixtures/themes/test/assets/js/subdir/script1.js create mode 100644 tests/unit/cms/classes/AssetTest.php diff --git a/modules/cms/classes/Asset.php b/modules/cms/classes/Asset.php index 12b79ea35..0bfc2eba8 100644 --- a/modules/cms/classes/Asset.php +++ b/modules/cms/classes/Asset.php @@ -287,25 +287,14 @@ class Asset extends Extendable $directory = $this->theme->getPath() . '/' . $this->dirName . '/'; $filePath = $directory . $fileName; - $path = realpath($filePath); - - /** - * If the path doesn't exist yet, then create it temporarily - * in order to run realpath() resolution on it to verify the - * final destination and then remove the temporary file. - */ - if (!$path) { - touch($filePath); - $path = realpath($filePath); - unlink($filePath); - } + $resolvedPath = resolve_path($filePath); // Limit paths to those under the theme's assets directory - if (!starts_with($path, $directory)) { + if (!starts_with($resolvedPath, $directory)) { return false; } - return $path; + return $resolvedPath; } /** diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index 798a065a0..714650150 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -227,7 +227,16 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->fileName; } - return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; + $directory = $this->theme->getPath() . '/' . $this->getObjectTypeDirName() . '/'; + $filePath = $directory . $fileName; + $resolvedPath = resolve_path($filePath); + + // Limit paths to those under the corresponding theme directory + if (!starts_with($resolvedPath, $directory)) { + return false; + } + + return $resolvedPath; } /** diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index e69de29bb..b8bbe4e13 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -0,0 +1 @@ +console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index e69de29bb..54e00dea3 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -0,0 +1 @@ +console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js new file mode 100644 index 000000000..bc27e099c --- /dev/null +++ b/tests/fixtures/themes/test/assets/js/subdir/script1.js @@ -0,0 +1 @@ +console.log('subdir/script1.js'); diff --git a/tests/unit/cms/classes/AssetTest.php b/tests/unit/cms/classes/AssetTest.php new file mode 100644 index 000000000..13519a161 --- /dev/null +++ b/tests/unit/cms/classes/AssetTest.php @@ -0,0 +1,119 @@ +assertStringContainsString( + 'console.log(\'script1.js\');', + Asset::load($theme, 'js/script1.js')->content + ); + + // Valid direct subdirectory path + $this->assertStringContainsString( + 'console.log(\'subdir/script1.js\');', + Asset::load($theme, 'js/subdir/script1.js')->content + ); + + // Valid relative path + $this->assertStringContainsString( + 'console.log(\'script2.js\');', + Asset::load($theme, 'js/subdir/../script2.js')->content + ); + + // Invalid theme path + $this->assertNull( + Asset::load($theme, 'js/invalid.js') + ); + + // Check that we cannot break out of assets directory + $this->assertNull( + Asset::load($theme, '../../../../js/helpers/fakeDom.js') + ); + $this->assertNull( + Asset::load($theme, '../content/html-content.htm') + ); + + // Check that we cannot load directories directly + $this->assertNull( + Asset::load($theme, 'js/subdir') + ); + + // Check that we definitely cannot load external PHP files + $this->assertNull( + Asset::load($theme, '../../../../../config/database.php') + ); + } + + public function testGetPath() + { + // Test some pathing fringe cases + + $theme = Theme::load('test'); + $assetClass = new Asset($theme); + $themeDir = $theme->getPath(); + + // Direct paths + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('js/script1.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('/js/script1.js') + ); + + // Direct path to a directory + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir') + ); + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir/') + ); + + // Relative paths + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('./js/script2.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('/js/subdir/../script2.js') + ); + + // Missing file, but valid directory (allows for new files) + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('/js/missing.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('js/missing.js') + ); + + // Missing file and missing directory (new directories are created as needed) + $this->assertEquals( + $themeDir . '/assets/js/missing/missing.js', + $assetClass->getFilePath('/js/missing/missing.js') + ); + + // Ensure we cannot get paths outside of the assets directory + $this->assertFalse( + $assetClass->getFilePath('../../../../js/helpers/fakeDom.js') + ); + $this->assertFalse( + $assetClass->getFilePath('../content/html-content.htm') + ); + $this->assertFalse( + $assetClass->getFilePath('../../../../../config/database.php') + ); + } +} diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 10d1b7d23..67d8f30e9 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -110,6 +110,7 @@ class CodeParserTest extends TestCase */ $this->assertTrue(@touch($layout->getFilePath())); + clearstatcache(); $layout = Layout::load($theme, 'php-parser-test.htm'); $this->assertNotEmpty($layout); $parser = new CodeParser($layout); From 69fef086cb61631fb4e76a7a42b6e2e3105fcd3a Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 7 Aug 2020 15:52:55 +0800 Subject: [PATCH 096/100] Add Discord to our contributor guidelines, minor tweaks --- .github/CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 021d7417a..f2b25d4c5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,7 +11,7 @@ Thank you for your interest in contributing to the OctoberCMS project. We apprec ## Reporting a Security Vulnerability -Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities. +Please review [our security policy](https://github.com/octobercms/october/security/policy) on how to report security vulnerabilities. Please do not report security vulnerabilities on GitHub. ## Reporting an issue with OctoberCMS @@ -21,6 +21,8 @@ We work hard to process bugs that are reported, to assist with this please ensur - **Summary**: Make sure your summary reflects what the problem is and where it is. Provide as much detail as possible, the more information we have to work with the more likely it is that your problem can be solved. +- **Installed build and plugins**: Please provide the build number of October CMS that is exhibiting the fault, and the version numbers of any installed and active plugins on your installation. You may retrieve this information by logging in to the Backend and navigating to *Settings* and then *Updates & Plugins*. + - **Reproduce steps**: Clearly mention the steps to reproduce the bug. - **Expected behavior**: Describe how OctoberCMS should behave on above mentioned steps. @@ -61,7 +63,7 @@ We do our best to attend to all reported issues. If you have an important issue >**NOTE:** Please don't use GitHub issues for suggesting a new feature. If you have a feature idea, the best place to suggest it is the [OctoberCMS website forum](https://octobercms.com/forum/chan/feature-requests). -Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [IRC](https://octobercms.com/chat) or [Slack](https://octobercms.slack.com). +Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [Discord](https://discord.gg/gEKgwSZ) or [IRC](https://octobercms.com/chat). #### GitHub feature requests From 46b0389678a4291d44add36f2768ecafcad98267 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 7 Aug 2020 15:56:42 +0800 Subject: [PATCH 097/100] Remove IRC link. --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f2b25d4c5..164bc3665 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -63,7 +63,7 @@ We do our best to attend to all reported issues. If you have an important issue >**NOTE:** Please don't use GitHub issues for suggesting a new feature. If you have a feature idea, the best place to suggest it is the [OctoberCMS website forum](https://octobercms.com/forum/chan/feature-requests). -Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [Discord](https://discord.gg/gEKgwSZ) or [IRC](https://octobercms.com/chat). +Only use GitHub if you are planning on contributing a new feature and developing it. If you want to discuss your idea first, before "officially" posting it anywhere, you can always join us on [Discord](https://discord.gg/gEKgwSZ). #### GitHub feature requests From 871c27b8f32f1898a5db435633219be6745e4910 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 9 Aug 2020 03:31:32 -0600 Subject: [PATCH 098/100] L6 finalizing for merging to develop --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 06edcd9a6..9542fc5a1 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "october/october", - "description": "OctoberCMS", + "description": "October CMS", "homepage": "https://octobercms.com", "type": "project", "keywords": ["october", "cms", "octobercms", "laravel"], @@ -32,10 +32,10 @@ }, "require": { "php": ">=7.2", - "october/rain": "dev-wip/laravel-6 as 1.0", - "october/system": "dev-wip/laravel-6", - "october/backend": "dev-wip/laravel-6", - "october/cms": "dev-wip/laravel-6", + "october/rain": "dev-develop as 1.0", + "october/system": "dev-develop", + "october/backend": "dev-develop", + "october/cms": "dev-develop", "laravel/framework": "~6.0", "wikimedia/composer-merge-plugin": "1.4.1" }, From ee0065d353099fb0de0bf6d0b361a71b4f799de1 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 9 Aug 2020 17:44:16 +0800 Subject: [PATCH 099/100] Rebase October CMS on Laravel 6 (#4893) Rebase October CMS on Laravel 6 --- .github/workflows/code-quality-pr.yaml | 23 +- .github/workflows/code-quality-push.yaml | 23 +- .github/workflows/frontend-tests.yaml | 24 - .github/workflows/matchers/phpcs-matcher.json | 23 + .github/workflows/tests.yml | 49 +- .gitignore | 28 +- artisan | 4 +- bootstrap/autoload.php | 17 - composer.json | 40 +- config/app.php | 35 +- config/database.php | 1 + config/develop.php | 22 + config/filesystems.php | 2 +- config/hashing.php | 52 + config/logging.php | 91 ++ config/mail.php | 2 +- config/services.php | 4 + index.php | 2 +- modules/backend/ServiceProvider.php | 2 + .../behaviors/ImportExportController.php | 9 +- modules/backend/classes/FormField.php | 4 +- modules/backend/composer.json | 11 +- .../backend/database/seeds/DatabaseSeeder.php | 6 +- modules/backend/formwidgets/Sensitive.php | 117 ++ .../sensitive/assets/css/sensitive.css | 2 + .../sensitive/assets/js/sensitive.js | 192 +++ .../sensitive/assets/less/sensitive.less | 10 + .../sensitive/partials/_sensitive.htm | 41 + modules/backend/models/ExportModel.php | 9 +- modules/backend/models/ImportModel.php | 27 +- modules/backend/models/User.php | 4 +- modules/backend/routes.php | 2 +- modules/cms/classes/Asset.php | 17 +- modules/cms/classes/CmsCompoundObject.php | 3 +- modules/cms/classes/CmsObject.php | 11 +- modules/cms/classes/CmsObjectCollection.php | 28 +- modules/cms/classes/CodeParser.php | 3 +- modules/cms/classes/Router.php | 6 +- modules/cms/classes/Theme.php | 3 +- modules/cms/composer.json | 11 +- modules/cms/routes.php | 2 +- modules/cms/traits/UrlMaker.php | 3 +- modules/cms/twig/DebugExtension.php | 2 +- modules/system/ServiceProvider.php | 1 + modules/system/aliases.php | 12 +- modules/system/classes/CombineAssets.php | 24 +- modules/system/classes/MediaLibrary.php | 3 +- modules/system/classes/UpdateManager.php | 56 +- modules/system/classes/VersionManager.php | 37 +- modules/system/composer.json | 11 +- modules/system/console/OctoberEnv.php | 2 +- modules/system/console/OctoberUpdate.php | 1 - modules/system/controllers/Settings.php | 53 +- .../system/database/seeds/DatabaseSeeder.php | 6 +- modules/system/lang/en/validation.php | 29 + modules/system/models/mailsetting/fields.yaml | 5 + modules/system/providers.php | 4 +- phpunit.xml | 1 - tests/PluginTestCase.php | 6 +- tests/README.md | 41 +- tests/UiTestCase.php | 111 -- tests/bootstrap.php | 11 - .../concerns/InteractsWithAuthentication.php | 8 +- .../plugins/database/tester/models/Author.php | 1 + .../database/tester/models/Country.php | 35 + .../plugins/database/tester/models/User.php | 13 + .../tester/updates/create_authors_table.php | 1 + .../tester/updates/create_countries_table.php | 23 + .../database/tester/updates/version.yaml | 1 + .../fixtures/themes/test/assets/js/script1.js | 1 + .../fixtures/themes/test/assets/js/script2.js | 1 + .../themes/test/assets/js/subdir/script1.js | 1 + tests/functional/backend/AuthTest.php | 90 -- tests/functional/cms/TemplateTest.php | 143 -- tests/functional/phpunit.xml | 18 - tests/resources/patches/php-generator-7.php | 1185 ----------------- .../unit/backend/classes/AuthManagerTest.php | 4 +- .../backend/classes/NavigationManagerTest.php | 14 +- .../backend/helpers/BackendHelperTest.php | 4 +- tests/unit/backend/traits/WidgetMakerTest.php | 2 +- tests/unit/cms/classes/AssetTest.php | 119 ++ .../cms/classes/CmsCompoundObjectTest.php | 30 +- tests/unit/cms/classes/CmsObjectQueryTest.php | 2 +- tests/unit/cms/classes/CmsObjectTest.php | 33 +- tests/unit/cms/classes/CodeParserTest.php | 25 +- .../unit/cms/classes/ComponentManagerTest.php | 2 +- tests/unit/cms/classes/ControllerTest.php | 71 +- tests/unit/cms/classes/RouterTest.php | 6 +- tests/unit/cms/classes/ThemeTest.php | 11 +- .../plugins/database/AttachManyModelTest.php | 2 +- .../plugins/database/AttachOneModelTest.php | 2 +- .../database/BelongsToManyModelTest.php | 2 +- .../plugins/database/BelongsToModelTest.php | 2 +- .../plugins/database/DeferredBindingTest.php | 2 +- .../plugins/database/HasManyModelTest.php | 4 +- .../database/HasManyThroughModelTest.php | 54 + .../unit/plugins/database/HasOneModelTest.php | 2 +- .../database/HasOneThroughModelTest.php | 39 + tests/unit/plugins/database/ModelTest.php | 9 +- .../plugins/database/MorphManyModelTest.php | 2 +- .../plugins/database/MorphOneModelTest.php | 2 +- .../plugins/database/MorphToModelTest.php | 2 +- .../plugins/database/NestedTreeModelTest.php | 2 +- .../plugins/database/NullableModelTest.php | 2 +- .../database/RevisionableModelTest.php | 2 +- .../plugins/database/SimpleTreeModelTest.php | 9 +- .../plugins/database/SluggableModelTest.php | 2 +- .../plugins/database/SoftDeleteModelTest.php | 2 +- .../plugins/database/ValidationModelTest.php | 7 +- tests/unit/system/AliasesTest.php | 22 + .../system/classes/AutoDatasourceTest.php | 6 +- .../unit/system/classes/CombineAssetsTest.php | 6 +- .../unit/system/classes/MarkupManagerTest.php | 2 +- .../unit/system/classes/MediaLibraryTest.php | 4 +- .../unit/system/classes/PluginManagerTest.php | 2 +- .../system/classes/VersionManagerTest.php | 6 +- tests/unit/system/console/OctoberEnvTest.php | 61 +- tests/unit/system/traits/AssetMakerTest.php | 2 +- 118 files changed, 1381 insertions(+), 2105 deletions(-) delete mode 100644 .github/workflows/frontend-tests.yaml create mode 100644 .github/workflows/matchers/phpcs-matcher.json create mode 100644 config/hashing.php create mode 100644 config/logging.php create mode 100644 modules/backend/formwidgets/Sensitive.php create mode 100644 modules/backend/formwidgets/sensitive/assets/css/sensitive.css create mode 100644 modules/backend/formwidgets/sensitive/assets/js/sensitive.js create mode 100644 modules/backend/formwidgets/sensitive/assets/less/sensitive.less create mode 100644 modules/backend/formwidgets/sensitive/partials/_sensitive.htm delete mode 100644 tests/UiTestCase.php create mode 100644 tests/fixtures/plugins/database/tester/models/Country.php create mode 100644 tests/fixtures/plugins/database/tester/updates/create_countries_table.php create mode 100644 tests/fixtures/themes/test/assets/js/subdir/script1.js delete mode 100644 tests/functional/backend/AuthTest.php delete mode 100644 tests/functional/cms/TemplateTest.php delete mode 100644 tests/functional/phpunit.xml delete mode 100644 tests/resources/patches/php-generator-7.php create mode 100644 tests/unit/cms/classes/AssetTest.php create mode 100644 tests/unit/plugins/database/HasManyThroughModelTest.php create mode 100644 tests/unit/plugins/database/HasOneThroughModelTest.php create mode 100644 tests/unit/system/AliasesTest.php diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index 65a0f4646..af05451d1 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,25 +6,18 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index 3de3f6a97..ff209d0b5 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,23 +9,16 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHP + name: PHPCS steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP - uses: shivammathur/setup-php@master + - name: Install PHP and PHP Code Sniffer + uses: shivammathur/setup-php@v1 with: - php-version: 7.2 - - name: Install Composer dependencies - run: composer install --no-interaction --no-progress --no-suggest - - name: Reset October modules and library - run: | - git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload + php-version: '7.3' + tools: phpcs + - name: Setup problem matcher for PHPCS + run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" - name: Run code quality checks - run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml deleted file mode 100644 index 9d0068a0a..000000000 --- a/.github/workflows/frontend-tests.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - - develop - pull_request: - -jobs: - frontendTests: - runs-on: ubuntu-latest - name: JavaScript - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install Node - uses: actions/setup-node@v1 - with: - node-version: 8 - - name: Install Node dependencies - run: npm install - - name: Run tests - run: npm run test diff --git a/.github/workflows/matchers/phpcs-matcher.json b/.github/workflows/matchers/phpcs-matcher.json new file mode 100644 index 000000000..5c80b26d2 --- /dev/null +++ b/.github/workflows/matchers/phpcs-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] + } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 072b795da..a27246d97 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,37 +8,56 @@ on: pull_request: jobs: + frontendTests: + runs-on: ubuntu-latest + name: JavaScript + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install Node + uses: actions/setup-node@v1 + with: + node-version: 8 + - name: Install Node dependencies + run: npm install + - name: Run tests + run: npm run test phpUnitTests: runs-on: ubuntu-latest strategy: max-parallel: 6 matrix: - phpVersions: ['7.1', '7.2', '7.3', '7.4'] + phpVersions: ['7.2', '7.3', '7.4'] fail-fast: false - name: PHP ${{ matrix.phpVersions }} + name: Unit Tests / PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@master + uses: shivammathur/setup-php@v1 with: php-version: ${{ matrix.phpVersions }} - extension-csv: mbstring, intl, gd, xml, sqlite + extensions: mbstring, intl, gd, xml, sqlite + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + - name: Set Composer cache + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Cache Composer dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - - name: Reset October modules and library + - name: Run post-update Composer scripts + run: php artisan package:discover + - name: Reset October modules run: | git reset --hard HEAD - rm -rf ./vendor/october/rain - wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip - unzip ./vendor/october/develop.zip -d ./vendor/october - mv ./vendor/october/library-develop ./vendor/october/rain - composer dump-autoload - - name: Run post-update Composer scripts - run: | - php artisan october:util set build - php artisan package:discover + composer dumpautoload - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . - ./vendor/bin/phpunit + ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php diff --git a/.gitignore b/.gitignore index 4cd08cf9a..63a1b61d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,32 @@ -/bootstrap/compiled.php +# Composer ignores /vendor composer.phar -.DS_Store -.idea +composer.lock + +# Framework ignores .env .env.*.php .env.php +selenium.php +/bootstrap/compiled.php +.phpunit.result.cache + +# Hosting ignores php_errors.log nginx-error.log nginx-access.log nginx-ssl.access.log nginx-ssl.error.log -php-errors.log sftp-config.json .ftpconfig -selenium.php -composer.lock -package-lock.json -/node_modules + +# Editor ignores +nbproject +.idea +.vscode _ide_helper.php -# for netbeans -nbproject +# Other ignores +.DS_Store +package-lock.json +/node_modules diff --git a/artisan b/artisan index 961e94d0b..df630d0d6 100644 --- a/artisan +++ b/artisan @@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); \ No newline at end of file +exit($status); diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index b980622d7..6533c54ca 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -35,20 +35,3 @@ require $helperPath; */ require __DIR__.'/../vendor/autoload.php'; - -/* -|-------------------------------------------------------------------------- -| Include The Compiled Class File -|-------------------------------------------------------------------------- -| -| To dramatically increase your application's performance, you may use a -| compiled class file which contains all of the classes commonly used -| by a request. The Artisan "optimize" is used to create this file. -| -*/ - -$compiledPath = __DIR__.'/../storage/framework/compiled.php'; - -if (file_exists($compiledPath)) { - require $compiledPath; -} diff --git a/composer.json b/composer.json index eb4f8fe37..9542fc5a1 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "october/october", - "description": "OctoberCMS", + "description": "October CMS", "homepage": "https://octobercms.com", "type": "project", "keywords": ["october", "cms", "octobercms", "laravel"], @@ -24,37 +24,34 @@ } ], "support": { + "paid": "https://octobercms.com/premium-support", "issues": "https://github.com/octobercms/october/issues", "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", - "irc": "irc://irc.freenode.net/october", "source": "https://github.com/octobercms/october" }, "require": { - "php": ">=7.0.8", - "ext-mbstring": "*", - "ext-openssl": "*", - "october/rain": "~1.0", - "october/system": "~1.0", - "october/backend": "~1.0", - "october/cms": "~1.0", - "laravel/framework": "~5.5.40", + "php": ">=7.2", + "october/rain": "dev-develop as 1.0", + "october/system": "dev-develop", + "october/backend": "dev-develop", + "october/cms": "dev-develop", + "laravel/framework": "~6.0", "wikimedia/composer-merge-plugin": "1.4.1" }, "require-dev": { - "fzaninotto/faker": "~1.7", - "phpunit/phpunit": "~6.5", - "phpunit/phpunit-selenium": "~1.2", - "meyfa/phpunit-assert-gd": "1.1.0", + "phpunit/phpunit": "^8.0|^9.0", + "fzaninotto/faker": "~1.9", "squizlabs/php_codesniffer": "3.*", - "php-parallel-lint/php-parallel-lint": "^1.0" + "php-parallel-lint/php-parallel-lint": "^1.0", + "meyfa/phpunit-assert-gd": "^2.0.0", + "dms/phpunit-arraysubset-asserts": "^0.1.0" }, "autoload-dev": { "classmap": [ "tests/concerns/InteractsWithAuthentication.php", "tests/fixtures/backend/models/UserFixture.php", "tests/TestCase.php", - "tests/UiTestCase.php", "tests/PluginTestCase.php" ] }, @@ -66,12 +63,21 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" + ], + "test": [ + "phpunit --stop-on-failure" + ], + "lint": [ + "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." + ], + "sniff": [ + "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { "preferred-install": "dist", "platform": { - "php": "7.0.8" + "php": "7.2" } }, "minimum-stability": "dev", diff --git a/config/app.php b/config/app.php index 23aad8473..56e4959ab 100644 --- a/config/app.php +++ b/config/app.php @@ -111,21 +111,6 @@ return [ 'cipher' => 'AES-256-CBC', - /* - |-------------------------------------------------------------------------- - | Logging Configuration - |-------------------------------------------------------------------------- - | - | Here you may configure the log settings for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Settings: "single", "daily", "syslog", "errorlog" - | - */ - - 'log' => 'single', - /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -144,6 +129,26 @@ return [ 'System\ServiceProvider', ]), + /* + |-------------------------------------------------------------------------- + | Load automatically discovered packages + |-------------------------------------------------------------------------- + | + | By default, October CMS disables the loading of discovered packages + | through Laravel's package discovery service, in order to allow packages + | used by plugins to be disabled if the plugin itself is disabled. + | + | Set this to `true` to enable automatic loading of these packages. This + | will result in packages being loaded, even if the plugin using them is + | disabled. This is NOT RECOMMENDED. + | + | Please note that packages defined in `app.providers` will still be loaded + | even if discovery is disabled. + | + */ + + 'loadDiscoveredPackages' => false, + /* |-------------------------------------------------------------------------- | Class Aliases diff --git a/config/database.php b/config/database.php index 70f1420c6..a06bc7586 100644 --- a/config/database.php +++ b/config/database.php @@ -116,6 +116,7 @@ return [ 'redis' => [ + 'client' => 'predis', 'cluster' => false, 'default' => [ diff --git a/config/develop.php b/config/develop.php index cd4aee7d7..2c1cbc642 100644 --- a/config/develop.php +++ b/config/develop.php @@ -20,5 +20,27 @@ return [ */ 'decompileBackendAssets' => false, + + /* + |-------------------------------------------------------------------------- + | Allow deep-level symlinks + |-------------------------------------------------------------------------- + | + | October CMS, by default, will allow symlinks within the first level of + | subdirectories. When this feature is enabled, the system will allow + | symlinks to be used at any directory level. This can be useful for + | symlinking individual plugins or themes. + | + | Please note that this has a negative effect on performance. This feature + | abides by "cms.restrictBaseDir" - if enabled, symlinks cannot point to + | resources outside of the root folder. + | + | true - allow symlinks at any level + | + | false - only allow symlinks at the first level of subdirectories (default) + | + */ + + 'allowDeepSymlinks' => false, ]; diff --git a/config/filesystems.php b/config/filesystems.php index 4d843013c..36d9a0dfa 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -11,7 +11,7 @@ return [ | by the framework. A "local" driver, as well as a variety of cloud | based drivers are available for your choosing. Just store away! | - | Supported: "local", "s3", "rackspace" + | Supported: "local", "ftp", "sftp", "s3", "rackspace" | */ diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 000000000..842577087 --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 000000000..900d48123 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,91 @@ + env('LOG_CHANNEL', 'single'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['daily'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/system.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/system.log'), + 'level' => 'debug', + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'October CMS Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => 'debug', + 'handler' => \Monolog\Handler\SyslogUdpHandler::class, + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => \Monolog\Handler\StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php index 7c2332d2f..4c471d568 100644 --- a/config/mail.php +++ b/config/mail.php @@ -12,7 +12,7 @@ return [ | your application here. By default, Laravel is setup for SMTP mail. | | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | "postmark", "sparkpost", "log", "array" | */ diff --git a/config/services.php b/config/services.php index c2d453065..d53643b03 100644 --- a/config/services.php +++ b/config/services.php @@ -24,6 +24,10 @@ return [ 'secret' => '', ], + 'postmark' => [ + 'token' => '', + ], + 'ses' => [ 'key' => '', 'secret' => '', diff --git a/index.php b/index.php index ba43df3ec..9c4d23a06 100644 --- a/index.php +++ b/index.php @@ -37,7 +37,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make('Illuminate\Contracts\Http\Kernel'); +$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index b0a5026f8..4c3825dd0 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -80,6 +80,7 @@ class ServiceProvider extends ModuleServiceProvider $combiner->registerBundle('~/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less'); $combiner->registerBundle('~/modules/backend/formwidgets/permissioneditor/assets/less/permissioneditor.less'); $combiner->registerBundle('~/modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less'); + $combiner->registerBundle('~/modules/backend/formwidgets/sensitive/assets/less/sensitive.less'); /* * Rich Editor is protected by DRM @@ -199,6 +200,7 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist'); $manager->registerFormWidget('Backend\FormWidgets\MediaFinder', 'mediafinder'); $manager->registerFormWidget('Backend\FormWidgets\NestedForm', 'nestedform'); + $manager->registerFormWidget('Backend\FormWidgets\Sensitive', 'sensitive'); }); } diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index c146050b6..b0278ccdc 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -11,7 +11,7 @@ use Backend\Behaviors\ImportExportController\TranscodeFilter; use Illuminate\Database\Eloquent\MassAssignmentException; use League\Csv\Reader as CsvReader; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; use Exception; @@ -624,9 +624,7 @@ class ImportExportController extends ControllerBehavior $csv->setDelimiter($options['delimiter']); $csv->setEnclosure($options['enclosure']); $csv->setEscape($options['escape']); - - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers @@ -662,9 +660,6 @@ class ImportExportController extends ControllerBehavior $record[] = $value; } - // Temporary until upgrading to league/csv >= 9.1.0 - $record = $formatter($record); - $csv->insertOne($record); } diff --git a/modules/backend/classes/FormField.php b/modules/backend/classes/FormField.php index f2b6efc68..d9782a267 100644 --- a/modules/backend/classes/FormField.php +++ b/modules/backend/classes/FormField.php @@ -124,7 +124,7 @@ class FormField /** * @var string Specifies a comment to accompany the field */ - public $comment; + public $comment = ''; /** * @var string Specifies the comment position. @@ -139,7 +139,7 @@ class FormField /** * @var string Specifies a message to display when there is no value supplied (placeholder). */ - public $placeholder; + public $placeholder = ''; /** * @var array Contains a list of attributes specified in the field configuration. diff --git a/modules/backend/composer.json b/modules/backend/composer.json index 15cfd2a3f..5d4231af2 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php index 7be5f9885..78c561441 100644 --- a/modules/backend/database/seeds/DatabaseSeeder.php +++ b/modules/backend/database/seeds/DatabaseSeeder.php @@ -12,8 +12,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + Eloquent::unguarded(function () { + $this->call('Backend\Database\Seeds\SeedSetupAdmin'); + }); } } diff --git a/modules/backend/formwidgets/Sensitive.php b/modules/backend/formwidgets/Sensitive.php new file mode 100644 index 000000000..a28a8d60b --- /dev/null +++ b/modules/backend/formwidgets/Sensitive.php @@ -0,0 +1,117 @@ +fillFromConfig([ + 'readOnly', + 'disabled', + 'allowCopy', + 'hiddenPlaceholder', + 'hideOnTabChange', + ]); + + if ($this->formField->disabled || $this->formField->readOnly) { + $this->previewMode = true; + } + } + + /** + * @inheritDoc + */ + public function render() + { + $this->prepareVars(); + + return $this->makePartial('sensitive'); + } + + /** + * Prepares the view data for the widget partial. + */ + public function prepareVars() + { + $this->vars['readOnly'] = $this->readOnly; + $this->vars['disabled'] = $this->disabled; + $this->vars['hasValue'] = !empty($this->getLoadValue()); + $this->vars['allowCopy'] = $this->allowCopy; + $this->vars['hiddenPlaceholder'] = $this->hiddenPlaceholder; + $this->vars['hideOnTabChange'] = $this->hideOnTabChange; + } + + /** + * Reveals the value of a hidden, unmodified sensitive field. + * + * @return array + */ + public function onShowValue() + { + return [ + 'value' => $this->getLoadValue() + ]; + } + + /** + * @inheritDoc + */ + public function getSaveValue($value) + { + if ($value === $this->hiddenPlaceholder) { + $value = $this->getLoadValue(); + } + + return $value; + } + + /** + * @inheritDoc + */ + protected function loadAssets() + { + $this->addCss('css/sensitive.css', 'core'); + $this->addJs('js/sensitive.js', 'core'); + } +} diff --git a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css new file mode 100644 index 000000000..c8ac9378a --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css @@ -0,0 +1,2 @@ +div[data-control="sensitive"] a[data-toggle], +div[data-control="sensitive"] a[data-copy] {box-shadow:none;border:1px solid #d1d6d9;border-left:0} \ No newline at end of file diff --git a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js new file mode 100644 index 000000000..69251304c --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js @@ -0,0 +1,192 @@ +/* + * Sensitive field widget plugin. + * + * Data attributes: + * - data-control="sensitive" - enables the plugin on an element + * + * JavaScript API: + * $('div#someElement').sensitive({...}) + */ ++function ($) { "use strict"; + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var Sensitive = function(element, options) { + this.$el = $(element) + this.options = options + this.clean = Boolean(this.$el.data('clean')) + this.hidden = true + + this.$input = this.$el.find('[data-input]').first() + this.$toggle = this.$el.find('[data-toggle]').first() + this.$icon = this.$el.find('[data-icon]').first() + this.$loader = this.$el.find('[data-loader]').first() + this.$copy = this.$el.find('[data-copy]').first() + + $.oc.foundation.controlUtils.markDisposable(element) + Base.call(this) + this.init() + } + + Sensitive.DEFAULTS = { + readOnly: false, + disabled: false, + eventHandler: null, + hideOnTabChange: false, + } + + Sensitive.prototype = Object.create(BaseProto) + Sensitive.prototype.constructor = Sensitive + + Sensitive.prototype.init = function() { + this.$input.on('keydown', this.proxy(this.onInput)) + this.$toggle.on('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + // Watch for tab change or minimise + document.addEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.on('click', this.proxy(this.onCopy)) + } + } + + Sensitive.prototype.dispose = function () { + this.$input.off('keydown', this.proxy(this.onInput)) + this.$toggle.off('click', this.proxy(this.onToggle)) + + if (this.options.hideOnTabChange) { + document.removeEventListener('visibilitychange', this.proxy(this.onTabChange)) + } + + if (this.$copy.length) { + this.$copy.off('click', this.proxy(this.onCopy)) + } + + this.$input = this.$toggle = this.$icon = this.$loader = null + this.$el = null + + BaseProto.dispose.call(this) + } + + Sensitive.prototype.onInput = function() { + if (this.clean) { + this.clean = false + this.$input.val('') + } + + return true + } + + Sensitive.prototype.onToggle = function() { + if (this.$input.val() !== '' && this.clean) { + this.reveal() + } else { + this.toggleVisibility() + } + + return true + } + + Sensitive.prototype.onTabChange = function() { + if (document.hidden && !this.hidden) { + this.toggleVisibility() + } + } + + Sensitive.prototype.onCopy = function() { + var that = this, + deferred = $.Deferred(), + isHidden = this.hidden + + deferred.then(function () { + if (that.hidden) { + that.toggleVisibility() + } + + that.$input.focus() + that.$input.select() + + try { + document.execCommand('copy') + } catch (err) { + } + + that.$input.blur() + if (isHidden) { + that.toggleVisibility() + } + }) + + if (this.$input.val() !== '' && this.clean) { + this.reveal(deferred) + } else { + deferred.resolve() + } + } + + Sensitive.prototype.toggleVisibility = function() { + if (this.hidden) { + this.$input.attr('type', 'text') + } else { + this.$input.attr('type', 'password') + } + + this.$icon.toggleClass('icon-eye icon-eye-slash') + + this.hidden = !this.hidden + } + + Sensitive.prototype.reveal = function(deferred) { + var that = this + this.$icon.css({ + visibility: 'hidden' + }) + this.$loader.removeClass('hide') + + this.$input.request(this.options.eventHandler, { + success: function (data) { + that.$input.val(data.value) + that.clean = false + + that.$icon.css({ + visibility: 'visible' + }) + that.$loader.addClass('hide') + + that.toggleVisibility() + + if (deferred) { + deferred.resolve() + } + } + }) + } + + var old = $.fn.sensitive + + $.fn.sensitive = function (option) { + var args = Array.prototype.slice.call(arguments, 1), result + this.each(function () { + var $this = $(this) + var data = $this.data('oc.sensitive') + var options = $.extend({}, Sensitive.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.sensitive', (data = new Sensitive(this, options))) + if (typeof option == 'string') result = data[option].apply(data, args) + if (typeof result != 'undefined') return false + }) + + return result ? result : this + } + + $.fn.sensitive.noConflict = function () { + $.fn.sensitive = old + return this + } + + $(document).render(function () { + $('[data-control="sensitive"]').sensitive() + }); + +}(window.jQuery); diff --git a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less new file mode 100644 index 000000000..5717658f2 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less @@ -0,0 +1,10 @@ +@import "../../../../assets/less/core/boot.less"; + +div[data-control="sensitive"] { + a[data-toggle], + a[data-copy] { + box-shadow: none; + border: 1px solid @input-group-addon-border-color; + border-left: 0; + } +} diff --git a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm new file mode 100644 index 000000000..b913070d7 --- /dev/null +++ b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm @@ -0,0 +1,41 @@ +
data-hide-on-tab-change="true" +> +
+
+ previewMode): ?>disabled="disabled" + autocomplete="off" + data-input + /> + + + + + + + + +
+
+ +
+
+
diff --git a/modules/backend/models/ExportModel.php b/modules/backend/models/ExportModel.php index 1a03a813b..e6a767e80 100644 --- a/modules/backend/models/ExportModel.php +++ b/modules/backend/models/ExportModel.php @@ -5,7 +5,7 @@ use Lang; use Model; use Response; use League\Csv\Writer as CsvWriter; -use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; +use League\Csv\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; @@ -112,8 +112,7 @@ abstract class ExportModel extends Model $csv->setEscape($options['escape']); } - // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) - $formatter = new CsvEscapeFormula(); + $csv->addFormatter(new CsvEscapeFormula()); /* * Add headers @@ -128,10 +127,6 @@ abstract class ExportModel extends Model */ foreach ($results as $result) { $data = $this->matchDataToColumns($result, $columns); - - // Temporary until upgrading to league/csv >= 9.1.0 - $data = $formatter($data); - $csv->insertOne($data); } diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index 12f937a16..09c1bf426 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -5,6 +5,7 @@ use Str; use Lang; use Model; use League\Csv\Reader as CsvReader; +use League\Csv\Statement as CsvStatement; /** * Model used for importing data @@ -108,11 +109,6 @@ abstract class ImportModel extends Model */ $reader = CsvReader::createFromPath($filePath, 'r'); - // Filter out empty rows - $reader->addFilter(function (array $row) { - return count($row) > 1 || reset($row) !== null; - }); - if ($options['delimiter'] !== null) { $reader->setDelimiter($options['delimiter']); } @@ -125,15 +121,11 @@ abstract class ImportModel extends Model $reader->setEscape($options['escape']); } - if ($options['firstRowTitles']) { - $reader->setOffset(1); - } - if ( $options['encoding'] !== null && - $reader->isActiveStreamFilter() + $reader->supportsStreamFilter() ) { - $reader->appendStreamFilter(sprintf( + $reader->addStreamFilter(sprintf( '%s%s:%s', TranscodeFilter::FILTER_NAME, strtolower($options['encoding']), @@ -141,8 +133,19 @@ abstract class ImportModel extends Model )); } + // Create reader statement + $stmt = (new CsvStatement) + ->where(function (array $row) { + // Filter out empty rows + return count($row) > 1 || reset($row) !== null; + }); + + if ($options['firstRowTitles']) { + $stmt = $stmt->offset(1); + } + $result = []; - $contents = $reader->fetch(); + $contents = $stmt->process($reader); foreach ($contents as $row) { $result[] = $this->processImportRow($row, $matches); } diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 650a1378e..46259792b 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -27,8 +27,8 @@ class User extends UserBase public $rules = [ 'email' => 'required|between:6,255|email|unique:backend_users', 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|between:4,255|confirmed', - 'password_confirmation' => 'required_with:password|between:4,255' + 'password' => 'required:create|min:4|confirmed', + 'password_confirmation' => 'required_with:password|min:4' ]; /** diff --git a/modules/backend/routes.php b/modules/backend/routes.php index 81904802f..0268f7efc 100644 --- a/modules/backend/routes.php +++ b/modules/backend/routes.php @@ -25,7 +25,7 @@ App::before(function ($request) { 'middleware' => ['web'], 'prefix' => Config::get('cms.backendUri', 'backend') ], function () { - Route::any('{slug}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); + Route::any('{slug?}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); }) ; diff --git a/modules/cms/classes/Asset.php b/modules/cms/classes/Asset.php index 12b79ea35..0bfc2eba8 100644 --- a/modules/cms/classes/Asset.php +++ b/modules/cms/classes/Asset.php @@ -287,25 +287,14 @@ class Asset extends Extendable $directory = $this->theme->getPath() . '/' . $this->dirName . '/'; $filePath = $directory . $fileName; - $path = realpath($filePath); - - /** - * If the path doesn't exist yet, then create it temporarily - * in order to run realpath() resolution on it to verify the - * final destination and then remove the temporary file. - */ - if (!$path) { - touch($filePath); - $path = realpath($filePath); - unlink($filePath); - } + $resolvedPath = resolve_path($filePath); // Limit paths to those under the theme's assets directory - if (!starts_with($path, $directory)) { + if (!starts_with($resolvedPath, $directory)) { return false; } - return $path; + return $resolvedPath; } /** diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index 5705515bc..fca40ff67 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -316,7 +316,8 @@ class CmsCompoundObject extends CmsObject self::$objectComponentPropertyMap = $objectComponentMap; - Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10)); + Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt); if (array_key_exists($componentName, $objectComponentMap[$objectCode])) { return $objectComponentMap[$objectCode][$componentName]; diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index 798a065a0..714650150 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -227,7 +227,16 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->fileName; } - return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; + $directory = $this->theme->getPath() . '/' . $this->getObjectTypeDirName() . '/'; + $filePath = $directory . $fileName; + $resolvedPath = resolve_path($filePath); + + // Limit paths to those under the corresponding theme directory + if (!starts_with($resolvedPath, $directory)) { + return false; + } + + return $resolvedPath; } /** diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index ffc7afc73..2105789ac 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -1,5 +1,6 @@ filter(function ($object) use ($property, $value, $strict) { + if (empty($value) || !is_string($value)) { + throw new ApplicationException('You must provide a string value to compare with when executing a "where" ' + . 'query for CMS object collections.'); + } + if (!isset($strict) || !is_bool($strict)) { + $strict = true; + } + + return $this->filter(function ($object) use ($property, $value, $strict) { if (!array_key_exists($property, $object->settings)) { return false; } diff --git a/modules/cms/classes/CodeParser.php b/modules/cms/classes/CodeParser.php index dd6687637..b747a20e5 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -224,7 +224,8 @@ class CodeParser $cached = $this->getCachedInfo() ?: []; $cached[$this->filePath] = $cacheItem; - Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440); + $expiresAt = now()->addMinutes(1440); + Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt); self::$cache[$this->filePath] = $result; } diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 0c033fd81..4f75656f1 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -127,10 +127,11 @@ class Router : $fileName; $key = $this->getUrlListCacheKey(); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); Cache::put( $key, base64_encode(serialize($urlList)), - Config::get('cms.urlCacheTtl', 1) + $expiresAt ); } } @@ -251,7 +252,8 @@ class Router $this->urlMap = $map; if ($cacheable) { - Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1)); + $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); + Cache::put($key, base64_encode(serialize($map)), $expiresAt); } return false; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index a5306c897..6ce06a1c5 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -158,7 +158,8 @@ class Theme if ($checkDatabase && App::hasDatabase()) { try { try { - $dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () { + $expiresAt = now()->addMinutes(1440); + $dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () { return Parameter::applyKey(self::ACTIVE_KEY)->value('value'); }); } diff --git a/modules/cms/composer.json b/modules/cms/composer.json index 49e2d944b..9e4cfa70e 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/cms/routes.php b/modules/cms/routes.php index 76f35c4b3..1aaf5ec5d 100644 --- a/modules/cms/routes.php +++ b/modules/cms/routes.php @@ -22,7 +22,7 @@ App::before(function ($request) { * The CMS module intercepts all URLs that were not * handled by the back-end modules. */ - Route::any('{slug}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); + Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); /** * @event cms.route diff --git a/modules/cms/traits/UrlMaker.php b/modules/cms/traits/UrlMaker.php index 210922cfa..ebe0c050f 100644 --- a/modules/cms/traits/UrlMaker.php +++ b/modules/cms/traits/UrlMaker.php @@ -190,7 +190,8 @@ trait UrlMaker 'mtime' => @File::lastModified($filePath) ]; - Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440)); + $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440)); + Cache::put($key, serialize($cached), $expiresAt); return static::$urlPageName = $baseFileName; } diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php index 242eb23a5..f974bb8e2 100644 --- a/modules/cms/twig/DebugExtension.php +++ b/modules/cms/twig/DebugExtension.php @@ -8,7 +8,7 @@ use Cms\Classes\Controller; use Cms\Classes\ComponentBase; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; -use Illuminate\Support\Debug\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Cloner\VarCloner; use October\Rain\Database\Model; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 9fc858f39..eb5b6c613 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -94,6 +94,7 @@ class ServiceProvider extends ModuleServiceProvider } } + Paginator::useBootstrapThree(); Paginator::defaultSimpleView('system::pagination.simple-default'); /* diff --git a/modules/system/aliases.php b/modules/system/aliases.php index bcb2c78b2..d6364cd13 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,7 +16,6 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Input' => Illuminate\Support\Facades\Input::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -30,7 +29,6 @@ return [ 'Storage' => Illuminate\Support\Facades\Storage::class, 'Url' => Illuminate\Support\Facades\URL::class, // Preferred 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, /* @@ -42,6 +40,7 @@ return [ 'Config' => October\Rain\Support\Facades\Config::class, 'Seeder' => October\Rain\Database\Updates\Seeder::class, 'Flash' => October\Rain\Support\Facades\Flash::class, + 'Input' => October\Rain\Support\Facades\Input::class, 'Form' => October\Rain\Support\Facades\Form::class, 'Html' => October\Rain\Support\Facades\Html::class, 'Http' => October\Rain\Support\Facades\Http::class, @@ -52,6 +51,7 @@ return [ 'Twig' => October\Rain\Support\Facades\Twig::class, 'DbDongle' => October\Rain\Support\Facades\DbDongle::class, 'Schema' => October\Rain\Support\Facades\Schema::class, + 'Validator' => October\Rain\Support\Facades\Validator::class, 'Cms' => Cms\Facades\Cms::class, 'Backend' => Backend\Facades\Backend::class, 'BackendMenu' => Backend\Facades\BackendMenu::class, @@ -60,4 +60,12 @@ return [ 'SystemException' => October\Rain\Exception\SystemException::class, 'ApplicationException' => October\Rain\Exception\ApplicationException::class, 'ValidationException' => October\Rain\Exception\ValidationException::class, + + /* + * Fallback aliases + */ + // Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility. + 'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class, + // Illuminate's HtmlDumper was "dumped" in Laravel 6 - we'll route this to Symfony's HtmlDumper as Laravel have done. + 'Illuminate\Support\Debug\HtmlDumper' => Symfony\Component\VarDumper\Dumper\HtmlDumper::class, ]; diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php index 50a03c292..17ec7c2bd 100644 --- a/modules/system/classes/CombineAssets.php +++ b/modules/system/classes/CombineAssets.php @@ -10,11 +10,11 @@ use Route; use Config; use Request; use Response; -use Assetic\Asset\FileAsset; -use Assetic\Asset\AssetCache; -use Assetic\Asset\AssetCollection; -use Assetic\Factory\AssetFactory; -use October\Rain\Parse\Assetic\FilesystemCache; +use October\Rain\Assetic\Asset\FileAsset; +use October\Rain\Assetic\Asset\AssetCache; +use October\Rain\Assetic\Asset\AssetCollection; +use October\Rain\Assetic\Cache\FilesystemCache; +use October\Rain\Assetic\Factory\AssetFactory; use System\Helpers\Cache as CacheHelper; use ApplicationException; use DateTime; @@ -126,22 +126,22 @@ class CombineAssets /* * Register JavaScript filters */ - $this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter); /* * Register CSS filters */ - $this->registerFilter('css', new \Assetic\Filter\CssImportFilter); - $this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter); - $this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler); - $this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler); + $this->registerFilter('css', new \October\Rain\Assetic\Filter\CssImportFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\CssRewriteFilter); + $this->registerFilter('less', new \October\Rain\Assetic\Filter\LessCompiler); + $this->registerFilter('scss', new \October\Rain\Assetic\Filter\ScssCompiler); /* * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \Assetic\Filter\JSMinFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); + $this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify); } /* diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 50a6cc8f9..40808854e 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -134,10 +134,11 @@ class MediaLibrary $folderContents = $this->scanFolderContents($fullFolderPath); $cached[$fullFolderPath] = $folderContents; + $expiresAt = now()->addMinutes(Config::get('cms.storage.media.ttl', 10)); Cache::put( $this->cacheKey, base64_encode(serialize($cached)), - Config::get('cms.storage.media.ttl', 10) + $expiresAt ); } diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 7e4779de1..1b9c3618f 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -29,11 +29,6 @@ class UpdateManager { use \October\Rain\Support\Traits\Singleton; - /** - * @var array The notes for the current operation. - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -345,13 +340,13 @@ class UpdateManager /* * Rollback modules */ + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } + while (true) { $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); - foreach ($this->migrator->getNotes() as $note) { - $this->note($note); - } - if (count($rolledBack) == 0) { break; } @@ -403,13 +398,13 @@ class UpdateManager */ public function migrateModule($module) { - $this->migrator->run(base_path() . '/modules/' . strtolower($module) . '/database/migrations'); + if (isset($this->notesOutput)) { + $this->migrator->setOutput($this->notesOutput); + } $this->note($module); - foreach ($this->migrator->getNotes() as $note) { - $this->note(' - ' . $note); - } + $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); return $this; } @@ -518,13 +513,9 @@ class UpdateManager $this->note($name); - $this->versionManager->resetNotes()->setNotesOutput($this->notesOutput); + $this->versionManager->setNotesOutput($this->notesOutput); - if ($this->versionManager->updatePlugin($plugin) !== false) { - foreach ($this->versionManager->getNotes() as $note) { - $this->note($note); - } - } + $this->versionManager->updatePlugin($plugin); return $this; } @@ -713,7 +704,8 @@ class UpdateManager } $data = $this->requestServerData($type . '/popular'); - Cache::put($cacheKey, base64_encode(serialize($data)), 60); + $expiresAt = now()->addMinutes(60); + Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt); foreach ($data as $product) { $code = array_get($product, 'code', -1); @@ -802,35 +794,11 @@ class UpdateManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); - } else { - $this->notes[] = $message; } return $this; } - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; - - return $this; - } - /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index 77127cf12..11d425d9a 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -29,12 +29,6 @@ class VersionManager const HISTORY_TYPE_COMMENT = 'comment'; const HISTORY_TYPE_SCRIPT = 'script'; - /** - * The notes for the current operation. - * @var array - */ - protected $notes = []; - /** * @var \Illuminate\Console\OutputStyle */ @@ -426,6 +420,7 @@ class VersionManager * Execute the database PHP script */ $updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script; + $this->updater->packDown($updateFile); Db::table('system_plugin_history') @@ -508,35 +503,11 @@ class VersionManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); - } else { - $this->notes[] = $message; } return $this; } - /** - * Get the notes for the last operation. - * @return array - */ - public function getNotes() - { - return $this->notes; - } - - /** - * Resets the notes store. - * @return self - */ - public function resetNotes() - { - $this->notesOutput = null; - - $this->notes = []; - - return $this; - } - /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output @@ -550,8 +521,7 @@ class VersionManager } /** - * @param $details - * + * Extract script and comments from version details * @return array */ protected function extractScriptsAndComments($details): array @@ -566,7 +536,8 @@ class VersionManager $scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) { return preg_match($fileNamePattern, $detail); })); - } else { + } + else { $comments = (array)$details; $scripts = []; } diff --git a/modules/system/composer.json b/modules/system/composer.json index 5bebae7be..81d59a4dc 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -8,11 +8,13 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com" + "email": "aleksey.bobkov@gmail.com", + "role": "Co-founder" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com" + "email": "daftspunky@gmail.com", + "role": "Co-founder" }, { "name": "Luke Towers", @@ -22,9 +24,10 @@ } ], "require": { - "php": ">=7.0", + "php": ">=7.2", "composer/installers": "~1.0", - "october/rain": "~1.0" + "october/rain": "~1.0", + "laravel/framework": "~6.0" }, "autoload": { "psr-4": { diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index cc735c2c4..43f2079bf 100644 --- a/modules/system/console/OctoberEnv.php +++ b/modules/system/console/OctoberEnv.php @@ -369,7 +369,7 @@ class OctoberEnv extends Command 'SESSION_DRIVER' => 'driver', ], 'queue' => [ - 'QUEUE_DRIVER' => 'default', + 'QUEUE_CONNECTION' => 'default', ], 'mail' => [ 'MAIL_DRIVER' => 'driver', diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index b826fe305..200dcd221 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -17,7 +17,6 @@ use Symfony\Component\Console\Input\InputOption; */ class OctoberUpdate extends Command { - /** * The console command name. */ diff --git a/modules/system/controllers/Settings.php b/modules/system/controllers/Settings.php index 7ba29dea7..719dff20e 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -2,6 +2,8 @@ use Lang; use Flash; +use Config; +use Request; use Backend; use BackendMenu; use System\Classes\SettingsManager; @@ -139,6 +141,22 @@ class Settings extends Controller return $this->formWidget->render($options); } + /** + * Returns the form widget used by this behavior. + * + * @return \Backend\Widgets\Form + */ + public function formGetWidget() + { + if (is_null($this->formWidget)) { + $item = $this->findSettingItem(); + $model = $this->createModel($item); + $this->initWidgets($model); + } + + return $this->formWidget; + } + /** * Prepare the widgets used by this action * Model $model @@ -169,10 +187,22 @@ class Settings extends Controller } /** - * Locates a setting item for a module or plugin + * Locates a setting item for a module or plugin. + * + * If none of the parameters are provided, they will be auto-guessed from the URL. + * + * @param string|null $author + * @param string|null $plugin + * @param string|null $code + * + * @return array */ - protected function findSettingItem($author, $plugin, $code) + protected function findSettingItem($author = null, $plugin = null, $code = null) { + if (is_null($author) || is_null($plugin)) { + [$author, $plugin, $code] = $this->guessSettingItem(); + } + $manager = SettingsManager::instance(); $moduleOwner = $author; @@ -187,4 +217,23 @@ class Settings extends Controller return $item; } + + /** + * Guesses the requested setting item from the current URL segments provided by the Request object. + * + * @return array + */ + protected function guessSettingItem() + { + $segments = Request::segments(); + + if (!empty(Config::get('cms.backendUri', 'backend'))) { + array_splice($segments, 0, 4); + } else { + array_splice($segments, 0, 3); + } + + // Ensure there's at least 3 segments + return array_pad($segments, 3, null); + } } diff --git a/modules/system/database/seeds/DatabaseSeeder.php b/modules/system/database/seeds/DatabaseSeeder.php index f2f924b13..7dec41339 100644 --- a/modules/system/database/seeds/DatabaseSeeder.php +++ b/modules/system/database/seeds/DatabaseSeeder.php @@ -13,8 +13,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguard(); - - $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + Eloquent::unguarded(function () { + $this->call('System\Database\Seeds\SeedSetupMailLayouts'); + }); } } diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index edc036dd0..ad6561c9e 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -32,6 +32,7 @@ return [ 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', @@ -39,9 +40,22 @@ return [ 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'numeric' => 'The :attribute must be greater than :value.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'string' => 'The :attribute must be greater than :value characters.', + 'array' => 'The :attribute must have more than :value items.', + ], + 'gte' => [ + 'numeric' => 'The :attribute must be greater than or equal :value.', + 'file' => 'The :attribute must be greater than or equal :value kilobytes.', + 'string' => 'The :attribute must be greater than or equal :value characters.', + 'array' => 'The :attribute must have :value items or more.', + ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', @@ -50,6 +64,18 @@ return [ 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'numeric' => 'The :attribute must be less than :value.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'string' => 'The :attribute must be less than :value characters.', + 'array' => 'The :attribute must have less than :value items.', + ], + 'lte' => [ + 'numeric' => 'The :attribute must be less than or equal :value.', + 'file' => 'The :attribute must be less than or equal :value kilobytes.', + 'string' => 'The :attribute must be less than or equal :value characters.', + 'array' => 'The :attribute must not have more than :value items.', + ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', @@ -65,6 +91,7 @@ return [ 'array' => 'The :attribute must have at least :min items.', ], 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', @@ -82,11 +109,13 @@ return [ 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', + 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- diff --git a/modules/system/models/mailsetting/fields.yaml b/modules/system/models/mailsetting/fields.yaml index c2457ec38..d49851cba 100644 --- a/modules/system/models/mailsetting/fields.yaml +++ b/modules/system/models/mailsetting/fields.yaml @@ -79,6 +79,7 @@ tabs: smtp_password: label: system::lang.mail.smtp_password tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -107,6 +108,7 @@ tabs: label: system::lang.mail.mailgun_secret commentAbove: system::lang.mail.mailgun_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -116,6 +118,7 @@ tabs: label: system::lang.mail.mandrill_secret commentAbove: system::lang.mail.mandrill_secret_comment tab: system::lang.mail.general + type: sensitive trigger: action: show field: send_mode @@ -135,6 +138,7 @@ tabs: label: system::lang.mail.ses_secret commentAbove: system::lang.mail.ses_secret_comment tab: system::lang.mail.general + type: sensitive span: right trigger: action: show @@ -154,6 +158,7 @@ tabs: sparkpost_secret: label: system::lang.mail.sparkpost_secret commentAbove: system::lang.mail.sparkpost_secret_comment + type: sensitive tab: system::lang.mail.general trigger: action: show diff --git a/modules/system/providers.php b/modules/system/providers.php index 7b63edb43..96951f747 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -15,9 +15,7 @@ return [ Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, @@ -36,5 +34,7 @@ return [ October\Rain\Flash\FlashServiceProvider::class, October\Rain\Mail\MailServiceProvider::class, October\Rain\Argon\ArgonServiceProvider::class, + October\Rain\Redis\RedisServiceProvider::class, + October\Rain\Validation\ValidationServiceProvider::class, ]; diff --git a/phpunit.xml b/phpunit.xml index 08cd19d58..de01655c0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" > diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 6618b17e0..1d4a66f00 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -28,7 +28,7 @@ abstract class PluginTestCase extends TestCase $app['cache']->setDefaultDriver('array'); $app->setLocale('en'); - $app->singleton('auth', function ($app) { + $app->singleton('backend.auth', function ($app) { $app['auth.loaded'] = true; return AuthManager::instance(); @@ -67,7 +67,7 @@ abstract class PluginTestCase extends TestCase * Perform test case set up. * @return void */ - public function setUp() + public function setUp() : void { /* * Force reload of October singletons @@ -105,7 +105,7 @@ abstract class PluginTestCase extends TestCase * Flush event listeners and collect garbage. * @return void */ - public function tearDown() + public function tearDown() : void { $this->flushModelEventListeners(); parent::tearDown(); diff --git a/tests/README.md b/tests/README.md index a9e7b78b5..0e985d79b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Plugin unit tests can be performed by running `phpunit` in the base plugin directory. +Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`. ### Creating plugin tests @@ -58,7 +58,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci class BaseTestCase extends PluginTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); @@ -72,7 +72,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci $pluginManager->bootAll(true); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); @@ -96,39 +96,10 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests -Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. +Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation. ### Functional tests -Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: +Functional tests can be performed by installing the [RainLab Dusk](https://octobercms.com/plugin/rainlab-dusk) in your October CMS installation. The RainLab Dusk plugin is powered by Laravel Dusk, a comprehensive testing suite for the Laravel framework that is designed to test interactions with a fully operational October CMS instance through a virtual browser. -- Active theme is `demo` -- Language preference is `en` - -#### Selenium set up - -1. Download latest Java SE from http://java.sun.com/ and install -1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). -1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. -1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. - -#### Selenium configuration - -Create a new file `selenium.php` in the root directory, add the following content: - - markTestSkipped('Selenium skipped'); - } - - if (defined('TEST_SELENIUM_HOST')) { - $this->setHost(TEST_SELENIUM_HOST); - } - if (defined('TEST_SELENIUM_PORT')) { - $this->setPort(TEST_SELENIUM_PORT); - } - if (defined('TEST_SELENIUM_BROWSER')) { - $this->setBrowser(TEST_SELENIUM_BROWSER); - } - $this->setBrowserUrl(TEST_SELENIUM_URL); - } - - // - // OctoberCMS Helpers - // - - protected function signInToBackend() - { - $this->open('backend'); - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - } - - /** - * Similar to the native getConfirmation() function - */ - protected function getSweetConfirmation($expectedText = null, $clickOk = true) - { - $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); - - if ($expectedText) { - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); - } - - $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); - - if ($clickOk) { - $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); - } - } - - // - // Selenium helpers - // - - protected function waitForElementPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if ($this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } - - protected function waitForElementNotPresent($target, $timeout = 60) - { - $second = 0; - - while (true) { - if ($second >= $timeout) { - $this->fail('timeout'); - } - - try { - if (!$this->isElementPresent($target)) { - break; - } - } - catch (Exception $e) { - } - - sleep(1); - ++$second; - } - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e7e53fd1a..5ed3e1296 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,14 +19,3 @@ $loader->addDirectories([ 'modules', 'plugins' ]); - -/* - * Monkey patch PHPUnit\Framework\MockObject\Generator to avoid - * "Function ReflectionType::__toString() is deprecated" warnings - */ -$generatorPatchPath = __DIR__ . '/resources/patches/php-generator-7.php'; -$generatorSourcePath = __DIR__ . '/../vendor/phpunit/phpunit-mock-objects/src/Generator.php'; - -if (file_exists($generatorSourcePath)) { - file_put_contents($generatorSourcePath, file_get_contents($generatorPatchPath)); -} diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/concerns/InteractsWithAuthentication.php index a7950f230..5f5c3c8c7 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/concerns/InteractsWithAuthentication.php @@ -29,7 +29,7 @@ trait InteractsWithAuthentication */ public function be(UserContract $user, $driver = null) { - $this->app['auth']->setUser($user); + $this->app['backend.auth']->setUser($user); } /** @@ -66,7 +66,7 @@ trait InteractsWithAuthentication */ protected function isAuthenticated($guard = null) { - return $this->app->make('auth')->guard($guard)->check(); + return $this->app->make('backend.auth')->guard($guard)->check(); } /** @@ -78,7 +78,7 @@ trait InteractsWithAuthentication */ public function assertAuthenticatedAs($user, $guard = null) { - $expected = $this->app->make('auth')->guard($guard)->user(); + $expected = $this->app->make('backend.auth')->guard($guard)->user(); $this->assertNotNull($expected, 'The current user is not authenticated.'); @@ -140,7 +140,7 @@ trait InteractsWithAuthentication */ protected function hasCredentials(array $credentials, $guard = null) { - $provider = $this->app->make('auth')->guard($guard)->getProvider(); + $provider = $this->app->make('backend.auth')->guard($guard)->getProvider(); $user = $provider->retrieveByCredentials($credentials); diff --git a/tests/fixtures/plugins/database/tester/models/Author.php b/tests/fixtures/plugins/database/tester/models/Author.php index 05e9a039f..2d9656fee 100644 --- a/tests/fixtures/plugins/database/tester/models/Author.php +++ b/tests/fixtures/plugins/database/tester/models/Author.php @@ -20,6 +20,7 @@ class Author extends Model */ public $belongsTo = [ 'user' => ['Database\Tester\Models\User', 'delete' => true], + 'country' => ['Database\Tester\Models\Country'], 'user_soft' => ['Database\Tester\Models\SoftDeleteUser', 'key' => 'user_id', 'softDelete' => true], ]; diff --git a/tests/fixtures/plugins/database/tester/models/Country.php b/tests/fixtures/plugins/database/tester/models/Country.php new file mode 100644 index 000000000..e7bb583c5 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/models/Country.php @@ -0,0 +1,35 @@ + [ + 'Database\Tester\Models\User', + ], + ]; + + public $hasManyThrough = [ + 'posts' => [ + 'Database\Tester\Models\Post', + 'through' => 'Database\Tester\Models\Author', + ] + ]; +} + +class SoftDeleteCountry extends Country +{ + use \October\Rain\Database\Traits\SoftDelete; +} diff --git a/tests/fixtures/plugins/database/tester/models/User.php b/tests/fixtures/plugins/database/tester/models/User.php index 867992c2c..130120e9c 100644 --- a/tests/fixtures/plugins/database/tester/models/User.php +++ b/tests/fixtures/plugins/database/tester/models/User.php @@ -17,6 +17,19 @@ class User extends Model /** * @var array Relations */ + public $hasOne = [ + 'author' => [ + 'Database\Tester\Models\Author', + ] + ]; + + public $hasOneThrough = [ + 'phone' => [ + 'Database\Tester\Models\Phone', + 'through' => 'Database\Tester\Models\Author', + ], + ]; + public $attachOne = [ 'avatar' => 'System\Models\File' ]; diff --git a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php index 6466e4e13..d6277e525 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php @@ -11,6 +11,7 @@ class CreateAuthorsTable extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->integer('user_id')->unsigned()->index()->nullable(); + $table->integer('country_id')->unsigned()->index()->nullable(); $table->string('name')->nullable(); $table->string('email')->nullable(); $table->softDeletes(); diff --git a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php new file mode 100644 index 000000000..7fc85ae54 --- /dev/null +++ b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php @@ -0,0 +1,23 @@ +engine = 'InnoDB'; + $table->increments('id'); + $table->string('name')->nullable(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('database_tester_countries'); + } +} diff --git a/tests/fixtures/plugins/database/tester/updates/version.yaml b/tests/fixtures/plugins/database/tester/updates/version.yaml index 613fc5337..590bb7892 100644 --- a/tests/fixtures/plugins/database/tester/updates/version.yaml +++ b/tests/fixtures/plugins/database/tester/updates/version.yaml @@ -9,3 +9,4 @@ - create_users_table.php - create_event_log_table.php - create_meta_table.php + - create_countries_table.php diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index e69de29bb..b8bbe4e13 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -0,0 +1 @@ +console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index e69de29bb..54e00dea3 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -0,0 +1 @@ +console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js new file mode 100644 index 000000000..bc27e099c --- /dev/null +++ b/tests/fixtures/themes/test/assets/js/subdir/script1.js @@ -0,0 +1 @@ +console.log('subdir/script1.js'); diff --git a/tests/functional/backend/AuthTest.php b/tests/functional/backend/AuthTest.php deleted file mode 100644 index 4c1fe925d..000000000 --- a/tests/functional/backend/AuthTest.php +++ /dev/null @@ -1,90 +0,0 @@ -open('backend'); - - $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("name=login")); - $this->assertTrue($this->isElementPresent("name=password")); - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Login"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - /* - * Sign in - */ - $this->type("name=login", TEST_SELENIUM_USER); - $this->type("name=password", TEST_SELENIUM_PASS); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Dashboard | October CMS'); - $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->verifyText('css='.$cssLogoutLink, "Sign out"); - - /* - * Log out - */ - $this->click('css='.$cssLogoutLink); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } - - public function testPasswordReset() - { - $this->open('backend'); - - try { - $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->click('link=exact:Forgot your password?'); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTrue($this->isElementPresent("//button[@type='submit']")); - $this->verifyText("//button[@type='submit']", "Restore"); - $this->assertTrue($this->isElementPresent("link=Cancel")); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - - $this->type("name=login", TEST_SELENIUM_USER); - sleep(1); - $this->click("//button[@type='submit']"); - $this->waitForPageToLoad("30000"); - - try { - $this->assertTitle('Administration Area'); - $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); - $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - array_push($this->verificationErrors, $e->toString()); - } - } -} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php deleted file mode 100644 index 5ff2a0a9e..000000000 --- a/tests/functional/cms/TemplateTest.php +++ /dev/null @@ -1,143 +0,0 @@ -signInToBackend(); - $this->open('cms'); - $this->waitForPageToLoad("30000"); - - // Fix the sidebar - $this->click("xpath=(//a[@class='fix-button'])[1]"); - - /* - * Page - */ - - // Create a new page - $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=settings[title]"); - - // Populate page details - $this->type('name=settings[title]', 'Functional Test Page'); - $this->type('name=settings[url]', '/xxx/functional/test/page'); - $this->type('name=fileName', 'xxx_functional_test_page'); - - // Save the new page - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); - $this->waitForElementPresent("name=settings[title]"); - sleep(1); - - // Delete the page - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this page?'); - // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); - $this->waitForElementNotPresent("name=settings[title]"); - - /* - * Partial - */ - - // Click partials menu item - $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); - - // Create a new partial - $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate partial details - $this->type('name=fileName', 'xxx_functional_test_partial'); - $this->type('name=settings[description]', 'Test partial'); - - // Save the new partial - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the partial - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this partial?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Layout - */ - - // Click layouts menu item - $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); - - // Create a new layout - $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate layout details - $this->type('name=fileName', 'xxx_functional_test_layout'); - $this->type('name=settings[description]', 'Test layout'); - - // Save the new layout - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the layout - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this layout?'); - $this->waitForElementNotPresent("name=fileName"); - - /* - * Content - */ - - // Click contents menu item - $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); - - // Create a new content - $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); - $this->waitForElementPresent("name=fileName"); - - // Populate content details - $this->type('name=fileName', 'xxx_functional_test_content.txt'); - - // Save the new content - $this->click("xpath=(//a[@data-request='onSave'])[1]"); - $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); - - // Close the tab - $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); - - // Reopen the tab - $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); - $this->waitForElementPresent("name=fileName"); - sleep(1); - - // Delete the content - $this->click("xpath=(//button[@data-request='onDelete'])[1]"); - $this->getSweetConfirmation('Do you really want delete this content file?'); - $this->waitForElementNotPresent("name=fileName"); - } -} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml deleted file mode 100644 index c8043149c..000000000 --- a/tests/functional/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - ./ - - - \ No newline at end of file diff --git a/tests/resources/patches/php-generator-7.php b/tests/resources/patches/php-generator-7.php deleted file mode 100644 index 677680d69..000000000 --- a/tests/resources/patches/php-generator-7.php +++ /dev/null @@ -1,1185 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - * Patched with: https://github.com/sebastianbergmann/phpunit/pull/3765/files - */ -namespace PHPUnit\Framework\MockObject; - -use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; -use Doctrine\Instantiator\Instantiator; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\Exception; -use PHPUnit\Util\InvalidArgumentHelper; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; -use SoapClient; -use Text_Template; -use Traversable; - -/** - * Mock Object Code Generator - */ -class Generator -{ - /** - * @var array - */ - private static $cache = []; - - /** - * @var Text_Template[] - */ - private static $templates = []; - - /** - * @var array - */ - private $blacklistedMethodNames = [ - '__CLASS__' => true, - '__DIR__' => true, - '__FILE__' => true, - '__FUNCTION__' => true, - '__LINE__' => true, - '__METHOD__' => true, - '__NAMESPACE__' => true, - '__TRAIT__' => true, - '__clone' => true, - '__halt_compiler' => true, - ]; - - /** - * Returns a mock object for the specified class. - * - * @param string|string[] $type - * @param array $methods - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * @param bool $allowMockingUnknownTypes - * - * @return MockObject - * - * @throws Exception - * @throws RuntimeException - * @throws \PHPUnit\Framework\Exception - * @throws \ReflectionException - */ - public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true) - { - if (!\is_array($type) && !\is_string($type)) { - throw InvalidArgumentHelper::factory(1, 'array or string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(4, 'string'); - } - - if (!\is_array($methods) && null !== $methods) { - throw InvalidArgumentHelper::factory(2, 'array', $methods); - } - - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - - if (\is_array($type)) { - $type = \array_unique( - \array_map( - function ($type) { - if ($type === 'Traversable' || - $type === '\\Traversable' || - $type === '\\Iterator') { - return 'Iterator'; - } - - return $type; - }, - $type - ) - ); - } - - if (!$allowMockingUnknownTypes) { - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\class_exists($_type, $callAutoload) && - !\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $_type - ) - ); - } - } - } else { - if (!\class_exists($type, $callAutoload) && - !\interface_exists($type, $callAutoload) - ) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock class or interface "%s" which does not exist', - $type - ) - ); - } - } - } - - if (null !== $methods) { - foreach ($methods as $method) { - if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock method with invalid name "%s"', - $method - ) - ); - } - } - - if ($methods !== \array_unique($methods)) { - throw new RuntimeException( - \sprintf( - 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', - \implode(', ', $methods), - \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods)))) - ) - ); - } - } - - if ($mockClassName !== '' && \class_exists($mockClassName, false)) { - $reflect = new ReflectionClass($mockClassName); - - if (!$reflect->implementsInterface(MockObject::class)) { - throw new RuntimeException( - \sprintf( - 'Class "%s" already exists.', - $mockClassName - ) - ); - } - } - - if ($callOriginalConstructor === false && $callOriginalMethods === true) { - throw new RuntimeException( - 'Proxying to original methods requires invoking the original constructor' - ); - } - - $mock = $this->generate( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - return $this->getObject( - $mock['code'], - $mock['mockClassName'], - $type, - $callOriginalConstructor, - $callAutoload, - $arguments, - $callOriginalMethods, - $proxyTarget - ); - } - - /** - * @param string $code - * @param string $className - * @param array|string $type - * @param bool $callOriginalConstructor - * @param bool $callAutoload - * @param array $arguments - * @param bool $callOriginalMethods - * @param object $proxyTarget - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - */ - private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null) - { - $this->evalClass($code, $className); - - if ($callOriginalConstructor && - \is_string($type) && - !\interface_exists($type, $callAutoload)) { - if (\count($arguments) === 0) { - $object = new $className; - } else { - $class = new ReflectionClass($className); - $object = $class->newInstanceArgs($arguments); - } - } else { - try { - $instantiator = new Instantiator; - $object = $instantiator->instantiate($className); - } catch (InstantiatorException $exception) { - throw new RuntimeException($exception->getMessage()); - } - } - - if ($callOriginalMethods) { - if (!\is_object($proxyTarget)) { - if (\count($arguments) === 0) { - $proxyTarget = new $type; - } else { - $class = new ReflectionClass($type); - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - - $object->__phpunit_setOriginalObject($proxyTarget); - } - - return $object; - } - - /** - * @param string $code - * @param string $className - */ - private function evalClass($code, $className) - { - if (!\class_exists($className, false)) { - eval($code); - } - } - - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods to mock can be specified with - * the last parameter - * - * @param string $originalClassName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($originalClassName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (\class_exists($originalClassName, $callAutoload) || - \interface_exists($originalClassName, $callAutoload)) { - $reflector = new ReflectionClass($originalClassName); - $methods = $mockedMethods; - - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !\in_array($method->getName(), $methods)) { - $methods[] = $method->getName(); - } - } - - if (empty($methods)) { - $methods = null; - } - - return $this->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments - ); - } - - throw new RuntimeException( - \sprintf('Class "%s" does not exist.', $originalClassName) - ); - } - - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @param string $traitName - * @param array $arguments - * @param string $mockClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param array $mockedMethods - * @param bool $cloneArguments - * - * @return MockObject - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($mockClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - '', - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => 'abstract ', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - $this->evalClass( - $classTemplate->render(), - $className['className'] - ); - - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - } - - /** - * Returns an object for the specified trait. - * - * @param string $traitName - * @param array $arguments - * @param string $traitClassName - * @param bool $callOriginalConstructor - * @param bool $callOriginalClone - * @param bool $callAutoload - * - * @return object - * - * @throws \ReflectionException - * @throws RuntimeException - * @throws Exception - */ - public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) - { - if (!\is_string($traitName)) { - throw InvalidArgumentHelper::factory(1, 'string'); - } - - if (!\is_string($traitClassName)) { - throw InvalidArgumentHelper::factory(3, 'string'); - } - - if (!\trait_exists($traitName, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Trait "%s" does not exist.', - $traitName - ) - ); - } - - $className = $this->generateClassName( - $traitName, - $traitClassName, - 'Trait_' - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => '', - 'class_name' => $className['className'], - 'trait_name' => $traitName - ] - ); - - return $this->getObject($classTemplate->render(), $className['className']); - } - - /** - * @param array|string $type - * @param array $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \ReflectionException - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) - { - if (\is_array($type)) { - \sort($type); - } - - if ($mockClassName === '') { - $key = \md5( - \is_array($type) ? \implode('_', $type) : $type . - \serialize($methods) . - \serialize($callOriginalClone) . - \serialize($cloneArguments) . - \serialize($callOriginalMethods) - ); - - if (isset(self::$cache[$key])) { - return self::$cache[$key]; - } - } - - $mock = $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods - ); - - if (isset($key)) { - self::$cache[$key] = $mock; - } - - return $mock; - } - - /** - * @param string $wsdlFile - * @param string $className - * @param array $methods - * @param array $options - * - * @return string - * - * @throws RuntimeException - */ - public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = []) - { - if (!\extension_loaded('soap')) { - throw new RuntimeException( - 'The SOAP extension is required to generate a mock object from WSDL.' - ); - } - - $options = \array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); - $client = new SoapClient($wsdlFile, $options); - $_methods = \array_unique($client->__getFunctions()); - unset($client); - - \sort($_methods); - - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - - foreach ($_methods as $method) { - $nameStart = \strpos($method, ' ') + 1; - $nameEnd = \strpos($method, '('); - $name = \substr($method, $nameStart, $nameEnd - $nameStart); - - if (empty($methods) || \in_array($name, $methods)) { - $args = \explode( - ',', - \substr( - $method, - $nameEnd + 1, - \strpos($method, ')') - $nameEnd - 1 - ) - ); - - foreach (\range(0, \count($args) - 1) as $i) { - $args[$i] = \substr($args[$i], \strpos($args[$i], '$')); - } - - $methodTemplate->setVar( - [ - 'method_name' => $name, - 'arguments' => \implode(', ', $args) - ] - ); - - $methodsBuffer .= $methodTemplate->render(); - } - } - - $optionsBuffer = 'array('; - - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - - $optionsBuffer .= ')'; - - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - - if (\strpos($className, '\\') !== false) { - $parts = \explode('\\', $className); - $className = \array_pop($parts); - $namespace = 'namespace ' . \implode('\\', $parts) . ';' . "\n\n"; - } - - $classTemplate->setVar( - [ - 'namespace' => $namespace, - 'class_name' => $className, - 'wsdl' => $wsdlFile, - 'options' => $optionsBuffer, - 'methods' => $methodsBuffer - ] - ); - - return $classTemplate->render(); - } - - /** - * @param array|string $type - * @param array|null $methods - * @param string $mockClassName - * @param bool $callOriginalClone - * @param bool $callAutoload - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return array - * - * @throws \InvalidArgumentException - * @throws \ReflectionException - * @throws RuntimeException - */ - private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) - { - $methodReflections = []; - $classTemplate = $this->getTemplate('mocked_class.tpl'); - - $additionalInterfaces = []; - $cloneTemplate = ''; - $isClass = false; - $isInterface = false; - $isMultipleInterfaces = false; - - if (\is_array($type)) { - foreach ($type as $_type) { - if (!\interface_exists($_type, $callAutoload)) { - throw new RuntimeException( - \sprintf( - 'Interface "%s" does not exist.', - $_type - ) - ); - } - - $isMultipleInterfaces = true; - - $additionalInterfaces[] = $_type; - $typeClass = new ReflectionClass($this->generateClassName( - $_type, - $mockClassName, - 'Mock_' - )['fullClassName'] - ); - - foreach ($this->getClassMethods($_type) as $method) { - if (\in_array($method, $methods)) { - throw new RuntimeException( - \sprintf( - 'Duplicate method "%s" not allowed.', - $method - ) - ); - } - - $methodReflections[$method] = $typeClass->getMethod($method); - $methods[] = $method; - } - } - } - - $mockClassName = $this->generateClassName( - $type, - $mockClassName, - 'Mock_' - ); - - if (\class_exists($mockClassName['fullClassName'], $callAutoload)) { - $isClass = true; - } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) { - $isInterface = true; - } - - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; - - if (!empty($mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $mockClassName['namespaceName'] . - " {\n\n" . $prologue . "}\n\n" . - "namespace {\n\n"; - - $epilogue = "\n\n}"; - } - - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } else { - $class = new ReflectionClass($mockClassName['fullClassName']); - - if ($class->isFinal()) { - throw new RuntimeException( - \sprintf( - 'Class "%s" is declared "final" and cannot be mocked.', - $mockClassName['fullClassName'] - ) - ); - } - - if ($class->hasMethod('__clone')) { - $cloneMethod = $class->getMethod('__clone'); - - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $cloneTemplate = $this->getTemplate('unmocked_clone.tpl'); - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - } else { - $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); - } - } - - if (\is_object($cloneTemplate)) { - $cloneTemplate = $cloneTemplate->render(); - } - - if (\is_array($methods) && empty($methods) && - ($isClass || $isInterface)) { - $methods = $this->getClassMethods($mockClassName['fullClassName']); - } - - if (!\is_array($methods)) { - $methods = []; - } - - $mockedMethods = ''; - $configurable = []; - - foreach ($methods as $methodName) { - if ($methodName !== '__construct' && $methodName !== '__clone') { - $configurable[] = \strtolower($methodName); - } - } - - if (isset($class)) { - // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && - !$class->implementsInterface(Iterator::class) && - !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - $methods = \array_merge($methods, $this->getClassMethods(Iterator::class)); - } - - foreach ($methods as $methodName) { - try { - $method = $class->getMethod($methodName); - - if ($this->canMockMethod($method)) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $method, - $cloneArguments, - $callOriginalMethods - ); - } - } catch (ReflectionException $e) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - } elseif ($isMultipleInterfaces) { - foreach ($methods as $methodName) { - if ($this->canMockMethod($methodReflections[$methodName])) { - $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( - $methodReflections[$methodName], - $cloneArguments, - $callOriginalMethods - ); - } - } - } else { - foreach ($methods as $methodName) { - $mockedMethods .= $this->generateMockedMethodDefinition( - $mockClassName['fullClassName'], - $methodName, - $cloneArguments - ); - } - } - - $method = ''; - - if (!\in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) { - $methodTemplate = $this->getTemplate('mocked_class_method.tpl'); - - $method = $methodTemplate->render(); - } - - $classTemplate->setVar( - [ - 'prologue' => $prologue ?? '', - 'epilogue' => $epilogue ?? '', - 'class_declaration' => $this->generateMockClassDeclaration( - $mockClassName, - $isInterface, - $additionalInterfaces - ), - 'clone' => $cloneTemplate, - 'mock_class_name' => $mockClassName['className'], - 'mocked_methods' => $mockedMethods, - 'method' => $method, - 'configurable' => '[' . \implode(', ', \array_map(function ($m) { - return '\'' . $m . '\''; - }, $configurable)) . ']' - ] - ); - - return [ - 'code' => $classTemplate->render(), - 'mockClassName' => $mockClassName['className'] - ]; - } - - /** - * @param array|string $type - * @param string $className - * @param string $prefix - * - * @return array - */ - private function generateClassName($type, $className, $prefix) - { - if (\is_array($type)) { - $type = \implode('_', $type); - } - - if ($type[0] === '\\') { - $type = \substr($type, 1); - } - - $classNameParts = \explode('\\', $type); - - if (\count($classNameParts) > 1) { - $type = \array_pop($classNameParts); - $namespaceName = \implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; - } else { - $namespaceName = ''; - $fullClassName = $type; - } - - if ($className === '') { - do { - $className = $prefix . $type . '_' . - \substr(\md5(\mt_rand()), 0, 8); - } while (\class_exists($className, false)); - } - - return [ - 'className' => $className, - 'originalClassName' => $type, - 'fullClassName' => $fullClassName, - 'namespaceName' => $namespaceName - ]; - } - - /** - * @param array $mockClassName - * @param bool $isInterface - * @param array $additionalInterfaces - * - * @return string - */ - private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = []) - { - $buffer = 'class '; - - $additionalInterfaces[] = MockObject::class; - $interfaces = \implode(', ', $additionalInterfaces); - - if ($isInterface) { - $buffer .= \sprintf( - '%s implements %s', - $mockClassName['className'], - $interfaces - ); - - if (!\in_array($mockClassName['originalClassName'], $additionalInterfaces)) { - $buffer .= ', '; - - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - - $buffer .= $mockClassName['originalClassName']; - } - } else { - $buffer .= \sprintf( - '%s extends %s%s implements %s', - $mockClassName['className'], - !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', - $mockClassName['originalClassName'], - $interfaces - ); - } - - return $buffer; - } - - /** - * @param ReflectionMethod $method - * @param bool $cloneArguments - * @param bool $callOriginalMethods - * - * @return string - * - * @throws \PHPUnit\Framework\MockObject\RuntimeException - */ - private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods) - { - if ($method->isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - - if ($method->isStatic()) { - $modifier .= ' static'; - } - - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - - if ($method->hasReturnType()) { - $returnType = $method->getReturnType()->getName(); - } else { - $returnType = ''; - } - - if (\preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) { - $deprecation = \trim(\preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = false; - } - - return $this->generateMockedMethodDefinition( - $method->getDeclaringClass()->getName(), - $method->getName(), - $cloneArguments, - $modifier, - $this->getMethodParameters($method), - $this->getMethodParameters($method, true), - $returnType, - $reference, - $callOriginalMethods, - $method->isStatic(), - $deprecation, - $method->hasReturnType() && PHP_VERSION_ID >= 70100 && $method->getReturnType()->allowsNull() - ); - } - - /** - * @param string $className - * @param string $methodName - * @param bool $cloneArguments - * @param string $modifier - * @param string $argumentsForDeclaration - * @param string $argumentsForCall - * @param string $returnType - * @param string $reference - * @param bool $callOriginalMethods - * @param bool $static - * @param bool|string $deprecation - * @param bool $allowsReturnNull - * - * @return string - * - * @throws \InvalidArgumentException - */ - private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false) - { - if ($static) { - $templateFile = 'mocked_static_method.tpl'; - } else { - if ($returnType === 'void') { - $templateFile = \sprintf( - '%s_method_void.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } else { - $templateFile = \sprintf( - '%s_method.tpl', - $callOriginalMethods ? 'proxied' : 'mocked' - ); - } - } - - // Mocked interfaces returning 'self' must explicitly declare the - // interface name as the return type. See - // https://bugs.php.net/bug.php?id=70722 - if ($returnType === 'self') { - $returnType = $className; - } - - if (false !== $deprecation) { - $deprecation = "The $className::$methodName method is deprecated ($deprecation)."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - - $deprecationTemplate->setVar( - [ - 'deprecation' => \var_export($deprecation, true), - ] - ); - - $deprecation = $deprecationTemplate->render(); - } - - $template = $this->getTemplate($templateFile); - - $template->setVar( - [ - 'arguments_decl' => $argumentsForDeclaration, - 'arguments_call' => $argumentsForCall, - 'return_delim' => $returnType ? ': ' : '', - 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType, - 'arguments_count' => !empty($argumentsForCall) ? \substr_count($argumentsForCall, ',') + 1 : 0, - 'class_name' => $className, - 'method_name' => $methodName, - 'modifier' => $modifier, - 'reference' => $reference, - 'clone_arguments' => $cloneArguments ? 'true' : 'false', - 'deprecation' => $deprecation - ] - ); - - return $template->render(); - } - - /** - * @param ReflectionMethod $method - * - * @return bool - * - * @throws \ReflectionException - */ - private function canMockMethod(ReflectionMethod $method) - { - return !($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())); - } - - /** - * Returns whether a method name is blacklisted - * - * @param string $name - * - * @return bool - */ - private function isMethodNameBlacklisted($name) - { - return isset($this->blacklistedMethodNames[$name]); - } - - /** - * Returns the parameters of a function or method. - * - * @param ReflectionMethod $method - * @param bool $forCall - * - * @return string - * - * @throws RuntimeException - */ - private function getMethodParameters(ReflectionMethod $method, $forCall = false) - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - if ($parameter->isVariadic()) { - if ($forCall) { - continue; - } - - $name = '...' . $name; - } - - $nullable = ''; - $default = ''; - $reference = ''; - $typeDeclaration = ''; - - if (!$forCall) { - if (PHP_VERSION_ID >= 70100 && $parameter->hasType() && $parameter->allowsNull()) { - $nullable = '?'; - } - - if ($parameter->hasType() && $parameter->getType()->getName() !== 'self') { - $typeDeclaration = $parameter->getType()->getName() . ' '; - } elseif ($parameter->isArray()) { - $typeDeclaration = 'array '; - } elseif ($parameter->isCallable()) { - $typeDeclaration = 'callable '; - } else { - try { - $class = $parameter->getClass(); - } catch (ReflectionException $e) { - throw new RuntimeException( - \sprintf( - 'Cannot mock %s::%s() because a class or ' . - 'interface used in the signature is not loaded', - $method->getDeclaringClass()->getName(), - $method->getName() - ), - 0, - $e - ); - } - - if ($class !== null) { - $typeDeclaration = $class->getName() . ' '; - } - } - - if (!$parameter->isVariadic()) { - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValueConstantName(); - - if ($value === null) { - $value = \var_export($parameter->getDefaultValue(), true); - } elseif (!\defined($value)) { - $rootValue = \preg_replace('/^.*\\\\/', '', $value); - $value = \defined($rootValue) ? $rootValue : $value; - } - - $default = ' = ' . $value; - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - } - } - - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - - $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; - } - - return \implode(', ', $parameters); - } - - /** - * @param string $className - * - * @return array - * - * @throws \ReflectionException - */ - public function getClassMethods($className) - { - $class = new ReflectionClass($className); - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } - - /** - * @param string $template - * - * @return Text_Template - * - * @throws \InvalidArgumentException - */ - private function getTemplate($template) - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - self::$templates[$filename] = new Text_Template($filename); - } - - return self::$templates[$filename]; - } -} diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index 6bb9f9684..65dd5e735 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -4,7 +4,7 @@ use October\Rain\Exception\SystemException; class AuthManagerTest extends TestCase { - public function setUp() + public function setUp(): void { $this->createApplication(); @@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase ]); } - public function tearDown() + public function tearDown(): void { AuthManager::forgetInstance(); } diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index 04ca2f183..f63645198 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -59,18 +59,18 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('posts', $items); $this->assertArrayHasKey('categories', $items); - $this->assertInternalType('object', $items['posts']); + $this->assertIsObject($items['posts']); $this->assertObjectHasAttribute('code', $items['posts']); $this->assertObjectHasAttribute('owner', $items['posts']); $this->assertEquals('posts', $items['posts']->code); $this->assertEquals('October.Tester', $items['posts']->owner); $this->assertObjectHasAttribute('permissions', $items['posts']); - $this->assertInternalType('array', $items['posts']->permissions); + $this->assertIsArray($items['posts']->permissions); $this->assertCount(1, $items['posts']->permissions); $this->assertObjectHasAttribute('order', $items['posts']); @@ -92,7 +92,7 @@ class NavigationManagerTest extends TestCase $items = $manager->listMainMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('OCTOBER.TESTER.PRINT', $items); $item = $items['OCTOBER.TESTER.PRINT']; @@ -143,10 +143,10 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertInternalType('array', $items); + $this->assertIsArray($items); $this->assertArrayHasKey('foo', $items); - $this->assertInternalType('object', $items['foo']); + $this->assertIsObject($items['foo']); $this->assertObjectHasAttribute('code', $items['foo']); $this->assertObjectHasAttribute('owner', $items['foo']); $this->assertObjectHasAttribute('order', $items['foo']); @@ -156,7 +156,7 @@ class NavigationManagerTest extends TestCase $this->assertEquals('October.Tester', $items['foo']->owner); $this->assertObjectHasAttribute('permissions', $items['foo']); - $this->assertInternalType('array', $items['foo']->permissions); + $this->assertIsArray($items['foo']->permissions); $this->assertCount(2, $items['foo']->permissions); $this->assertContains('october.tester.access_foo', $items['foo']->permissions); $this->assertContains('october.tester.access_bar', $items['foo']->permissions); diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index b4ee7c0c6..176690b78 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -11,8 +11,8 @@ class BackendHelperTest extends TestCase $assets = $backendHelper->decompileAsset('tests/fixtures/backend/assets/compilation.js'); $this->assertCount(2, $assets); - $this->assertContains('file1.js', $assets[0]); - $this->assertContains('file2.js', $assets[1]); + $this->assertStringContainsString('file1.js', $assets[0]); + $this->assertStringContainsString('file2.js', $assets[1]); } public function testDecompileMissingFile() diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 1369a34d1..9a290b509 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -28,7 +28,7 @@ class WidgetMakerTest extends TestCase * * @return void */ - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/AssetTest.php b/tests/unit/cms/classes/AssetTest.php new file mode 100644 index 000000000..13519a161 --- /dev/null +++ b/tests/unit/cms/classes/AssetTest.php @@ -0,0 +1,119 @@ +assertStringContainsString( + 'console.log(\'script1.js\');', + Asset::load($theme, 'js/script1.js')->content + ); + + // Valid direct subdirectory path + $this->assertStringContainsString( + 'console.log(\'subdir/script1.js\');', + Asset::load($theme, 'js/subdir/script1.js')->content + ); + + // Valid relative path + $this->assertStringContainsString( + 'console.log(\'script2.js\');', + Asset::load($theme, 'js/subdir/../script2.js')->content + ); + + // Invalid theme path + $this->assertNull( + Asset::load($theme, 'js/invalid.js') + ); + + // Check that we cannot break out of assets directory + $this->assertNull( + Asset::load($theme, '../../../../js/helpers/fakeDom.js') + ); + $this->assertNull( + Asset::load($theme, '../content/html-content.htm') + ); + + // Check that we cannot load directories directly + $this->assertNull( + Asset::load($theme, 'js/subdir') + ); + + // Check that we definitely cannot load external PHP files + $this->assertNull( + Asset::load($theme, '../../../../../config/database.php') + ); + } + + public function testGetPath() + { + // Test some pathing fringe cases + + $theme = Theme::load('test'); + $assetClass = new Asset($theme); + $themeDir = $theme->getPath(); + + // Direct paths + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('js/script1.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script1.js', + $assetClass->getFilePath('/js/script1.js') + ); + + // Direct path to a directory + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir') + ); + $this->assertEquals( + $themeDir . '/assets/js/subdir', + $assetClass->getFilePath('/js/subdir/') + ); + + // Relative paths + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('./js/script2.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/script2.js', + $assetClass->getFilePath('/js/subdir/../script2.js') + ); + + // Missing file, but valid directory (allows for new files) + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('/js/missing.js') + ); + $this->assertEquals( + $themeDir . '/assets/js/missing.js', + $assetClass->getFilePath('js/missing.js') + ); + + // Missing file and missing directory (new directories are created as needed) + $this->assertEquals( + $themeDir . '/assets/js/missing/missing.js', + $assetClass->getFilePath('/js/missing/missing.js') + ); + + // Ensure we cannot get paths outside of the assets directory + $this->assertFalse( + $assetClass->getFilePath('../../../../js/helpers/fakeDom.js') + ); + $this->assertFalse( + $assetClass->getFilePath('../content/html-content.htm') + ); + $this->assertFalse( + $assetClass->getFilePath('../../../../../config/database.php') + ); + } +} diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index c66d31ee5..ec82153f9 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -30,7 +30,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject class CmsCompoundObjectTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); Model::clearBootedModels(); @@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase $theme = Theme::load('test'); $obj = TestCmsCompoundObject::load($theme, 'compound.htm'); - $this->assertContains("\$controller->data['something'] = 'some value'", $obj->code); + $this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('section', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -69,7 +69,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'component.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive', $obj->settings['components']); $this->assertArrayHasKey('posts-per-page', $obj->settings['components']['testArchive']); $this->assertEquals(10, $obj->settings['components']['testArchive']['posts-per-page']); @@ -82,7 +82,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'components.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']); + $this->assertIsArray($obj->settings['components']); $this->assertArrayHasKey('testArchive firstAlias', $obj->settings['components']); $this->assertArrayHasKey('October\Tester\Components\Post secondAlias', $obj->settings['components']); @@ -108,7 +108,7 @@ class CmsCompoundObjectTest extends TestCase $properties = $obj->getComponentProperties('October\Tester\Components\Post'); $emptyProperties = $obj->getComponentProperties('October\Tester\Components\Archive'); $notExistingProperties = $obj->getComponentProperties('This\Is\Not\Component'); - $this->assertInternalType('array', $properties); + $this->assertIsArray($properties); $this->assertArrayHasKey('show-featured', $properties); $this->assertTrue((bool)$properties['show-featured']); $this->assertEquals('true', $properties['show-featured']); @@ -148,18 +148,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -173,18 +173,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertInternalType('array', $obj->settings['components']['section']); + $this->assertIsArray($obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); } @@ -280,14 +280,14 @@ class CmsCompoundObjectTest extends TestCase $obj = TestParsedCmsCompoundObject::load($theme, 'viewbag.htm'); $this->assertNull($obj->code); $this->assertEquals('

Chop Suey!

', $obj->markup); - $this->assertInternalType('array', $obj->settings); + $this->assertIsArray($obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('viewBag', $obj->settings['components']); - $this->assertInternalType('array', $obj->settings['components']['viewBag']); + $this->assertIsArray($obj->settings['components']['viewBag']); $this->assertArrayHasKey('title', $obj->settings['components']['viewBag']); $this->assertEquals('Toxicity', $obj->settings['components']['viewBag']['title']); diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index 978745ae1..dc5172add 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Model; class CmsObjectQueryTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 95d4f50d9..2c65761b4 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -148,12 +148,11 @@ class CmsObjectTest extends TestCase $this->assertNull($obj->something); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNameSymbol() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -164,12 +163,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileNamePath() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -180,12 +178,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage Invalid file name - */ public function testFillInvalidFileSlash() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('Invalid file name'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -196,12 +193,11 @@ class CmsObjectTest extends TestCase $obj->save(); } - /** - * @expectedException \October\Rain\Exception\ValidationException - * @expectedExceptionMessage The File Name field is required - */ public function testFillEmptyFileName() { + $this->expectException(\October\Rain\Exception\ValidationException::class); + $this->expectExceptionMessage('The File Name field is required'); + $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -266,11 +262,12 @@ class CmsObjectTest extends TestCase /** * @depends testRename - * @expectedException \October\Rain\Exception\ApplicationException - * @expectedExceptionMessage already exists */ public function testRenameToExistingFile() { + $this->expectException(\October\Rain\Exception\ApplicationException::class); + $this->expectExceptionMessageMatches('/already\sexists/'); + $theme = Theme::load('apitest'); $srcFilePath = $theme->getPath().'/testobjects/anotherobj.htm'; diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 43eb34a84..67d8f30e9 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -10,7 +10,7 @@ use Cms\Classes\Controller; class CodeParserTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setup(); @@ -41,7 +41,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -78,7 +78,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -91,7 +91,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -101,7 +101,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -110,13 +110,14 @@ class CodeParserTest extends TestCase */ $this->assertTrue(@touch($layout->getFilePath())); + clearstatcache(); $layout = Layout::load($theme, 'php-parser-test.htm'); $this->assertNotEmpty($layout); $parser = new CodeParser($layout); $property->setValue($parser, []); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertEquals('parser', $info['source']); $this->assertFileExists($info['filePath']); } @@ -131,7 +132,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -157,7 +158,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -191,7 +192,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -220,7 +221,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -255,7 +256,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -284,7 +285,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertInternalType('array', $info); + $this->assertIsArray($info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index a12255580..152367b8d 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -9,7 +9,7 @@ use Cms\Classes\ComponentManager; class ComponentManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 02b2abc34..1496055a3 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -6,7 +6,7 @@ use October\Rain\Halcyon\Model; class ControllerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -69,7 +69,7 @@ class ControllerTest extends TestCase $response = $controller->run('/some-page-that-doesnt-exist'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

Page not found

', $content); } @@ -83,16 +83,15 @@ class ControllerTest extends TestCase $response = $controller->run('/'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertInternalType('string', $content); + $this->assertIsString($content); $this->assertEquals('

My Webpage

', trim($content)); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage is not found - */ public function testLayoutNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-layout'); @@ -146,12 +145,11 @@ class ControllerTest extends TestCase $this->assertEquals("
LAYOUT CONTENT

This page is a subdirectory

", $response); } - /** - * @expectedException \Twig\Error\RuntimeError - * @expectedExceptionMessage is not found - */ public function testPartialNotFound() { + $this->expectException(\Twig\Error\RuntimeError::class); + $this->expectExceptionMessageMatches('/is\snot\sfound/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-partial')->getContent(); @@ -193,12 +191,11 @@ class ControllerTest extends TestCase return $requestMock; } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage AJAX handler 'onNoHandler' was not found. - */ public function testAjaxHandlerNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('AJAX handler \'onNoHandler\' was not found.'); + Request::swap($this->configAjaxRequestMock('onNoHandler', '')); $theme = Theme::load('test'); @@ -206,12 +203,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid AJAX handler name: delete. - */ public function testAjaxInvalidHandlerName() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid AJAX handler name: delete.'); + Request::swap($this->configAjaxRequestMock('delete')); $theme = Theme::load('test'); @@ -219,12 +215,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage Invalid partial name: p:artial. - */ public function testAjaxInvalidPartial() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('Invalid partial name: p:artial.'); + Request::swap($this->configAjaxRequestMock('onTest', 'p:artial')); $theme = Theme::load('test'); @@ -232,12 +227,11 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } - /** - * @expectedException Cms\Classes\CmsException - * @expectedExceptionMessage The partial 'partial' is not found. - */ public function testAjaxPartialNotFound() { + $this->expectException(\Cms\Classes\CmsException::class); + $this->expectExceptionMessage('The partial \'partial\' is not found.'); + Request::swap($this->configAjaxRequestMock('onTest', 'partial')); $theme = Theme::load('test'); @@ -255,7 +249,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -272,7 +266,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -289,7 +283,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(2, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -303,7 +297,7 @@ class ControllerTest extends TestCase $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-component')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -331,7 +325,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-components')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = self::getProtectedProperty($controller, 'page'); $this->assertArrayHasKey('firstAlias', $page->components); $this->assertArrayHasKey('secondAlias', $page->components); @@ -363,19 +357,18 @@ ESC; $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertInternalType('array', $content); + $this->assertIsArray($content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); $this->assertEquals('page', $content['ajax-result']); } - /** - * @expectedException October\Rain\Exception\SystemException - * @expectedExceptionMessage is not registered for the component - */ public function testComponentClassNotFound() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessageMatches('/is\snot\sregistered\sfor\sthe\scomponent/'); + $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-component-class')->getContent(); @@ -395,7 +388,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -421,7 +414,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class-alias')->getContent(); - $page = $this->readAttribute($controller, 'page'); + $page = $controller->getPage(); $this->assertArrayHasKey('someAlias', $page->components); $component = $page->components['someAlias']; diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index 9bbcd3247..f4eeda27a 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -7,7 +7,7 @@ class RouterTest extends TestCase { protected static $theme = null; - public function setUp() + public function setUp() : void { parent::setUp(); @@ -43,7 +43,7 @@ class RouterTest extends TestCase $this->assertFalse($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); /* @@ -52,7 +52,7 @@ class RouterTest extends TestCase $value = $method->invoke($router); $this->assertTrue($value); $map = $property->getValue($router); - $this->assertInternalType('array', $map); + $this->assertIsArray($map); $this->assertGreaterThanOrEqual(4, count($map)); } diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index a45087ede..07a15f2db 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; class ThemeTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -44,7 +44,7 @@ class ThemeTest extends TestCase $pageCollection = $theme->listPages(); $pages = array_values($pageCollection->all()); - $this->assertInternalType('array', $pages); + $this->assertIsArray($pages); $expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages'); $this->assertCount($expectedPageNum, $pages); @@ -63,12 +63,11 @@ class ThemeTest extends TestCase $this->assertEquals('test', $activeTheme->getDirName()); } - /** - * @expectedException \October\Rain\Exception\SystemException - * @expectedExceptionMessage The active theme is not set. - */ public function testNoActiveTheme() { + $this->expectException(\October\Rain\Exception\SystemException::class); + $this->expectExceptionMessage('The active theme is not set.'); + Config::set('cms.activeTheme', null); Theme::getActiveTheme(); } diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 79c03c443..9c316c032 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; class AttachManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index 52cb62fdf..d50debdb6 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e151d2304..e850b7752 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 6d87dbd3a..8fca6ff06 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index a8f354a65..e36460255 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Models\DeferredBinding; class DeferredBindingTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 64559dbc1..9a1def095 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class HasManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -63,8 +63,6 @@ class HasManyModelTest extends PluginTestCase public function testGetRelationValue() { - $this->markTestSkipped('Marked as \'skipped\' for further investigation'); - Model::unguard(); $author = Author::create(['name' => 'Stevie']); $post1 = Post::create(['title' => "First post", 'author_id' => $author->id]); diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php new file mode 100644 index 000000000..85ac947f2 --- /dev/null +++ b/tests/unit/plugins/database/HasManyThroughModelTest.php @@ -0,0 +1,54 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $country = Country::create(['name' => 'Australia']); + $author1 = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $author2 = Author::create(['name' => 'Louie', 'email' => 'louie@email.tld']); + $post1 = Post::create(['title' => "First post", 'description' => "Yay!!"]); + $post2 = Post::create(['title' => "Second post", 'description' => "Woohoo!!"]); + $post3 = Post::create(['title' => "Third post", 'description' => "Yipiee!!"]); + $post4 = Post::make(['title' => "Fourth post", 'description' => "Hooray!!"]); + Model::reguard(); + + // Set data + $author1->country = $country; + $author2->country = $country; + + $author1->posts = new Collection([$post1, $post2]); + $author2->posts = new Collection([$post3, $post4]); + + $author1->save(); + $author2->save(); + + $country = Country::with([ + 'posts' + ])->find($country->id); + + $this->assertEquals([ + $post1->id, + $post2->id, + $post3->id, + $post4->id + ], $country->posts->pluck('id')->toArray()); + } +} diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index 7a5e30157..e6bd0f45e 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Phone; class HasOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php new file mode 100644 index 000000000..91be83b60 --- /dev/null +++ b/tests/unit/plugins/database/HasOneThroughModelTest.php @@ -0,0 +1,39 @@ +runPluginRefreshCommand('Database.Tester'); + } + + public function testGet() + { + Model::unguard(); + $phone = Phone::create(['number' => '08 1234 5678']); + $author = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + $user = User::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); + Model::reguard(); + + // Set data + $author->phone = $phone; + $author->user = $user; + $author->save(); + + $user = User::with([ + 'phone' + ])->find($user->id); + + $this->assertEquals($phone->id, $user->phone->id); + } +} diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 492b39505..16bf07c1d 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; class ModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -23,12 +23,11 @@ class ModelTest extends PluginTestCase $this->assertEquals(1, $post->id); } - /** - * @expectedException \Illuminate\Database\Eloquent\MassAssignmentException - * @expectedExceptionMessage title - */ public function testGuardedAttribute() { + $this->expectException(\Illuminate\Database\Eloquent\MassAssignmentException::class); + $this->expectExceptionMessageMatches('/title/'); + Post::create(['title' => 'Hi!', 'slug' => 'authenticity']); } } diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index e9b77aced..a7ba3041c 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class MorphManyModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index 4051e0b0c..e8c6f60fd 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\Meta; class MorphOneModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index 962f2d658..b0eb37ff7 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\EventLog; class MorphToModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index b2f0ae019..49d46764c 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategoryNested; class NestedTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 0484409d4..9d6d61114 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\NullablePost; class NullableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index afee0c93d..c8fa19a54 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\RevisionablePost; class RevisionableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index fac6a48f3..c22b11d16 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategorySimple; class SimpleTreeModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -165,12 +165,11 @@ class SimpleTreeModelTest extends PluginTestCase ], $array); } - /** - * @expectedException \Exception - * @expectedExceptionMessage Column mismatch in listsNested method - */ public function testListsNestedUnknownColumn() { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Column mismatch in listsNested method'); + CategorySimple::listsNested('custom_name', 'id'); } diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index f1f7aac71..22cad1837 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\SluggablePost; class SluggableModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index 23a0ef436..fc5212433 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -10,7 +10,7 @@ use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; class SoftDeleteModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index ce1f4e382..f5ddb55b7 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\ValidationPost; class ValidationModelTest extends PluginTestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -13,11 +13,10 @@ class ValidationModelTest extends PluginTestCase $this->runPluginRefreshCommand('Database.Tester'); } - /** - * @expectedException October\Rain\Database\ModelException - */ public function testUniqueTableValidation() { + $this->expectException(\October\Rain\Database\ModelException::class); + $post = ValidationPost::create([ 'title' => 'This is a new post', 'slug' => 'post-1', diff --git a/tests/unit/system/AliasesTest.php b/tests/unit/system/AliasesTest.php new file mode 100644 index 000000000..8d389cdcc --- /dev/null +++ b/tests/unit/system/AliasesTest.php @@ -0,0 +1,22 @@ +assertTrue(class_exists('Illuminate\Support\Facades\Input')); + $this->assertInstanceOf( + \October\Rain\Support\Facades\Input::class, + new \Illuminate\Support\Facades\Input() + ); + } + + public function testHtmlDumperAlias() + { + $this->assertTrue(class_exists('Illuminate\Support\Debug\HtmlDumper')); + $this->assertInstanceOf( + \Symfony\Component\VarDumper\Dumper\HtmlDumper::class, + new \Illuminate\Support\Debug\HtmlDumper() + ); + } +} diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 43410cc77..14dc6f54b 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Datasource\FileDatasource; class CmsThemeTemplateFixture extends Model { - protected $fillable = ['*']; + protected $guarded = []; public $timestamps = false; @@ -30,7 +30,7 @@ class AutoDatasourceTest extends PluginTestCase */ public $datasource; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -73,7 +73,7 @@ class AutoDatasourceTest extends PluginTestCase ]); } - public function tearDown() + public function tearDown(): void { foreach ($this->fixtures as $fixture) { $fixture->delete(); diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index c9fed4a04..5c5d313bd 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -5,7 +5,7 @@ use System\Classes\CombineAssets; class CombineAssetsTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -24,10 +24,10 @@ class CombineAssetsTest extends TestCase * Supported file extensions should exist */ $jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions'); - $this->assertInternalType('array', $jsExt); + $this->assertIsArray($jsExt); $cssExt = self::getProtectedProperty($combiner, 'cssExtensions'); - $this->assertInternalType('array', $cssExt); + $this->assertIsArray($cssExt); /* * Check service methods diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index 48a82ba3e..f4ee00a32 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\MarkupManager; class MarkupManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 35e561594..54c4a6a3e 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -4,7 +4,7 @@ use System\Classes\MediaLibrary; class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine { - protected function tearDown() + protected function tearDown() : void { $this->removeMedia(); parent::tearDown(); @@ -66,7 +66,7 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine public function testValidPathsOnValidatePath($path) { $result = MediaLibrary::validatePath($path); - $this->assertInternalType('string', $result); + $this->assertIsString($result); } public function testListFolderContents() diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index 4b20f8509..c963f2dd6 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -5,7 +5,7 @@ class PluginManagerTest extends TestCase { public $manager; - public function setUp() + public function setUp() : void { parent::setUp(); diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index b9934d3e5..ba163d18c 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\VersionManager; class VersionManagerTest extends TestCase { - public function setUp() + public function setUp() : void { parent::setUp(); @@ -100,8 +100,8 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]); - $this->assertInternalType('array', $comments); - $this->assertInternalType('array', $scripts); + $this->assertIsArray($comments); + $this->assertIsArray($scripts); $this->assertEquals($expectedComments, $comments); $this->assertEquals($expectedScripts, $scripts); diff --git a/tests/unit/system/console/OctoberEnvTest.php b/tests/unit/system/console/OctoberEnvTest.php index 6170cc739..184c3e68d 100644 --- a/tests/unit/system/console/OctoberEnvTest.php +++ b/tests/unit/system/console/OctoberEnvTest.php @@ -13,7 +13,7 @@ class OctoberEnvTest extends TestCase /** @var string Stores the original config path from the app container */ public static $origConfigPath; - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -30,59 +30,32 @@ class OctoberEnvTest extends TestCase // Check environment file $envFile = file_get_contents(base_path('.env')); - // Forward compatible assertions - // @TODO: Use only `assertStringContainsString` after L6 upgrade - - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('APP_DEBUG=true', $envFile); - $this->assertStringContainsString('APP_URL=https://localhost', $envFile); - $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); - $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); - $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); - $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertStringContainsString('DB_PORT=3306', $envFile); - } else { - $this->assertContains('APP_DEBUG=true', $envFile); - $this->assertContains('APP_URL=https://localhost', $envFile); - $this->assertContains('DB_CONNECTION=mysql', $envFile); - $this->assertContains('DB_DATABASE="data#base"', $envFile); - $this->assertContains('DB_USERNAME="teal\'c"', $envFile); - $this->assertContains('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertContains('DB_PORT=3306', $envFile); - } + $this->assertStringContainsString('APP_DEBUG=true', $envFile); + $this->assertStringContainsString('APP_URL=https://localhost', $envFile); + $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); + $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); + $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); + $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); + $this->assertStringContainsString('DB_PORT=3306', $envFile); // Check app.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/app.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } else { - $this->assertContains('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertContains('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); - } + $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); + $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); // Check database.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/database.php')); - if (method_exists($this, 'assertStringContainsString')) { - $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } else { - $this->assertContains('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertContains('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertContains('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertContains('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); - } + $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); + $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); + // Both the following configurations had values in the original config, they should be stripped out once + // the .env file is generated. + $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); + $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); } - protected function tearDown() + protected function tearDown(): void { $this->tearDownConfigFixtures(); $this->restoreEnvFile(); diff --git a/tests/unit/system/traits/AssetMakerTest.php b/tests/unit/system/traits/AssetMakerTest.php index 352d66107..606df9562 100644 --- a/tests/unit/system/traits/AssetMakerTest.php +++ b/tests/unit/system/traits/AssetMakerTest.php @@ -10,7 +10,7 @@ class AssetMakerTest extends TestCase { private $stub; - public function setUp() + public function setUp() : void { $this->createApplication(); $this->stub = new AssetMakerStub(); From 60afd41cc283ad204c566bca2c2f189bef8890ed Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 9 Aug 2020 03:48:03 -0600 Subject: [PATCH 100/100] Revert "Rebase October CMS on Laravel 6 (#4893)" This reverts commit ee0065d353099fb0de0bf6d0b361a71b4f799de1. --- .github/workflows/code-quality-pr.yaml | 23 +- .github/workflows/code-quality-push.yaml | 23 +- .github/workflows/frontend-tests.yaml | 24 + .github/workflows/matchers/phpcs-matcher.json | 23 - .github/workflows/tests.yml | 49 +- .gitignore | 28 +- artisan | 4 +- bootstrap/autoload.php | 17 + composer.json | 40 +- config/app.php | 35 +- config/database.php | 1 - config/develop.php | 22 - config/filesystems.php | 2 +- config/hashing.php | 52 - config/logging.php | 91 -- config/mail.php | 2 +- config/services.php | 4 - index.php | 2 +- modules/backend/ServiceProvider.php | 2 - .../behaviors/ImportExportController.php | 9 +- modules/backend/classes/FormField.php | 4 +- modules/backend/composer.json | 11 +- .../backend/database/seeds/DatabaseSeeder.php | 6 +- modules/backend/formwidgets/Sensitive.php | 117 -- .../sensitive/assets/css/sensitive.css | 2 - .../sensitive/assets/js/sensitive.js | 192 --- .../sensitive/assets/less/sensitive.less | 10 - .../sensitive/partials/_sensitive.htm | 41 - modules/backend/models/ExportModel.php | 9 +- modules/backend/models/ImportModel.php | 27 +- modules/backend/models/User.php | 4 +- modules/backend/routes.php | 2 +- modules/cms/classes/Asset.php | 17 +- modules/cms/classes/CmsCompoundObject.php | 3 +- modules/cms/classes/CmsObject.php | 11 +- modules/cms/classes/CmsObjectCollection.php | 28 +- modules/cms/classes/CodeParser.php | 3 +- modules/cms/classes/Router.php | 6 +- modules/cms/classes/Theme.php | 3 +- modules/cms/composer.json | 11 +- modules/cms/routes.php | 2 +- modules/cms/traits/UrlMaker.php | 3 +- modules/cms/twig/DebugExtension.php | 2 +- modules/system/ServiceProvider.php | 1 - modules/system/aliases.php | 12 +- modules/system/classes/CombineAssets.php | 24 +- modules/system/classes/MediaLibrary.php | 3 +- modules/system/classes/UpdateManager.php | 56 +- modules/system/classes/VersionManager.php | 37 +- modules/system/composer.json | 11 +- modules/system/console/OctoberEnv.php | 2 +- modules/system/console/OctoberUpdate.php | 1 + modules/system/controllers/Settings.php | 53 +- .../system/database/seeds/DatabaseSeeder.php | 6 +- modules/system/lang/en/validation.php | 29 - modules/system/models/mailsetting/fields.yaml | 5 - modules/system/providers.php | 4 +- phpunit.xml | 1 + tests/PluginTestCase.php | 6 +- tests/README.md | 41 +- tests/UiTestCase.php | 111 ++ tests/bootstrap.php | 11 + .../concerns/InteractsWithAuthentication.php | 8 +- .../plugins/database/tester/models/Author.php | 1 - .../database/tester/models/Country.php | 35 - .../plugins/database/tester/models/User.php | 13 - .../tester/updates/create_authors_table.php | 1 - .../tester/updates/create_countries_table.php | 23 - .../database/tester/updates/version.yaml | 1 - .../fixtures/themes/test/assets/js/script1.js | 1 - .../fixtures/themes/test/assets/js/script2.js | 1 - .../themes/test/assets/js/subdir/script1.js | 1 - tests/functional/backend/AuthTest.php | 90 ++ tests/functional/cms/TemplateTest.php | 143 ++ tests/functional/phpunit.xml | 18 + tests/resources/patches/php-generator-7.php | 1185 +++++++++++++++++ .../unit/backend/classes/AuthManagerTest.php | 4 +- .../backend/classes/NavigationManagerTest.php | 14 +- .../backend/helpers/BackendHelperTest.php | 4 +- tests/unit/backend/traits/WidgetMakerTest.php | 2 +- tests/unit/cms/classes/AssetTest.php | 119 -- .../cms/classes/CmsCompoundObjectTest.php | 30 +- tests/unit/cms/classes/CmsObjectQueryTest.php | 2 +- tests/unit/cms/classes/CmsObjectTest.php | 33 +- tests/unit/cms/classes/CodeParserTest.php | 25 +- .../unit/cms/classes/ComponentManagerTest.php | 2 +- tests/unit/cms/classes/ControllerTest.php | 71 +- tests/unit/cms/classes/RouterTest.php | 6 +- tests/unit/cms/classes/ThemeTest.php | 11 +- .../plugins/database/AttachManyModelTest.php | 2 +- .../plugins/database/AttachOneModelTest.php | 2 +- .../database/BelongsToManyModelTest.php | 2 +- .../plugins/database/BelongsToModelTest.php | 2 +- .../plugins/database/DeferredBindingTest.php | 2 +- .../plugins/database/HasManyModelTest.php | 4 +- .../database/HasManyThroughModelTest.php | 54 - .../unit/plugins/database/HasOneModelTest.php | 2 +- .../database/HasOneThroughModelTest.php | 39 - tests/unit/plugins/database/ModelTest.php | 9 +- .../plugins/database/MorphManyModelTest.php | 2 +- .../plugins/database/MorphOneModelTest.php | 2 +- .../plugins/database/MorphToModelTest.php | 2 +- .../plugins/database/NestedTreeModelTest.php | 2 +- .../plugins/database/NullableModelTest.php | 2 +- .../database/RevisionableModelTest.php | 2 +- .../plugins/database/SimpleTreeModelTest.php | 9 +- .../plugins/database/SluggableModelTest.php | 2 +- .../plugins/database/SoftDeleteModelTest.php | 2 +- .../plugins/database/ValidationModelTest.php | 7 +- tests/unit/system/AliasesTest.php | 22 - .../system/classes/AutoDatasourceTest.php | 6 +- .../unit/system/classes/CombineAssetsTest.php | 6 +- .../unit/system/classes/MarkupManagerTest.php | 2 +- .../unit/system/classes/MediaLibraryTest.php | 4 +- .../unit/system/classes/PluginManagerTest.php | 2 +- .../system/classes/VersionManagerTest.php | 6 +- tests/unit/system/console/OctoberEnvTest.php | 61 +- tests/unit/system/traits/AssetMakerTest.php | 2 +- 118 files changed, 2105 insertions(+), 1381 deletions(-) create mode 100644 .github/workflows/frontend-tests.yaml delete mode 100644 .github/workflows/matchers/phpcs-matcher.json delete mode 100644 config/hashing.php delete mode 100644 config/logging.php delete mode 100644 modules/backend/formwidgets/Sensitive.php delete mode 100644 modules/backend/formwidgets/sensitive/assets/css/sensitive.css delete mode 100644 modules/backend/formwidgets/sensitive/assets/js/sensitive.js delete mode 100644 modules/backend/formwidgets/sensitive/assets/less/sensitive.less delete mode 100644 modules/backend/formwidgets/sensitive/partials/_sensitive.htm create mode 100644 tests/UiTestCase.php delete mode 100644 tests/fixtures/plugins/database/tester/models/Country.php delete mode 100644 tests/fixtures/plugins/database/tester/updates/create_countries_table.php delete mode 100644 tests/fixtures/themes/test/assets/js/subdir/script1.js create mode 100644 tests/functional/backend/AuthTest.php create mode 100644 tests/functional/cms/TemplateTest.php create mode 100644 tests/functional/phpunit.xml create mode 100644 tests/resources/patches/php-generator-7.php delete mode 100644 tests/unit/cms/classes/AssetTest.php delete mode 100644 tests/unit/plugins/database/HasManyThroughModelTest.php delete mode 100644 tests/unit/plugins/database/HasOneThroughModelTest.php delete mode 100644 tests/unit/system/AliasesTest.php diff --git a/.github/workflows/code-quality-pr.yaml b/.github/workflows/code-quality-pr.yaml index af05451d1..65a0f4646 100644 --- a/.github/workflows/code-quality-pr.yaml +++ b/.github/workflows/code-quality-pr.yaml @@ -6,18 +6,25 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHPCS + name: PHP steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP and PHP Code Sniffer - uses: shivammathur/setup-php@v1 + - name: Install PHP + uses: shivammathur/setup-php@master with: - php-version: '7.3' - tools: phpcs - - name: Setup problem matcher for PHPCS - run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" + php-version: 7.2 + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --no-suggest + - name: Reset October modules and library + run: | + git reset --hard HEAD + rm -rf ./vendor/october/rain + wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip + unzip ./vendor/october/develop.zip -d ./vendor/october + mv ./vendor/october/library-develop ./vendor/october/rain + composer dump-autoload - name: Run code quality checks run: | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch - phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) + ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD) diff --git a/.github/workflows/code-quality-push.yaml b/.github/workflows/code-quality-push.yaml index ff209d0b5..3de3f6a97 100644 --- a/.github/workflows/code-quality-push.yaml +++ b/.github/workflows/code-quality-push.yaml @@ -9,16 +9,23 @@ on: jobs: codeQuality: runs-on: ubuntu-latest - name: PHPCS + name: PHP steps: - name: Checkout changes uses: actions/checkout@v1 - - name: Install PHP and PHP Code Sniffer - uses: shivammathur/setup-php@v1 + - name: Install PHP + uses: shivammathur/setup-php@master with: - php-version: '7.3' - tools: phpcs - - name: Setup problem matcher for PHPCS - run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json" + php-version: 7.2 + - name: Install Composer dependencies + run: composer install --no-interaction --no-progress --no-suggest + - name: Reset October modules and library + run: | + git reset --hard HEAD + rm -rf ./vendor/october/rain + wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip + unzip ./vendor/october/develop.zip -d ./vendor/october + mv ./vendor/october/library-develop ./vendor/october/rain + composer dump-autoload - name: Run code quality checks - run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) + run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }}) diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml new file mode 100644 index 000000000..9d0068a0a --- /dev/null +++ b/.github/workflows/frontend-tests.yaml @@ -0,0 +1,24 @@ +name: Tests + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + frontendTests: + runs-on: ubuntu-latest + name: JavaScript + steps: + - name: Checkout changes + uses: actions/checkout@v1 + - name: Install Node + uses: actions/setup-node@v1 + with: + node-version: 8 + - name: Install Node dependencies + run: npm install + - name: Run tests + run: npm run test diff --git a/.github/workflows/matchers/phpcs-matcher.json b/.github/workflows/matchers/phpcs-matcher.json deleted file mode 100644 index 5c80b26d2..000000000 --- a/.github/workflows/matchers/phpcs-matcher.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "phpcs", - "severity": "error", - "pattern": [ - { - "regexp": "^$", - "file": 1 - }, - { - "regexp": "+)$", - "line": 1, - "column": 2, - "severity": 3, - "message": 4, - "code": 5, - "loop": true - } - ] - } - ] - } diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a27246d97..072b795da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,56 +8,37 @@ on: pull_request: jobs: - frontendTests: - runs-on: ubuntu-latest - name: JavaScript - steps: - - name: Checkout changes - uses: actions/checkout@v1 - - name: Install Node - uses: actions/setup-node@v1 - with: - node-version: 8 - - name: Install Node dependencies - run: npm install - - name: Run tests - run: npm run test phpUnitTests: runs-on: ubuntu-latest strategy: max-parallel: 6 matrix: - phpVersions: ['7.2', '7.3', '7.4'] + phpVersions: ['7.1', '7.2', '7.3', '7.4'] fail-fast: false - name: Unit Tests / PHP ${{ matrix.phpVersions }} + name: PHP ${{ matrix.phpVersions }} steps: - name: Checkout changes uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@v1 + uses: shivammathur/setup-php@master with: php-version: ${{ matrix.phpVersions }} - extensions: mbstring, intl, gd, xml, sqlite - - name: Setup problem matchers for PHPUnit - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - name: Set Composer cache - id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Cache Composer dependencies - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- + extension-csv: mbstring, intl, gd, xml, sqlite - name: Install Composer dependencies run: composer install --no-interaction --no-progress --no-suggest --no-scripts - - name: Run post-update Composer scripts - run: php artisan package:discover - - name: Reset October modules + - name: Reset October modules and library run: | git reset --hard HEAD - composer dumpautoload + rm -rf ./vendor/october/rain + wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip + unzip ./vendor/october/develop.zip -d ./vendor/october + mv ./vendor/october/library-develop ./vendor/october/rain + composer dump-autoload + - name: Run post-update Composer scripts + run: | + php artisan october:util set build + php artisan package:discover - name: Run Linting and Tests run: | ./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php . - ./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php + ./vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 63a1b61d7..4cd08cf9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,24 @@ -# Composer ignores +/bootstrap/compiled.php /vendor composer.phar -composer.lock - -# Framework ignores +.DS_Store +.idea .env .env.*.php .env.php -selenium.php -/bootstrap/compiled.php -.phpunit.result.cache - -# Hosting ignores php_errors.log nginx-error.log nginx-access.log nginx-ssl.access.log nginx-ssl.error.log +php-errors.log sftp-config.json .ftpconfig - -# Editor ignores -nbproject -.idea -.vscode -_ide_helper.php - -# Other ignores -.DS_Store +selenium.php +composer.lock package-lock.json /node_modules +_ide_helper.php + +# for netbeans +nbproject diff --git a/artisan b/artisan index df630d0d6..961e94d0b 100644 --- a/artisan +++ b/artisan @@ -28,7 +28,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); +$kernel = $app->make('Illuminate\Contracts\Console\Kernel'); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, @@ -48,4 +48,4 @@ $status = $kernel->handle( $kernel->terminate($input, $status); -exit($status); +exit($status); \ No newline at end of file diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php index 6533c54ca..b980622d7 100644 --- a/bootstrap/autoload.php +++ b/bootstrap/autoload.php @@ -35,3 +35,20 @@ require $helperPath; */ require __DIR__.'/../vendor/autoload.php'; + +/* +|-------------------------------------------------------------------------- +| Include The Compiled Class File +|-------------------------------------------------------------------------- +| +| To dramatically increase your application's performance, you may use a +| compiled class file which contains all of the classes commonly used +| by a request. The Artisan "optimize" is used to create this file. +| +*/ + +$compiledPath = __DIR__.'/../storage/framework/compiled.php'; + +if (file_exists($compiledPath)) { + require $compiledPath; +} diff --git a/composer.json b/composer.json index 9542fc5a1..eb4f8fe37 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "october/october", - "description": "October CMS", + "description": "OctoberCMS", "homepage": "https://octobercms.com", "type": "project", "keywords": ["october", "cms", "octobercms", "laravel"], @@ -24,34 +24,37 @@ } ], "support": { - "paid": "https://octobercms.com/premium-support", "issues": "https://github.com/octobercms/october/issues", "forum": "https://octobercms.com/forum/", "docs": "https://octobercms.com/docs/", + "irc": "irc://irc.freenode.net/october", "source": "https://github.com/octobercms/october" }, "require": { - "php": ">=7.2", - "october/rain": "dev-develop as 1.0", - "october/system": "dev-develop", - "october/backend": "dev-develop", - "october/cms": "dev-develop", - "laravel/framework": "~6.0", + "php": ">=7.0.8", + "ext-mbstring": "*", + "ext-openssl": "*", + "october/rain": "~1.0", + "october/system": "~1.0", + "october/backend": "~1.0", + "october/cms": "~1.0", + "laravel/framework": "~5.5.40", "wikimedia/composer-merge-plugin": "1.4.1" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.0", - "fzaninotto/faker": "~1.9", + "fzaninotto/faker": "~1.7", + "phpunit/phpunit": "~6.5", + "phpunit/phpunit-selenium": "~1.2", + "meyfa/phpunit-assert-gd": "1.1.0", "squizlabs/php_codesniffer": "3.*", - "php-parallel-lint/php-parallel-lint": "^1.0", - "meyfa/phpunit-assert-gd": "^2.0.0", - "dms/phpunit-arraysubset-asserts": "^0.1.0" + "php-parallel-lint/php-parallel-lint": "^1.0" }, "autoload-dev": { "classmap": [ "tests/concerns/InteractsWithAuthentication.php", "tests/fixtures/backend/models/UserFixture.php", "tests/TestCase.php", + "tests/UiTestCase.php", "tests/PluginTestCase.php" ] }, @@ -63,21 +66,12 @@ "post-update-cmd": [ "php artisan october:util set build", "php artisan package:discover" - ], - "test": [ - "phpunit --stop-on-failure" - ], - "lint": [ - "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ." - ], - "sniff": [ - "phpcs --colors -nq --report=\"full\" --extensions=\"php\"" ] }, "config": { "preferred-install": "dist", "platform": { - "php": "7.2" + "php": "7.0.8" } }, "minimum-stability": "dev", diff --git a/config/app.php b/config/app.php index 56e4959ab..23aad8473 100644 --- a/config/app.php +++ b/config/app.php @@ -111,6 +111,21 @@ return [ 'cipher' => 'AES-256-CBC', + /* + |-------------------------------------------------------------------------- + | Logging Configuration + |-------------------------------------------------------------------------- + | + | Here you may configure the log settings for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Settings: "single", "daily", "syslog", "errorlog" + | + */ + + 'log' => 'single', + /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -129,26 +144,6 @@ return [ 'System\ServiceProvider', ]), - /* - |-------------------------------------------------------------------------- - | Load automatically discovered packages - |-------------------------------------------------------------------------- - | - | By default, October CMS disables the loading of discovered packages - | through Laravel's package discovery service, in order to allow packages - | used by plugins to be disabled if the plugin itself is disabled. - | - | Set this to `true` to enable automatic loading of these packages. This - | will result in packages being loaded, even if the plugin using them is - | disabled. This is NOT RECOMMENDED. - | - | Please note that packages defined in `app.providers` will still be loaded - | even if discovery is disabled. - | - */ - - 'loadDiscoveredPackages' => false, - /* |-------------------------------------------------------------------------- | Class Aliases diff --git a/config/database.php b/config/database.php index a06bc7586..70f1420c6 100644 --- a/config/database.php +++ b/config/database.php @@ -116,7 +116,6 @@ return [ 'redis' => [ - 'client' => 'predis', 'cluster' => false, 'default' => [ diff --git a/config/develop.php b/config/develop.php index 2c1cbc642..cd4aee7d7 100644 --- a/config/develop.php +++ b/config/develop.php @@ -20,27 +20,5 @@ return [ */ 'decompileBackendAssets' => false, - - /* - |-------------------------------------------------------------------------- - | Allow deep-level symlinks - |-------------------------------------------------------------------------- - | - | October CMS, by default, will allow symlinks within the first level of - | subdirectories. When this feature is enabled, the system will allow - | symlinks to be used at any directory level. This can be useful for - | symlinking individual plugins or themes. - | - | Please note that this has a negative effect on performance. This feature - | abides by "cms.restrictBaseDir" - if enabled, symlinks cannot point to - | resources outside of the root folder. - | - | true - allow symlinks at any level - | - | false - only allow symlinks at the first level of subdirectories (default) - | - */ - - 'allowDeepSymlinks' => false, ]; diff --git a/config/filesystems.php b/config/filesystems.php index 36d9a0dfa..4d843013c 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -11,7 +11,7 @@ return [ | by the framework. A "local" driver, as well as a variety of cloud | based drivers are available for your choosing. Just store away! | - | Supported: "local", "ftp", "sftp", "s3", "rackspace" + | Supported: "local", "s3", "rackspace" | */ diff --git a/config/hashing.php b/config/hashing.php deleted file mode 100644 index 842577087..000000000 --- a/config/hashing.php +++ /dev/null @@ -1,52 +0,0 @@ - 'bcrypt', - - /* - |-------------------------------------------------------------------------- - | Bcrypt Options - |-------------------------------------------------------------------------- - | - | Here you may specify the configuration options that should be used when - | passwords are hashed using the Bcrypt algorithm. This will allow you - | to control the amount of time it takes to hash the given password. - | - */ - - 'bcrypt' => [ - 'rounds' => env('BCRYPT_ROUNDS', 10), - ], - - /* - |-------------------------------------------------------------------------- - | Argon Options - |-------------------------------------------------------------------------- - | - | Here you may specify the configuration options that should be used when - | passwords are hashed using the Argon algorithm. These will allow you - | to control the amount of time it takes to hash the given password. - | - */ - - 'argon' => [ - 'memory' => 1024, - 'threads' => 2, - 'time' => 2, - ], - -]; diff --git a/config/logging.php b/config/logging.php deleted file mode 100644 index 900d48123..000000000 --- a/config/logging.php +++ /dev/null @@ -1,91 +0,0 @@ - env('LOG_CHANNEL', 'single'), - - /* - |-------------------------------------------------------------------------- - | Log Channels - |-------------------------------------------------------------------------- - | - | Here you may configure the log channels for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Drivers: "single", "daily", "slack", "syslog", - | "errorlog", "monolog", - | "custom", "stack" - | - */ - - 'channels' => [ - 'stack' => [ - 'driver' => 'stack', - 'channels' => ['daily'], - 'ignore_exceptions' => false, - ], - - 'single' => [ - 'driver' => 'single', - 'path' => storage_path('logs/system.log'), - 'level' => 'debug', - ], - - 'daily' => [ - 'driver' => 'daily', - 'path' => storage_path('logs/system.log'), - 'level' => 'debug', - 'days' => 14, - ], - - 'slack' => [ - 'driver' => 'slack', - 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => 'October CMS Log', - 'emoji' => ':boom:', - 'level' => 'critical', - ], - - 'papertrail' => [ - 'driver' => 'monolog', - 'level' => 'debug', - 'handler' => \Monolog\Handler\SyslogUdpHandler::class, - 'handler_with' => [ - 'host' => env('PAPERTRAIL_URL'), - 'port' => env('PAPERTRAIL_PORT'), - ], - ], - - 'stderr' => [ - 'driver' => 'monolog', - 'handler' => \Monolog\Handler\StreamHandler::class, - 'formatter' => env('LOG_STDERR_FORMATTER'), - 'with' => [ - 'stream' => 'php://stderr', - ], - ], - - 'syslog' => [ - 'driver' => 'syslog', - 'level' => 'debug', - ], - - 'errorlog' => [ - 'driver' => 'errorlog', - 'level' => 'debug', - ], - ], - -]; diff --git a/config/mail.php b/config/mail.php index 4c471d568..7c2332d2f 100644 --- a/config/mail.php +++ b/config/mail.php @@ -12,7 +12,7 @@ return [ | your application here. By default, Laravel is setup for SMTP mail. | | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "postmark", "sparkpost", "log", "array" + | "sparkpost", "log", "array" | */ diff --git a/config/services.php b/config/services.php index d53643b03..c2d453065 100644 --- a/config/services.php +++ b/config/services.php @@ -24,10 +24,6 @@ return [ 'secret' => '', ], - 'postmark' => [ - 'token' => '', - ], - 'ses' => [ 'key' => '', 'secret' => '', diff --git a/index.php b/index.php index 9c4d23a06..ba43df3ec 100644 --- a/index.php +++ b/index.php @@ -37,7 +37,7 @@ $app = require_once __DIR__.'/bootstrap/app.php'; | */ -$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); +$kernel = $app->make('Illuminate\Contracts\Http\Kernel'); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 4c3825dd0..b0a5026f8 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -80,7 +80,6 @@ class ServiceProvider extends ModuleServiceProvider $combiner->registerBundle('~/modules/backend/formwidgets/colorpicker/assets/less/colorpicker.less'); $combiner->registerBundle('~/modules/backend/formwidgets/permissioneditor/assets/less/permissioneditor.less'); $combiner->registerBundle('~/modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less'); - $combiner->registerBundle('~/modules/backend/formwidgets/sensitive/assets/less/sensitive.less'); /* * Rich Editor is protected by DRM @@ -200,7 +199,6 @@ class ServiceProvider extends ModuleServiceProvider $manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist'); $manager->registerFormWidget('Backend\FormWidgets\MediaFinder', 'mediafinder'); $manager->registerFormWidget('Backend\FormWidgets\NestedForm', 'nestedform'); - $manager->registerFormWidget('Backend\FormWidgets\Sensitive', 'sensitive'); }); } diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index b0278ccdc..c146050b6 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -11,7 +11,7 @@ use Backend\Behaviors\ImportExportController\TranscodeFilter; use Illuminate\Database\Eloquent\MassAssignmentException; use League\Csv\Reader as CsvReader; use League\Csv\Writer as CsvWriter; -use League\Csv\EscapeFormula as CsvEscapeFormula; +use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; use Exception; @@ -624,7 +624,9 @@ class ImportExportController extends ControllerBehavior $csv->setDelimiter($options['delimiter']); $csv->setEnclosure($options['enclosure']); $csv->setEscape($options['escape']); - $csv->addFormatter(new CsvEscapeFormula()); + + // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) + $formatter = new CsvEscapeFormula(); /* * Add headers @@ -660,6 +662,9 @@ class ImportExportController extends ControllerBehavior $record[] = $value; } + // Temporary until upgrading to league/csv >= 9.1.0 + $record = $formatter($record); + $csv->insertOne($record); } diff --git a/modules/backend/classes/FormField.php b/modules/backend/classes/FormField.php index d9782a267..f2b6efc68 100644 --- a/modules/backend/classes/FormField.php +++ b/modules/backend/classes/FormField.php @@ -124,7 +124,7 @@ class FormField /** * @var string Specifies a comment to accompany the field */ - public $comment = ''; + public $comment; /** * @var string Specifies the comment position. @@ -139,7 +139,7 @@ class FormField /** * @var string Specifies a message to display when there is no value supplied (placeholder). */ - public $placeholder = ''; + public $placeholder; /** * @var array Contains a list of attributes specified in the field configuration. diff --git a/modules/backend/composer.json b/modules/backend/composer.json index 5d4231af2..15cfd2a3f 100644 --- a/modules/backend/composer.json +++ b/modules/backend/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/backend/database/seeds/DatabaseSeeder.php b/modules/backend/database/seeds/DatabaseSeeder.php index 78c561441..7be5f9885 100644 --- a/modules/backend/database/seeds/DatabaseSeeder.php +++ b/modules/backend/database/seeds/DatabaseSeeder.php @@ -12,8 +12,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguarded(function () { - $this->call('Backend\Database\Seeds\SeedSetupAdmin'); - }); + Eloquent::unguard(); + + $this->call('Backend\Database\Seeds\SeedSetupAdmin'); } } diff --git a/modules/backend/formwidgets/Sensitive.php b/modules/backend/formwidgets/Sensitive.php deleted file mode 100644 index a28a8d60b..000000000 --- a/modules/backend/formwidgets/Sensitive.php +++ /dev/null @@ -1,117 +0,0 @@ -fillFromConfig([ - 'readOnly', - 'disabled', - 'allowCopy', - 'hiddenPlaceholder', - 'hideOnTabChange', - ]); - - if ($this->formField->disabled || $this->formField->readOnly) { - $this->previewMode = true; - } - } - - /** - * @inheritDoc - */ - public function render() - { - $this->prepareVars(); - - return $this->makePartial('sensitive'); - } - - /** - * Prepares the view data for the widget partial. - */ - public function prepareVars() - { - $this->vars['readOnly'] = $this->readOnly; - $this->vars['disabled'] = $this->disabled; - $this->vars['hasValue'] = !empty($this->getLoadValue()); - $this->vars['allowCopy'] = $this->allowCopy; - $this->vars['hiddenPlaceholder'] = $this->hiddenPlaceholder; - $this->vars['hideOnTabChange'] = $this->hideOnTabChange; - } - - /** - * Reveals the value of a hidden, unmodified sensitive field. - * - * @return array - */ - public function onShowValue() - { - return [ - 'value' => $this->getLoadValue() - ]; - } - - /** - * @inheritDoc - */ - public function getSaveValue($value) - { - if ($value === $this->hiddenPlaceholder) { - $value = $this->getLoadValue(); - } - - return $value; - } - - /** - * @inheritDoc - */ - protected function loadAssets() - { - $this->addCss('css/sensitive.css', 'core'); - $this->addJs('js/sensitive.js', 'core'); - } -} diff --git a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css b/modules/backend/formwidgets/sensitive/assets/css/sensitive.css deleted file mode 100644 index c8ac9378a..000000000 --- a/modules/backend/formwidgets/sensitive/assets/css/sensitive.css +++ /dev/null @@ -1,2 +0,0 @@ -div[data-control="sensitive"] a[data-toggle], -div[data-control="sensitive"] a[data-copy] {box-shadow:none;border:1px solid #d1d6d9;border-left:0} \ No newline at end of file diff --git a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js b/modules/backend/formwidgets/sensitive/assets/js/sensitive.js deleted file mode 100644 index 69251304c..000000000 --- a/modules/backend/formwidgets/sensitive/assets/js/sensitive.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Sensitive field widget plugin. - * - * Data attributes: - * - data-control="sensitive" - enables the plugin on an element - * - * JavaScript API: - * $('div#someElement').sensitive({...}) - */ -+function ($) { "use strict"; - var Base = $.oc.foundation.base, - BaseProto = Base.prototype - - var Sensitive = function(element, options) { - this.$el = $(element) - this.options = options - this.clean = Boolean(this.$el.data('clean')) - this.hidden = true - - this.$input = this.$el.find('[data-input]').first() - this.$toggle = this.$el.find('[data-toggle]').first() - this.$icon = this.$el.find('[data-icon]').first() - this.$loader = this.$el.find('[data-loader]').first() - this.$copy = this.$el.find('[data-copy]').first() - - $.oc.foundation.controlUtils.markDisposable(element) - Base.call(this) - this.init() - } - - Sensitive.DEFAULTS = { - readOnly: false, - disabled: false, - eventHandler: null, - hideOnTabChange: false, - } - - Sensitive.prototype = Object.create(BaseProto) - Sensitive.prototype.constructor = Sensitive - - Sensitive.prototype.init = function() { - this.$input.on('keydown', this.proxy(this.onInput)) - this.$toggle.on('click', this.proxy(this.onToggle)) - - if (this.options.hideOnTabChange) { - // Watch for tab change or minimise - document.addEventListener('visibilitychange', this.proxy(this.onTabChange)) - } - - if (this.$copy.length) { - this.$copy.on('click', this.proxy(this.onCopy)) - } - } - - Sensitive.prototype.dispose = function () { - this.$input.off('keydown', this.proxy(this.onInput)) - this.$toggle.off('click', this.proxy(this.onToggle)) - - if (this.options.hideOnTabChange) { - document.removeEventListener('visibilitychange', this.proxy(this.onTabChange)) - } - - if (this.$copy.length) { - this.$copy.off('click', this.proxy(this.onCopy)) - } - - this.$input = this.$toggle = this.$icon = this.$loader = null - this.$el = null - - BaseProto.dispose.call(this) - } - - Sensitive.prototype.onInput = function() { - if (this.clean) { - this.clean = false - this.$input.val('') - } - - return true - } - - Sensitive.prototype.onToggle = function() { - if (this.$input.val() !== '' && this.clean) { - this.reveal() - } else { - this.toggleVisibility() - } - - return true - } - - Sensitive.prototype.onTabChange = function() { - if (document.hidden && !this.hidden) { - this.toggleVisibility() - } - } - - Sensitive.prototype.onCopy = function() { - var that = this, - deferred = $.Deferred(), - isHidden = this.hidden - - deferred.then(function () { - if (that.hidden) { - that.toggleVisibility() - } - - that.$input.focus() - that.$input.select() - - try { - document.execCommand('copy') - } catch (err) { - } - - that.$input.blur() - if (isHidden) { - that.toggleVisibility() - } - }) - - if (this.$input.val() !== '' && this.clean) { - this.reveal(deferred) - } else { - deferred.resolve() - } - } - - Sensitive.prototype.toggleVisibility = function() { - if (this.hidden) { - this.$input.attr('type', 'text') - } else { - this.$input.attr('type', 'password') - } - - this.$icon.toggleClass('icon-eye icon-eye-slash') - - this.hidden = !this.hidden - } - - Sensitive.prototype.reveal = function(deferred) { - var that = this - this.$icon.css({ - visibility: 'hidden' - }) - this.$loader.removeClass('hide') - - this.$input.request(this.options.eventHandler, { - success: function (data) { - that.$input.val(data.value) - that.clean = false - - that.$icon.css({ - visibility: 'visible' - }) - that.$loader.addClass('hide') - - that.toggleVisibility() - - if (deferred) { - deferred.resolve() - } - } - }) - } - - var old = $.fn.sensitive - - $.fn.sensitive = function (option) { - var args = Array.prototype.slice.call(arguments, 1), result - this.each(function () { - var $this = $(this) - var data = $this.data('oc.sensitive') - var options = $.extend({}, Sensitive.DEFAULTS, $this.data(), typeof option == 'object' && option) - if (!data) $this.data('oc.sensitive', (data = new Sensitive(this, options))) - if (typeof option == 'string') result = data[option].apply(data, args) - if (typeof result != 'undefined') return false - }) - - return result ? result : this - } - - $.fn.sensitive.noConflict = function () { - $.fn.sensitive = old - return this - } - - $(document).render(function () { - $('[data-control="sensitive"]').sensitive() - }); - -}(window.jQuery); diff --git a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less b/modules/backend/formwidgets/sensitive/assets/less/sensitive.less deleted file mode 100644 index 5717658f2..000000000 --- a/modules/backend/formwidgets/sensitive/assets/less/sensitive.less +++ /dev/null @@ -1,10 +0,0 @@ -@import "../../../../assets/less/core/boot.less"; - -div[data-control="sensitive"] { - a[data-toggle], - a[data-copy] { - box-shadow: none; - border: 1px solid @input-group-addon-border-color; - border-left: 0; - } -} diff --git a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm b/modules/backend/formwidgets/sensitive/partials/_sensitive.htm deleted file mode 100644 index b913070d7..000000000 --- a/modules/backend/formwidgets/sensitive/partials/_sensitive.htm +++ /dev/null @@ -1,41 +0,0 @@ -
data-hide-on-tab-change="true" -> -
-
- previewMode): ?>disabled="disabled" - autocomplete="off" - data-input - /> - - - - - - - - -
-
- -
-
-
diff --git a/modules/backend/models/ExportModel.php b/modules/backend/models/ExportModel.php index e6a767e80..1a03a813b 100644 --- a/modules/backend/models/ExportModel.php +++ b/modules/backend/models/ExportModel.php @@ -5,7 +5,7 @@ use Lang; use Model; use Response; use League\Csv\Writer as CsvWriter; -use League\Csv\EscapeFormula as CsvEscapeFormula; +use October\Rain\Parse\League\EscapeFormula as CsvEscapeFormula; use ApplicationException; use SplTempFileObject; @@ -112,7 +112,8 @@ abstract class ExportModel extends Model $csv->setEscape($options['escape']); } - $csv->addFormatter(new CsvEscapeFormula()); + // Temporary until upgrading to league/csv >= 9.1.0 (will be $csv->addFormatter($formatter)) + $formatter = new CsvEscapeFormula(); /* * Add headers @@ -127,6 +128,10 @@ abstract class ExportModel extends Model */ foreach ($results as $result) { $data = $this->matchDataToColumns($result, $columns); + + // Temporary until upgrading to league/csv >= 9.1.0 + $data = $formatter($data); + $csv->insertOne($data); } diff --git a/modules/backend/models/ImportModel.php b/modules/backend/models/ImportModel.php index 09c1bf426..12f937a16 100644 --- a/modules/backend/models/ImportModel.php +++ b/modules/backend/models/ImportModel.php @@ -5,7 +5,6 @@ use Str; use Lang; use Model; use League\Csv\Reader as CsvReader; -use League\Csv\Statement as CsvStatement; /** * Model used for importing data @@ -109,6 +108,11 @@ abstract class ImportModel extends Model */ $reader = CsvReader::createFromPath($filePath, 'r'); + // Filter out empty rows + $reader->addFilter(function (array $row) { + return count($row) > 1 || reset($row) !== null; + }); + if ($options['delimiter'] !== null) { $reader->setDelimiter($options['delimiter']); } @@ -121,11 +125,15 @@ abstract class ImportModel extends Model $reader->setEscape($options['escape']); } + if ($options['firstRowTitles']) { + $reader->setOffset(1); + } + if ( $options['encoding'] !== null && - $reader->supportsStreamFilter() + $reader->isActiveStreamFilter() ) { - $reader->addStreamFilter(sprintf( + $reader->appendStreamFilter(sprintf( '%s%s:%s', TranscodeFilter::FILTER_NAME, strtolower($options['encoding']), @@ -133,19 +141,8 @@ abstract class ImportModel extends Model )); } - // Create reader statement - $stmt = (new CsvStatement) - ->where(function (array $row) { - // Filter out empty rows - return count($row) > 1 || reset($row) !== null; - }); - - if ($options['firstRowTitles']) { - $stmt = $stmt->offset(1); - } - $result = []; - $contents = $stmt->process($reader); + $contents = $reader->fetch(); foreach ($contents as $row) { $result[] = $this->processImportRow($row, $matches); } diff --git a/modules/backend/models/User.php b/modules/backend/models/User.php index 46259792b..650a1378e 100644 --- a/modules/backend/models/User.php +++ b/modules/backend/models/User.php @@ -27,8 +27,8 @@ class User extends UserBase public $rules = [ 'email' => 'required|between:6,255|email|unique:backend_users', 'login' => 'required|between:2,255|unique:backend_users', - 'password' => 'required:create|min:4|confirmed', - 'password_confirmation' => 'required_with:password|min:4' + 'password' => 'required:create|between:4,255|confirmed', + 'password_confirmation' => 'required_with:password|between:4,255' ]; /** diff --git a/modules/backend/routes.php b/modules/backend/routes.php index 0268f7efc..81904802f 100644 --- a/modules/backend/routes.php +++ b/modules/backend/routes.php @@ -25,7 +25,7 @@ App::before(function ($request) { 'middleware' => ['web'], 'prefix' => Config::get('cms.backendUri', 'backend') ], function () { - Route::any('{slug?}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); + Route::any('{slug}', 'Backend\Classes\BackendController@run')->where('slug', '(.*)?'); }) ; diff --git a/modules/cms/classes/Asset.php b/modules/cms/classes/Asset.php index 0bfc2eba8..12b79ea35 100644 --- a/modules/cms/classes/Asset.php +++ b/modules/cms/classes/Asset.php @@ -287,14 +287,25 @@ class Asset extends Extendable $directory = $this->theme->getPath() . '/' . $this->dirName . '/'; $filePath = $directory . $fileName; - $resolvedPath = resolve_path($filePath); + $path = realpath($filePath); + + /** + * If the path doesn't exist yet, then create it temporarily + * in order to run realpath() resolution on it to verify the + * final destination and then remove the temporary file. + */ + if (!$path) { + touch($filePath); + $path = realpath($filePath); + unlink($filePath); + } // Limit paths to those under the theme's assets directory - if (!starts_with($resolvedPath, $directory)) { + if (!starts_with($path, $directory)) { return false; } - return $resolvedPath; + return $path; } /** diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index fca40ff67..5705515bc 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -316,8 +316,7 @@ class CmsCompoundObject extends CmsObject self::$objectComponentPropertyMap = $objectComponentMap; - $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 10)); - Cache::put($key, base64_encode(serialize($objectComponentMap)), $expiresAt); + Cache::put($key, base64_encode(serialize($objectComponentMap)), Config::get('cms.parsedPageCacheTTL', 10)); if (array_key_exists($componentName, $objectComponentMap[$objectCode])) { return $objectComponentMap[$objectCode][$componentName]; diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index 714650150..798a065a0 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -227,16 +227,7 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->fileName; } - $directory = $this->theme->getPath() . '/' . $this->getObjectTypeDirName() . '/'; - $filePath = $directory . $fileName; - $resolvedPath = resolve_path($filePath); - - // Limit paths to those under the corresponding theme directory - if (!starts_with($resolvedPath, $directory)) { - return false; - } - - return $resolvedPath; + return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; } /** diff --git a/modules/cms/classes/CmsObjectCollection.php b/modules/cms/classes/CmsObjectCollection.php index 2105789ac..ffc7afc73 100644 --- a/modules/cms/classes/CmsObjectCollection.php +++ b/modules/cms/classes/CmsObjectCollection.php @@ -1,6 +1,5 @@ filter(function ($object) use ($property, $value, $strict) { + if (!array_key_exists($property, $object->settings)) { return false; } diff --git a/modules/cms/classes/CodeParser.php b/modules/cms/classes/CodeParser.php index b747a20e5..dd6687637 100644 --- a/modules/cms/classes/CodeParser.php +++ b/modules/cms/classes/CodeParser.php @@ -224,8 +224,7 @@ class CodeParser $cached = $this->getCachedInfo() ?: []; $cached[$this->filePath] = $cacheItem; - $expiresAt = now()->addMinutes(1440); - Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), $expiresAt); + Cache::put($this->dataCacheKey, base64_encode(serialize($cached)), 1440); self::$cache[$this->filePath] = $result; } diff --git a/modules/cms/classes/Router.php b/modules/cms/classes/Router.php index 4f75656f1..0c033fd81 100644 --- a/modules/cms/classes/Router.php +++ b/modules/cms/classes/Router.php @@ -127,11 +127,10 @@ class Router : $fileName; $key = $this->getUrlListCacheKey(); - $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); Cache::put( $key, base64_encode(serialize($urlList)), - $expiresAt + Config::get('cms.urlCacheTtl', 1) ); } } @@ -252,8 +251,7 @@ class Router $this->urlMap = $map; if ($cacheable) { - $expiresAt = now()->addMinutes(Config::get('cms.urlCacheTtl', 1)); - Cache::put($key, base64_encode(serialize($map)), $expiresAt); + Cache::put($key, base64_encode(serialize($map)), Config::get('cms.urlCacheTtl', 1)); } return false; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index 6ce06a1c5..a5306c897 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -158,8 +158,7 @@ class Theme if ($checkDatabase && App::hasDatabase()) { try { try { - $expiresAt = now()->addMinutes(1440); - $dbResult = Cache::remember(self::ACTIVE_KEY, $expiresAt, function () { + $dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () { return Parameter::applyKey(self::ACTIVE_KEY)->value('value'); }); } diff --git a/modules/cms/composer.json b/modules/cms/composer.json index 9e4cfa70e..49e2d944b 100644 --- a/modules/cms/composer.json +++ b/modules/cms/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/cms/routes.php b/modules/cms/routes.php index 1aaf5ec5d..76f35c4b3 100644 --- a/modules/cms/routes.php +++ b/modules/cms/routes.php @@ -22,7 +22,7 @@ App::before(function ($request) { * The CMS module intercepts all URLs that were not * handled by the back-end modules. */ - Route::any('{slug?}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); + Route::any('{slug}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web'); /** * @event cms.route diff --git a/modules/cms/traits/UrlMaker.php b/modules/cms/traits/UrlMaker.php index ebe0c050f..210922cfa 100644 --- a/modules/cms/traits/UrlMaker.php +++ b/modules/cms/traits/UrlMaker.php @@ -190,8 +190,7 @@ trait UrlMaker 'mtime' => @File::lastModified($filePath) ]; - $expiresAt = now()->addMinutes(Config::get('cms.parsedPageCacheTTL', 1440)); - Cache::put($key, serialize($cached), $expiresAt); + Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440)); return static::$urlPageName = $baseFileName; } diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php index f974bb8e2..242eb23a5 100644 --- a/modules/cms/twig/DebugExtension.php +++ b/modules/cms/twig/DebugExtension.php @@ -8,7 +8,7 @@ use Cms\Classes\Controller; use Cms\Classes\ComponentBase; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Illuminate\Support\Debug\HtmlDumper; use Symfony\Component\VarDumper\Cloner\VarCloner; use October\Rain\Database\Model; diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index eb5b6c613..9fc858f39 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -94,7 +94,6 @@ class ServiceProvider extends ModuleServiceProvider } } - Paginator::useBootstrapThree(); Paginator::defaultSimpleView('system::pagination.simple-default'); /* diff --git a/modules/system/aliases.php b/modules/system/aliases.php index d6364cd13..bcb2c78b2 100644 --- a/modules/system/aliases.php +++ b/modules/system/aliases.php @@ -16,6 +16,7 @@ return [ 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 'Event' => Illuminate\Support\Facades\Event::class, 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Input' => Illuminate\Support\Facades\Input::class, 'Lang' => Illuminate\Support\Facades\Lang::class, 'Log' => Illuminate\Support\Facades\Log::class, 'Mail' => Illuminate\Support\Facades\Mail::class, @@ -29,6 +30,7 @@ return [ 'Storage' => Illuminate\Support\Facades\Storage::class, 'Url' => Illuminate\Support\Facades\URL::class, // Preferred 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, /* @@ -40,7 +42,6 @@ return [ 'Config' => October\Rain\Support\Facades\Config::class, 'Seeder' => October\Rain\Database\Updates\Seeder::class, 'Flash' => October\Rain\Support\Facades\Flash::class, - 'Input' => October\Rain\Support\Facades\Input::class, 'Form' => October\Rain\Support\Facades\Form::class, 'Html' => October\Rain\Support\Facades\Html::class, 'Http' => October\Rain\Support\Facades\Http::class, @@ -51,7 +52,6 @@ return [ 'Twig' => October\Rain\Support\Facades\Twig::class, 'DbDongle' => October\Rain\Support\Facades\DbDongle::class, 'Schema' => October\Rain\Support\Facades\Schema::class, - 'Validator' => October\Rain\Support\Facades\Validator::class, 'Cms' => Cms\Facades\Cms::class, 'Backend' => Backend\Facades\Backend::class, 'BackendMenu' => Backend\Facades\BackendMenu::class, @@ -60,12 +60,4 @@ return [ 'SystemException' => October\Rain\Exception\SystemException::class, 'ApplicationException' => October\Rain\Exception\ApplicationException::class, 'ValidationException' => October\Rain\Exception\ValidationException::class, - - /* - * Fallback aliases - */ - // Input facade was removed in Laravel 6 - we are keeping it in the Rain library for backwards compatibility. - 'Illuminate\Support\Facades\Input' => October\Rain\Support\Facades\Input::class, - // Illuminate's HtmlDumper was "dumped" in Laravel 6 - we'll route this to Symfony's HtmlDumper as Laravel have done. - 'Illuminate\Support\Debug\HtmlDumper' => Symfony\Component\VarDumper\Dumper\HtmlDumper::class, ]; diff --git a/modules/system/classes/CombineAssets.php b/modules/system/classes/CombineAssets.php index 17ec7c2bd..50a03c292 100644 --- a/modules/system/classes/CombineAssets.php +++ b/modules/system/classes/CombineAssets.php @@ -10,11 +10,11 @@ use Route; use Config; use Request; use Response; -use October\Rain\Assetic\Asset\FileAsset; -use October\Rain\Assetic\Asset\AssetCache; -use October\Rain\Assetic\Asset\AssetCollection; -use October\Rain\Assetic\Cache\FilesystemCache; -use October\Rain\Assetic\Factory\AssetFactory; +use Assetic\Asset\FileAsset; +use Assetic\Asset\AssetCache; +use Assetic\Asset\AssetCollection; +use Assetic\Factory\AssetFactory; +use October\Rain\Parse\Assetic\FilesystemCache; use System\Helpers\Cache as CacheHelper; use ApplicationException; use DateTime; @@ -126,22 +126,22 @@ class CombineAssets /* * Register JavaScript filters */ - $this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter); + $this->registerFilter('js', new \October\Rain\Parse\Assetic\JavascriptImporter); /* * Register CSS filters */ - $this->registerFilter('css', new \October\Rain\Assetic\Filter\CssImportFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\CssRewriteFilter); - $this->registerFilter('less', new \October\Rain\Assetic\Filter\LessCompiler); - $this->registerFilter('scss', new \October\Rain\Assetic\Filter\ScssCompiler); + $this->registerFilter('css', new \Assetic\Filter\CssImportFilter); + $this->registerFilter(['css', 'less', 'scss'], new \Assetic\Filter\CssRewriteFilter); + $this->registerFilter('less', new \October\Rain\Parse\Assetic\LessCompiler); + $this->registerFilter('scss', new \October\Rain\Parse\Assetic\ScssCompiler); /* * Minification filters */ if ($this->useMinify) { - $this->registerFilter('js', new \October\Rain\Assetic\Filter\JSMinFilter); - $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Assetic\Filter\StylesheetMinify); + $this->registerFilter('js', new \Assetic\Filter\JSMinFilter); + $this->registerFilter(['css', 'less', 'scss'], new \October\Rain\Parse\Assetic\StylesheetMinify); } /* diff --git a/modules/system/classes/MediaLibrary.php b/modules/system/classes/MediaLibrary.php index 40808854e..50a6cc8f9 100644 --- a/modules/system/classes/MediaLibrary.php +++ b/modules/system/classes/MediaLibrary.php @@ -134,11 +134,10 @@ class MediaLibrary $folderContents = $this->scanFolderContents($fullFolderPath); $cached[$fullFolderPath] = $folderContents; - $expiresAt = now()->addMinutes(Config::get('cms.storage.media.ttl', 10)); Cache::put( $this->cacheKey, base64_encode(serialize($cached)), - $expiresAt + Config::get('cms.storage.media.ttl', 10) ); } diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 1b9c3618f..7e4779de1 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -29,6 +29,11 @@ class UpdateManager { use \October\Rain\Support\Traits\Singleton; + /** + * @var array The notes for the current operation. + */ + protected $notes = []; + /** * @var \Illuminate\Console\OutputStyle */ @@ -340,13 +345,13 @@ class UpdateManager /* * Rollback modules */ - if (isset($this->notesOutput)) { - $this->migrator->setOutput($this->notesOutput); - } - while (true) { $rolledBack = $this->migrator->rollback($paths, ['pretend' => false]); + foreach ($this->migrator->getNotes() as $note) { + $this->note($note); + } + if (count($rolledBack) == 0) { break; } @@ -398,13 +403,13 @@ class UpdateManager */ public function migrateModule($module) { - if (isset($this->notesOutput)) { - $this->migrator->setOutput($this->notesOutput); - } + $this->migrator->run(base_path() . '/modules/' . strtolower($module) . '/database/migrations'); $this->note($module); - $this->migrator->run(base_path() . '/modules/'.strtolower($module).'/database/migrations'); + foreach ($this->migrator->getNotes() as $note) { + $this->note(' - ' . $note); + } return $this; } @@ -513,9 +518,13 @@ class UpdateManager $this->note($name); - $this->versionManager->setNotesOutput($this->notesOutput); + $this->versionManager->resetNotes()->setNotesOutput($this->notesOutput); - $this->versionManager->updatePlugin($plugin); + if ($this->versionManager->updatePlugin($plugin) !== false) { + foreach ($this->versionManager->getNotes() as $note) { + $this->note($note); + } + } return $this; } @@ -704,8 +713,7 @@ class UpdateManager } $data = $this->requestServerData($type . '/popular'); - $expiresAt = now()->addMinutes(60); - Cache::put($cacheKey, base64_encode(serialize($data)), $expiresAt); + Cache::put($cacheKey, base64_encode(serialize($data)), 60); foreach ($data as $product) { $code = array_get($product, 'code', -1); @@ -794,11 +802,35 @@ class UpdateManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); + } else { + $this->notes[] = $message; } return $this; } + /** + * Get the notes for the last operation. + * @return array + */ + public function getNotes() + { + return $this->notes; + } + + /** + * Resets the notes store. + * @return self + */ + public function resetNotes() + { + $this->notesOutput = null; + + $this->notes = []; + + return $this; + } + /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index 11d425d9a..77127cf12 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -29,6 +29,12 @@ class VersionManager const HISTORY_TYPE_COMMENT = 'comment'; const HISTORY_TYPE_SCRIPT = 'script'; + /** + * The notes for the current operation. + * @var array + */ + protected $notes = []; + /** * @var \Illuminate\Console\OutputStyle */ @@ -420,7 +426,6 @@ class VersionManager * Execute the database PHP script */ $updateFile = $this->pluginManager->getPluginPath($code) . '/updates/' . $script; - $this->updater->packDown($updateFile); Db::table('system_plugin_history') @@ -503,11 +508,35 @@ class VersionManager { if ($this->notesOutput !== null) { $this->notesOutput->writeln($message); + } else { + $this->notes[] = $message; } return $this; } + /** + * Get the notes for the last operation. + * @return array + */ + public function getNotes() + { + return $this->notes; + } + + /** + * Resets the notes store. + * @return self + */ + public function resetNotes() + { + $this->notesOutput = null; + + $this->notes = []; + + return $this; + } + /** * Sets an output stream for writing notes. * @param Illuminate\Console\Command $output @@ -521,7 +550,8 @@ class VersionManager } /** - * Extract script and comments from version details + * @param $details + * * @return array */ protected function extractScriptsAndComments($details): array @@ -536,8 +566,7 @@ class VersionManager $scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) { return preg_match($fileNamePattern, $detail); })); - } - else { + } else { $comments = (array)$details; $scripts = []; } diff --git a/modules/system/composer.json b/modules/system/composer.json index 81d59a4dc..5bebae7be 100644 --- a/modules/system/composer.json +++ b/modules/system/composer.json @@ -8,13 +8,11 @@ "authors": [ { "name": "Alexey Bobkov", - "email": "aleksey.bobkov@gmail.com", - "role": "Co-founder" + "email": "aleksey.bobkov@gmail.com" }, { "name": "Samuel Georges", - "email": "daftspunky@gmail.com", - "role": "Co-founder" + "email": "daftspunky@gmail.com" }, { "name": "Luke Towers", @@ -24,10 +22,9 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.0", "composer/installers": "~1.0", - "october/rain": "~1.0", - "laravel/framework": "~6.0" + "october/rain": "~1.0" }, "autoload": { "psr-4": { diff --git a/modules/system/console/OctoberEnv.php b/modules/system/console/OctoberEnv.php index 43f2079bf..cc735c2c4 100644 --- a/modules/system/console/OctoberEnv.php +++ b/modules/system/console/OctoberEnv.php @@ -369,7 +369,7 @@ class OctoberEnv extends Command 'SESSION_DRIVER' => 'driver', ], 'queue' => [ - 'QUEUE_CONNECTION' => 'default', + 'QUEUE_DRIVER' => 'default', ], 'mail' => [ 'MAIL_DRIVER' => 'driver', diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index 200dcd221..b826fe305 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption; */ class OctoberUpdate extends Command { + /** * The console command name. */ diff --git a/modules/system/controllers/Settings.php b/modules/system/controllers/Settings.php index 719dff20e..7ba29dea7 100644 --- a/modules/system/controllers/Settings.php +++ b/modules/system/controllers/Settings.php @@ -2,8 +2,6 @@ use Lang; use Flash; -use Config; -use Request; use Backend; use BackendMenu; use System\Classes\SettingsManager; @@ -141,22 +139,6 @@ class Settings extends Controller return $this->formWidget->render($options); } - /** - * Returns the form widget used by this behavior. - * - * @return \Backend\Widgets\Form - */ - public function formGetWidget() - { - if (is_null($this->formWidget)) { - $item = $this->findSettingItem(); - $model = $this->createModel($item); - $this->initWidgets($model); - } - - return $this->formWidget; - } - /** * Prepare the widgets used by this action * Model $model @@ -187,22 +169,10 @@ class Settings extends Controller } /** - * Locates a setting item for a module or plugin. - * - * If none of the parameters are provided, they will be auto-guessed from the URL. - * - * @param string|null $author - * @param string|null $plugin - * @param string|null $code - * - * @return array + * Locates a setting item for a module or plugin */ - protected function findSettingItem($author = null, $plugin = null, $code = null) + protected function findSettingItem($author, $plugin, $code) { - if (is_null($author) || is_null($plugin)) { - [$author, $plugin, $code] = $this->guessSettingItem(); - } - $manager = SettingsManager::instance(); $moduleOwner = $author; @@ -217,23 +187,4 @@ class Settings extends Controller return $item; } - - /** - * Guesses the requested setting item from the current URL segments provided by the Request object. - * - * @return array - */ - protected function guessSettingItem() - { - $segments = Request::segments(); - - if (!empty(Config::get('cms.backendUri', 'backend'))) { - array_splice($segments, 0, 4); - } else { - array_splice($segments, 0, 3); - } - - // Ensure there's at least 3 segments - return array_pad($segments, 3, null); - } } diff --git a/modules/system/database/seeds/DatabaseSeeder.php b/modules/system/database/seeds/DatabaseSeeder.php index 7dec41339..f2f924b13 100644 --- a/modules/system/database/seeds/DatabaseSeeder.php +++ b/modules/system/database/seeds/DatabaseSeeder.php @@ -13,8 +13,8 @@ class DatabaseSeeder extends Seeder */ public function run() { - Eloquent::unguarded(function () { - $this->call('System\Database\Seeds\SeedSetupMailLayouts'); - }); + Eloquent::unguard(); + + $this->call('System\Database\Seeds\SeedSetupMailLayouts'); } } diff --git a/modules/system/lang/en/validation.php b/modules/system/lang/en/validation.php index ad6561c9e..edc036dd0 100644 --- a/modules/system/lang/en/validation.php +++ b/modules/system/lang/en/validation.php @@ -32,7 +32,6 @@ return [ 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', - 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', @@ -40,22 +39,9 @@ return [ 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', - 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', - 'gt' => [ - 'numeric' => 'The :attribute must be greater than :value.', - 'file' => 'The :attribute must be greater than :value kilobytes.', - 'string' => 'The :attribute must be greater than :value characters.', - 'array' => 'The :attribute must have more than :value items.', - ], - 'gte' => [ - 'numeric' => 'The :attribute must be greater than or equal :value.', - 'file' => 'The :attribute must be greater than or equal :value kilobytes.', - 'string' => 'The :attribute must be greater than or equal :value characters.', - 'array' => 'The :attribute must have :value items or more.', - ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', @@ -64,18 +50,6 @@ return [ 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', - 'lt' => [ - 'numeric' => 'The :attribute must be less than :value.', - 'file' => 'The :attribute must be less than :value kilobytes.', - 'string' => 'The :attribute must be less than :value characters.', - 'array' => 'The :attribute must have less than :value items.', - ], - 'lte' => [ - 'numeric' => 'The :attribute must be less than or equal :value.', - 'file' => 'The :attribute must be less than or equal :value kilobytes.', - 'string' => 'The :attribute must be less than or equal :value characters.', - 'array' => 'The :attribute must not have more than :value items.', - ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', @@ -91,7 +65,6 @@ return [ 'array' => 'The :attribute must have at least :min items.', ], 'not_in' => 'The selected :attribute is invalid.', - 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', @@ -109,13 +82,11 @@ return [ 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], - 'starts_with' => 'The :attribute must start with one of the following: :values.', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', - 'uuid' => 'The :attribute must be a valid UUID.', /* |-------------------------------------------------------------------------- diff --git a/modules/system/models/mailsetting/fields.yaml b/modules/system/models/mailsetting/fields.yaml index d49851cba..c2457ec38 100644 --- a/modules/system/models/mailsetting/fields.yaml +++ b/modules/system/models/mailsetting/fields.yaml @@ -79,7 +79,6 @@ tabs: smtp_password: label: system::lang.mail.smtp_password tab: system::lang.mail.general - type: sensitive span: right trigger: action: show @@ -108,7 +107,6 @@ tabs: label: system::lang.mail.mailgun_secret commentAbove: system::lang.mail.mailgun_secret_comment tab: system::lang.mail.general - type: sensitive trigger: action: show field: send_mode @@ -118,7 +116,6 @@ tabs: label: system::lang.mail.mandrill_secret commentAbove: system::lang.mail.mandrill_secret_comment tab: system::lang.mail.general - type: sensitive trigger: action: show field: send_mode @@ -138,7 +135,6 @@ tabs: label: system::lang.mail.ses_secret commentAbove: system::lang.mail.ses_secret_comment tab: system::lang.mail.general - type: sensitive span: right trigger: action: show @@ -158,7 +154,6 @@ tabs: sparkpost_secret: label: system::lang.mail.sparkpost_secret commentAbove: system::lang.mail.sparkpost_secret_comment - type: sensitive tab: system::lang.mail.general trigger: action: show diff --git a/modules/system/providers.php b/modules/system/providers.php index 96951f747..7b63edb43 100644 --- a/modules/system/providers.php +++ b/modules/system/providers.php @@ -15,7 +15,9 @@ return [ Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Laravel\Tinker\TinkerServiceProvider::class, @@ -34,7 +36,5 @@ return [ October\Rain\Flash\FlashServiceProvider::class, October\Rain\Mail\MailServiceProvider::class, October\Rain\Argon\ArgonServiceProvider::class, - October\Rain\Redis\RedisServiceProvider::class, - October\Rain\Validation\ValidationServiceProvider::class, ]; diff --git a/phpunit.xml b/phpunit.xml index de01655c0..08cd19d58 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,6 +8,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" + syntaxCheck="false" > diff --git a/tests/PluginTestCase.php b/tests/PluginTestCase.php index 1d4a66f00..6618b17e0 100644 --- a/tests/PluginTestCase.php +++ b/tests/PluginTestCase.php @@ -28,7 +28,7 @@ abstract class PluginTestCase extends TestCase $app['cache']->setDefaultDriver('array'); $app->setLocale('en'); - $app->singleton('backend.auth', function ($app) { + $app->singleton('auth', function ($app) { $app['auth.loaded'] = true; return AuthManager::instance(); @@ -67,7 +67,7 @@ abstract class PluginTestCase extends TestCase * Perform test case set up. * @return void */ - public function setUp() : void + public function setUp() { /* * Force reload of October singletons @@ -105,7 +105,7 @@ abstract class PluginTestCase extends TestCase * Flush event listeners and collect garbage. * @return void */ - public function tearDown() : void + public function tearDown() { $this->flushModelEventListeners(); parent::tearDown(); diff --git a/tests/README.md b/tests/README.md index 0e985d79b..a9e7b78b5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ # Plugin testing -Individual plugin test cases can be run by running `../../../vendor/bin/phpunit` in the plugin's base directory (ex. `plugins/acme/demo`. +Plugin unit tests can be performed by running `phpunit` in the base plugin directory. ### Creating plugin tests @@ -58,7 +58,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci class BaseTestCase extends PluginTestCase { - public function setUp(): void + public function setUp() { parent::setUp(); @@ -72,7 +72,7 @@ The test class should extend the base class `PluginTestCase` and this is a speci $pluginManager->bootAll(true); } - public function tearDown(): void + public function tearDown() { parent::tearDown(); @@ -96,10 +96,39 @@ To perform unit testing on the core October files, you should download a develop ### Unit tests -Unit tests can be performed by running `vendor/bin/phpunit` in the root directory of your October CMS installation. +Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`. ### Functional tests -Functional tests can be performed by installing the [RainLab Dusk](https://octobercms.com/plugin/rainlab-dusk) in your October CMS installation. The RainLab Dusk plugin is powered by Laravel Dusk, a comprehensive testing suite for the Laravel framework that is designed to test interactions with a fully operational October CMS instance through a virtual browser. +Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met: -For information on installing and setting up your October CMS install to run functional tests, please review the [README](https://github.com/rainlab/dusk-plugin/blob/master/README.md) for the plugin. +- Active theme is `demo` +- Language preference is `en` + +#### Selenium set up + +1. Download latest Java SE from http://java.sun.com/ and install +1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/). +1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance. +1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`. + +#### Selenium configuration + +Create a new file `selenium.php` in the root directory, add the following content: + + markTestSkipped('Selenium skipped'); + } + + if (defined('TEST_SELENIUM_HOST')) { + $this->setHost(TEST_SELENIUM_HOST); + } + if (defined('TEST_SELENIUM_PORT')) { + $this->setPort(TEST_SELENIUM_PORT); + } + if (defined('TEST_SELENIUM_BROWSER')) { + $this->setBrowser(TEST_SELENIUM_BROWSER); + } + $this->setBrowserUrl(TEST_SELENIUM_URL); + } + + // + // OctoberCMS Helpers + // + + protected function signInToBackend() + { + $this->open('backend'); + $this->type("name=login", TEST_SELENIUM_USER); + $this->type("name=password", TEST_SELENIUM_PASS); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + } + + /** + * Similar to the native getConfirmation() function + */ + protected function getSweetConfirmation($expectedText = null, $clickOk = true) + { + $this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]"); + + if ($expectedText) { + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText); + } + + $this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK"); + + if ($clickOk) { + $this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]"); + } + } + + // + // Selenium helpers + // + + protected function waitForElementPresent($target, $timeout = 60) + { + $second = 0; + + while (true) { + if ($second >= $timeout) { + $this->fail('timeout'); + } + + try { + if ($this->isElementPresent($target)) { + break; + } + } + catch (Exception $e) { + } + + sleep(1); + ++$second; + } + } + + protected function waitForElementNotPresent($target, $timeout = 60) + { + $second = 0; + + while (true) { + if ($second >= $timeout) { + $this->fail('timeout'); + } + + try { + if (!$this->isElementPresent($target)) { + break; + } + } + catch (Exception $e) { + } + + sleep(1); + ++$second; + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5ed3e1296..e7e53fd1a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -19,3 +19,14 @@ $loader->addDirectories([ 'modules', 'plugins' ]); + +/* + * Monkey patch PHPUnit\Framework\MockObject\Generator to avoid + * "Function ReflectionType::__toString() is deprecated" warnings + */ +$generatorPatchPath = __DIR__ . '/resources/patches/php-generator-7.php'; +$generatorSourcePath = __DIR__ . '/../vendor/phpunit/phpunit-mock-objects/src/Generator.php'; + +if (file_exists($generatorSourcePath)) { + file_put_contents($generatorSourcePath, file_get_contents($generatorPatchPath)); +} diff --git a/tests/concerns/InteractsWithAuthentication.php b/tests/concerns/InteractsWithAuthentication.php index 5f5c3c8c7..a7950f230 100644 --- a/tests/concerns/InteractsWithAuthentication.php +++ b/tests/concerns/InteractsWithAuthentication.php @@ -29,7 +29,7 @@ trait InteractsWithAuthentication */ public function be(UserContract $user, $driver = null) { - $this->app['backend.auth']->setUser($user); + $this->app['auth']->setUser($user); } /** @@ -66,7 +66,7 @@ trait InteractsWithAuthentication */ protected function isAuthenticated($guard = null) { - return $this->app->make('backend.auth')->guard($guard)->check(); + return $this->app->make('auth')->guard($guard)->check(); } /** @@ -78,7 +78,7 @@ trait InteractsWithAuthentication */ public function assertAuthenticatedAs($user, $guard = null) { - $expected = $this->app->make('backend.auth')->guard($guard)->user(); + $expected = $this->app->make('auth')->guard($guard)->user(); $this->assertNotNull($expected, 'The current user is not authenticated.'); @@ -140,7 +140,7 @@ trait InteractsWithAuthentication */ protected function hasCredentials(array $credentials, $guard = null) { - $provider = $this->app->make('backend.auth')->guard($guard)->getProvider(); + $provider = $this->app->make('auth')->guard($guard)->getProvider(); $user = $provider->retrieveByCredentials($credentials); diff --git a/tests/fixtures/plugins/database/tester/models/Author.php b/tests/fixtures/plugins/database/tester/models/Author.php index 2d9656fee..05e9a039f 100644 --- a/tests/fixtures/plugins/database/tester/models/Author.php +++ b/tests/fixtures/plugins/database/tester/models/Author.php @@ -20,7 +20,6 @@ class Author extends Model */ public $belongsTo = [ 'user' => ['Database\Tester\Models\User', 'delete' => true], - 'country' => ['Database\Tester\Models\Country'], 'user_soft' => ['Database\Tester\Models\SoftDeleteUser', 'key' => 'user_id', 'softDelete' => true], ]; diff --git a/tests/fixtures/plugins/database/tester/models/Country.php b/tests/fixtures/plugins/database/tester/models/Country.php deleted file mode 100644 index e7bb583c5..000000000 --- a/tests/fixtures/plugins/database/tester/models/Country.php +++ /dev/null @@ -1,35 +0,0 @@ - [ - 'Database\Tester\Models\User', - ], - ]; - - public $hasManyThrough = [ - 'posts' => [ - 'Database\Tester\Models\Post', - 'through' => 'Database\Tester\Models\Author', - ] - ]; -} - -class SoftDeleteCountry extends Country -{ - use \October\Rain\Database\Traits\SoftDelete; -} diff --git a/tests/fixtures/plugins/database/tester/models/User.php b/tests/fixtures/plugins/database/tester/models/User.php index 130120e9c..867992c2c 100644 --- a/tests/fixtures/plugins/database/tester/models/User.php +++ b/tests/fixtures/plugins/database/tester/models/User.php @@ -17,19 +17,6 @@ class User extends Model /** * @var array Relations */ - public $hasOne = [ - 'author' => [ - 'Database\Tester\Models\Author', - ] - ]; - - public $hasOneThrough = [ - 'phone' => [ - 'Database\Tester\Models\Phone', - 'through' => 'Database\Tester\Models\Author', - ], - ]; - public $attachOne = [ 'avatar' => 'System\Models\File' ]; diff --git a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php index d6277e525..6466e4e13 100644 --- a/tests/fixtures/plugins/database/tester/updates/create_authors_table.php +++ b/tests/fixtures/plugins/database/tester/updates/create_authors_table.php @@ -11,7 +11,6 @@ class CreateAuthorsTable extends Migration $table->engine = 'InnoDB'; $table->increments('id'); $table->integer('user_id')->unsigned()->index()->nullable(); - $table->integer('country_id')->unsigned()->index()->nullable(); $table->string('name')->nullable(); $table->string('email')->nullable(); $table->softDeletes(); diff --git a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php b/tests/fixtures/plugins/database/tester/updates/create_countries_table.php deleted file mode 100644 index 7fc85ae54..000000000 --- a/tests/fixtures/plugins/database/tester/updates/create_countries_table.php +++ /dev/null @@ -1,23 +0,0 @@ -engine = 'InnoDB'; - $table->increments('id'); - $table->string('name')->nullable(); - $table->softDeletes(); - $table->timestamps(); - }); - } - - public function down() - { - Schema::dropIfExists('database_tester_countries'); - } -} diff --git a/tests/fixtures/plugins/database/tester/updates/version.yaml b/tests/fixtures/plugins/database/tester/updates/version.yaml index 590bb7892..613fc5337 100644 --- a/tests/fixtures/plugins/database/tester/updates/version.yaml +++ b/tests/fixtures/plugins/database/tester/updates/version.yaml @@ -9,4 +9,3 @@ - create_users_table.php - create_event_log_table.php - create_meta_table.php - - create_countries_table.php diff --git a/tests/fixtures/themes/test/assets/js/script1.js b/tests/fixtures/themes/test/assets/js/script1.js index b8bbe4e13..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script1.js +++ b/tests/fixtures/themes/test/assets/js/script1.js @@ -1 +0,0 @@ -console.log('script1.js'); diff --git a/tests/fixtures/themes/test/assets/js/script2.js b/tests/fixtures/themes/test/assets/js/script2.js index 54e00dea3..e69de29bb 100644 --- a/tests/fixtures/themes/test/assets/js/script2.js +++ b/tests/fixtures/themes/test/assets/js/script2.js @@ -1 +0,0 @@ -console.log('script2.js'); diff --git a/tests/fixtures/themes/test/assets/js/subdir/script1.js b/tests/fixtures/themes/test/assets/js/subdir/script1.js deleted file mode 100644 index bc27e099c..000000000 --- a/tests/fixtures/themes/test/assets/js/subdir/script1.js +++ /dev/null @@ -1 +0,0 @@ -console.log('subdir/script1.js'); diff --git a/tests/functional/backend/AuthTest.php b/tests/functional/backend/AuthTest.php new file mode 100644 index 000000000..4c1fe925d --- /dev/null +++ b/tests/functional/backend/AuthTest.php @@ -0,0 +1,90 @@ +open('backend'); + + $cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a'; + + try { + $this->assertTitle('Administration Area'); + $this->assertTrue($this->isElementPresent("name=login")); + $this->assertTrue($this->isElementPresent("name=password")); + $this->assertTrue($this->isElementPresent("//button[@type='submit']")); + $this->verifyText("//button[@type='submit']", "Login"); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + /* + * Sign in + */ + $this->type("name=login", TEST_SELENIUM_USER); + $this->type("name=password", TEST_SELENIUM_PASS); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Dashboard | October CMS'); + $this->assertTrue($this->isElementPresent('css='.$cssLogoutLink)); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->verifyText('css='.$cssLogoutLink, "Sign out"); + + /* + * Log out + */ + $this->click('css='.$cssLogoutLink); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Administration Area'); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + } + + public function testPasswordReset() + { + $this->open('backend'); + + try { + $this->assertTrue($this->isElementPresent("link=exact:Forgot your password?")); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->click('link=exact:Forgot your password?'); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTrue($this->isElementPresent("//button[@type='submit']")); + $this->verifyText("//button[@type='submit']", "Restore"); + $this->assertTrue($this->isElementPresent("link=Cancel")); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + + $this->type("name=login", TEST_SELENIUM_USER); + sleep(1); + $this->click("//button[@type='submit']"); + $this->waitForPageToLoad("30000"); + + try { + $this->assertTitle('Administration Area'); + $this->assertTrue($this->isElementPresent("css=p.flash-message.success")); + $this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×"); + } + catch (PHPUnit_Framework_AssertionFailedError $e) { + array_push($this->verificationErrors, $e->toString()); + } + } +} diff --git a/tests/functional/cms/TemplateTest.php b/tests/functional/cms/TemplateTest.php new file mode 100644 index 000000000..5ff2a0a9e --- /dev/null +++ b/tests/functional/cms/TemplateTest.php @@ -0,0 +1,143 @@ +signInToBackend(); + $this->open('cms'); + $this->waitForPageToLoad("30000"); + + // Fix the sidebar + $this->click("xpath=(//a[@class='fix-button'])[1]"); + + /* + * Page + */ + + // Create a new page + $this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=settings[title]"); + + // Populate page details + $this->type('name=settings[title]', 'Functional Test Page'); + $this->type('name=settings[url]', '/xxx/functional/test/page'); + $this->type('name=fileName', 'xxx_functional_test_page'); + + // Save the new page + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]"); + $this->waitForElementPresent("name=settings[title]"); + sleep(1); + + // Delete the page + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this page?'); + // $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation())); + $this->waitForElementNotPresent("name=settings[title]"); + + /* + * Partial + */ + + // Click partials menu item + $this->click("xpath=(//li[@data-menu-item='partials']/a)[1]"); + + // Create a new partial + $this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate partial details + $this->type('name=fileName', 'xxx_functional_test_partial'); + $this->type('name=settings[description]', 'Test partial'); + + // Save the new partial + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the partial + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this partial?'); + $this->waitForElementNotPresent("name=fileName"); + + /* + * Layout + */ + + // Click layouts menu item + $this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]"); + + // Create a new layout + $this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate layout details + $this->type('name=fileName', 'xxx_functional_test_layout'); + $this->type('name=settings[description]', 'Test layout'); + + // Save the new layout + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the layout + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this layout?'); + $this->waitForElementNotPresent("name=fileName"); + + /* + * Content + */ + + // Click contents menu item + $this->click("xpath=(//li[@data-menu-item='content']/a)[1]"); + + // Create a new content + $this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]"); + $this->waitForElementPresent("name=fileName"); + + // Populate content details + $this->type('name=fileName', 'xxx_functional_test_content.txt'); + + // Save the new content + $this->click("xpath=(//a[@data-request='onSave'])[1]"); + $this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]"); + + // Close the tab + $this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]"); + + // Reopen the tab + $this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); + $this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]"); + $this->waitForElementPresent("name=fileName"); + sleep(1); + + // Delete the content + $this->click("xpath=(//button[@data-request='onDelete'])[1]"); + $this->getSweetConfirmation('Do you really want delete this content file?'); + $this->waitForElementNotPresent("name=fileName"); + } +} diff --git a/tests/functional/phpunit.xml b/tests/functional/phpunit.xml new file mode 100644 index 000000000..c8043149c --- /dev/null +++ b/tests/functional/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./ + + + \ No newline at end of file diff --git a/tests/resources/patches/php-generator-7.php b/tests/resources/patches/php-generator-7.php new file mode 100644 index 000000000..677680d69 --- /dev/null +++ b/tests/resources/patches/php-generator-7.php @@ -0,0 +1,1185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Patched with: https://github.com/sebastianbergmann/phpunit/pull/3765/files + */ +namespace PHPUnit\Framework\MockObject; + +use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; +use Doctrine\Instantiator\Instantiator; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\Exception; +use PHPUnit\Util\InvalidArgumentHelper; +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use SoapClient; +use Text_Template; +use Traversable; + +/** + * Mock Object Code Generator + */ +class Generator +{ + /** + * @var array + */ + private static $cache = []; + + /** + * @var Text_Template[] + */ + private static $templates = []; + + /** + * @var array + */ + private $blacklistedMethodNames = [ + '__CLASS__' => true, + '__DIR__' => true, + '__FILE__' => true, + '__FUNCTION__' => true, + '__LINE__' => true, + '__METHOD__' => true, + '__NAMESPACE__' => true, + '__TRAIT__' => true, + '__clone' => true, + '__halt_compiler' => true, + ]; + + /** + * Returns a mock object for the specified class. + * + * @param string|string[] $type + * @param array $methods + * @param array $arguments + * @param string $mockClassName + * @param bool $callOriginalConstructor + * @param bool $callOriginalClone + * @param bool $callAutoload + * @param bool $cloneArguments + * @param bool $callOriginalMethods + * @param object $proxyTarget + * @param bool $allowMockingUnknownTypes + * + * @return MockObject + * + * @throws Exception + * @throws RuntimeException + * @throws \PHPUnit\Framework\Exception + * @throws \ReflectionException + */ + public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true) + { + if (!\is_array($type) && !\is_string($type)) { + throw InvalidArgumentHelper::factory(1, 'array or string'); + } + + if (!\is_string($mockClassName)) { + throw InvalidArgumentHelper::factory(4, 'string'); + } + + if (!\is_array($methods) && null !== $methods) { + throw InvalidArgumentHelper::factory(2, 'array', $methods); + } + + if ($type === 'Traversable' || $type === '\\Traversable') { + $type = 'Iterator'; + } + + if (\is_array($type)) { + $type = \array_unique( + \array_map( + function ($type) { + if ($type === 'Traversable' || + $type === '\\Traversable' || + $type === '\\Iterator') { + return 'Iterator'; + } + + return $type; + }, + $type + ) + ); + } + + if (!$allowMockingUnknownTypes) { + if (\is_array($type)) { + foreach ($type as $_type) { + if (!\class_exists($_type, $callAutoload) && + !\interface_exists($_type, $callAutoload)) { + throw new RuntimeException( + \sprintf( + 'Cannot stub or mock class or interface "%s" which does not exist', + $_type + ) + ); + } + } + } else { + if (!\class_exists($type, $callAutoload) && + !\interface_exists($type, $callAutoload) + ) { + throw new RuntimeException( + \sprintf( + 'Cannot stub or mock class or interface "%s" which does not exist', + $type + ) + ); + } + } + } + + if (null !== $methods) { + foreach ($methods as $method) { + if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { + throw new RuntimeException( + \sprintf( + 'Cannot stub or mock method with invalid name "%s"', + $method + ) + ); + } + } + + if ($methods !== \array_unique($methods)) { + throw new RuntimeException( + \sprintf( + 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")', + \implode(', ', $methods), + \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods)))) + ) + ); + } + } + + if ($mockClassName !== '' && \class_exists($mockClassName, false)) { + $reflect = new ReflectionClass($mockClassName); + + if (!$reflect->implementsInterface(MockObject::class)) { + throw new RuntimeException( + \sprintf( + 'Class "%s" already exists.', + $mockClassName + ) + ); + } + } + + if ($callOriginalConstructor === false && $callOriginalMethods === true) { + throw new RuntimeException( + 'Proxying to original methods requires invoking the original constructor' + ); + } + + $mock = $this->generate( + $type, + $methods, + $mockClassName, + $callOriginalClone, + $callAutoload, + $cloneArguments, + $callOriginalMethods + ); + + return $this->getObject( + $mock['code'], + $mock['mockClassName'], + $type, + $callOriginalConstructor, + $callAutoload, + $arguments, + $callOriginalMethods, + $proxyTarget + ); + } + + /** + * @param string $code + * @param string $className + * @param array|string $type + * @param bool $callOriginalConstructor + * @param bool $callAutoload + * @param array $arguments + * @param bool $callOriginalMethods + * @param object $proxyTarget + * + * @return MockObject + * + * @throws \ReflectionException + * @throws RuntimeException + */ + private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null) + { + $this->evalClass($code, $className); + + if ($callOriginalConstructor && + \is_string($type) && + !\interface_exists($type, $callAutoload)) { + if (\count($arguments) === 0) { + $object = new $className; + } else { + $class = new ReflectionClass($className); + $object = $class->newInstanceArgs($arguments); + } + } else { + try { + $instantiator = new Instantiator; + $object = $instantiator->instantiate($className); + } catch (InstantiatorException $exception) { + throw new RuntimeException($exception->getMessage()); + } + } + + if ($callOriginalMethods) { + if (!\is_object($proxyTarget)) { + if (\count($arguments) === 0) { + $proxyTarget = new $type; + } else { + $class = new ReflectionClass($type); + $proxyTarget = $class->newInstanceArgs($arguments); + } + } + + $object->__phpunit_setOriginalObject($proxyTarget); + } + + return $object; + } + + /** + * @param string $code + * @param string $className + */ + private function evalClass($code, $className) + { + if (!\class_exists($className, false)) { + eval($code); + } + } + + /** + * Returns a mock object for the specified abstract class with all abstract + * methods of the class mocked. Concrete methods to mock can be specified with + * the last parameter + * + * @param string $originalClassName + * @param array $arguments + * @param string $mockClassName + * @param bool $callOriginalConstructor + * @param bool $callOriginalClone + * @param bool $callAutoload + * @param array $mockedMethods + * @param bool $cloneArguments + * + * @return MockObject + * + * @throws \ReflectionException + * @throws RuntimeException + * @throws Exception + */ + public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) + { + if (!\is_string($originalClassName)) { + throw InvalidArgumentHelper::factory(1, 'string'); + } + + if (!\is_string($mockClassName)) { + throw InvalidArgumentHelper::factory(3, 'string'); + } + + if (\class_exists($originalClassName, $callAutoload) || + \interface_exists($originalClassName, $callAutoload)) { + $reflector = new ReflectionClass($originalClassName); + $methods = $mockedMethods; + + foreach ($reflector->getMethods() as $method) { + if ($method->isAbstract() && !\in_array($method->getName(), $methods)) { + $methods[] = $method->getName(); + } + } + + if (empty($methods)) { + $methods = null; + } + + return $this->getMock( + $originalClassName, + $methods, + $arguments, + $mockClassName, + $callOriginalConstructor, + $callOriginalClone, + $callAutoload, + $cloneArguments + ); + } + + throw new RuntimeException( + \sprintf('Class "%s" does not exist.', $originalClassName) + ); + } + + /** + * Returns a mock object for the specified trait with all abstract methods + * of the trait mocked. Concrete methods to mock can be specified with the + * `$mockedMethods` parameter. + * + * @param string $traitName + * @param array $arguments + * @param string $mockClassName + * @param bool $callOriginalConstructor + * @param bool $callOriginalClone + * @param bool $callAutoload + * @param array $mockedMethods + * @param bool $cloneArguments + * + * @return MockObject + * + * @throws \ReflectionException + * @throws RuntimeException + * @throws Exception + */ + public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true) + { + if (!\is_string($traitName)) { + throw InvalidArgumentHelper::factory(1, 'string'); + } + + if (!\is_string($mockClassName)) { + throw InvalidArgumentHelper::factory(3, 'string'); + } + + if (!\trait_exists($traitName, $callAutoload)) { + throw new RuntimeException( + \sprintf( + 'Trait "%s" does not exist.', + $traitName + ) + ); + } + + $className = $this->generateClassName( + $traitName, + '', + 'Trait_' + ); + + $classTemplate = $this->getTemplate('trait_class.tpl'); + + $classTemplate->setVar( + [ + 'prologue' => 'abstract ', + 'class_name' => $className['className'], + 'trait_name' => $traitName + ] + ); + + $this->evalClass( + $classTemplate->render(), + $className['className'] + ); + + return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); + } + + /** + * Returns an object for the specified trait. + * + * @param string $traitName + * @param array $arguments + * @param string $traitClassName + * @param bool $callOriginalConstructor + * @param bool $callOriginalClone + * @param bool $callAutoload + * + * @return object + * + * @throws \ReflectionException + * @throws RuntimeException + * @throws Exception + */ + public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) + { + if (!\is_string($traitName)) { + throw InvalidArgumentHelper::factory(1, 'string'); + } + + if (!\is_string($traitClassName)) { + throw InvalidArgumentHelper::factory(3, 'string'); + } + + if (!\trait_exists($traitName, $callAutoload)) { + throw new RuntimeException( + \sprintf( + 'Trait "%s" does not exist.', + $traitName + ) + ); + } + + $className = $this->generateClassName( + $traitName, + $traitClassName, + 'Trait_' + ); + + $classTemplate = $this->getTemplate('trait_class.tpl'); + + $classTemplate->setVar( + [ + 'prologue' => '', + 'class_name' => $className['className'], + 'trait_name' => $traitName + ] + ); + + return $this->getObject($classTemplate->render(), $className['className']); + } + + /** + * @param array|string $type + * @param array $methods + * @param string $mockClassName + * @param bool $callOriginalClone + * @param bool $callAutoload + * @param bool $cloneArguments + * @param bool $callOriginalMethods + * + * @return array + * + * @throws \ReflectionException + * @throws \PHPUnit\Framework\MockObject\RuntimeException + */ + public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) + { + if (\is_array($type)) { + \sort($type); + } + + if ($mockClassName === '') { + $key = \md5( + \is_array($type) ? \implode('_', $type) : $type . + \serialize($methods) . + \serialize($callOriginalClone) . + \serialize($cloneArguments) . + \serialize($callOriginalMethods) + ); + + if (isset(self::$cache[$key])) { + return self::$cache[$key]; + } + } + + $mock = $this->generateMock( + $type, + $methods, + $mockClassName, + $callOriginalClone, + $callAutoload, + $cloneArguments, + $callOriginalMethods + ); + + if (isset($key)) { + self::$cache[$key] = $mock; + } + + return $mock; + } + + /** + * @param string $wsdlFile + * @param string $className + * @param array $methods + * @param array $options + * + * @return string + * + * @throws RuntimeException + */ + public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = []) + { + if (!\extension_loaded('soap')) { + throw new RuntimeException( + 'The SOAP extension is required to generate a mock object from WSDL.' + ); + } + + $options = \array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); + $client = new SoapClient($wsdlFile, $options); + $_methods = \array_unique($client->__getFunctions()); + unset($client); + + \sort($_methods); + + $methodTemplate = $this->getTemplate('wsdl_method.tpl'); + $methodsBuffer = ''; + + foreach ($_methods as $method) { + $nameStart = \strpos($method, ' ') + 1; + $nameEnd = \strpos($method, '('); + $name = \substr($method, $nameStart, $nameEnd - $nameStart); + + if (empty($methods) || \in_array($name, $methods)) { + $args = \explode( + ',', + \substr( + $method, + $nameEnd + 1, + \strpos($method, ')') - $nameEnd - 1 + ) + ); + + foreach (\range(0, \count($args) - 1) as $i) { + $args[$i] = \substr($args[$i], \strpos($args[$i], '$')); + } + + $methodTemplate->setVar( + [ + 'method_name' => $name, + 'arguments' => \implode(', ', $args) + ] + ); + + $methodsBuffer .= $methodTemplate->render(); + } + } + + $optionsBuffer = 'array('; + + foreach ($options as $key => $value) { + $optionsBuffer .= $key . ' => ' . $value; + } + + $optionsBuffer .= ')'; + + $classTemplate = $this->getTemplate('wsdl_class.tpl'); + $namespace = ''; + + if (\strpos($className, '\\') !== false) { + $parts = \explode('\\', $className); + $className = \array_pop($parts); + $namespace = 'namespace ' . \implode('\\', $parts) . ';' . "\n\n"; + } + + $classTemplate->setVar( + [ + 'namespace' => $namespace, + 'class_name' => $className, + 'wsdl' => $wsdlFile, + 'options' => $optionsBuffer, + 'methods' => $methodsBuffer + ] + ); + + return $classTemplate->render(); + } + + /** + * @param array|string $type + * @param array|null $methods + * @param string $mockClassName + * @param bool $callOriginalClone + * @param bool $callAutoload + * @param bool $cloneArguments + * @param bool $callOriginalMethods + * + * @return array + * + * @throws \InvalidArgumentException + * @throws \ReflectionException + * @throws RuntimeException + */ + private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) + { + $methodReflections = []; + $classTemplate = $this->getTemplate('mocked_class.tpl'); + + $additionalInterfaces = []; + $cloneTemplate = ''; + $isClass = false; + $isInterface = false; + $isMultipleInterfaces = false; + + if (\is_array($type)) { + foreach ($type as $_type) { + if (!\interface_exists($_type, $callAutoload)) { + throw new RuntimeException( + \sprintf( + 'Interface "%s" does not exist.', + $_type + ) + ); + } + + $isMultipleInterfaces = true; + + $additionalInterfaces[] = $_type; + $typeClass = new ReflectionClass($this->generateClassName( + $_type, + $mockClassName, + 'Mock_' + )['fullClassName'] + ); + + foreach ($this->getClassMethods($_type) as $method) { + if (\in_array($method, $methods)) { + throw new RuntimeException( + \sprintf( + 'Duplicate method "%s" not allowed.', + $method + ) + ); + } + + $methodReflections[$method] = $typeClass->getMethod($method); + $methods[] = $method; + } + } + } + + $mockClassName = $this->generateClassName( + $type, + $mockClassName, + 'Mock_' + ); + + if (\class_exists($mockClassName['fullClassName'], $callAutoload)) { + $isClass = true; + } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) { + $isInterface = true; + } + + if (!$isClass && !$isInterface) { + $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; + + if (!empty($mockClassName['namespaceName'])) { + $prologue = 'namespace ' . $mockClassName['namespaceName'] . + " {\n\n" . $prologue . "}\n\n" . + "namespace {\n\n"; + + $epilogue = "\n\n}"; + } + + $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); + } else { + $class = new ReflectionClass($mockClassName['fullClassName']); + + if ($class->isFinal()) { + throw new RuntimeException( + \sprintf( + 'Class "%s" is declared "final" and cannot be mocked.', + $mockClassName['fullClassName'] + ) + ); + } + + if ($class->hasMethod('__clone')) { + $cloneMethod = $class->getMethod('__clone'); + + if (!$cloneMethod->isFinal()) { + if ($callOriginalClone && !$isInterface) { + $cloneTemplate = $this->getTemplate('unmocked_clone.tpl'); + } else { + $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); + } + } + } else { + $cloneTemplate = $this->getTemplate('mocked_clone.tpl'); + } + } + + if (\is_object($cloneTemplate)) { + $cloneTemplate = $cloneTemplate->render(); + } + + if (\is_array($methods) && empty($methods) && + ($isClass || $isInterface)) { + $methods = $this->getClassMethods($mockClassName['fullClassName']); + } + + if (!\is_array($methods)) { + $methods = []; + } + + $mockedMethods = ''; + $configurable = []; + + foreach ($methods as $methodName) { + if ($methodName !== '__construct' && $methodName !== '__clone') { + $configurable[] = \strtolower($methodName); + } + } + + if (isset($class)) { + // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + if ($isInterface && $class->implementsInterface(Traversable::class) && + !$class->implementsInterface(Iterator::class) && + !$class->implementsInterface(IteratorAggregate::class)) { + $additionalInterfaces[] = Iterator::class; + $methods = \array_merge($methods, $this->getClassMethods(Iterator::class)); + } + + foreach ($methods as $methodName) { + try { + $method = $class->getMethod($methodName); + + if ($this->canMockMethod($method)) { + $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( + $method, + $cloneArguments, + $callOriginalMethods + ); + } + } catch (ReflectionException $e) { + $mockedMethods .= $this->generateMockedMethodDefinition( + $mockClassName['fullClassName'], + $methodName, + $cloneArguments + ); + } + } + } elseif ($isMultipleInterfaces) { + foreach ($methods as $methodName) { + if ($this->canMockMethod($methodReflections[$methodName])) { + $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( + $methodReflections[$methodName], + $cloneArguments, + $callOriginalMethods + ); + } + } + } else { + foreach ($methods as $methodName) { + $mockedMethods .= $this->generateMockedMethodDefinition( + $mockClassName['fullClassName'], + $methodName, + $cloneArguments + ); + } + } + + $method = ''; + + if (!\in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) { + $methodTemplate = $this->getTemplate('mocked_class_method.tpl'); + + $method = $methodTemplate->render(); + } + + $classTemplate->setVar( + [ + 'prologue' => $prologue ?? '', + 'epilogue' => $epilogue ?? '', + 'class_declaration' => $this->generateMockClassDeclaration( + $mockClassName, + $isInterface, + $additionalInterfaces + ), + 'clone' => $cloneTemplate, + 'mock_class_name' => $mockClassName['className'], + 'mocked_methods' => $mockedMethods, + 'method' => $method, + 'configurable' => '[' . \implode(', ', \array_map(function ($m) { + return '\'' . $m . '\''; + }, $configurable)) . ']' + ] + ); + + return [ + 'code' => $classTemplate->render(), + 'mockClassName' => $mockClassName['className'] + ]; + } + + /** + * @param array|string $type + * @param string $className + * @param string $prefix + * + * @return array + */ + private function generateClassName($type, $className, $prefix) + { + if (\is_array($type)) { + $type = \implode('_', $type); + } + + if ($type[0] === '\\') { + $type = \substr($type, 1); + } + + $classNameParts = \explode('\\', $type); + + if (\count($classNameParts) > 1) { + $type = \array_pop($classNameParts); + $namespaceName = \implode('\\', $classNameParts); + $fullClassName = $namespaceName . '\\' . $type; + } else { + $namespaceName = ''; + $fullClassName = $type; + } + + if ($className === '') { + do { + $className = $prefix . $type . '_' . + \substr(\md5(\mt_rand()), 0, 8); + } while (\class_exists($className, false)); + } + + return [ + 'className' => $className, + 'originalClassName' => $type, + 'fullClassName' => $fullClassName, + 'namespaceName' => $namespaceName + ]; + } + + /** + * @param array $mockClassName + * @param bool $isInterface + * @param array $additionalInterfaces + * + * @return string + */ + private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = []) + { + $buffer = 'class '; + + $additionalInterfaces[] = MockObject::class; + $interfaces = \implode(', ', $additionalInterfaces); + + if ($isInterface) { + $buffer .= \sprintf( + '%s implements %s', + $mockClassName['className'], + $interfaces + ); + + if (!\in_array($mockClassName['originalClassName'], $additionalInterfaces)) { + $buffer .= ', '; + + if (!empty($mockClassName['namespaceName'])) { + $buffer .= $mockClassName['namespaceName'] . '\\'; + } + + $buffer .= $mockClassName['originalClassName']; + } + } else { + $buffer .= \sprintf( + '%s extends %s%s implements %s', + $mockClassName['className'], + !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', + $mockClassName['originalClassName'], + $interfaces + ); + } + + return $buffer; + } + + /** + * @param ReflectionMethod $method + * @param bool $cloneArguments + * @param bool $callOriginalMethods + * + * @return string + * + * @throws \PHPUnit\Framework\MockObject\RuntimeException + */ + private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods) + { + if ($method->isPrivate()) { + $modifier = 'private'; + } elseif ($method->isProtected()) { + $modifier = 'protected'; + } else { + $modifier = 'public'; + } + + if ($method->isStatic()) { + $modifier .= ' static'; + } + + if ($method->returnsReference()) { + $reference = '&'; + } else { + $reference = ''; + } + + if ($method->hasReturnType()) { + $returnType = $method->getReturnType()->getName(); + } else { + $returnType = ''; + } + + if (\preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) { + $deprecation = \trim(\preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); + } else { + $deprecation = false; + } + + return $this->generateMockedMethodDefinition( + $method->getDeclaringClass()->getName(), + $method->getName(), + $cloneArguments, + $modifier, + $this->getMethodParameters($method), + $this->getMethodParameters($method, true), + $returnType, + $reference, + $callOriginalMethods, + $method->isStatic(), + $deprecation, + $method->hasReturnType() && PHP_VERSION_ID >= 70100 && $method->getReturnType()->allowsNull() + ); + } + + /** + * @param string $className + * @param string $methodName + * @param bool $cloneArguments + * @param string $modifier + * @param string $argumentsForDeclaration + * @param string $argumentsForCall + * @param string $returnType + * @param string $reference + * @param bool $callOriginalMethods + * @param bool $static + * @param bool|string $deprecation + * @param bool $allowsReturnNull + * + * @return string + * + * @throws \InvalidArgumentException + */ + private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false) + { + if ($static) { + $templateFile = 'mocked_static_method.tpl'; + } else { + if ($returnType === 'void') { + $templateFile = \sprintf( + '%s_method_void.tpl', + $callOriginalMethods ? 'proxied' : 'mocked' + ); + } else { + $templateFile = \sprintf( + '%s_method.tpl', + $callOriginalMethods ? 'proxied' : 'mocked' + ); + } + } + + // Mocked interfaces returning 'self' must explicitly declare the + // interface name as the return type. See + // https://bugs.php.net/bug.php?id=70722 + if ($returnType === 'self') { + $returnType = $className; + } + + if (false !== $deprecation) { + $deprecation = "The $className::$methodName method is deprecated ($deprecation)."; + $deprecationTemplate = $this->getTemplate('deprecation.tpl'); + + $deprecationTemplate->setVar( + [ + 'deprecation' => \var_export($deprecation, true), + ] + ); + + $deprecation = $deprecationTemplate->render(); + } + + $template = $this->getTemplate($templateFile); + + $template->setVar( + [ + 'arguments_decl' => $argumentsForDeclaration, + 'arguments_call' => $argumentsForCall, + 'return_delim' => $returnType ? ': ' : '', + 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType, + 'arguments_count' => !empty($argumentsForCall) ? \substr_count($argumentsForCall, ',') + 1 : 0, + 'class_name' => $className, + 'method_name' => $methodName, + 'modifier' => $modifier, + 'reference' => $reference, + 'clone_arguments' => $cloneArguments ? 'true' : 'false', + 'deprecation' => $deprecation + ] + ); + + return $template->render(); + } + + /** + * @param ReflectionMethod $method + * + * @return bool + * + * @throws \ReflectionException + */ + private function canMockMethod(ReflectionMethod $method) + { + return !($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName())); + } + + /** + * Returns whether a method name is blacklisted + * + * @param string $name + * + * @return bool + */ + private function isMethodNameBlacklisted($name) + { + return isset($this->blacklistedMethodNames[$name]); + } + + /** + * Returns the parameters of a function or method. + * + * @param ReflectionMethod $method + * @param bool $forCall + * + * @return string + * + * @throws RuntimeException + */ + private function getMethodParameters(ReflectionMethod $method, $forCall = false) + { + $parameters = []; + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + if ($parameter->isVariadic()) { + if ($forCall) { + continue; + } + + $name = '...' . $name; + } + + $nullable = ''; + $default = ''; + $reference = ''; + $typeDeclaration = ''; + + if (!$forCall) { + if (PHP_VERSION_ID >= 70100 && $parameter->hasType() && $parameter->allowsNull()) { + $nullable = '?'; + } + + if ($parameter->hasType() && $parameter->getType()->getName() !== 'self') { + $typeDeclaration = $parameter->getType()->getName() . ' '; + } elseif ($parameter->isArray()) { + $typeDeclaration = 'array '; + } elseif ($parameter->isCallable()) { + $typeDeclaration = 'callable '; + } else { + try { + $class = $parameter->getClass(); + } catch (ReflectionException $e) { + throw new RuntimeException( + \sprintf( + 'Cannot mock %s::%s() because a class or ' . + 'interface used in the signature is not loaded', + $method->getDeclaringClass()->getName(), + $method->getName() + ), + 0, + $e + ); + } + + if ($class !== null) { + $typeDeclaration = $class->getName() . ' '; + } + } + + if (!$parameter->isVariadic()) { + if ($parameter->isDefaultValueAvailable()) { + $value = $parameter->getDefaultValueConstantName(); + + if ($value === null) { + $value = \var_export($parameter->getDefaultValue(), true); + } elseif (!\defined($value)) { + $rootValue = \preg_replace('/^.*\\\\/', '', $value); + $value = \defined($rootValue) ? $rootValue : $value; + } + + $default = ' = ' . $value; + } elseif ($parameter->isOptional()) { + $default = ' = null'; + } + } + } + + if ($parameter->isPassedByReference()) { + $reference = '&'; + } + + $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default; + } + + return \implode(', ', $parameters); + } + + /** + * @param string $className + * + * @return array + * + * @throws \ReflectionException + */ + public function getClassMethods($className) + { + $class = new ReflectionClass($className); + $methods = []; + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } + + /** + * @param string $template + * + * @return Text_Template + * + * @throws \InvalidArgumentException + */ + private function getTemplate($template) + { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; + + if (!isset(self::$templates[$filename])) { + self::$templates[$filename] = new Text_Template($filename); + } + + return self::$templates[$filename]; + } +} diff --git a/tests/unit/backend/classes/AuthManagerTest.php b/tests/unit/backend/classes/AuthManagerTest.php index 65dd5e735..6bb9f9684 100644 --- a/tests/unit/backend/classes/AuthManagerTest.php +++ b/tests/unit/backend/classes/AuthManagerTest.php @@ -4,7 +4,7 @@ use October\Rain\Exception\SystemException; class AuthManagerTest extends TestCase { - public function setUp(): void + public function setUp() { $this->createApplication(); @@ -23,7 +23,7 @@ class AuthManagerTest extends TestCase ]); } - public function tearDown(): void + public function tearDown() { AuthManager::forgetInstance(); } diff --git a/tests/unit/backend/classes/NavigationManagerTest.php b/tests/unit/backend/classes/NavigationManagerTest.php index f63645198..04ca2f183 100644 --- a/tests/unit/backend/classes/NavigationManagerTest.php +++ b/tests/unit/backend/classes/NavigationManagerTest.php @@ -59,18 +59,18 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertIsArray($items); + $this->assertInternalType('array', $items); $this->assertArrayHasKey('posts', $items); $this->assertArrayHasKey('categories', $items); - $this->assertIsObject($items['posts']); + $this->assertInternalType('object', $items['posts']); $this->assertObjectHasAttribute('code', $items['posts']); $this->assertObjectHasAttribute('owner', $items['posts']); $this->assertEquals('posts', $items['posts']->code); $this->assertEquals('October.Tester', $items['posts']->owner); $this->assertObjectHasAttribute('permissions', $items['posts']); - $this->assertIsArray($items['posts']->permissions); + $this->assertInternalType('array', $items['posts']->permissions); $this->assertCount(1, $items['posts']->permissions); $this->assertObjectHasAttribute('order', $items['posts']); @@ -92,7 +92,7 @@ class NavigationManagerTest extends TestCase $items = $manager->listMainMenuItems(); - $this->assertIsArray($items); + $this->assertInternalType('array', $items); $this->assertArrayHasKey('OCTOBER.TESTER.PRINT', $items); $item = $items['OCTOBER.TESTER.PRINT']; @@ -143,10 +143,10 @@ class NavigationManagerTest extends TestCase $manager->setContext('October.Tester', 'blog'); $items = $manager->listSideMenuItems(); - $this->assertIsArray($items); + $this->assertInternalType('array', $items); $this->assertArrayHasKey('foo', $items); - $this->assertIsObject($items['foo']); + $this->assertInternalType('object', $items['foo']); $this->assertObjectHasAttribute('code', $items['foo']); $this->assertObjectHasAttribute('owner', $items['foo']); $this->assertObjectHasAttribute('order', $items['foo']); @@ -156,7 +156,7 @@ class NavigationManagerTest extends TestCase $this->assertEquals('October.Tester', $items['foo']->owner); $this->assertObjectHasAttribute('permissions', $items['foo']); - $this->assertIsArray($items['foo']->permissions); + $this->assertInternalType('array', $items['foo']->permissions); $this->assertCount(2, $items['foo']->permissions); $this->assertContains('october.tester.access_foo', $items['foo']->permissions); $this->assertContains('october.tester.access_bar', $items['foo']->permissions); diff --git a/tests/unit/backend/helpers/BackendHelperTest.php b/tests/unit/backend/helpers/BackendHelperTest.php index 176690b78..b4ee7c0c6 100644 --- a/tests/unit/backend/helpers/BackendHelperTest.php +++ b/tests/unit/backend/helpers/BackendHelperTest.php @@ -11,8 +11,8 @@ class BackendHelperTest extends TestCase $assets = $backendHelper->decompileAsset('tests/fixtures/backend/assets/compilation.js'); $this->assertCount(2, $assets); - $this->assertStringContainsString('file1.js', $assets[0]); - $this->assertStringContainsString('file2.js', $assets[1]); + $this->assertContains('file1.js', $assets[0]); + $this->assertContains('file2.js', $assets[1]); } public function testDecompileMissingFile() diff --git a/tests/unit/backend/traits/WidgetMakerTest.php b/tests/unit/backend/traits/WidgetMakerTest.php index 9a290b509..1369a34d1 100644 --- a/tests/unit/backend/traits/WidgetMakerTest.php +++ b/tests/unit/backend/traits/WidgetMakerTest.php @@ -28,7 +28,7 @@ class WidgetMakerTest extends TestCase * * @return void */ - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/AssetTest.php b/tests/unit/cms/classes/AssetTest.php deleted file mode 100644 index 13519a161..000000000 --- a/tests/unit/cms/classes/AssetTest.php +++ /dev/null @@ -1,119 +0,0 @@ -assertStringContainsString( - 'console.log(\'script1.js\');', - Asset::load($theme, 'js/script1.js')->content - ); - - // Valid direct subdirectory path - $this->assertStringContainsString( - 'console.log(\'subdir/script1.js\');', - Asset::load($theme, 'js/subdir/script1.js')->content - ); - - // Valid relative path - $this->assertStringContainsString( - 'console.log(\'script2.js\');', - Asset::load($theme, 'js/subdir/../script2.js')->content - ); - - // Invalid theme path - $this->assertNull( - Asset::load($theme, 'js/invalid.js') - ); - - // Check that we cannot break out of assets directory - $this->assertNull( - Asset::load($theme, '../../../../js/helpers/fakeDom.js') - ); - $this->assertNull( - Asset::load($theme, '../content/html-content.htm') - ); - - // Check that we cannot load directories directly - $this->assertNull( - Asset::load($theme, 'js/subdir') - ); - - // Check that we definitely cannot load external PHP files - $this->assertNull( - Asset::load($theme, '../../../../../config/database.php') - ); - } - - public function testGetPath() - { - // Test some pathing fringe cases - - $theme = Theme::load('test'); - $assetClass = new Asset($theme); - $themeDir = $theme->getPath(); - - // Direct paths - $this->assertEquals( - $themeDir . '/assets/js/script1.js', - $assetClass->getFilePath('js/script1.js') - ); - $this->assertEquals( - $themeDir . '/assets/js/script1.js', - $assetClass->getFilePath('/js/script1.js') - ); - - // Direct path to a directory - $this->assertEquals( - $themeDir . '/assets/js/subdir', - $assetClass->getFilePath('/js/subdir') - ); - $this->assertEquals( - $themeDir . '/assets/js/subdir', - $assetClass->getFilePath('/js/subdir/') - ); - - // Relative paths - $this->assertEquals( - $themeDir . '/assets/js/script2.js', - $assetClass->getFilePath('./js/script2.js') - ); - $this->assertEquals( - $themeDir . '/assets/js/script2.js', - $assetClass->getFilePath('/js/subdir/../script2.js') - ); - - // Missing file, but valid directory (allows for new files) - $this->assertEquals( - $themeDir . '/assets/js/missing.js', - $assetClass->getFilePath('/js/missing.js') - ); - $this->assertEquals( - $themeDir . '/assets/js/missing.js', - $assetClass->getFilePath('js/missing.js') - ); - - // Missing file and missing directory (new directories are created as needed) - $this->assertEquals( - $themeDir . '/assets/js/missing/missing.js', - $assetClass->getFilePath('/js/missing/missing.js') - ); - - // Ensure we cannot get paths outside of the assets directory - $this->assertFalse( - $assetClass->getFilePath('../../../../js/helpers/fakeDom.js') - ); - $this->assertFalse( - $assetClass->getFilePath('../content/html-content.htm') - ); - $this->assertFalse( - $assetClass->getFilePath('../../../../../config/database.php') - ); - } -} diff --git a/tests/unit/cms/classes/CmsCompoundObjectTest.php b/tests/unit/cms/classes/CmsCompoundObjectTest.php index ec82153f9..c66d31ee5 100644 --- a/tests/unit/cms/classes/CmsCompoundObjectTest.php +++ b/tests/unit/cms/classes/CmsCompoundObjectTest.php @@ -30,7 +30,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject class CmsCompoundObjectTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); Model::clearBootedModels(); @@ -44,16 +44,16 @@ class CmsCompoundObjectTest extends TestCase $theme = Theme::load('test'); $obj = TestCmsCompoundObject::load($theme, 'compound.htm'); - $this->assertStringContainsString("\$controller->data['something'] = 'some value'", $obj->code); + $this->assertContains("\$controller->data['something'] = 'some value'", $obj->code); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('section', $obj->settings['components']); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -69,7 +69,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'component.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']); + $this->assertInternalType('array', $obj->settings['components']); $this->assertArrayHasKey('testArchive', $obj->settings['components']); $this->assertArrayHasKey('posts-per-page', $obj->settings['components']['testArchive']); $this->assertEquals(10, $obj->settings['components']['testArchive']['posts-per-page']); @@ -82,7 +82,7 @@ class CmsCompoundObjectTest extends TestCase $obj = TestCmsCompoundObject::load($theme, 'components.htm'); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']); + $this->assertInternalType('array', $obj->settings['components']); $this->assertArrayHasKey('testArchive firstAlias', $obj->settings['components']); $this->assertArrayHasKey('October\Tester\Components\Post secondAlias', $obj->settings['components']); @@ -108,7 +108,7 @@ class CmsCompoundObjectTest extends TestCase $properties = $obj->getComponentProperties('October\Tester\Components\Post'); $emptyProperties = $obj->getComponentProperties('October\Tester\Components\Archive'); $notExistingProperties = $obj->getComponentProperties('This\Is\Not\Component'); - $this->assertIsArray($properties); + $this->assertInternalType('array', $properties); $this->assertArrayHasKey('show-featured', $properties); $this->assertTrue((bool)$properties['show-featured']); $this->assertEquals('true', $properties['show-featured']); @@ -148,18 +148,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); @@ -173,18 +173,18 @@ class CmsCompoundObjectTest extends TestCase $this->assertEquals($testContent, $obj->getContent()); $this->assertEquals('testcompound.htm', $obj->getFileName()); $this->assertEquals('

This is a paragraph

', $obj->markup); - $this->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); $this->assertEquals('value', $obj->var); - $this->assertIsArray($obj->settings['components']['section']); + $this->assertInternalType('array', $obj->settings['components']['section']); $this->assertArrayHasKey('version', $obj->settings['components']['section']); $this->assertEquals(10, $obj->settings['components']['section']['version']); } @@ -280,14 +280,14 @@ class CmsCompoundObjectTest extends TestCase $obj = TestParsedCmsCompoundObject::load($theme, 'viewbag.htm'); $this->assertNull($obj->code); $this->assertEquals('

Chop Suey!

', $obj->markup); - $this->assertIsArray($obj->settings); + $this->assertInternalType('array', $obj->settings); $this->assertArrayHasKey('var', $obj->settings); $this->assertEquals('value', $obj->settings['var']); $this->assertArrayHasKey('components', $obj->settings); $this->assertArrayHasKey('viewBag', $obj->settings['components']); - $this->assertIsArray($obj->settings['components']['viewBag']); + $this->assertInternalType('array', $obj->settings['components']['viewBag']); $this->assertArrayHasKey('title', $obj->settings['components']['viewBag']); $this->assertEquals('Toxicity', $obj->settings['components']['viewBag']['title']); diff --git a/tests/unit/cms/classes/CmsObjectQueryTest.php b/tests/unit/cms/classes/CmsObjectQueryTest.php index dc5172add..978745ae1 100644 --- a/tests/unit/cms/classes/CmsObjectQueryTest.php +++ b/tests/unit/cms/classes/CmsObjectQueryTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Model; class CmsObjectQueryTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/CmsObjectTest.php b/tests/unit/cms/classes/CmsObjectTest.php index 2c65761b4..95d4f50d9 100644 --- a/tests/unit/cms/classes/CmsObjectTest.php +++ b/tests/unit/cms/classes/CmsObjectTest.php @@ -148,11 +148,12 @@ class CmsObjectTest extends TestCase $this->assertNull($obj->something); } + /** + * @expectedException \October\Rain\Exception\ValidationException + * @expectedExceptionMessage Invalid file name + */ public function testFillInvalidFileNameSymbol() { - $this->expectException(\October\Rain\Exception\ValidationException::class); - $this->expectExceptionMessage('Invalid file name'); - $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -163,11 +164,12 @@ class CmsObjectTest extends TestCase $obj->save(); } + /** + * @expectedException \October\Rain\Exception\ValidationException + * @expectedExceptionMessage Invalid file name + */ public function testFillInvalidFileNamePath() { - $this->expectException(\October\Rain\Exception\ValidationException::class); - $this->expectExceptionMessage('Invalid file name'); - $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -178,11 +180,12 @@ class CmsObjectTest extends TestCase $obj->save(); } + /** + * @expectedException \October\Rain\Exception\ValidationException + * @expectedExceptionMessage Invalid file name + */ public function testFillInvalidFileSlash() { - $this->expectException(\October\Rain\Exception\ValidationException::class); - $this->expectExceptionMessage('Invalid file name'); - $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -193,11 +196,12 @@ class CmsObjectTest extends TestCase $obj->save(); } + /** + * @expectedException \October\Rain\Exception\ValidationException + * @expectedExceptionMessage The File Name field is required + */ public function testFillEmptyFileName() { - $this->expectException(\October\Rain\Exception\ValidationException::class); - $this->expectExceptionMessage('The File Name field is required'); - $theme = Theme::load('apitest'); $testContents = 'mytestcontent'; @@ -262,12 +266,11 @@ class CmsObjectTest extends TestCase /** * @depends testRename + * @expectedException \October\Rain\Exception\ApplicationException + * @expectedExceptionMessage already exists */ public function testRenameToExistingFile() { - $this->expectException(\October\Rain\Exception\ApplicationException::class); - $this->expectExceptionMessageMatches('/already\sexists/'); - $theme = Theme::load('apitest'); $srcFilePath = $theme->getPath().'/testobjects/anotherobj.htm'; diff --git a/tests/unit/cms/classes/CodeParserTest.php b/tests/unit/cms/classes/CodeParserTest.php index 67d8f30e9..43eb34a84 100644 --- a/tests/unit/cms/classes/CodeParserTest.php +++ b/tests/unit/cms/classes/CodeParserTest.php @@ -10,7 +10,7 @@ use Cms\Classes\Controller; class CodeParserTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setup(); @@ -41,7 +41,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -78,7 +78,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -91,7 +91,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -101,7 +101,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('request-cache', $info['source']); $this->assertFileExists($info['filePath']); @@ -110,14 +110,13 @@ class CodeParserTest extends TestCase */ $this->assertTrue(@touch($layout->getFilePath())); - clearstatcache(); $layout = Layout::load($theme, 'php-parser-test.htm'); $this->assertNotEmpty($layout); $parser = new CodeParser($layout); $property->setValue($parser, []); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertEquals('parser', $info['source']); $this->assertFileExists($info['filePath']); } @@ -132,7 +131,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($layout); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -158,7 +157,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -192,7 +191,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -221,7 +220,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -256,7 +255,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); @@ -285,7 +284,7 @@ class CodeParserTest extends TestCase $parser = new CodeParser($page); $info = $parser->parse(); - $this->assertIsArray($info); + $this->assertInternalType('array', $info); $this->assertArrayHasKey('filePath', $info); $this->assertArrayHasKey('className', $info); $this->assertArrayHasKey('source', $info); diff --git a/tests/unit/cms/classes/ComponentManagerTest.php b/tests/unit/cms/classes/ComponentManagerTest.php index 152367b8d..a12255580 100644 --- a/tests/unit/cms/classes/ComponentManagerTest.php +++ b/tests/unit/cms/classes/ComponentManagerTest.php @@ -9,7 +9,7 @@ use Cms\Classes\ComponentManager; class ComponentManagerTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php index 1496055a3..02b2abc34 100644 --- a/tests/unit/cms/classes/ControllerTest.php +++ b/tests/unit/cms/classes/ControllerTest.php @@ -6,7 +6,7 @@ use October\Rain\Halcyon\Model; class ControllerTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -69,7 +69,7 @@ class ControllerTest extends TestCase $response = $controller->run('/some-page-that-doesnt-exist'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertIsString($content); + $this->assertInternalType('string', $content); $this->assertEquals('

Page not found

', $content); } @@ -83,15 +83,16 @@ class ControllerTest extends TestCase $response = $controller->run('/'); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getContent(); - $this->assertIsString($content); + $this->assertInternalType('string', $content); $this->assertEquals('

My Webpage

', trim($content)); } + /** + * @expectedException Cms\Classes\CmsException + * @expectedExceptionMessage is not found + */ public function testLayoutNotFound() { - $this->expectException(\Cms\Classes\CmsException::class); - $this->expectExceptionMessageMatches('/is\snot\sfound/'); - $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-layout'); @@ -145,11 +146,12 @@ class ControllerTest extends TestCase $this->assertEquals("
LAYOUT CONTENT

This page is a subdirectory

", $response); } + /** + * @expectedException \Twig\Error\RuntimeError + * @expectedExceptionMessage is not found + */ public function testPartialNotFound() { - $this->expectException(\Twig\Error\RuntimeError::class); - $this->expectExceptionMessageMatches('/is\snot\sfound/'); - $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-partial')->getContent(); @@ -191,11 +193,12 @@ class ControllerTest extends TestCase return $requestMock; } + /** + * @expectedException Cms\Classes\CmsException + * @expectedExceptionMessage AJAX handler 'onNoHandler' was not found. + */ public function testAjaxHandlerNotFound() { - $this->expectException(\Cms\Classes\CmsException::class); - $this->expectExceptionMessage('AJAX handler \'onNoHandler\' was not found.'); - Request::swap($this->configAjaxRequestMock('onNoHandler', '')); $theme = Theme::load('test'); @@ -203,11 +206,12 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } + /** + * @expectedException Cms\Classes\CmsException + * @expectedExceptionMessage Invalid AJAX handler name: delete. + */ public function testAjaxInvalidHandlerName() { - $this->expectException(\Cms\Classes\CmsException::class); - $this->expectExceptionMessage('Invalid AJAX handler name: delete.'); - Request::swap($this->configAjaxRequestMock('delete')); $theme = Theme::load('test'); @@ -215,11 +219,12 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } + /** + * @expectedException Cms\Classes\CmsException + * @expectedExceptionMessage Invalid partial name: p:artial. + */ public function testAjaxInvalidPartial() { - $this->expectException(\Cms\Classes\CmsException::class); - $this->expectExceptionMessage('Invalid partial name: p:artial.'); - Request::swap($this->configAjaxRequestMock('onTest', 'p:artial')); $theme = Theme::load('test'); @@ -227,11 +232,12 @@ class ControllerTest extends TestCase $controller->run('/ajax-test'); } + /** + * @expectedException Cms\Classes\CmsException + * @expectedExceptionMessage The partial 'partial' is not found. + */ public function testAjaxPartialNotFound() { - $this->expectException(\Cms\Classes\CmsException::class); - $this->expectExceptionMessage('The partial \'partial\' is not found.'); - Request::swap($this->configAjaxRequestMock('onTest', 'partial')); $theme = Theme::load('test'); @@ -249,7 +255,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -266,7 +272,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -283,7 +289,7 @@ class ControllerTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(2, $content); $this->assertArrayHasKey('ajax-result', $content); @@ -297,7 +303,7 @@ class ControllerTest extends TestCase $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-component')->getContent(); - $page = self::getProtectedProperty($controller, 'page'); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -325,7 +331,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-components')->getContent(); - $page = self::getProtectedProperty($controller, 'page'); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('firstAlias', $page->components); $this->assertArrayHasKey('secondAlias', $page->components); @@ -357,18 +363,19 @@ ESC; $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); $content = $response->getOriginalContent(); - $this->assertIsArray($content); + $this->assertInternalType('array', $content); $this->assertEquals(200, $response->getStatusCode()); $this->assertCount(1, $content); $this->assertArrayHasKey('ajax-result', $content); $this->assertEquals('page', $content['ajax-result']); } + /** + * @expectedException October\Rain\Exception\SystemException + * @expectedExceptionMessage is not registered for the component + */ public function testComponentClassNotFound() { - $this->expectException(\October\Rain\Exception\SystemException::class); - $this->expectExceptionMessageMatches('/is\snot\sregistered\sfor\sthe\scomponent/'); - $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/no-component-class')->getContent(); @@ -388,7 +395,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class')->getContent(); - $page = $controller->getPage(); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('testArchive', $page->components); $component = $page->components['testArchive']; @@ -414,7 +421,7 @@ ESC; $theme = Theme::load('test'); $controller = new Controller($theme); $response = $controller->run('/with-soft-component-class-alias')->getContent(); - $page = $controller->getPage(); + $page = $this->readAttribute($controller, 'page'); $this->assertArrayHasKey('someAlias', $page->components); $component = $page->components['someAlias']; diff --git a/tests/unit/cms/classes/RouterTest.php b/tests/unit/cms/classes/RouterTest.php index f4eeda27a..9bbcd3247 100644 --- a/tests/unit/cms/classes/RouterTest.php +++ b/tests/unit/cms/classes/RouterTest.php @@ -7,7 +7,7 @@ class RouterTest extends TestCase { protected static $theme = null; - public function setUp() : void + public function setUp() { parent::setUp(); @@ -43,7 +43,7 @@ class RouterTest extends TestCase $this->assertFalse($value); $map = $property->getValue($router); - $this->assertIsArray($map); + $this->assertInternalType('array', $map); $this->assertGreaterThanOrEqual(4, count($map)); /* @@ -52,7 +52,7 @@ class RouterTest extends TestCase $value = $method->invoke($router); $this->assertTrue($value); $map = $property->getValue($router); - $this->assertIsArray($map); + $this->assertInternalType('array', $map); $this->assertGreaterThanOrEqual(4, count($map)); } diff --git a/tests/unit/cms/classes/ThemeTest.php b/tests/unit/cms/classes/ThemeTest.php index 07a15f2db..a45087ede 100644 --- a/tests/unit/cms/classes/ThemeTest.php +++ b/tests/unit/cms/classes/ThemeTest.php @@ -4,7 +4,7 @@ use Cms\Classes\Theme; class ThemeTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -44,7 +44,7 @@ class ThemeTest extends TestCase $pageCollection = $theme->listPages(); $pages = array_values($pageCollection->all()); - $this->assertIsArray($pages); + $this->assertInternalType('array', $pages); $expectedPageNum = $this->countThemePages(base_path().'/tests/fixtures/themes/test/pages'); $this->assertCount($expectedPageNum, $pages); @@ -63,11 +63,12 @@ class ThemeTest extends TestCase $this->assertEquals('test', $activeTheme->getDirName()); } + /** + * @expectedException \October\Rain\Exception\SystemException + * @expectedExceptionMessage The active theme is not set. + */ public function testNoActiveTheme() { - $this->expectException(\October\Rain\Exception\SystemException::class); - $this->expectExceptionMessage('The active theme is not set.'); - Config::set('cms.activeTheme', null); Theme::getActiveTheme(); } diff --git a/tests/unit/plugins/database/AttachManyModelTest.php b/tests/unit/plugins/database/AttachManyModelTest.php index 9c316c032..79c03c443 100644 --- a/tests/unit/plugins/database/AttachManyModelTest.php +++ b/tests/unit/plugins/database/AttachManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\User; class AttachManyModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/AttachOneModelTest.php b/tests/unit/plugins/database/AttachOneModelTest.php index d50debdb6..52cb62fdf 100644 --- a/tests/unit/plugins/database/AttachOneModelTest.php +++ b/tests/unit/plugins/database/AttachOneModelTest.php @@ -7,7 +7,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachOneModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToManyModelTest.php b/tests/unit/plugins/database/BelongsToManyModelTest.php index e850b7752..e151d2304 100644 --- a/tests/unit/plugins/database/BelongsToManyModelTest.php +++ b/tests/unit/plugins/database/BelongsToManyModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToManyModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/BelongsToModelTest.php b/tests/unit/plugins/database/BelongsToModelTest.php index 8fca6ff06..6d87dbd3a 100644 --- a/tests/unit/plugins/database/BelongsToModelTest.php +++ b/tests/unit/plugins/database/BelongsToModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Author; class BelongsToModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/DeferredBindingTest.php b/tests/unit/plugins/database/DeferredBindingTest.php index e36460255..a8f354a65 100644 --- a/tests/unit/plugins/database/DeferredBindingTest.php +++ b/tests/unit/plugins/database/DeferredBindingTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Models\DeferredBinding; class DeferredBindingTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/HasManyModelTest.php b/tests/unit/plugins/database/HasManyModelTest.php index 9a1def095..64559dbc1 100644 --- a/tests/unit/plugins/database/HasManyModelTest.php +++ b/tests/unit/plugins/database/HasManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class HasManyModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -63,6 +63,8 @@ class HasManyModelTest extends PluginTestCase public function testGetRelationValue() { + $this->markTestSkipped('Marked as \'skipped\' for further investigation'); + Model::unguard(); $author = Author::create(['name' => 'Stevie']); $post1 = Post::create(['title' => "First post", 'author_id' => $author->id]); diff --git a/tests/unit/plugins/database/HasManyThroughModelTest.php b/tests/unit/plugins/database/HasManyThroughModelTest.php deleted file mode 100644 index 85ac947f2..000000000 --- a/tests/unit/plugins/database/HasManyThroughModelTest.php +++ /dev/null @@ -1,54 +0,0 @@ -runPluginRefreshCommand('Database.Tester'); - } - - public function testGet() - { - Model::unguard(); - $country = Country::create(['name' => 'Australia']); - $author1 = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); - $author2 = Author::create(['name' => 'Louie', 'email' => 'louie@email.tld']); - $post1 = Post::create(['title' => "First post", 'description' => "Yay!!"]); - $post2 = Post::create(['title' => "Second post", 'description' => "Woohoo!!"]); - $post3 = Post::create(['title' => "Third post", 'description' => "Yipiee!!"]); - $post4 = Post::make(['title' => "Fourth post", 'description' => "Hooray!!"]); - Model::reguard(); - - // Set data - $author1->country = $country; - $author2->country = $country; - - $author1->posts = new Collection([$post1, $post2]); - $author2->posts = new Collection([$post3, $post4]); - - $author1->save(); - $author2->save(); - - $country = Country::with([ - 'posts' - ])->find($country->id); - - $this->assertEquals([ - $post1->id, - $post2->id, - $post3->id, - $post4->id - ], $country->posts->pluck('id')->toArray()); - } -} diff --git a/tests/unit/plugins/database/HasOneModelTest.php b/tests/unit/plugins/database/HasOneModelTest.php index e6bd0f45e..7a5e30157 100644 --- a/tests/unit/plugins/database/HasOneModelTest.php +++ b/tests/unit/plugins/database/HasOneModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\Phone; class HasOneModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/HasOneThroughModelTest.php b/tests/unit/plugins/database/HasOneThroughModelTest.php deleted file mode 100644 index 91be83b60..000000000 --- a/tests/unit/plugins/database/HasOneThroughModelTest.php +++ /dev/null @@ -1,39 +0,0 @@ -runPluginRefreshCommand('Database.Tester'); - } - - public function testGet() - { - Model::unguard(); - $phone = Phone::create(['number' => '08 1234 5678']); - $author = Author::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); - $user = User::create(['name' => 'Stevie', 'email' => 'stevie@email.tld']); - Model::reguard(); - - // Set data - $author->phone = $phone; - $author->user = $user; - $author->save(); - - $user = User::with([ - 'phone' - ])->find($user->id); - - $this->assertEquals($phone->id, $user->phone->id); - } -} diff --git a/tests/unit/plugins/database/ModelTest.php b/tests/unit/plugins/database/ModelTest.php index 16bf07c1d..492b39505 100644 --- a/tests/unit/plugins/database/ModelTest.php +++ b/tests/unit/plugins/database/ModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\Post; class ModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -23,11 +23,12 @@ class ModelTest extends PluginTestCase $this->assertEquals(1, $post->id); } + /** + * @expectedException \Illuminate\Database\Eloquent\MassAssignmentException + * @expectedExceptionMessage title + */ public function testGuardedAttribute() { - $this->expectException(\Illuminate\Database\Eloquent\MassAssignmentException::class); - $this->expectExceptionMessageMatches('/title/'); - Post::create(['title' => 'Hi!', 'slug' => 'authenticity']); } } diff --git a/tests/unit/plugins/database/MorphManyModelTest.php b/tests/unit/plugins/database/MorphManyModelTest.php index a7ba3041c..e9b77aced 100644 --- a/tests/unit/plugins/database/MorphManyModelTest.php +++ b/tests/unit/plugins/database/MorphManyModelTest.php @@ -6,7 +6,7 @@ use October\Rain\Database\Collection; class MorphManyModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphOneModelTest.php b/tests/unit/plugins/database/MorphOneModelTest.php index e8c6f60fd..4051e0b0c 100644 --- a/tests/unit/plugins/database/MorphOneModelTest.php +++ b/tests/unit/plugins/database/MorphOneModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\Meta; class MorphOneModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/MorphToModelTest.php b/tests/unit/plugins/database/MorphToModelTest.php index b0eb37ff7..962f2d658 100644 --- a/tests/unit/plugins/database/MorphToModelTest.php +++ b/tests/unit/plugins/database/MorphToModelTest.php @@ -6,7 +6,7 @@ use Database\Tester\Models\EventLog; class MorphToModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/NestedTreeModelTest.php b/tests/unit/plugins/database/NestedTreeModelTest.php index 49d46764c..b2f0ae019 100644 --- a/tests/unit/plugins/database/NestedTreeModelTest.php +++ b/tests/unit/plugins/database/NestedTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategoryNested; class NestedTreeModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/NullableModelTest.php b/tests/unit/plugins/database/NullableModelTest.php index 9d6d61114..0484409d4 100644 --- a/tests/unit/plugins/database/NullableModelTest.php +++ b/tests/unit/plugins/database/NullableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\NullablePost; class NullableModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/RevisionableModelTest.php b/tests/unit/plugins/database/RevisionableModelTest.php index c8fa19a54..afee0c93d 100644 --- a/tests/unit/plugins/database/RevisionableModelTest.php +++ b/tests/unit/plugins/database/RevisionableModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\RevisionablePost; class RevisionableModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/SimpleTreeModelTest.php b/tests/unit/plugins/database/SimpleTreeModelTest.php index c22b11d16..fac6a48f3 100644 --- a/tests/unit/plugins/database/SimpleTreeModelTest.php +++ b/tests/unit/plugins/database/SimpleTreeModelTest.php @@ -5,7 +5,7 @@ use Database\Tester\Models\CategorySimple; class SimpleTreeModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -165,11 +165,12 @@ class SimpleTreeModelTest extends PluginTestCase ], $array); } + /** + * @expectedException \Exception + * @expectedExceptionMessage Column mismatch in listsNested method + */ public function testListsNestedUnknownColumn() { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Column mismatch in listsNested method'); - CategorySimple::listsNested('custom_name', 'id'); } diff --git a/tests/unit/plugins/database/SluggableModelTest.php b/tests/unit/plugins/database/SluggableModelTest.php index 22cad1837..f1f7aac71 100644 --- a/tests/unit/plugins/database/SluggableModelTest.php +++ b/tests/unit/plugins/database/SluggableModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\SluggablePost; class SluggableModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/SoftDeleteModelTest.php b/tests/unit/plugins/database/SoftDeleteModelTest.php index fc5212433..23a0ef436 100644 --- a/tests/unit/plugins/database/SoftDeleteModelTest.php +++ b/tests/unit/plugins/database/SoftDeleteModelTest.php @@ -10,7 +10,7 @@ use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete; class SoftDeleteModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/plugins/database/ValidationModelTest.php b/tests/unit/plugins/database/ValidationModelTest.php index f5ddb55b7..ce1f4e382 100644 --- a/tests/unit/plugins/database/ValidationModelTest.php +++ b/tests/unit/plugins/database/ValidationModelTest.php @@ -4,7 +4,7 @@ use Database\Tester\Models\ValidationPost; class ValidationModelTest extends PluginTestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -13,10 +13,11 @@ class ValidationModelTest extends PluginTestCase $this->runPluginRefreshCommand('Database.Tester'); } + /** + * @expectedException October\Rain\Database\ModelException + */ public function testUniqueTableValidation() { - $this->expectException(\October\Rain\Database\ModelException::class); - $post = ValidationPost::create([ 'title' => 'This is a new post', 'slug' => 'post-1', diff --git a/tests/unit/system/AliasesTest.php b/tests/unit/system/AliasesTest.php deleted file mode 100644 index 8d389cdcc..000000000 --- a/tests/unit/system/AliasesTest.php +++ /dev/null @@ -1,22 +0,0 @@ -assertTrue(class_exists('Illuminate\Support\Facades\Input')); - $this->assertInstanceOf( - \October\Rain\Support\Facades\Input::class, - new \Illuminate\Support\Facades\Input() - ); - } - - public function testHtmlDumperAlias() - { - $this->assertTrue(class_exists('Illuminate\Support\Debug\HtmlDumper')); - $this->assertInstanceOf( - \Symfony\Component\VarDumper\Dumper\HtmlDumper::class, - new \Illuminate\Support\Debug\HtmlDumper() - ); - } -} diff --git a/tests/unit/system/classes/AutoDatasourceTest.php b/tests/unit/system/classes/AutoDatasourceTest.php index 14dc6f54b..43410cc77 100644 --- a/tests/unit/system/classes/AutoDatasourceTest.php +++ b/tests/unit/system/classes/AutoDatasourceTest.php @@ -7,7 +7,7 @@ use October\Rain\Halcyon\Datasource\FileDatasource; class CmsThemeTemplateFixture extends Model { - protected $guarded = []; + protected $fillable = ['*']; public $timestamps = false; @@ -30,7 +30,7 @@ class AutoDatasourceTest extends PluginTestCase */ public $datasource; - public function setUp(): void + public function setUp() { parent::setUp(); @@ -73,7 +73,7 @@ class AutoDatasourceTest extends PluginTestCase ]); } - public function tearDown(): void + public function tearDown() { foreach ($this->fixtures as $fixture) { $fixture->delete(); diff --git a/tests/unit/system/classes/CombineAssetsTest.php b/tests/unit/system/classes/CombineAssetsTest.php index 5c5d313bd..c9fed4a04 100644 --- a/tests/unit/system/classes/CombineAssetsTest.php +++ b/tests/unit/system/classes/CombineAssetsTest.php @@ -5,7 +5,7 @@ use System\Classes\CombineAssets; class CombineAssetsTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -24,10 +24,10 @@ class CombineAssetsTest extends TestCase * Supported file extensions should exist */ $jsExt = $cssExt = self::getProtectedProperty($combiner, 'jsExtensions'); - $this->assertIsArray($jsExt); + $this->assertInternalType('array', $jsExt); $cssExt = self::getProtectedProperty($combiner, 'cssExtensions'); - $this->assertIsArray($cssExt); + $this->assertInternalType('array', $cssExt); /* * Check service methods diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php index f4ee00a32..48a82ba3e 100644 --- a/tests/unit/system/classes/MarkupManagerTest.php +++ b/tests/unit/system/classes/MarkupManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\MarkupManager; class MarkupManagerTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/system/classes/MediaLibraryTest.php b/tests/unit/system/classes/MediaLibraryTest.php index 54c4a6a3e..35e561594 100644 --- a/tests/unit/system/classes/MediaLibraryTest.php +++ b/tests/unit/system/classes/MediaLibraryTest.php @@ -4,7 +4,7 @@ use System\Classes\MediaLibrary; class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine { - protected function tearDown() : void + protected function tearDown() { $this->removeMedia(); parent::tearDown(); @@ -66,7 +66,7 @@ class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine public function testValidPathsOnValidatePath($path) { $result = MediaLibrary::validatePath($path); - $this->assertIsString($result); + $this->assertInternalType('string', $result); } public function testListFolderContents() diff --git a/tests/unit/system/classes/PluginManagerTest.php b/tests/unit/system/classes/PluginManagerTest.php index c963f2dd6..4b20f8509 100644 --- a/tests/unit/system/classes/PluginManagerTest.php +++ b/tests/unit/system/classes/PluginManagerTest.php @@ -5,7 +5,7 @@ class PluginManagerTest extends TestCase { public $manager; - public function setUp() : void + public function setUp() { parent::setUp(); diff --git a/tests/unit/system/classes/VersionManagerTest.php b/tests/unit/system/classes/VersionManagerTest.php index ba163d18c..b9934d3e5 100644 --- a/tests/unit/system/classes/VersionManagerTest.php +++ b/tests/unit/system/classes/VersionManagerTest.php @@ -5,7 +5,7 @@ use System\Classes\VersionManager; class VersionManagerTest extends TestCase { - public function setUp() : void + public function setUp() { parent::setUp(); @@ -100,8 +100,8 @@ class VersionManagerTest extends TestCase $manager = VersionManager::instance(); list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]); - $this->assertIsArray($comments); - $this->assertIsArray($scripts); + $this->assertInternalType('array', $comments); + $this->assertInternalType('array', $scripts); $this->assertEquals($expectedComments, $comments); $this->assertEquals($expectedScripts, $scripts); diff --git a/tests/unit/system/console/OctoberEnvTest.php b/tests/unit/system/console/OctoberEnvTest.php index 184c3e68d..6170cc739 100644 --- a/tests/unit/system/console/OctoberEnvTest.php +++ b/tests/unit/system/console/OctoberEnvTest.php @@ -13,7 +13,7 @@ class OctoberEnvTest extends TestCase /** @var string Stores the original config path from the app container */ public static $origConfigPath; - protected function setUp(): void + protected function setUp() { parent::setUp(); @@ -30,32 +30,59 @@ class OctoberEnvTest extends TestCase // Check environment file $envFile = file_get_contents(base_path('.env')); - $this->assertStringContainsString('APP_DEBUG=true', $envFile); - $this->assertStringContainsString('APP_URL=https://localhost', $envFile); - $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); - $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); - $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); - $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); - $this->assertStringContainsString('DB_PORT=3306', $envFile); + // Forward compatible assertions + // @TODO: Use only `assertStringContainsString` after L6 upgrade + + if (method_exists($this, 'assertStringContainsString')) { + $this->assertStringContainsString('APP_DEBUG=true', $envFile); + $this->assertStringContainsString('APP_URL=https://localhost', $envFile); + $this->assertStringContainsString('DB_CONNECTION=mysql', $envFile); + $this->assertStringContainsString('DB_DATABASE="data#base"', $envFile); + $this->assertStringContainsString('DB_USERNAME="teal\'c"', $envFile); + $this->assertStringContainsString('DB_PASSWORD="test\\"quotes\'test"', $envFile); + $this->assertStringContainsString('DB_PORT=3306', $envFile); + } else { + $this->assertContains('APP_DEBUG=true', $envFile); + $this->assertContains('APP_URL=https://localhost', $envFile); + $this->assertContains('DB_CONNECTION=mysql', $envFile); + $this->assertContains('DB_DATABASE="data#base"', $envFile); + $this->assertContains('DB_USERNAME="teal\'c"', $envFile); + $this->assertContains('DB_PASSWORD="test\\"quotes\'test"', $envFile); + $this->assertContains('DB_PORT=3306', $envFile); + } // Check app.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/app.php')); - $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); - $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); + if (method_exists($this, 'assertStringContainsString')) { + $this->assertStringContainsString('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); + $this->assertStringContainsString('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); + } else { + $this->assertContains('\'debug\' => env(\'APP_DEBUG\', true),', $appConfigFile); + $this->assertContains('\'url\' => env(\'APP_URL\', \'https://localhost\'),', $appConfigFile); + } // Check database.php config file $appConfigFile = file_get_contents(storage_path('temp/tests/config/database.php')); - $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); - $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); - // Both the following configurations had values in the original config, they should be stripped out once - // the .env file is generated. - $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); - $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); + if (method_exists($this, 'assertStringContainsString')) { + $this->assertStringContainsString('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); + $this->assertStringContainsString('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); + // Both the following configurations had values in the original config, they should be stripped out once + // the .env file is generated. + $this->assertStringContainsString('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); + $this->assertStringContainsString('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); + } else { + $this->assertContains('\'default\' => env(\'DB_CONNECTION\', \'mysql\')', $appConfigFile); + $this->assertContains('\'port\' => env(\'DB_PORT\', 3306),', $appConfigFile); + // Both the following configurations had values in the original config, they should be stripped out once + // the .env file is generated. + $this->assertContains('\'username\' => env(\'DB_USERNAME\', \'\'),', $appConfigFile); + $this->assertContains('\'password\' => env(\'DB_PASSWORD\', \'\'),', $appConfigFile); + } } - protected function tearDown(): void + protected function tearDown() { $this->tearDownConfigFixtures(); $this->restoreEnvFile(); diff --git a/tests/unit/system/traits/AssetMakerTest.php b/tests/unit/system/traits/AssetMakerTest.php index 606df9562..352d66107 100644 --- a/tests/unit/system/traits/AssetMakerTest.php +++ b/tests/unit/system/traits/AssetMakerTest.php @@ -10,7 +10,7 @@ class AssetMakerTest extends TestCase { private $stub; - public function setUp() : void + public function setUp() { $this->createApplication(); $this->stub = new AssetMakerStub();