First Local Commit - After Clean up.
Signed-off-by: Rick Hays <rhays@haysgang.com>
This commit is contained in:
356
system/Test/CIDatabaseTestCase.php
Normal file
356
system/Test/CIDatabaseTestCase.php
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use CodeIgniter\Config\Config;
|
||||
use Config\Autoload;
|
||||
use Config\Database;
|
||||
use Config\Migrations;
|
||||
use Config\Services;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\MigrationRunner;
|
||||
use CodeIgniter\Exceptions\ConfigException;
|
||||
|
||||
/**
|
||||
* CIDatabaseTestCase
|
||||
*/
|
||||
class CIDatabaseTestCase extends CIUnitTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* Should the db be refreshed before
|
||||
* each test?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $refresh = true;
|
||||
|
||||
/**
|
||||
* The name of the fixture used for all tests
|
||||
* within this test case.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $seed = '';
|
||||
|
||||
/**
|
||||
* The path to where we can find the seeds directory.
|
||||
* Allows overriding the default application directories.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basePath = TESTPATH . '_support/Database';
|
||||
|
||||
/**
|
||||
* The namespace to help us find the migration classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'Tests\Support\DatabaseTestMigrations';
|
||||
|
||||
/**
|
||||
* The name of the database group to connect to.
|
||||
* If not present, will use the defaultGroup.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $DBGroup = 'tests';
|
||||
|
||||
/**
|
||||
* Our database connection.
|
||||
*
|
||||
* @var BaseConnection
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* Migration Runner instance.
|
||||
*
|
||||
* @var MigrationRunner|mixed
|
||||
*/
|
||||
protected $migrations;
|
||||
|
||||
/**
|
||||
* Seeder instance
|
||||
*
|
||||
* @var \CodeIgniter\Database\Seeder
|
||||
*/
|
||||
protected $seeder;
|
||||
|
||||
/**
|
||||
* Stores information needed to remove any
|
||||
* rows inserted via $this->hasInDatabase();
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $insertCache = [];
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Load any database test dependencies.
|
||||
*/
|
||||
public function loadDependencies()
|
||||
{
|
||||
if ($this->db === null)
|
||||
{
|
||||
$this->db = Database::connect($this->DBGroup);
|
||||
$this->db->initialize();
|
||||
}
|
||||
|
||||
if ($this->migrations === null)
|
||||
{
|
||||
// Ensure that we can run migrations
|
||||
$config = new Migrations();
|
||||
$config->enabled = true;
|
||||
|
||||
$this->migrations = Services::migrations($config, $this->db);
|
||||
$this->migrations->setSilent(false);
|
||||
}
|
||||
|
||||
if ($this->seeder === null)
|
||||
{
|
||||
$this->seeder = Database::seeder($this->DBGroup);
|
||||
$this->seeder->setSilent(true);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensures that the database is cleaned up to a known state
|
||||
* before each test runs.
|
||||
*
|
||||
* @throws ConfigException
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Add namespaces we need for testing
|
||||
Services::autoloader()->addNamespace('Tests\Support\DatabaseTestMigrations', TESTPATH . '_support/DatabaseTestMigrations');
|
||||
|
||||
$this->loadDependencies();
|
||||
|
||||
if ($this->refresh === true)
|
||||
{
|
||||
if (! empty($this->namespace))
|
||||
{
|
||||
$this->migrations->setNamespace($this->namespace);
|
||||
}
|
||||
$this->migrations->regress(0, 'tests');
|
||||
|
||||
// Delete all of the tables to ensure we're at a clean start.
|
||||
$tables = $this->db->listTables();
|
||||
|
||||
if (is_array($tables))
|
||||
{
|
||||
$forge = Database::forge('tests');
|
||||
|
||||
foreach ($tables as $table)
|
||||
{
|
||||
if ($table === $this->db->DBPrefix . 'migrations')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$forge->dropTable($table, true);
|
||||
}
|
||||
}
|
||||
|
||||
$this->migrations->latest('tests');
|
||||
}
|
||||
|
||||
if (! empty($this->seed))
|
||||
{
|
||||
if (! empty($this->basePath))
|
||||
{
|
||||
$this->seeder->setPath(rtrim($this->basePath, '/') . '/Seeds');
|
||||
}
|
||||
|
||||
$this->seed($this->seed);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Takes care of any required cleanup after the test, like
|
||||
* removing any rows inserted via $this->hasInDatabase()
|
||||
*/
|
||||
public function tearDown(): void
|
||||
{
|
||||
if (! empty($this->insertCache))
|
||||
{
|
||||
foreach ($this->insertCache as $row)
|
||||
{
|
||||
$this->db->table($row[0])
|
||||
->where($row[1])
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Seeds that database with a specific seeder.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function seed(string $name)
|
||||
{
|
||||
return $this->seeder->call($name);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------
|
||||
// Database Test Helpers
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that records that match the conditions in $where do
|
||||
* not exist in the database.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $where
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function dontSeeInDatabase(string $table, array $where)
|
||||
{
|
||||
$count = $this->db->table($table)
|
||||
->where($where)
|
||||
->countAllResults();
|
||||
|
||||
$this->assertTrue($count === 0, 'Row was found in database');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that records that match the conditions in $where DO
|
||||
* exist in the database.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $where
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
|
||||
*/
|
||||
public function seeInDatabase(string $table, array $where)
|
||||
{
|
||||
$count = $this->db->table($table)
|
||||
->where($where)
|
||||
->countAllResults();
|
||||
|
||||
$this->assertTrue($count > 0, 'Row not found in database: ' . $this->db->showLastQuery());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fetches a single column from a database row with criteria
|
||||
* matching $where.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @param array $where
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
|
||||
*/
|
||||
public function grabFromDatabase(string $table, string $column, array $where)
|
||||
{
|
||||
$query = $this->db->table($table)
|
||||
->select($column)
|
||||
->where($where)
|
||||
->get();
|
||||
|
||||
$query = $query->getRow();
|
||||
|
||||
return $query->$column ?? false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Inserts a row into to the database. This row will be removed
|
||||
* after the test has run.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $data
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasInDatabase(string $table, array $data)
|
||||
{
|
||||
$this->insertCache[] = [
|
||||
$table,
|
||||
$data,
|
||||
];
|
||||
|
||||
return $this->db->table($table)
|
||||
->insert($data);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that the number of rows in the database that match $where
|
||||
* is equal to $expected.
|
||||
*
|
||||
* @param integer $expected
|
||||
* @param string $table
|
||||
* @param array $where
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \CodeIgniter\Database\Exceptions\DatabaseException
|
||||
*/
|
||||
public function seeNumRecords(int $expected, string $table, array $where)
|
||||
{
|
||||
$count = $this->db->table($table)
|
||||
->where($where)
|
||||
->countAllResults();
|
||||
|
||||
$this->assertEquals($expected, $count, 'Wrong number of matching rows in database.');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
}
|
||||
287
system/Test/CIUnitTestCase.php
Normal file
287
system/Test/CIUnitTestCase.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use Config\Paths;
|
||||
use CodeIgniter\Events\Events;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Support\Log\TestLogger;
|
||||
|
||||
/**
|
||||
* PHPunit test case.
|
||||
*/
|
||||
class CIUnitTestCase extends TestCase
|
||||
{
|
||||
|
||||
use ReflectionHelper;
|
||||
|
||||
/**
|
||||
* @var \CodeIgniter\CodeIgniter
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (! $this->app)
|
||||
{
|
||||
$this->app = $this->createApplication();
|
||||
}
|
||||
|
||||
helper('url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom function to hook into CodeIgniter's Logging mechanism
|
||||
* to check if certain messages were logged during code execution.
|
||||
*
|
||||
* @param string $level
|
||||
* @param null $expectedMessage
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertLogged(string $level, $expectedMessage = null)
|
||||
{
|
||||
$result = TestLogger::didLog($level, $expectedMessage);
|
||||
|
||||
$this->assertTrue($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into CodeIgniter's Events system to check if a specific
|
||||
* event was triggered or not.
|
||||
*
|
||||
* @param string $eventName
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertEventTriggered(string $eventName): bool
|
||||
{
|
||||
$found = false;
|
||||
$eventName = strtolower($eventName);
|
||||
|
||||
foreach (Events::getPerformanceLogs() as $log)
|
||||
{
|
||||
if ($log['event'] !== $eventName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->assertTrue($found);
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into xdebug's headers capture, looking for a specific header
|
||||
* emitted
|
||||
*
|
||||
* @param string $header The leading portion of the header we are looking for
|
||||
* @param boolean $ignoreCase
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertHeaderEmitted(string $header, bool $ignoreCase = false): void
|
||||
{
|
||||
$found = false;
|
||||
|
||||
if (! function_exists('xdebug_get_headers'))
|
||||
{
|
||||
$this->markTestSkipped('XDebug not found.');
|
||||
}
|
||||
|
||||
foreach (xdebug_get_headers() as $emitted)
|
||||
{
|
||||
$found = $ignoreCase ?
|
||||
(stripos($emitted, $header) === 0) :
|
||||
(strpos($emitted, $header) === 0);
|
||||
if ($found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue($found, "Didn't find header for {$header}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into xdebug's headers capture, looking for a specific header
|
||||
* emitted
|
||||
*
|
||||
* @param string $header The leading portion of the header we don't want to find
|
||||
* @param boolean $ignoreCase
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertHeaderNotEmitted(string $header, bool $ignoreCase = false): void
|
||||
{
|
||||
$found = false;
|
||||
|
||||
if (! function_exists('xdebug_get_headers'))
|
||||
{
|
||||
$this->markTestSkipped('XDebug not found.');
|
||||
}
|
||||
|
||||
foreach (xdebug_get_headers() as $emitted)
|
||||
{
|
||||
$found = $ignoreCase ?
|
||||
(stripos($emitted, $header) === 0) :
|
||||
(strpos($emitted, $header) === 0);
|
||||
if ($found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$success = ! $found;
|
||||
$this->assertTrue($success, "Found header for {$header}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom function to test that two values are "close enough".
|
||||
* This is intended for extended execution time testing,
|
||||
* where the result is close but not exactly equal to the
|
||||
* expected time, for reasons beyond our control.
|
||||
*
|
||||
* @param integer $expected
|
||||
* @param mixed $actual
|
||||
* @param string $message
|
||||
* @param integer $tolerance
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertCloseEnough(int $expected, $actual, string $message = '', int $tolerance = 1)
|
||||
{
|
||||
$difference = abs($expected - (int) floor($actual));
|
||||
|
||||
$this->assertLessThanOrEqual($tolerance, $difference, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom function to test that two values are "close enough".
|
||||
* This is intended for extended execution time testing,
|
||||
* where the result is close but not exactly equal to the
|
||||
* expected time, for reasons beyond our control.
|
||||
*
|
||||
* @param mixed $expected
|
||||
* @param mixed $actual
|
||||
* @param string $message
|
||||
* @param integer $tolerance
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertCloseEnoughString($expected, $actual, string $message = '', int $tolerance = 1)
|
||||
{
|
||||
$expected = (string) $expected;
|
||||
$actual = (string) $actual;
|
||||
if (strlen($expected) !== strlen($actual))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$expected = (int) substr($expected, -2);
|
||||
$actual = (int) substr($actual, -2);
|
||||
$difference = abs($expected - $actual);
|
||||
|
||||
$this->assertLessThanOrEqual($tolerance, $difference, $message);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up an instance of CodeIgniter
|
||||
* and gets the environment setup.
|
||||
*
|
||||
* @return \CodeIgniter\CodeIgniter
|
||||
*/
|
||||
protected function createApplication()
|
||||
{
|
||||
$paths = new Paths();
|
||||
|
||||
return require realpath(__DIR__ . '/../') . '/bootstrap.php';
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
/**
|
||||
* Return first matching emitted header.
|
||||
*
|
||||
* @param string $header Identifier of the header of interest
|
||||
* @param bool $ignoreCase
|
||||
*
|
||||
* @return string|null The value of the header found, null if not found
|
||||
*/
|
||||
//
|
||||
protected function getHeaderEmitted(string $header, bool $ignoreCase = false): ?string
|
||||
{
|
||||
$found = false;
|
||||
|
||||
if (! function_exists('xdebug_get_headers'))
|
||||
{
|
||||
$this->markTestSkipped('XDebug not found.');
|
||||
}
|
||||
|
||||
foreach (xdebug_get_headers() as $emitted)
|
||||
{
|
||||
$found = $ignoreCase ?
|
||||
(stripos($emitted, $header) === 0) :
|
||||
(strpos($emitted, $header) === 0);
|
||||
if ($found)
|
||||
{
|
||||
return $emitted;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
227
system/Test/ControllerResponse.php
Normal file
227
system/Test/ControllerResponse.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Testable response from a controller
|
||||
*/
|
||||
class ControllerResponse {
|
||||
|
||||
/**
|
||||
* The request.
|
||||
*
|
||||
* @var \CodeIgniter\HTTP\IncomingRequest
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The response.
|
||||
*
|
||||
* @var \CodeIgniter\HTTP\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* The message payload.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* DOM for the body.
|
||||
*
|
||||
* @var DOMParser
|
||||
*/
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->dom = new DOMParser();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Getters / Setters
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the body & DOM.
|
||||
*
|
||||
* @param string $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBody(string $body)
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
if (! empty($body))
|
||||
{
|
||||
$this->dom = $this->dom->withString($body);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the body.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request.
|
||||
*
|
||||
* @param \CodeIgniter\HTTP\RequestInterface $request
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRequest(RequestInterface $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response.
|
||||
*
|
||||
* @param \CodeIgniter\HTTP\ResponseInterface $response
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setResponse(ResponseInterface $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
|
||||
$this->setBody($response->getBody() ?? '');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request accessor.
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\IncomingRequest
|
||||
*/
|
||||
public function request()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response accessor.
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\Response
|
||||
*/
|
||||
public function response()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Simple Response Checks
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Boils down the possible responses into a boolean valid/not-valid
|
||||
* response type.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOK(): bool
|
||||
{
|
||||
// Only 200 and 300 range status codes
|
||||
// are considered valid.
|
||||
if ($this->response->getStatusCode() >= 400 || $this->response->getStatusCode() < 200)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty bodies are not considered valid.
|
||||
if (empty($this->response->getBody()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the Response was a redirect response
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRedirect(): bool
|
||||
{
|
||||
return $this->response instanceof RedirectResponse;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Utility
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Forward any unrecognized method calls to our DOMParser instance.
|
||||
*
|
||||
* @param string $function Method name
|
||||
* @param mixed $params Any method parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($function, $params)
|
||||
{
|
||||
if (method_exists($this->dom, $function))
|
||||
{
|
||||
return $this->dom->{$function}(...$params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
308
system/Test/ControllerTester.php
Normal file
308
system/Test/ControllerTester.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\UserAgent;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use Config\App;
|
||||
use Config\Services;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* ControllerTester Trait
|
||||
*
|
||||
* Provides features that make testing controllers simple and fluent.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $this->withRequest($request)
|
||||
* ->withResponse($response)
|
||||
* ->withURI($uri)
|
||||
* ->withBody($body)
|
||||
* ->controller('App\Controllers\Home')
|
||||
* ->run('methodName');
|
||||
*/
|
||||
trait ControllerTester
|
||||
{
|
||||
|
||||
/**
|
||||
* Controller configuration.
|
||||
*
|
||||
* @var BaseConfig
|
||||
*/
|
||||
protected $appConfig;
|
||||
|
||||
/**
|
||||
* Request.
|
||||
*
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* Response.
|
||||
*
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
/**
|
||||
* Message logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
/**
|
||||
* Initialized controller.
|
||||
*
|
||||
* @var Controller
|
||||
*/
|
||||
protected $controller;
|
||||
/**
|
||||
* URI of this request.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uri = 'http://example.com';
|
||||
/**
|
||||
* Request or response body.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* Loads the specified controller, and generates any needed dependencies.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function controller(string $name)
|
||||
{
|
||||
if (! class_exists($name))
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid Controller: ' . $name);
|
||||
}
|
||||
|
||||
if (empty($this->appConfig))
|
||||
{
|
||||
$this->appConfig = new App();
|
||||
}
|
||||
|
||||
if (empty($this->request))
|
||||
{
|
||||
$this->request = new IncomingRequest($this->appConfig, $this->uri, $this->body, new UserAgent());
|
||||
}
|
||||
|
||||
if (empty($this->response))
|
||||
{
|
||||
$this->response = new Response($this->appConfig);
|
||||
}
|
||||
|
||||
if (empty($this->logger))
|
||||
{
|
||||
$this->logger = Services::logger();
|
||||
}
|
||||
|
||||
$this->controller = new $name();
|
||||
$this->controller->initController($this->request, $this->response, $this->logger);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the specified method on the controller and returns the results.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\ControllerResponse|\InvalidArgumentException
|
||||
*/
|
||||
public function execute(string $method, ...$params)
|
||||
{
|
||||
if (! method_exists($this->controller, $method) || ! is_callable([$this->controller, $method]))
|
||||
{
|
||||
throw new InvalidArgumentException('Method does not exist or is not callable in controller: ' . $method);
|
||||
}
|
||||
|
||||
// The URL helper is always loaded by the system
|
||||
// so ensure it's available.
|
||||
helper('url');
|
||||
|
||||
$result = (new ControllerResponse())
|
||||
->setRequest($this->request)
|
||||
->setResponse($this->response);
|
||||
|
||||
$response = null;
|
||||
try
|
||||
{
|
||||
ob_start();
|
||||
|
||||
$response = $this->controller->{$method}(...$params);
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
$result->response()
|
||||
->setStatusCode($e->getCode());
|
||||
}
|
||||
finally
|
||||
{
|
||||
$output = ob_get_clean();
|
||||
|
||||
// If the controller returned a response, use it
|
||||
if (isset($response) && $response instanceof Response)
|
||||
{
|
||||
$result->setResponse($response);
|
||||
}
|
||||
|
||||
// check if controller returned a view rather than echoing it
|
||||
if (is_string($response))
|
||||
{
|
||||
$output = $response;
|
||||
$result->response()->setBody($output);
|
||||
$result->setBody($output);
|
||||
}
|
||||
elseif (! empty($response) && ! empty($response->getBody()))
|
||||
{
|
||||
$result->setBody($response->getBody());
|
||||
}
|
||||
else
|
||||
{
|
||||
$result->setBody('');
|
||||
}
|
||||
}
|
||||
|
||||
// If not response code has been sent, assume a success
|
||||
if (empty($result->response()->getStatusCode()))
|
||||
{
|
||||
$result->response()->setStatusCode(200);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set controller's config, with method chaining.
|
||||
*
|
||||
* @param mixed $appConfig
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withConfig($appConfig)
|
||||
{
|
||||
$this->appConfig = $appConfig;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set controller's request, with method chaining.
|
||||
*
|
||||
* @param mixed $request
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withRequest($request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set controller's response, with method chaining.
|
||||
*
|
||||
* @param mixed $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withResponse($response)
|
||||
{
|
||||
$this->response = $response;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set controller's logger, with method chaining.
|
||||
*
|
||||
* @param mixed $logger
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withLogger($logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the controller's URI, with method chaining.
|
||||
*
|
||||
* @param string $uri
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withUri(string $uri)
|
||||
{
|
||||
$this->uri = new URI($uri);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method's body, with method chaining.
|
||||
*
|
||||
* @param mixed $body
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function withBody($body)
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
361
system/Test/DOMParser.php
Normal file
361
system/Test/DOMParser.php
Normal file
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
/**
|
||||
* Load a response into a DOMDocument for testing assertions based on that
|
||||
*/
|
||||
class DOMParser
|
||||
{
|
||||
/**
|
||||
* DOM for the body,
|
||||
*
|
||||
* @var \DOMDocument
|
||||
*/
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (! extension_loaded('DOM'))
|
||||
{
|
||||
// always there in travis-ci
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \BadMethodCallException('DOM extension is required, but not currently loaded.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$this->dom = new \DOMDocument('1.0', 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body of the current document.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->dom->saveHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a string as the body that we want to work with.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withString(string $content)
|
||||
{
|
||||
// converts all special characters to utf-8
|
||||
$content = mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
//turning off some errors
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
if (! $this->dom->loadHTML($content))
|
||||
{
|
||||
// unclear how we would get here, given that we are trapping libxml errors
|
||||
// @codeCoverageIgnoreStart
|
||||
libxml_clear_errors();
|
||||
throw new \BadMethodCallException('Invalid HTML');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// ignore the whitespace.
|
||||
$this->dom->preserveWhiteSpace = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the contents of a file as a string
|
||||
* so that we can work with it.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return \CodeIgniter\Test\DOMParser
|
||||
*/
|
||||
public function withFile(string $path)
|
||||
{
|
||||
if (! is_file($path))
|
||||
{
|
||||
throw new \InvalidArgumentException(basename($path) . ' is not a valid file.');
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
|
||||
return $this->withString($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the text is found within the result.
|
||||
*
|
||||
* @param string $search
|
||||
* @param string $element
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function see(string $search = null, string $element = null): bool
|
||||
{
|
||||
// If Element is null, we're just scanning for text
|
||||
if (is_null($element))
|
||||
{
|
||||
$content = $this->dom->saveHTML();
|
||||
return strpos($content, $search) !== false;
|
||||
}
|
||||
|
||||
$result = $this->doXPath($search, $element);
|
||||
|
||||
return (bool)$result->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the text is NOT found within the result.
|
||||
*
|
||||
* @param string $search
|
||||
* @param string|null $element
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function dontSee(string $search = null, string $element = null): bool
|
||||
{
|
||||
return ! $this->see($search, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if an element with the matching CSS specifier
|
||||
* is found within the current DOM.
|
||||
*
|
||||
* @param string $element
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function seeElement(string $element): bool
|
||||
{
|
||||
return $this->see(null, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the element is available within the result.
|
||||
*
|
||||
* @param string $element
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function dontSeeElement(string $element): bool
|
||||
{
|
||||
return $this->dontSee(null, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a link with the specified text is found
|
||||
* within the results.
|
||||
*
|
||||
* @param string $text
|
||||
* @param string|null $details
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function seeLink(string $text, string $details = null): bool
|
||||
{
|
||||
return $this->see($text, 'a' . $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an input named $field with a value of $value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function seeInField(string $field, string $value): bool
|
||||
{
|
||||
$result = $this->doXPath(null, 'input', ["[@value=\"{$value}\"][@name=\"{$field}\"]"]);
|
||||
|
||||
return (bool)$result->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for checkboxes that are currently checked.
|
||||
*
|
||||
* @param string $element
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function seeCheckboxIsChecked(string $element): bool
|
||||
{
|
||||
$result = $this->doXPath(null, 'input' . $element, [
|
||||
'[@type="checkbox"]',
|
||||
'[@checked="checked"]',
|
||||
]);
|
||||
|
||||
return (bool)$result->length;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
/**
|
||||
* Search the DOM using an XPath expression.
|
||||
*
|
||||
* @param string $search
|
||||
* @param string $element
|
||||
* @param array $paths
|
||||
* @return type
|
||||
*/
|
||||
|
||||
protected function doXPath(string $search = null, string $element, array $paths = [])
|
||||
{
|
||||
// Otherwise, grab any elements that match
|
||||
// the selector
|
||||
$selector = $this->parseSelector($element);
|
||||
|
||||
$path = '';
|
||||
|
||||
// By ID
|
||||
if (! empty($selector['id']))
|
||||
{
|
||||
$path = empty($selector['tag'])
|
||||
? "id(\"{$selector['id']}\")"
|
||||
: "//body//{$selector['tag']}[@id=\"{$selector['id']}\"]";
|
||||
}
|
||||
// By Class
|
||||
else if (! empty($selector['class']))
|
||||
{
|
||||
$path = empty($selector['tag'])
|
||||
? "//*[@class=\"{$selector['class']}\"]"
|
||||
: "//body//{$selector['tag']}[@class=\"{$selector['class']}\"]";
|
||||
}
|
||||
// By tag only
|
||||
else if (! empty($selector['tag']))
|
||||
{
|
||||
$path = "//body//{$selector['tag']}";
|
||||
}
|
||||
|
||||
if (! empty($selector['attr']))
|
||||
{
|
||||
foreach ($selector['attr'] as $key => $value)
|
||||
{
|
||||
$path .= "[@{$key}=\"{$value}\"]";
|
||||
}
|
||||
}
|
||||
|
||||
// $paths might contain a number of different
|
||||
// ready to go xpath portions to tack on.
|
||||
if (! empty($paths) && is_array($paths))
|
||||
{
|
||||
foreach ($paths as $extra)
|
||||
{
|
||||
$path .= $extra;
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_null($search))
|
||||
{
|
||||
$path .= "[contains(., \"{$search}\")]";
|
||||
}
|
||||
|
||||
$xpath = new \DOMXPath($this->dom);
|
||||
|
||||
$result = $xpath->query($path);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for the a selector in the passed text.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return type
|
||||
*/
|
||||
public function parseSelector(string $selector)
|
||||
{
|
||||
$tag = null;
|
||||
$id = null;
|
||||
$class = null;
|
||||
$attr = null;
|
||||
|
||||
// ID?
|
||||
if ($pos = strpos($selector, '#') !== false)
|
||||
{
|
||||
list($tag, $id) = explode('#', $selector);
|
||||
}
|
||||
// Attribute
|
||||
elseif (strpos($selector, '[') !== false && strpos($selector, ']') !== false)
|
||||
{
|
||||
$open = strpos($selector, '[');
|
||||
$close = strpos($selector, ']');
|
||||
|
||||
$tag = substr($selector, 0, $open);
|
||||
$text = substr($selector, $open + 1, $close - 2);
|
||||
|
||||
// We only support a single attribute currently
|
||||
$text = explode(',', $text);
|
||||
$text = trim(array_shift($text));
|
||||
|
||||
list($name, $value) = explode('=', $text);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
$attr = [$name => trim($value, '] ')];
|
||||
}
|
||||
// Class?
|
||||
elseif ($pos = strpos($selector, '.') !== false)
|
||||
{
|
||||
list($tag, $class) = explode('.', $selector);
|
||||
}
|
||||
// Otherwise, assume the entire string is our tag
|
||||
else
|
||||
{
|
||||
$tag = $selector;
|
||||
}
|
||||
|
||||
return [
|
||||
'tag' => $tag,
|
||||
'id' => $id,
|
||||
'class' => $class,
|
||||
'attr' => $attr,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
413
system/Test/FeatureResponse.php
Normal file
413
system/Test/FeatureResponse.php
Normal file
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
use Config\Format;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Assertions for a response
|
||||
*/
|
||||
class FeatureResponse extends TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* The response.
|
||||
*
|
||||
* @var \CodeIgniter\HTTP\Response
|
||||
*/
|
||||
public $response;
|
||||
|
||||
/**
|
||||
* DOM for the body.
|
||||
*
|
||||
* @var \CodeIgniter\Test\DOMParser
|
||||
*/
|
||||
protected $domParser;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(Response $response = null)
|
||||
{
|
||||
$this->response = $response;
|
||||
|
||||
$body = $response->getBody();
|
||||
if (! empty($body) && is_string($body))
|
||||
{
|
||||
$this->domParser = (new DOMParser())->withString($body);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Simple Response Checks
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Boils down the possible responses into a bolean valid/not-valid
|
||||
* response type.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOK(): bool
|
||||
{
|
||||
// Only 200 and 300 range status codes
|
||||
// are considered valid.
|
||||
if ($this->response->getStatusCode() >= 400 || $this->response->getStatusCode() < 200)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty bodies are not considered valid.
|
||||
if (empty($this->response->getBody()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the Response was a redirect response
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRedirect(): bool
|
||||
{
|
||||
return $this->response instanceof RedirectResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given response was a redirect.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertRedirect()
|
||||
{
|
||||
$this->assertTrue($this->isRedirect(), 'Response is not a RedirectResponse.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the status is a specific value.
|
||||
*
|
||||
* @param integer $code
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertStatus(int $code)
|
||||
{
|
||||
$this->assertEquals($code, (int) $this->response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the Response is considered OK.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertOK()
|
||||
{
|
||||
$this->assertTrue($this->isOK(), "{$this->response->getStatusCode()} is not a successful status code, or the Response has an empty body.");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Session Assertions
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that an SESSION key has been set and, optionally, test it's value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param null $value
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSessionHas(string $key, $value = null)
|
||||
{
|
||||
$this->assertTrue(array_key_exists($key, $_SESSION), "'{$key}' is not in the current \$_SESSION");
|
||||
|
||||
if ($value !== null)
|
||||
{
|
||||
$this->assertEquals($value, $_SESSION[$key], "The value of '{$key}' ({$value}) does not match expected value.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the session is missing $key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSessionMissing(string $key)
|
||||
{
|
||||
$this->assertFalse(array_key_exists($key, $_SESSION), "'{$key}' should not be present in \$_SESSION.");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Header Assertions
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that the Response contains a specific header.
|
||||
*
|
||||
* @param string $key
|
||||
* @param null $value
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertHeader(string $key, $value = null)
|
||||
{
|
||||
$this->assertTrue($this->response->hasHeader($key), "'{$key}' is not a valid Response header.");
|
||||
|
||||
if ($value !== null)
|
||||
{
|
||||
$this->assertEquals($value, $this->response->getHeaderLine($key), "The value of '{$key}' header ({$this->response->getHeaderLine($key)}) does not match expected value.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the Response headers does not contain the specified header.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertHeaderMissing(string $key)
|
||||
{
|
||||
$this->assertFalse($this->response->hasHeader($key), "'{$key}' should not be in the Response headers.");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Cookie Assertions
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Asserts that the response has the specified cookie.
|
||||
*
|
||||
* @param string $key
|
||||
* @param null $value
|
||||
* @param string|null $prefix
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertCookie(string $key, $value = null, string $prefix = '')
|
||||
{
|
||||
$this->assertTrue($this->response->hasCookie($key, $value, $prefix), "No cookie found named '{$key}'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the Response does not have the specified cookie set.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function assertCookieMissing(string $key)
|
||||
{
|
||||
$this->assertFalse($this->response->hasCookie($key), "Cookie named '{$key}' should not be set.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a cookie exists and has an expired time.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $prefix
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertCookieExpired(string $key, string $prefix = '')
|
||||
{
|
||||
$this->assertTrue($this->response->hasCookie($key, null, $prefix));
|
||||
$this->assertGreaterThan(time(), $this->response->getCookie($key, $prefix)['expires']);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// DomParser Assertions
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Assert that the desired text can be found in the result body.
|
||||
*
|
||||
* @param string|null $search
|
||||
* @param string|null $element
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSee(string $search = null, string $element = null)
|
||||
{
|
||||
$this->assertTrue($this->domParser->see($search, $element), "Do not see '{$search}' in response.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that we do not see the specified text.
|
||||
*
|
||||
* @param string|null $search
|
||||
* @param string|null $element
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertDontSee(string $search = null, string $element = null)
|
||||
{
|
||||
$this->assertTrue($this->domParser->dontSee($search, $element), "I should not see '{$search}' in response.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we see an element selected via a CSS selector.
|
||||
*
|
||||
* @param string $search
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSeeElement(string $search)
|
||||
{
|
||||
$this->assertTrue($this->domParser->seeElement($search), "Do not see element with selector '{$search} in response.'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we do not see an element selected via a CSS selector.
|
||||
*
|
||||
* @param string $search
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertDontSeeElement(string $search)
|
||||
{
|
||||
$this->assertTrue($this->domParser->dontSeeElement($search), "I should not see an element with selector '{$search}' in response.'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we see a link with the matching text and/or class.
|
||||
*
|
||||
* @param string $text
|
||||
* @param string|null $details
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSeeLink(string $text, string $details = null)
|
||||
{
|
||||
$this->assertTrue($this->domParser->seeLink($text, $details), "Do no see anchor tag with the text {$text} in response.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we see an input with name/value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string|null $value
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertSeeInField(string $field, string $value = null)
|
||||
{
|
||||
$this->assertTrue($this->domParser->seeInField($field, $value), "Do no see input named {$field} with value {$value} in response.");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// JSON Methods
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the response's body as JSON
|
||||
*
|
||||
* @return mixed|false
|
||||
*/
|
||||
public function getJSON()
|
||||
{
|
||||
$response = $this->response->getJSON();
|
||||
|
||||
if (is_null($response))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the response contains a matching JSON fragment.
|
||||
*
|
||||
* @param array $fragment
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertJSONFragment(array $fragment)
|
||||
{
|
||||
$json = json_decode($this->getJSON(), true);
|
||||
|
||||
$this->assertArraySubset($fragment, $json, false, 'Response does not contain a matching JSON fragment.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the JSON exactly matches the passed in data.
|
||||
* If the value being passed in is a string, it must be a json_encoded string.
|
||||
*
|
||||
* @param string|array $test
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function assertJSONExact($test)
|
||||
{
|
||||
$json = $this->getJSON();
|
||||
|
||||
if (is_array($test))
|
||||
{
|
||||
$config = new Format();
|
||||
$formatter = $config->getFormatter('application/json');
|
||||
$test = $formatter->format($test);
|
||||
}
|
||||
|
||||
$this->assertJsonStringEqualsJsonString($test, $json, 'Response does not contain matching JSON.');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// XML Methods
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the response' body as XML
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getXML()
|
||||
{
|
||||
return $this->response->getXML();
|
||||
}
|
||||
|
||||
}
|
||||
336
system/Test/FeatureTestCase.php
Normal file
336
system/Test/FeatureTestCase.php
Normal file
@@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\HTTP\Request;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\HTTP\UserAgent;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use Config\App;
|
||||
use Config\Services;
|
||||
|
||||
/**
|
||||
* Class FeatureTestCase
|
||||
*
|
||||
* Provides additional utilities for doing full HTTP testing
|
||||
* against your application.
|
||||
*
|
||||
* @package CodeIgniter\Test
|
||||
*/
|
||||
class FeatureTestCase extends CIDatabaseTestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* If present, will override application
|
||||
* routes when using call().
|
||||
*
|
||||
* @var \CodeIgniter\Router\RouteCollection
|
||||
*/
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* Values to be set in the SESSION global
|
||||
* before running the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $session = [];
|
||||
|
||||
/**
|
||||
* Enabled auto clean op buffer after request call
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $clean = true;
|
||||
|
||||
/**
|
||||
* Sets a RouteCollection that will override
|
||||
* the application's route collection.
|
||||
*
|
||||
* Example routes:
|
||||
* [
|
||||
* ['get', 'home', 'Home::index']
|
||||
* ]
|
||||
*
|
||||
* @param array $routes
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function withRoutes(array $routes = null)
|
||||
{
|
||||
$collection = Services::routes();
|
||||
|
||||
if ($routes)
|
||||
{
|
||||
$collection->resetRoutes();
|
||||
foreach ($routes as $route)
|
||||
{
|
||||
$collection->{$route[0]}($route[1], $route[2]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->routes = $collection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets any values that should exist during this session.
|
||||
*
|
||||
* @param array $values
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withSession(array $values)
|
||||
{
|
||||
$this->session = $values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't run any events while running this test.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function skipEvents()
|
||||
{
|
||||
Events::simulate(true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a single URI, executes it, and returns a FeatureResponse
|
||||
* instance that can be used to run many assertions against.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function call(string $method, string $path, array $params = null)
|
||||
{
|
||||
// Simulate having a blank session
|
||||
$_SESSION = [];
|
||||
$_SERVER['REQUEST_METHOD'] = $method;
|
||||
|
||||
$request = $this->setupRequest($method, $path, $params);
|
||||
$request = $this->populateGlobals($method, $request, $params);
|
||||
|
||||
// Make sure the RouteCollection knows what method we're using...
|
||||
if (! empty($this->routes))
|
||||
{
|
||||
$this->routes->setHTTPVerb($method);
|
||||
}
|
||||
|
||||
// Make sure any other classes that might call the request
|
||||
// instance get the right one.
|
||||
Services::injectMock('request', $request);
|
||||
|
||||
$response = $this->app
|
||||
->setRequest($request)
|
||||
->run($this->routes, true);
|
||||
|
||||
$output = ob_get_contents();
|
||||
if (empty($response->getBody()) && ! empty($output))
|
||||
{
|
||||
$response->setBody($output);
|
||||
}
|
||||
|
||||
// Clean up any open output buffers
|
||||
// not relevant to unit testing
|
||||
// @codeCoverageIgnoreStart
|
||||
|
||||
if (ob_get_level() > 0 && $this->clean)
|
||||
{
|
||||
ob_end_clean();
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$featureResponse = new FeatureResponse($response);
|
||||
|
||||
return $featureResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a GET request.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('get', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a POST request.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function post(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('post', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a PUT request
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function put(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('put', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performss a PATCH request
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function patch(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('patch', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a DELETE request.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('delete', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an OPTIONS request.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\Test\FeatureResponse
|
||||
* @throws \CodeIgniter\Router\Exceptions\RedirectException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function options(string $path, array $params = null)
|
||||
{
|
||||
return $this->call('options', $path, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a Request object to use so that CodeIgniter
|
||||
* won't try to auto-populate some of the items.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string|null $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\IncomingRequest
|
||||
*/
|
||||
protected function setupRequest(string $method, string $path = null, array $params = null): IncomingRequest
|
||||
{
|
||||
$config = config(App::class);
|
||||
$uri = new URI($config->baseURL . '/' . trim($path, '/ '));
|
||||
|
||||
$request = new IncomingRequest($config, clone($uri), $params, new UserAgent());
|
||||
$request->uri = $uri;
|
||||
|
||||
$request->setMethod($method);
|
||||
$request->setProtocolVersion('1.1');
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the data of our Request with "global" data
|
||||
* relevant to the request, like $_POST data.
|
||||
*
|
||||
* Always populate the GET vars based on the URI.
|
||||
*
|
||||
* @param string $method
|
||||
* @param \CodeIgniter\HTTP\Request $request
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return \CodeIgniter\HTTP\Request
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function populateGlobals(string $method, Request $request, array $params = null)
|
||||
{
|
||||
$request->setGlobal('get', $this->getPrivateProperty($request->uri, 'query'));
|
||||
if ($method !== 'get')
|
||||
{
|
||||
$request->setGlobal($method, $params);
|
||||
}
|
||||
|
||||
$_SESSION = $this->session;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
}
|
||||
81
system/Test/Filters/CITestStreamFilter.php
Normal file
81
system/Test/Filters/CITestStreamFilter.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test\Filters;
|
||||
|
||||
/**
|
||||
* Class to extract an output snapshot.
|
||||
* Used to capture output during unit testing, so that it can
|
||||
* be used in assertions.
|
||||
*/
|
||||
|
||||
class CITestStreamFilter extends \php_user_filter
|
||||
{
|
||||
|
||||
/**
|
||||
* Buffer to capture stream content.
|
||||
*
|
||||
* @var type
|
||||
*/
|
||||
public static $buffer = '';
|
||||
|
||||
/**
|
||||
* Output filtering - catch it all.
|
||||
*
|
||||
* @param type $in
|
||||
* @param type $out
|
||||
* @param type $consumed
|
||||
* @param type $closing
|
||||
* @return type
|
||||
*/
|
||||
public function filter($in, $out, &$consumed, $closing)
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in))
|
||||
{
|
||||
static::$buffer .= $bucket->data;
|
||||
$consumed += $bucket->datalen;
|
||||
}
|
||||
return PSFS_PASS_ON;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
stream_filter_register('CITestStreamFilter', 'CodeIgniter\Test\Filters\CITestStreamFilter');
|
||||
// @codeCoverageIgnoreEnd
|
||||
128
system/Test/ReflectionHelper.php
Normal file
128
system/Test/ReflectionHelper.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodeIgniter
|
||||
*
|
||||
* An open source application development framework for PHP
|
||||
*
|
||||
* This content is released under the MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
||||
* Copyright (c) 2019 CodeIgniter Foundation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @package CodeIgniter
|
||||
* @author CodeIgniter Dev Team
|
||||
* @copyright 2019 CodeIgniter Foundation
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link https://codeigniter.com
|
||||
* @since Version 4.0.0
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Test;
|
||||
|
||||
use ReflectionMethod;
|
||||
use ReflectionObject;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Testing helper.
|
||||
*/
|
||||
trait ReflectionHelper
|
||||
{
|
||||
/**
|
||||
* Find a private method invoker.
|
||||
*
|
||||
* @param object|string $obj object or class name
|
||||
* @param string $method method name
|
||||
*
|
||||
* @return \Closure
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function getPrivateMethodInvoker($obj, $method)
|
||||
{
|
||||
$ref_method = new ReflectionMethod($obj, $method);
|
||||
$ref_method->setAccessible(true);
|
||||
$obj = (gettype($obj) === 'object') ? $obj : null;
|
||||
|
||||
return function () use ($obj, $ref_method) {
|
||||
$args = func_get_args();
|
||||
return $ref_method->invokeArgs($obj, $args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an accessible property.
|
||||
*
|
||||
* @param object $obj
|
||||
* @param string $property
|
||||
*
|
||||
* @return \ReflectionProperty
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
private static function getAccessibleRefProperty($obj, $property)
|
||||
{
|
||||
if (is_object($obj))
|
||||
{
|
||||
$ref_class = new ReflectionObject($obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
$ref_class = new ReflectionClass($obj);
|
||||
}
|
||||
|
||||
$ref_property = $ref_class->getProperty($property);
|
||||
$ref_property->setAccessible(true);
|
||||
|
||||
return $ref_property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a private property.
|
||||
*
|
||||
* @param object|string $obj object or class name
|
||||
* @param string $property property name
|
||||
* @param mixed $value value
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function setPrivateProperty($obj, $property, $value)
|
||||
{
|
||||
$ref_property = self::getAccessibleRefProperty($obj, $property);
|
||||
$ref_property->setValue($obj, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a private property.
|
||||
*
|
||||
* @param object|string $obj object or class name
|
||||
* @param string $property property name
|
||||
*
|
||||
* @return mixed value
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function getPrivateProperty($obj, $property)
|
||||
{
|
||||
$ref_property = self::getAccessibleRefProperty($obj, $property);
|
||||
return $ref_property->getValue($obj);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user