<?php /** * Base test file for all widget tests * * @link http://www.egroupware.org * @author Nathan Gray * @package api * @copyright (c) 2017 Nathan Gray * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License */ namespace EGroupware\Api\Etemplate; use Egroupware\Api\Etemplate; // test base providing Egw environment, since we need the DB require_once realpath(__DIR__.'/../LoggedInTest.php'); // Store request in the session, file access probably won't work due to permissions \EGroupware\Api\Etemplate\Request::$request_class = 'EGroupware\Api\Etemplate\Request\Session'; /** * Base class for all widget tests doing needed setup so the tests can run, and * providing common utilities to make testing a little easier. * * Widget scans the apps for widgets, which needs the app list, pulled from the * database, so we need to log in. */ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest { /** * We use our own callback for results of executing, here we store the results * until returned * * @var Array */ protected static $mocked_exec_result = array(); protected $ajax_response = null; public static function setUpBeforeClass() : void { parent::setUpBeforeClass(); // Call Etemplate constructor once to make sure etemplate::$request is set, // otherwise some tests will fail. // In normal usage, this is not needed as Etemplate constructor does it. new \EGroupware\Api\Etemplate(); } protected function setUp() : void { // Mock AJAX response $this->ajax_response = $this->mock_ajax_response(); } protected function tearDown() : void { // Clean up AJAX response $this->ajax_response->initResponseArray(); $ref = new \ReflectionProperty('\\EGroupware\\Api\\Json\\Response', 'response'); $ref->setAccessible(true); $ref->setValue(null, null); } /** * Use a known, public callback so we can hook into it, if needed */ public static function public_callback($content) { static::$mocked_exec_result = $content; } /** * Mocks what is needed to fake a call to exec, and catch its output. * The resulting array of information, which would normally be sent to the * client as JSON, is returned for evaluation. * * @param Etemplate $etemplate * @param array $content * @param array $sel_options * @param array $readonlys * @param array $preserv */ protected function mockedExec(Etemplate $etemplate, array $content,array $sel_options=null,array $readonlys=null,array $preserv=null) { ob_start(); // Exec the template $etemplate->exec(__CLASS__ . '::public_callback', $content, $sel_options, $readonlys, $preserv, 4); $result = $this->ajax_response->returnResult(); // Store & clean the request //$etemplate->destroy_request(); ob_end_clean(); return $result; } /** * Mocks what is needed to fake a call to Etemplate->exec(), and catch its output. * The resulting array of information, which would normally be sent to the * client as JSON, is then processed and validated by the server as if it had * been sent from the client. * * @param \EGroupware\Api\Etemplate $etemplate * @param array $content * @param array $sel_options * @param array $readonlys * @param array $preserv * @return type */ protected function mockedRoundTrip(\EGroupware\Api\Etemplate $etemplate, array $content,array $sel_options=null,array $readonlys=null,array $preserv=null) { $result = $this->mockedExec($etemplate, $content, $sel_options, $readonlys, $preserv); // Check for the load $data = array(); foreach($result as $command) { if($command['type'] == 'et2_load') { $data = $command['data']; break; } } Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false); $content = static::$mocked_exec_result; static::$mocked_exec_result = array(); return $content; } /** * Mock the ajax response and override it so it doesn't try to send headers, * which generates errors since PHPUnit has already done that */ protected function mock_ajax_response() { $response = $this->getMockBuilder('\\EGroupware\\Api\\Json\\Response') ->disableOriginalConstructor() ->setMethods(['get'/*,'generic'*/]) ->getMock(); // Replace protected self reference with mock object $ref = new \ReflectionProperty('\\EGroupware\\Api\\Json\\Response', 'response'); $ref->setAccessible(true); $ref->setValue(null, $response); $response ->method('get') ->with(function() { // Don't send headers, like the real one does return self::$response; }); return $response; } /** * Exec the template with the provided content, change the values according to * $set_values to simulate client side changes by the user, then validate * against $expected_values. Optionally, it can check that validation errors * are created by particular widgets. * * * @param \EGroupware\Api\Etemplate $etemplate * @param array $content * @param array $set_values * @param array $expected_values * @param array $validation_errors Indexed by widget ID, we just check that an error * was found, not what that error was. */ protected function validateRoundTrip(\EGroupware\Api\Etemplate $etemplate, Array $content, Array $set_values, Array $expected_values = null, Array $validation_errors = array()) { if(is_null($expected_values)) { $expected_values = $set_values; } $result = $this->mockedExec($etemplate, $content, array(), array(), array()); // Check for the load $data = array(); foreach($result as $command) { if($command['type'] == 'et2_load') { $data = $command['data']; break; } } // 'Edit' the data client side $data['data']['content'] = $set_values; // Let it validate Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], false); $content = static::$mocked_exec_result; static::$mocked_exec_result = array(); return $this->validateTest($content, $expected_values, $validation_errors); } /** * Test that the content matches expected_values, and any widgets listed in * $validation_errors actually did raise a validation error. * * Note that in most (all?) cases, a validation error will clear the value. * * @param array $content * @param array $expected_values * @param array $validation_errors */ protected function validateTest(Array $content, Array $expected_values, Array $validation_errors = array()) { // Make validation errors accessible $ref = new \ReflectionProperty('\\EGroupware\\Api\\Etemplate\\Widget', 'validation_errors'); $ref->setAccessible(true); $errors = $ref->getValue(); // Test values foreach($expected_values as $widget_id => $value) { $this->assertEquals($value, $content[$widget_id], 'Widget "' . $widget_id . '" did not get expected value'); } // Check validation errors foreach($validation_errors as $widget_id => $errored) { $this->assertTrue(array_key_exists($widget_id, $errors), "Widget $widget_id did not cause a validation error"); } $ref->setValue(array()); } }