From 457dbc3fcba3ea17f5d17f1e264fc9d0310d1c74 Mon Sep 17 00:00:00 2001 From: MGatner Date: Tue, 23 Mar 2021 21:05:36 +0000 Subject: [PATCH 1/6] Apply development toolkit --- .gitattributes | 13 +++++ .github/dependabot.yml | 12 ++++ .github/workflows/analyze.yml | 74 +++++++++++++++++++++++++ .github/workflows/test.yml | 80 +++++++++++++++++++++++++++ .gitignore | 1 + README.md | 6 +- composer.json | 44 ++++++++------- {bin => examples}/Forms.php | 0 infection.json.dist | 19 +++++++ phpstan.neon.dist | 24 ++++++++ phpunit.xml.dist | 101 +++++++++++++++++++++------------- 11 files changed, 315 insertions(+), 59 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/analyze.yml create mode 100644 .github/workflows/test.yml rename {bin => examples}/Forms.php (100%) create mode 100644 infection.json.dist create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcf895c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +/.github export-ignore +/docs export-ignore +/examples export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/phpunit.xml.dist export-ignore +/phpstan.neon.dist export-ignore + +# Configure diff output for .php and .phar files. +*.php diff=php +*.phar -diff diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fd20b19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml new file mode 100644 index 0000000..debf364 --- /dev/null +++ b/.github/workflows/analyze.yml @@ -0,0 +1,74 @@ +# When a PR is opened or a push is made, perform +# a static analysis check on the code using PHPStan. +name: PHPStan + +on: + pull_request: + branches: + - 'develop' + paths: + - 'src/**' + - 'tests/**' + - 'composer.**' + - 'phpstan*' + - '.github/workflows/analyze.yml' + push: + branches: + - 'develop' + paths: + - 'src/**' + - 'tests/**' + - 'composer.**' + - 'phpstan*' + - '.github/workflows/analyze.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Static Analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0'] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer, pecl, phpunit + extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Create composer cache directory + run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create PHPStan cache directory + run: mkdir -p build/phpstan + + - name: Cache PHPStan results + uses: actions/cache@v2 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + + - name: Run static analysis + run: vendor/bin/phpstan analyze diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9ded22a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: PHPUnit + +on: + pull_request: + branches: + - develop + push: + branches: + - develop + +jobs: + main: + name: PHP ${{ matrix.php-versions }} Unit Tests + + strategy: + matrix: + php-versions: ['7.3', '7.4', '8.0'] + + runs-on: ubuntu-latest + + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer, pecl, phpunit + extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 + coverage: xdebug + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + + - name: Test with PHPUnit + run: vendor/bin/phpunit --verbose --coverage-text + env: + TERM: xterm-256color + + - if: matrix.php-versions == '8.0' + name: Mutate with Infection + run: | + composer global require infection/infection + git fetch --depth=1 origin $GITHUB_BASE_REF + infection --threads=2 --skip-initial-tests --coverage=build/phpunit --git-diff-base=origin/$GITHUB_BASE_REF --git-diff-filter=AM --logger-github --ignore-msi-with-no-mutations + + - if: matrix.php-versions == '8.0' + name: Run Coveralls + run: vendor/bin/php-coveralls --verbose --coverage_clover=build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} + + coveralls: + needs: [main] + name: Coveralls Finished + runs-on: ubuntu-latest + steps: + - name: Upload Coveralls results + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true diff --git a/.gitignore b/.gitignore index c5c5f07..11192f3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ vendor/ build/ phpunit*.xml phpunit +*.cache composer.lock .DS_Store diff --git a/README.md b/README.md index ed776b2..a1e2309 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Tatter\Forms RESTful AJAX forms for CodeIgniter 4 +[![](https://github.com/tattersoftware/codeigniter4-forms/workflows/PHPUnit/badge.svg)](https://github.com/tattersoftware/codeigniter4-forms/actions?query=workflow%3A%22PHPUnit) +[![](https://github.com/tattersoftware/codeigniter4-forms/workflows/PHPStan/badge.svg)](https://github.com/tattersoftware/codeigniter4-forms/actions?query=workflow%3A%22PHPStan) +[![Coverage Status](https://coveralls.io/repos/github/tattersoftware/codeigniter4-forms/badge.svg?branch=develop)](https://coveralls.io/github/tattersoftware/codeigniter4-forms?branch=develop) + ## Quick Start 1. Install with Composer: `> composer require tatter/forms` @@ -46,7 +50,7 @@ may vary): ## Configuration (optional) The library's default behavior can be overridden or augment by its config file. Copy -**bin/Forms.php** to **app/Config/Forms.php** and follow the instructions in the +**examples/Forms.php** to **app/Config/Forms.php** and follow the instructions in the comments. If no config file is found the library will use its defaults. ## Usage diff --git a/composer.json b/composer.json index 8be0236..0bd62d4 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "tatter/forms", + "type": "library", "description": "RESTful AJAX forms for CodeIgniter 4", "keywords": [ "codeigniter", @@ -20,40 +21,43 @@ "role": "Developer" } ], - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/CodeIgniter4" - } - ], - "minimum-stability": "dev", "require": { - "php" : ">=7.2", - "tatter/assets": "^2.0", + "php": "^7.3 || ^8.0", + "components/jquery": "^3.3", "tatter/alerts": "^2.0", - "components/jquery": "^3.3", - "twbs/bootstrap": "^4.3" + "tatter/assets": "^2.0", + "twbs/bootstrap": "^4.3" }, "require-dev": { "codeigniter4/codeigniter4": "dev-develop", - "mikey179/vfsstream": "1.6.*", - "mockery/mockery": "^1.0", - "phpunit/phpunit" : "^7.0" + "mikey179/vfsstream": "^1.6", + "tatter/tools": "^1.7" }, "autoload": { "psr-4": { "Tatter\\Forms\\": "src" - } + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] }, "autoload-dev": { "psr-4": { - "ModuleTests\\Support\\": "tests/_support" + "Tests\\Support\\": "tests/_support" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/codeigniter4/CodeIgniter4" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, "scripts": { - "test": "phpunit", - "post-update-cmd": [ - "composer dump-autoload" - ] + "analyze": "phpstan analyze", + "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", + "style": "phpcbf --standard=./vendor/codeigniter4/codeigniter4-standard/CodeIgniter4 tests/ src/", + "test": "phpunit" } } diff --git a/bin/Forms.php b/examples/Forms.php similarity index 100% rename from bin/Forms.php rename to examples/Forms.php diff --git a/infection.json.dist b/infection.json.dist new file mode 100644 index 0000000..b175102 --- /dev/null +++ b/infection.json.dist @@ -0,0 +1,19 @@ +{ + "source": { + "directories": [ + "src" + ], + "excludes": [ + "Config", + "Database/Migrations", + "Views" + ] + }, + "logs": { + "text": "build/infection.log" + }, + "mutators": { + "@default": true + }, + "bootstrap": "vendor/codeigniter4/codeigniter4/system/Test/bootstrap.php" +} diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..6fbb80c --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,24 @@ +parameters: + tmpDir: build/phpstan + level: 5 + paths: + - src + - tests + bootstrapFiles: + - vendor/codeigniter4/codeigniter4/system/Test/bootstrap.php + excludes_analyse: + - src/Config/Routes.php + - src/Views/* + ignoreErrors: + - '#Call to an undefined static method Config\\Services::[A-Za-z]+\(\)#' + - '#Cannot access property [\$a-z_]+ on (array|object)#' + - '#Unsafe usage of new static\(\)*#' + universalObjectCratesClasses: + - CodeIgniter\Entity + - Faker\Generator + scanDirectories: + - vendor/codeigniter4/codeigniter4/system/Helpers + dynamicConstantNames: + - APP_NAMESPACE + - CI_DEBUG + - ENVIRONMENT diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3b2cf45..745249c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,50 +1,75 @@ - + + + + + ./src + + + ./src/Views + ./src/Config/Routes.php + + + + + + + + + + - + ./tests - - - ./src - - ./src/Views - ./src/Config/Routes.php - - - - + + + + - - - - - - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + From aaaa79893642e39243d5d15e7044979054cdbfbf Mon Sep 17 00:00:00 2001 From: MGatner Date: Wed, 24 Mar 2021 01:37:36 +0000 Subject: [PATCH 2/6] Fix static analysis --- phpstan.neon.dist | 3 --- src/Controllers/ResourceController.php | 14 ++++++++++---- src/Controllers/ResourcePresenter.php | 3 ++- src/Language/en/Forms.php | 2 +- src/Traits/ResourceTrait.php | 7 ++++++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 6fbb80c..c45b9b7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,9 +10,6 @@ parameters: - src/Config/Routes.php - src/Views/* ignoreErrors: - - '#Call to an undefined static method Config\\Services::[A-Za-z]+\(\)#' - - '#Cannot access property [\$a-z_]+ on (array|object)#' - - '#Unsafe usage of new static\(\)*#' universalObjectCratesClasses: - CodeIgniter\Entity - Faker\Generator diff --git a/src/Controllers/ResourceController.php b/src/Controllers/ResourceController.php index 7046a34..c0175f5 100644 --- a/src/Controllers/ResourceController.php +++ b/src/Controllers/ResourceController.php @@ -1,13 +1,17 @@ respondDeleted(null, lang('Forms.deleted', [$this->name])); } - /************* SUPPORT METHODS *************/ + //-------------------------------------------------------------------- + // Support Methods + //-------------------------------------------------------------------- protected function ensureExists($id = null) { @@ -90,6 +96,6 @@ protected function actionFailed(string $action, int $status = 400) 'messages' => $this->model->errors(), ]; - return $this->respond($response, $status, $message); + return $this->respond($response, $status); } } diff --git a/src/Controllers/ResourcePresenter.php b/src/Controllers/ResourcePresenter.php index e2a3b5d..c9dce50 100644 --- a/src/Controllers/ResourcePresenter.php +++ b/src/Controllers/ResourcePresenter.php @@ -1,9 +1,10 @@ 'New {0} created successfully.', 'updated' => '{0} updated successfully.', 'deleted' => '{0} deleted successfully.', - 'updateFailed' => 'Unable to create a new {0}.', + 'createFailed' => 'Unable to create a new {0}.', 'updateFailed' => 'Unable to update that {0}.', 'deleteFailed' => 'Unable to delete that {0}.', ]; diff --git a/src/Traits/ResourceTrait.php b/src/Traits/ResourceTrait.php index 1ab7781..f5348e9 100644 --- a/src/Traits/ResourceTrait.php +++ b/src/Traits/ResourceTrait.php @@ -1,7 +1,12 @@ model instanceof \CodeIgniter\Model) + if (! $this->model instanceof Model) { throw FormsException::forMissingModel(get_class($this)); } From 2f93f222d4fb9a78cb09d6b9a436ac678b92b5fe Mon Sep 17 00:00:00 2001 From: MGatner Date: Wed, 24 Mar 2021 02:03:15 +0000 Subject: [PATCH 3/6] Spruce up test classes --- phpstan.neon.dist | 1 + src/Config/Forms.php | 13 ++- tests/_support/Controllers/API/Factories.php | 3 +- tests/_support/Controllers/Factories.php | 5 +- .../2019-09-02-092335_create_test_tables.php | 2 +- .../Database/Seeds/IndustrialSeeder.php | 6 +- tests/_support/DatabaseTestCase.php | 99 ------------------- tests/_support/FormsTestCase.php | 86 ++++++++++++++++ tests/_support/MockRenderer.php | 17 +++- tests/_support/Models/FactoryModel.php | 2 +- tests/_support/bootstrap.php | 54 ---------- tests/database/ControllerTest.php | 6 +- tests/database/PresenterTest.php | 7 +- 13 files changed, 133 insertions(+), 168 deletions(-) delete mode 100644 tests/_support/DatabaseTestCase.php create mode 100644 tests/_support/FormsTestCase.php delete mode 100644 tests/_support/bootstrap.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c45b9b7..c5d2e15 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,6 +10,7 @@ parameters: - src/Config/Routes.php - src/Views/* ignoreErrors: + - '#Cannot access property \$[A-Za-z]+ on array\|object#' universalObjectCratesClasses: - CodeIgniter\Entity - Faker\Generator diff --git a/src/Config/Forms.php b/src/Config/Forms.php index c34da15..8a367c4 100644 --- a/src/Config/Forms.php +++ b/src/Config/Forms.php @@ -4,9 +4,18 @@ class Forms extends BaseConfig { - // Whether to continue instead of throwing exceptions + /** + * Whether to continue instead of throwing exceptions + * + * @var bool + */ public $silent = true; - // URL base for Resource controllers + + /** + * URL base for Resource controllers + * + * @var string + */ public $apiUrl = 'api/'; } diff --git a/tests/_support/Controllers/API/Factories.php b/tests/_support/Controllers/API/Factories.php index b2ce180..0020410 100644 --- a/tests/_support/Controllers/API/Factories.php +++ b/tests/_support/Controllers/API/Factories.php @@ -1,8 +1,9 @@ routes = Services::routes(); - - $this->routes->presenter('factories', ['controller' => 'ModuleTests\Support\Controllers\Factories']); - $this->routes->resource('api/factories', ['controller' => 'ModuleTests\Support\Controllers\API\Factories']); - - Services::injectMock('routes', $this->routes); - - // Inject mock renderer - $config = new \Config\View(); - $viewPath = config('Paths')->viewDirectory; - $renderer = new MockRenderer($config, $viewPath, Services::locator(true), CI_DEBUG, Services::logger(true)); - Services::injectMock('renderer', $renderer); - - // Mock framework - $config = config('App'); - $this->codeigniter = new MockCodeIgniter($config); - - // Module classes - $this->config = new \Tatter\Forms\Config\Forms(); - $this->model = new \ModuleTests\Support\Models\FactoryModel(); - } - - public function tearDown(): void - { - parent::tearDown(); - - if (count(ob_list_handlers()) > 1) - { - ob_end_clean(); - } - } -} diff --git a/tests/_support/FormsTestCase.php b/tests/_support/FormsTestCase.php new file mode 100644 index 0000000..105096c --- /dev/null +++ b/tests/_support/FormsTestCase.php @@ -0,0 +1,86 @@ +presenter('factories', ['controller' => 'Tests\Support\Controllers\Factories']); + $routes->resource('api/factories', ['controller' => 'Tests\Support\Controllers\API\Factories']); + + Services::injectMock('routes', $routes); + $this->routes = $routes; + + // Mock the renderer + $renderer = new MockRenderer(config('View'), config('Paths')->viewDirectory, Services::locator(true), CI_DEBUG, Services::logger(true)); + Services::injectMock('renderer', $renderer); + + // Load the test classes + $this->config = config('Forms'); + $this->model = new FactoryModel(); + $this->codeigniter = new MockCodeIgniter(config('App')); + } +} diff --git a/tests/_support/MockRenderer.php b/tests/_support/MockRenderer.php index 26f40dd..9f43f6e 100644 --- a/tests/_support/MockRenderer.php +++ b/tests/_support/MockRenderer.php @@ -1,9 +1,18 @@ - $view, 'data' => $this->data]); diff --git a/tests/_support/Models/FactoryModel.php b/tests/_support/Models/FactoryModel.php index 93c1213..2529af0 100644 --- a/tests/_support/Models/FactoryModel.php +++ b/tests/_support/Models/FactoryModel.php @@ -1,4 +1,4 @@ -appDirectory) . DIRECTORY_SEPARATOR); -define('ROOTPATH', realpath(APPPATH . '../') . DIRECTORY_SEPARATOR); -define('FCPATH', realpath(ROOTPATH . 'public') . DIRECTORY_SEPARATOR); -define('SYSTEMPATH', realpath($paths->systemDirectory) . DIRECTORY_SEPARATOR); -define('WRITEPATH', realpath($paths->writableDirectory) . DIRECTORY_SEPARATOR); -define('SUPPORTPATH', realpath(ROOTPATH . 'tests/_support') . DIRECTORY_SEPARATOR); - -// Define necessary module test path constants -define('MODULESUPPORTPATH', realpath(__DIR__) . DIRECTORY_SEPARATOR); -define('TESTPATH', realpath(MODULESUPPORTPATH . '../') . DIRECTORY_SEPARATOR); -define('MODULEPATH', realpath(__DIR__ . '/../../') . DIRECTORY_SEPARATOR); -define('COMPOSER_PATH', MODULEPATH . 'vendor/autoload.php'); - -// Set environment values that would otherwise stop the framework from functioning during tests. -if (! isset($_SERVER['app.baseURL'])) -{ - $_SERVER['app.baseURL'] = 'http://example.com'; -} - -// Load necessary modules -require_once APPPATH . 'Config/Autoload.php'; -require_once APPPATH . 'Config/Constants.php'; -require_once APPPATH . 'Config/Modules.php'; - -require_once SYSTEMPATH . 'Autoloader/Autoloader.php'; -require_once SYSTEMPATH . 'Config/BaseService.php'; -require_once APPPATH . 'Config/Services.php'; - -// Use Config\Services as CodeIgniter\Services -if (! class_exists('CodeIgniter\Services', false)) -{ - class_alias('Config\Services', 'CodeIgniter\Services'); -} - -// Launch the autoloader to gather namespaces (includes composer.json's "autoload-dev") -$loader = \CodeIgniter\Services::autoloader(); -$loader->initialize(new Config\Autoload(), new Config\Modules()); -$loader->register(); // Register the loader with the SPL autoloader stack. diff --git a/tests/database/ControllerTest.php b/tests/database/ControllerTest.php index 44430e3..0112edd 100644 --- a/tests/database/ControllerTest.php +++ b/tests/database/ControllerTest.php @@ -6,8 +6,12 @@ * tests/system/RESTful/ResourceControllerTest.php */ -class ControllerTest extends ModuleTests\Support\DatabaseTestCase +use CodeIgniter\Test\DatabaseTestTrait; +use Tests\Support\FormsTestCase; + +class ControllerTest extends FormsTestCase { + use DatabaseTestTrait; public function testResourceGet() { diff --git a/tests/database/PresenterTest.php b/tests/database/PresenterTest.php index a596cb5..b488ab3 100644 --- a/tests/database/PresenterTest.php +++ b/tests/database/PresenterTest.php @@ -6,8 +6,13 @@ * tests/system/RESTful/ResourceControllerTest.php */ -class PresenterTest extends ModuleTests\Support\DatabaseTestCase +use CodeIgniter\Test\DatabaseTestTrait; +use Tests\Support\FormsTestCase; + +class PresenterTest extends FormsTestCase { + use DatabaseTestTrait; + public function testResourceGet() { From fc8f7e2e57ae03a06030ce79907abf93f76f9e47 Mon Sep 17 00:00:00 2001 From: MGatner Date: Thu, 25 Mar 2021 19:12:44 +0000 Subject: [PATCH 4/6] Switch to Tachycardia --- phpunit.xml.dist | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 745249c..6ab0206 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -36,9 +36,26 @@ - - - + + + + + + 0.50 + + + 30 + + + 2 + + + true + + + + + From b08832754b004c1d8ac4488463459a0fad9dcf02 Mon Sep 17 00:00:00 2001 From: MGatner Date: Mon, 29 Mar 2021 17:10:37 +0000 Subject: [PATCH 5/6] Refactor tests --- src/Controllers/ResourceController.php | 47 ++-- src/Controllers/ResourcePresenter.php | 98 ++++---- tests/_support/Controllers/API/Factories.php | 2 +- .../2019-09-02-092335_create_test_tables.php | 6 +- tests/_support/FormsTestCase.php | 4 - tests/_support/MockRenderer.php | 2 +- tests/_support/Models/FactoryModel.php | 16 +- tests/_support/PresenterResponse.php | 63 +++++ tests/_support/PresenterTrait.php | 55 +++++ tests/database/ControllerTest.php | 226 ------------------ tests/database/PresenterTest.php | 219 ----------------- tests/read/ControllerReadTest.php | 157 ++++++++++++ tests/read/PresenterReadTest.php | 116 +++++++++ tests/write/ControllerWriteTest.php | 81 +++++++ tests/write/PresenterWriteTest.php | 83 +++++++ 15 files changed, 649 insertions(+), 526 deletions(-) create mode 100644 tests/_support/PresenterResponse.php create mode 100644 tests/_support/PresenterTrait.php delete mode 100644 tests/database/ControllerTest.php delete mode 100644 tests/database/PresenterTest.php create mode 100644 tests/read/ControllerReadTest.php create mode 100644 tests/read/PresenterReadTest.php create mode 100644 tests/write/ControllerWriteTest.php create mode 100644 tests/write/PresenterWriteTest.php diff --git a/src/Controllers/ResourceController.php b/src/Controllers/ResourceController.php index c0175f5..501f6c7 100644 --- a/src/Controllers/ResourceController.php +++ b/src/Controllers/ResourceController.php @@ -12,7 +12,7 @@ class ResourceController extends BaseController //-------------------------------------------------------------------- // CRUD Methods //-------------------------------------------------------------------- - + public function create() { $data = $this->request->getPost(); @@ -22,14 +22,14 @@ public function create() return $this->actionFailed('create', 422); } - return $this->respondCreated(null, lang('Forms.created', [$this->name])); + return $this->respondCreated($this->model->find($id), lang('Forms.created', [ucfirst($this->name)])); } public function index() { return $this->respond($this->model->findAll()); } - + public function show($id = null) { if (($object = $this->ensureExists($id)) instanceof ResponseInterface) @@ -37,9 +37,9 @@ public function show($id = null) return $object; } - return $this->respond([$this->model->find($id)]); + return $this->respond($object); } - + public function update($id = null) { if (($object = $this->ensureExists($id)) instanceof ResponseInterface) @@ -54,7 +54,7 @@ public function update($id = null) return $this->actionFailed('update', 422); } - return $this->respond(null, 200, lang('Forms.updated', [$this->name])); + return $this->respond($this->model->find($id), 200, lang('Forms.updated', [ucfirst($this->name)])); } public function delete($id = null) @@ -63,39 +63,54 @@ public function delete($id = null) { return $object; } - + if (! $this->model->delete($id)) { return $this->actionFailed('delete'); } - return $this->respondDeleted(null, lang('Forms.deleted', [$this->name])); + return $this->respondDeleted($object, lang('Forms.deleted', [ucfirst($this->name)])); } - + //-------------------------------------------------------------------- // Support Methods //-------------------------------------------------------------------- - + + /** + * Fetches an object or returns a failure Response. + * + * @param string|int|null $id + * + * @return mixed + */ protected function ensureExists($id = null) { - if ($object = $this->model->find($id)) + if (isset($id) && $object = $this->model->find($id)) { return $object; } - - return $this->failNotFound('Not Found', null, lang('Forms.notFound', [$this->name])); + + return $this->failNotFound('Not Found', null, lang('Forms.notFound', [ucfirst($this->name)])); } + /** + * Creates a standardized failure Response. + * + * @param string $action + * @param int $status + * + * @return ResponseInterface + */ protected function actionFailed(string $action, int $status = 400) { - $errors = $this->model->errors() ?? [lang("Forms.{$action}Failed", [$this->name])]; + $errors = $this->model->errors() ?? [lang("Forms.{$action}Failed", [ucfirst($this->name)])]; $response = [ 'status' => $status, - 'error' => "{$action} Failed", + 'error' => ucfirst($action) . ' Failed', 'messages' => $this->model->errors(), ]; - + return $this->respond($response, $status); } } diff --git a/src/Controllers/ResourcePresenter.php b/src/Controllers/ResourcePresenter.php index c9dce50..478a7e7 100644 --- a/src/Controllers/ResourcePresenter.php +++ b/src/Controllers/ResourcePresenter.php @@ -7,39 +7,39 @@ class ResourcePresenter extends BasePresenter { use ResourceTrait; - - protected $helpers = ['alerts']; - /************* CRUD METHODS *************/ - + protected $helpers = ['alerts', 'form']; + + //-------------------------------------------------------------------- + // CRUD Methods + //-------------------------------------------------------------------- + public function new() { - helper('form'); - return $this->request->isAJAX() ? - view("{$this->names}/form") : - view("{$this->names}/new"); + return view($this->request->isAJAX() ? "{$this->names}/form" : "{$this->names}/new"); } - + public function create() { $data = $this->request->getPost(); - + if (! $id = $this->model->insert($data)) { return $this->actionFailed('create'); } $this->alert('success', lang('Forms.created', [$this->name])); - - return redirect()->to($this->names); + + return redirect()->to(site_url($this->names)); } public function index() { - $data = [$this->names => $this->model->findAll()]; - return view("{$this->names}/index", $data); + return view("{$this->names}/index", [ + $this->names => $this->model->findAll(), + ]); } - + public function show($id = null) { if (($object = $this->ensureExists($id)) instanceof RedirectResponse) @@ -47,82 +47,82 @@ public function show($id = null) return $object; } - $data = [$this->name => $object]; - - return view("{$this->names}/show", $data); + return view("{$this->names}/show", [$this->name => $object]); } - + public function edit($id = null) { if (($object = $this->ensureExists($id)) instanceof RedirectResponse) { return $object; } - - $data = [$this->name => $object]; - - helper('form'); - return $this->request->isAJAX() ? - view("{$this->names}/form", $data) : - view("{$this->names}/edit", $data); + + return view($this->request->isAJAX() ? "{$this->names}/form" : "{$this->names}/edit", [ + $this->name => $object + ]); } - + public function update($id = null) { if (($object = $this->ensureExists($id)) instanceof RedirectResponse) { return $object; } - + $data = $this->request->getPost(); if (! $this->model->update($id, $data)) { return $this->actionFailed('update'); } - + $this->alert('success', lang('Forms.updated', [$this->name])); - - return redirect()->to("{$this->names}/{$id}"); + + return redirect()->to(site_url("{$this->names}/{$id}")); } - + public function remove($id = null) { if (($object = $this->ensureExists($id)) instanceof RedirectResponse) { return $object; } - + $data = [$this->name => $object]; - - helper('form'); - return $this->request->isAJAX() ? - view("{$this->names}/confirm", $data) : - view("{$this->names}/remove", $data); + + return view($this->request->isAJAX() ? "{$this->names}/confirm" : "{$this->names}/remove", [ + $this->name => $object, + ]); } - + public function delete($id = null) { if (($object = $this->ensureExists($id)) instanceof RedirectResponse) { return $object; } - + if (! $this->model->delete($id)) { - return $this->actionFailed('delete'); + return $this->actionFailed('delete'); } + + $this->alert('success', lang('Forms.deleted', [$this->name])); + + return redirect()->to(site_url("{$this->names}")); } - - /************* SUPPORT METHODS *************/ - + + //-------------------------------------------------------------------- + // Support Methods + //-------------------------------------------------------------------- + protected function ensureExists($id = null) { if ($object = $this->model->find($id)) { return $object; } - + $error = lang('Forms.notFound', [$this->name]); $this->alert('danger', $error); @@ -132,16 +132,18 @@ protected function ensureExists($id = null) protected function actionFailed(string $action) { - $errors = $this->model->errors() ?? [lang("Forms.{$action}Failed", [$this->name])]; + $errors = $this->model->errors() ?? [ + lang("Forms.{$action}Failed", [$this->name]), + ]; foreach ($errors as $error) { $this->alert('warning', $error); } - + return redirect()->back()->withInput()->with('errors', $errors); } - + protected function alert($status, $message) { if ($alerts = service('alerts')) diff --git a/tests/_support/Controllers/API/Factories.php b/tests/_support/Controllers/API/Factories.php index 0020410..9546a8a 100644 --- a/tests/_support/Controllers/API/Factories.php +++ b/tests/_support/Controllers/API/Factories.php @@ -1,4 +1,4 @@ - ['type' => 'varchar', 'constraint' => 31], 'uid' => ['type' => 'varchar', 'constraint' => 31], - 'class' => ['type' => 'varchar', 'constraint' => 63], - 'icon' => ['type' => 'varchar', 'constraint' => 31], - 'summary' => ['type' => 'varchar', 'constraint' => 255], + 'class' => ['type' => 'varchar', 'constraint' => 63, 'null' => true], + 'icon' => ['type' => 'varchar', 'constraint' => 31, 'default' => ''], + 'summary' => ['type' => 'varchar', 'constraint' => 255, 'default' => ''], 'created_at' => ['type' => 'datetime', 'null' => true], 'updated_at' => ['type' => 'datetime', 'null' => true], 'deleted_at' => ['type' => 'datetime', 'null' => true], diff --git a/tests/_support/FormsTestCase.php b/tests/_support/FormsTestCase.php index 105096c..2328c87 100644 --- a/tests/_support/FormsTestCase.php +++ b/tests/_support/FormsTestCase.php @@ -73,10 +73,6 @@ protected function setUp(): void Services::injectMock('routes', $routes); $this->routes = $routes; - - // Mock the renderer - $renderer = new MockRenderer(config('View'), config('Paths')->viewDirectory, Services::locator(true), CI_DEBUG, Services::logger(true)); - Services::injectMock('renderer', $renderer); // Load the test classes $this->config = config('Forms'); diff --git a/tests/_support/MockRenderer.php b/tests/_support/MockRenderer.php index 9f43f6e..5d86115 100644 --- a/tests/_support/MockRenderer.php +++ b/tests/_support/MockRenderer.php @@ -15,6 +15,6 @@ class MockRenderer extends View */ public function render(string $view, array $options = null, bool $saveData = null): string { - return serialize(['view' => $view, 'data' => $this->data]); + return serialize(['view' => $view, 'data' => $this->tempData]); } } diff --git a/tests/_support/Models/FactoryModel.php b/tests/_support/Models/FactoryModel.php index 2529af0..ae1f9c0 100644 --- a/tests/_support/Models/FactoryModel.php +++ b/tests/_support/Models/FactoryModel.php @@ -6,15 +6,15 @@ class FactoryModel extends Model { protected $table = 'factories'; protected $primaryKey = 'id'; - protected $returnType = 'object'; - protected $useSoftDeletes = false; - protected $allowedFields = ['name', 'uid', 'class', 'icon', 'summary']; - - protected $useTimestamps = true; + protected $useTimestamps = true; + protected $useSoftDeletes = false; + protected $skipValidation = false; - protected $validationRules = []; - protected $validationMessages = []; - protected $skipValidation = false; + protected $allowedFields = ['name', 'uid', 'class', 'icon', 'summary']; + protected $validationRules = [ + 'name' => 'required|max_length[31]', + 'uid' => 'required|max_length[31]', + ]; } diff --git a/tests/_support/PresenterResponse.php b/tests/_support/PresenterResponse.php new file mode 100644 index 0000000..6467250 --- /dev/null +++ b/tests/_support/PresenterResponse.php @@ -0,0 +1,63 @@ +getBody()) + { + throw new RuntimeException('Empty body from ' . $response->request()->uri); + } + + try + { + $result = unserialize($response->getBody()); + } + catch (Throwable $e) + { + throw new RuntimeException('Invalid response ' . $response->getBody(), $e->getCode(), $e); + } + + if (! is_array($result)) + { + throw new RuntimeException('Indecipherable response ' . $response->getBody()); + } + + $this->response = $response; + $this->view = $result['view']; + $this->data = $result['data']; + } +} diff --git a/tests/_support/PresenterTrait.php b/tests/_support/PresenterTrait.php new file mode 100644 index 0000000..5ec2561 --- /dev/null +++ b/tests/_support/PresenterTrait.php @@ -0,0 +1,55 @@ +execute($method, ...$params)); + } + + /** + * Sets the headers to trigger the next call + * as an AJAX method. + * + * @return $this + */ + protected function asAjax(): self + { + $this->request->setHeader('X-Requested-With', 'xmlhttprequest'); + + return $this; + } +} diff --git a/tests/database/ControllerTest.php b/tests/database/ControllerTest.php deleted file mode 100644 index 0112edd..0000000 --- a/tests/database/ControllerTest.php +++ /dev/null @@ -1,226 +0,0 @@ -codeigniter->useSafeOutput(true)->run($this->routes); - $output = json_decode(ob_get_clean()); - - $expected = [ - 'view' => 'factories/index', - 'data' => [ - 'factories' => $this->model->findAll(), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceShow() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'show', - '1', - ]; - $_SERVER['argc'] = 5; - $_SERVER['REQUEST_URI'] = '/api/factories/show/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = json_decode(ob_get_clean()); - - $expected = [ - 'view' => 'factories/show', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceNew() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'new', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/api/factories/new'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = json_decode(ob_get_clean()); - - $expected = [ - 'view' => 'factories/new', - 'data' => [], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceCreate() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'create', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/api/factories/create'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - $_POST['name'] = 'Rainbow Factory'; - $_POST['uid'] = 'bow'; - $_POST['class'] = 'ModuleTests\Rainbows\Factory'; - $_POST['icon'] = ''; - $_POST['summary'] = ''; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $factory = $this->model->find(4); - $this->assertEquals($_POST['name'], $factory->name); - } - - public function testResourceRemove() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'remove', - '1', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/api/factories/remove/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = json_decode(ob_get_clean()); - - $expected = [ - 'view' => 'factories/remove', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceDelete() - { - $this->assertNotNull($this->model->find(3)); - - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'delete', - '3', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/api/factories/delete/3'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $this->assertNull($this->model->find(3)); - } - - public function testResourceEdit() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'edit', - '1', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/api/factories/edit/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = json_decode(ob_get_clean()); - - $expected = [ - 'view' => 'factories/edit', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceUpdate() - { - $_SERVER['argv'] = [ - 'index.php', - 'api', - 'factories', - 'update', - '1', - ]; - $_SERVER['argc'] = 5; - $_SERVER['REQUEST_URI'] = '/api/factories/update/1'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - $_POST['name'] = 'Rainbow Factory'; - $_POST['uid'] = 'bow'; - $_POST['class'] = 'ModuleTests\Rainbows\Factory'; - $_POST['icon'] = ''; - $_POST['summary'] = ''; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $factory = $this->model->find(1); - $this->assertEquals($_POST['name'], $factory->name); - } -} diff --git a/tests/database/PresenterTest.php b/tests/database/PresenterTest.php deleted file mode 100644 index b488ab3..0000000 --- a/tests/database/PresenterTest.php +++ /dev/null @@ -1,219 +0,0 @@ -codeigniter->useSafeOutput(true)->run($this->routes); - $output = unserialize(ob_get_clean()); - - $expected = [ - 'view' => 'factories/index', - 'data' => [ - 'factories' => $this->model->findAll(), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceShow() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'show', - '1', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/factories/show/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = unserialize(ob_get_clean()); - - $expected = [ - 'view' => 'factories/show', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceNew() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'new', - ]; - $_SERVER['argc'] = 3; - $_SERVER['REQUEST_URI'] = '/factories/new'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = unserialize(ob_get_clean()); - - $expected = [ - 'view' => 'factories/new', - 'data' => [], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceCreate() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'create', - ]; - $_SERVER['argc'] = 3; - $_SERVER['REQUEST_URI'] = '/factories/create'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - $_POST['name'] = 'Rainbow Factory'; - $_POST['uid'] = 'bow'; - $_POST['class'] = 'ModuleTests\Rainbows\Factory'; - $_POST['icon'] = ''; - $_POST['summary'] = ''; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $factory = $this->model->find(4); - $this->assertEquals($_POST['name'], $factory->name); - } - - public function testResourceRemove() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'remove', - '1', - ]; - $_SERVER['argc'] = 3; - $_SERVER['REQUEST_URI'] = '/factories/remove/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = unserialize(ob_get_clean()); - - $expected = [ - 'view' => 'factories/remove', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceDelete() - { - $this->assertNotNull($this->model->find(3)); - - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'delete', - '3', - ]; - $_SERVER['argc'] = 3; - $_SERVER['REQUEST_URI'] = '/factories/delete/3'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $this->assertNull($this->model->find(3)); - } - - public function testResourceEdit() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'edit', - '1', - ]; - $_SERVER['argc'] = 3; - $_SERVER['REQUEST_URI'] = '/factories/edit/1'; - $_SERVER['REQUEST_METHOD'] = 'GET'; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = unserialize(ob_get_clean()); - - $expected = [ - 'view' => 'factories/edit', - 'data' => [ - 'factory' => $this->model->find(1), - ], - ]; - - $this->assertEquals($expected, $output); - } - - public function testResourceUpdate() - { - $_SERVER['argv'] = [ - 'index.php', - 'factories', - 'update', - '1', - ]; - $_SERVER['argc'] = 4; - $_SERVER['REQUEST_URI'] = '/factories/update/1'; - $_SERVER['REQUEST_METHOD'] = 'POST'; - - $_POST['name'] = 'Rainbow Factory'; - $_POST['uid'] = 'bow'; - $_POST['class'] = 'ModuleTests\Rainbows\Factory'; - $_POST['icon'] = ''; - $_POST['summary'] = ''; - - ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); - $output = ob_get_clean(); - - $this->assertEquals('', $output); - - $factory = $this->model->find(1); - $this->assertEquals($_POST['name'], $factory->name); - } -} diff --git a/tests/read/ControllerReadTest.php b/tests/read/ControllerReadTest.php new file mode 100644 index 0000000..0aa03d1 --- /dev/null +++ b/tests/read/ControllerReadTest.php @@ -0,0 +1,157 @@ +controller(Factories::class); + } + + public function testIndex() + { + $result = $this->execute('index'); + + $result->assertOK(); + $result->assertJSONExact($this->model->findAll()); + } + + public function testShow() + { + $result = $this->execute('show', 1); + + $result->assertOK(); + $result->assertJSONExact((array) $this->model->find(1)); + } + + public function testShowNull() + { + $result = $this->execute('show'); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testShowNonexistant() + { + $result = $this->execute('show', 42); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testCreateFailed() + { + // Missing "name" + $_POST = [ + 'uid' => 'bow', + 'class' => 'ModuleTests\Rainbows\Factory', + 'icon' => '', + 'summary' => '', + ]; + + $result = $this->execute('create'); + + $result->assertNotOK(); + $result->assertStatus(422); + + $body = json_decode($result->response()->getBody()); + + $this->assertSame('Create Failed', $body->error); + $this->assertSame(['name' => 'The name field is required.'], (array) $body->messages); + } + + public function testUpdateNull() + { + $result = $this->execute('update'); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testUpdateNonexistant() + { + $result = $this->execute('update', 42); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testUpdateFailed() + { + $factory = model(FactoryModel::class)->first(); + + $_POST = ['name' => 'This name exceeds the limit of 31 characters']; + + $result = $this->execute('update', $factory->id); + + $result->assertNotOK(); + $result->assertStatus(422); + + $body = json_decode($result->response()->getBody()); + + $this->assertSame('Update Failed', $body->error); + $this->assertSame(['name' => 'The name field cannot exceed 31 characters in length.'], (array) $body->messages); + } + + public function testDeleteNull() + { + $result = $this->execute('delete'); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testDeleteNonexistant() + { + $result = $this->execute('delete', 42); + + $result->assertNotOK(); + $result->assertStatus(404); + } + + public function testDeleteFailed() + { + // Mock the Model so all deletes fail + $model = new class extends FactoryModel { + + protected function doDelete($id = null, bool $purge = false) + { + return false; + } + }; + $this->controller->setModel($model); + + $factory = model(FactoryModel::class)->first(); + $result = $this->execute('delete', $factory->id); + + $result->assertNotOK(); + $result->assertStatus(400); + + $body = json_decode($result->response()->getBody()); + + $this->assertSame('Delete Failed', $body->error); + } +} diff --git a/tests/read/PresenterReadTest.php b/tests/read/PresenterReadTest.php new file mode 100644 index 0000000..7a43518 --- /dev/null +++ b/tests/read/PresenterReadTest.php @@ -0,0 +1,116 @@ +controller(Factories::class); + } + + public function testNew() + { + $result = $this->call('new'); + $result->response->assertOK(); + + $this->assertEquals('factories/new', $result->view); + } + + public function testNewAjax() + { + $result = $this->asAjax()->call('new'); + $result->response->assertOK(); + + $this->assertEquals('factories/form', $result->view); + } + + public function testIndex() + { + $data = [ + 'factories' => model(FactoryModel::class)->findAll(), + ]; + + $result = $this->call('index'); + $result->response->assertOK(); + + $this->assertEquals('factories/index', $result->view); + $this->assertEquals($data, $result->data); + } + + public function testShow() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->call('show', $factory->id); + $result->response->assertOK(); + + $this->assertEquals('factories/show', $result->view); + $this->assertEquals(['factory' => $factory], $result->data); + } + + public function testEdit() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->call('edit', $factory->id); + $result->response->assertOK(); + + $this->assertEquals('factories/edit', $result->view); + $this->assertEquals(['factory' => $factory], $result->data); + } + + public function testEditAjax() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->asAjax()->call('edit', $factory->id); + $result->response->assertOK(); + + $this->assertEquals('factories/form', $result->view); + $this->assertEquals(['factory' => $factory], $result->data); + } + + public function testRemove() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->call('remove', $factory->id); + $result->response->assertOK(); + + $this->assertEquals('factories/remove', $result->view); + $this->assertEquals(['factory' => $factory], $result->data); + } + + public function testRemoveAjax() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->asAjax()->call('remove', $factory->id); + $result->response->assertOK(); + + $this->assertEquals('factories/confirm', $result->view); + $this->assertEquals(['factory' => $factory], $result->data); + } +} diff --git a/tests/write/ControllerWriteTest.php b/tests/write/ControllerWriteTest.php new file mode 100644 index 0000000..aa077ab --- /dev/null +++ b/tests/write/ControllerWriteTest.php @@ -0,0 +1,81 @@ +controller(Factories::class); + } + + public function testCreate() + { + $_POST = [ + 'name' => 'Rainbow Factory', + 'uid' => 'bow', + 'class' => 'ModuleTests\Rainbows\Factory', + 'icon' => '', + 'summary' => '', + ]; + + $result = $this->execute('create'); + + $result->assertOK(); + $result->assertStatus(201); + $this->assertEquals('New Factory created successfully.', $result->response()->getReason()); + + // Get the last Factory to confirm the response + $factories = model(FactoryModel::class)->findAll(); + $factory = end($factories); + + $this->assertEquals($factory, json_decode($result->response()->getBody())); + } + + public function testUpdate() + { + $factory = model(FactoryModel::class)->first(); + + $_POST = ['name' => 'Banana Factory']; + + $result = $this->execute('update', $factory->id); + + $result->assertOK(); + $result->assertStatus(200); + $this->assertEquals('Factory updated successfully.', $result->response()->getReason()); + + $factory = model(FactoryModel::class)->find($factory->id); + $this->assertEquals($factory, json_decode($result->response()->getBody())); + } + + public function testDelete() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->execute('delete', $factory->id); + + $result->assertOK(); + $result->assertStatus(200); + $this->assertEquals('Factory deleted successfully.', $result->response()->getReason()); + + $factory = model(FactoryModel::class)->find($factory->id); + $this->assertNull($factory); + } +} diff --git a/tests/write/PresenterWriteTest.php b/tests/write/PresenterWriteTest.php new file mode 100644 index 0000000..298de6e --- /dev/null +++ b/tests/write/PresenterWriteTest.php @@ -0,0 +1,83 @@ +controller(Factories::class); + } + + public function testCreate() + { + $_POST = [ + 'name' => 'Rainbow Factory', + 'uid' => 'bow', + 'class' => 'ModuleTests\Rainbows\Factory', + 'icon' => '', + 'summary' => '', + ]; + + $result = $this->execute('create'); + + $result->assertOK(); + $result->assertRedirectTo('factories'); + + // Get the last Factory to confirm the response + $factories = model(FactoryModel::class)->findAll(); + $factory = end($factories); + + $expected = [ + [ + 'class' => 'success', + 'text' => 'New factory created successfully.', + ], + ]; + $result->assertSessionHas('alerts-queue', $expected); + } + + public function testUpdate() + { + $factory = model(FactoryModel::class)->first(); + + $_POST = ['name' => 'Banana Factory']; + + $result = $this->execute('update', $factory->id); + $result->assertOK(); + $result->assertRedirectTo('factories/' . $factory->id); + + $factory = model(FactoryModel::class)->find($factory->id); + $this->assertEquals('Banana Factory', $factory->name); + } + + public function testDelete() + { + $factory = model(FactoryModel::class)->first(); + + $result = $this->execute('delete', $factory->id); + + $result->assertOK(); + $result->assertRedirectTo('factories'); + + $factory = model(FactoryModel::class)->find($factory->id); + $this->assertNull($factory); + } +} From 0ac8d9010d195f9de2cde641a27b8a15fe7d4f00 Mon Sep 17 00:00:00 2001 From: MGatner Date: Mon, 29 Mar 2021 17:49:09 +0000 Subject: [PATCH 6/6] Fix static analysis --- phpstan.neon.dist | 1 - tests/_support/PresenterTrait.php | 4 ++++ tests/read/ControllerReadTest.php | 3 +++ tests/write/PresenterWriteTest.php | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c5d2e15..c45b9b7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,7 +10,6 @@ parameters: - src/Config/Routes.php - src/Views/* ignoreErrors: - - '#Cannot access property \$[A-Za-z]+ on array\|object#' universalObjectCratesClasses: - CodeIgniter\Entity - Faker\Generator diff --git a/tests/_support/PresenterTrait.php b/tests/_support/PresenterTrait.php index 5ec2561..f7c8254 100644 --- a/tests/_support/PresenterTrait.php +++ b/tests/_support/PresenterTrait.php @@ -1,5 +1,7 @@ 'New factory created successfully.', ], ]; + + // @phpstan-ignore-next-line Remove after https://github.com/codeigniter4/CodeIgniter4/pull/4503 $result->assertSessionHas('alerts-queue', $expected); }