Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ bin/cake plugin load TestHelper --only-debug

This will also load the routes.

### Authorization Plugin

If you are using the [CakePHP Authorization plugin](https://github.com/cakephp/authorization), you need to configure TestHelper to bypass authorization checks. Add this to your `config/bootstrap.php`:

```php
Configure::write('TestHelper.ignoreAuthorization', true);
```

This is similar to how DebugKit handles authorization and is necessary to prevent `AuthorizationRequiredException` errors when accessing TestHelper routes.

### non-dev mode
In certain apps it can be useful to have some of the helper functionality available also for staging and prod.
Here you must make sure then to not load the routes, though:
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
"homepage": "https://github.com/dereuromark/cakephp-test-helper",
"require": {
"php": ">=8.1",
"cakephp/cakephp": "^5.1.1",
"dereuromark/cakephp-tools": "^3.0.0"
"cakephp/cakephp": "^5.1.1"
},
"require-dev": {
"dereuromark/cakephp-templating": "^0.2.12",
"fig-r/psr2r-sniffer": "dev-master",
"phpunit/phpunit": "^10.5 || ^11.5 || ^12.1",
"sebastian/diff": "^5.0.0 || ^6.0.0 || ^7.0.0"
Expand Down
6 changes: 5 additions & 1 deletion config/config.dist.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<?php

return [
'TestHelper' => [],
'TestHelper' => [
// Set to true to bypass authorization checks for TestHelper routes
// when using the CakePHP Authorization plugin
'ignoreAuthorization' => true,
],

'Icon' => [
'map' => [
Expand Down
16 changes: 13 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Browse `/test-helper` to see all functionality available.
- Run tests and display results or coverage in backend.

## Configuration
- TestHelper.command: If you need a custom phpunit command to run with.
Both `php phpunit.phar` and `vendor/bin/phpunit` work out of the box.
- TestHelper.coverage: Set to `xdebug` if you have this enabled, it otherwise uses pcov by default.
- **TestHelper.command**: If you need a custom phpunit command to run with. Both `php phpunit.phar` and `vendor/bin/phpunit` work out of the box.
- **TestHelper.coverage**: Set to `xdebug` if you have this enabled, it otherwise uses pcov by default.
- **TestHelper.ignoreAuthorization**: Set to `true` to bypass authorization checks when using the CakePHP Authorization plugin. Default: `false`.

### Your own template
The default template ships with bootstrap (5) and fontawesome icons.
Expand All @@ -21,6 +21,16 @@ Overwrite the `test_cases` element if you want to support e.g. foundation and th

## Troubleshooting

### Authorization Plugin Errors

If you are using the [CakePHP Authorization plugin](https://github.com/cakephp/authorization) and encounter `AuthorizationRequiredException` errors when accessing TestHelper routes, add this to your `config/bootstrap.php`:

```php
Configure::write('TestHelper.ignoreAuthorization', true);
```

This will skip authorization checks for all TestHelper routes, similar to how DebugKit handles authorization.

### Generated code coverage is black&white
If the assets don't work, make sure your Nginx/Apache (like CakeBox Vagrant VM by default) doesn't block hidden files.

Expand Down
32 changes: 25 additions & 7 deletions src/Command/FixtureCheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace TestHelper\Command;

use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
Expand All @@ -11,8 +12,6 @@
use Cake\Datasource\ConnectionManager;
use Cake\Error\Debugger;
use Cake\ORM\Table;
use Shim\Command\Command;
use Shim\Filesystem\Folder;

if (!defined('TESTS')) {
define('TESTS', ROOT . DS . 'tests' . DS);
Expand Down Expand Up @@ -58,6 +57,16 @@ class FixtureCheckCommand extends Command {
*/
protected array $_missingFields = [];

/**
* @var \Cake\Console\Arguments
*/
protected Arguments $args;

/**
* @var \Cake\Console\ConsoleIo
*/
protected ConsoleIo $io;

/**
* @inheritDoc
*/
Expand All @@ -76,6 +85,9 @@ public function initialize(): void {
public function execute(Arguments $args, ConsoleIo $io) {
parent::execute($args, $io);

$this->args = $args;
$this->io = $io;

$fixtures = $this->_getFixtures();
$this->io->out(count($fixtures) . ' fixtures found, processing:');
$this->io->out('');
Expand Down Expand Up @@ -439,12 +451,18 @@ protected function _getFixtureFiles() {
$fixtureFolder = Plugin::path($plugin) . 'tests' . DS . 'Fixture' . DS;
}

$folder = new Folder($fixtureFolder);
$content = $folder->read();

$fixtures = [];
foreach ($content[1] as $file) {
$fixture = substr($file, 0, -4);
if (!is_dir($fixtureFolder)) {
return $fixtures;
}

$files = array_values(array_diff(scandir($fixtureFolder), ['.', '..']));
foreach ($files as $file) {
if (!is_file($fixtureFolder . $file) || pathinfo($file, PATHINFO_EXTENSION) !== 'php') {
continue;
}

$fixture = pathinfo($file, PATHINFO_FILENAME);
if (substr($fixture, -7) !== 'Fixture') {
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Controller/Component/PluginsComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ protected function addProperty(string $content, string $part, array $result): st

$pos = null;
foreach ($pieces as $i => $piece) {
if (strpos($piece, 'class Plugin extends BasePlugin') === false) {
if (!str_contains($piece, 'class Plugin extends BasePlugin')) {
continue;
}

Expand All @@ -264,7 +264,7 @@ protected function addProperty(string $content, string $part, array $result): st
$indentation . '/**',
$indentation . ' * @var bool',
$indentation . ' */',
$indentation . 'protected $' . $part . 'Enabled = ' . ($result[$part . 'Exists'] ? 'true' : 'false') . ';',
$indentation . 'protected $' . $part . 'Enabled = ' . (!empty($result[$part . 'Exists']) ? 'true' : 'false') . ';',
];
if (trim($pieces[$pos + 1]) !== '{') {
array_unshift($add, '');
Expand Down
33 changes: 22 additions & 11 deletions src/Controller/Component/TestGeneratorComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Cake\Controller\Component;
use Cake\Core\Plugin;
use Shim\Filesystem\Folder;
use DirectoryIterator;

/**
* @property \Cake\Controller\Component\FlashComponent $Flash
Expand Down Expand Up @@ -107,19 +107,30 @@ public function generateFixture($name, $plugin, array $options = []) {
public function getFiles(array $folders) {
$names = [];
foreach ($folders as $folder) {
$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);

foreach ($folderContent[1] as $file) {
$name = pathinfo($file, PATHINFO_FILENAME);
$names[] = $name;
if (!is_dir($folder)) {
continue;
}

foreach ($folderContent[0] as $subFolder) {
$folderContent = (new Folder($folder . $subFolder))->read(Folder::SORT_NAME, true);
// Get files in the main folder
$iterator = new DirectoryIterator($folder);
foreach ($iterator as $fileInfo) {
if ($fileInfo->isFile() && $fileInfo->getExtension() === 'php') {
$name = $fileInfo->getBasename('.php');
$names[] = $name;
}
}

foreach ($folderContent[1] as $file) {
$name = pathinfo($file, PATHINFO_FILENAME);
$names[] = $subFolder . '/' . $name;
// Get files in subdirectories
foreach ($iterator as $fileInfo) {
if ($fileInfo->isDir() && !$fileInfo->isDot()) {
$subFolder = $fileInfo->getFilename();
$subIterator = new DirectoryIterator($folder . $subFolder);
foreach ($subIterator as $subFileInfo) {
if ($subFileInfo->isFile() && $subFileInfo->getExtension() === 'php') {
$name = $subFileInfo->getBasename('.php');
$names[] = $subFolder . '/' . $name;
}
}
}
}
}
Expand Down
23 changes: 1 addition & 22 deletions src/Controller/DemoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,7 @@

namespace TestHelper\Controller;

use App\Controller\AppController;
use Cake\Event\EventInterface;

class DemoController extends AppController {

/**
* @param \Cake\Event\EventInterface $event
* @return void
*/
public function beforeFilter(EventInterface $event): void {
parent::beforeFilter($event);

if ($this->components()->has('Security')) {
$this->components()->get('Security')->setConfig('validatePost', false);
}

if ($this->components()->has('Auth') && method_exists($this->components()->get('Auth'), 'allow')) {
$this->components()->get('Auth')->allow();
} elseif ($this->components()->has('Authentication') && method_exists($this->components()->get('Authentication'), 'addUnauthenticatedActions')) {
$this->components()->get('Authentication')->addUnauthenticatedActions(['index']);
}
}
class DemoController extends TestHelperAppController {

/**
* @return \Cake\Http\Response|null|void
Expand Down
66 changes: 38 additions & 28 deletions src/Controller/MigrationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

namespace TestHelper\Controller;

use App\Controller\AppController;
use Cake\Datasource\ConnectionManager;
use Cake\Event\EventInterface;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
use Shim\Filesystem\Folder;

/**
* @property \TestHelper\Controller\Component\MigrationsComponent $Migrations
*/
class MigrationsController extends AppController {
class MigrationsController extends TestHelperAppController {

protected ?string $defaultTable = '';

Expand All @@ -32,16 +30,6 @@ public function initialize(): void {
public function beforeFilter(EventInterface $event): void {
parent::beforeFilter($event);

if ($this->components()->has('Security')) {
$this->components()->get('Security')->setConfig('validatePost', false);
}

if ($this->components()->has('Auth') && method_exists($this->components()->get('Auth'), 'allow')) {
$this->components()->get('Auth')->allow();
} elseif ($this->components()->has('Authentication') && method_exists($this->components()->get('Authentication'), 'addUnauthenticatedActions')) {
$this->components()->get('Authentication')->addUnauthenticatedActions(['index']);
}

if (!file_exists(ROOT . DS . 'vendor/cakephp/migrations/composer.json')) {
$this->Flash->error('It seems the Migrations plugin is missing.');

Expand Down Expand Up @@ -91,8 +79,13 @@ public function tmpDb() {
* @return \Cake\Http\Response|null|void
*/
public function snapshot() {
$x = (new Folder(CONFIG . 'MigrationsTmp'))->read();
$files = $x[1] ?? [];
$files = [];
$migrationsTmpPath = CONFIG . 'MigrationsTmp';
if (is_dir($migrationsTmpPath)) {
$files = array_values(array_diff(scandir($migrationsTmpPath), ['.', '..']));
$files = array_filter($files, fn ($file) => is_file($migrationsTmpPath . DS . $file));
}

if ($this->request->is('post') && $this->request->getData('clear')) {
foreach ($files as $file) {
unlink(CONFIG . 'MigrationsTmp' . DS . $file);
Expand Down Expand Up @@ -131,8 +124,12 @@ public function snapshotTest() {
$database = $dbConfig['database'] ?? [];
$tmpDatabase = $database . '_tmp';

$x = (new Folder(CONFIG . 'MigrationsTmp'))->read();
$files = $x[1] ?? [];
$files = [];
$migrationsTmpPath = CONFIG . 'MigrationsTmp';
if (is_dir($migrationsTmpPath)) {
$files = array_values(array_diff(scandir($migrationsTmpPath), ['.', '..']));
$files = array_filter($files, fn ($file) => is_file($migrationsTmpPath . DS . $file));
}

if ($this->request->is('post') && $this->request->getData('test')) {
$connectionConfig = [
Expand Down Expand Up @@ -185,8 +182,12 @@ public function seedTest() {
$database = $dbConfig['database'] ?? [];
$tmpDatabase = $database . '_tmp';

$x = (new Folder(CONFIG . 'Seeds'))->read();
$seeds = $x[1] ?? [];
$seeds = [];
$seedsPath = CONFIG . 'Seeds';
if (is_dir($seedsPath)) {
$seeds = array_values(array_diff(scandir($seedsPath), ['.', '..']));
$seeds = array_filter($seeds, fn ($file) => is_file($seedsPath . DS . $file));
}

if ($this->request->is('post') && $this->request->getData('test')) {
$command = 'bin/cake migrations seed -c "mysql://[email protected]/' . $tmpDatabase . '"';
Expand All @@ -213,16 +214,25 @@ public function confirm() {
$tmpDatabase = $database . '_tmp';

if ($this->request->is('post') && $this->request->getData('confirm')) {
$folder = (new Folder(CONFIG . 'Migrations'))->read();
$files = $folder[1] ?? [];
foreach ($files as $file) {
unlink(CONFIG . 'Migrations' . DS . $file);
// Remove old migration files
$migrationsPath = CONFIG . 'Migrations';
if (is_dir($migrationsPath)) {
$files = array_values(array_diff(scandir($migrationsPath), ['.', '..']));
$files = array_filter($files, fn ($file) => is_file($migrationsPath . DS . $file));
foreach ($files as $file) {
unlink($migrationsPath . DS . $file);
}
}
$folder = (new Folder(CONFIG . 'MigrationsTmp'))->read();
$files = $folder[1] ?? [];
foreach ($files as $file) {
copy(CONFIG . 'MigrationsTmp' . DS . $file, CONFIG . 'Migrations' . DS . $file);
unlink(CONFIG . 'MigrationsTmp' . DS . $file);

// Copy tmp migration files to Migrations folder
$migrationsTmpPath = CONFIG . 'MigrationsTmp';
if (is_dir($migrationsTmpPath)) {
$files = array_values(array_diff(scandir($migrationsTmpPath), ['.', '..']));
$files = array_filter($files, fn ($file) => is_file($migrationsTmpPath . DS . $file));
foreach ($files as $file) {
copy($migrationsTmpPath . DS . $file, $migrationsPath . DS . $file);
unlink($migrationsTmpPath . DS . $file);
}
}

/** @var \Cake\Database\Connection $connection */
Expand Down
Loading