First Local Commit - After Clean up.

Signed-off-by: Rick Hays <rhays@haysgang.com>
This commit is contained in:
2019-12-02 14:54:38 -06:00
commit 10412ab7f6
486 changed files with 123242 additions and 0 deletions

View 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.');
}
//--------------------------------------------------------------------
}

View 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;
}
}

View 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);
}
}
}

View 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
View 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,
];
}
}

View 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();
}
}

View 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;
}
}

View 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

View 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);
}
}