text: <?php
  
  
  
  echo "\n************************************************************************************";
  echo "\nBegin gpEasy Tests\n\n";
  
  
  defined('gpdebug') or define('gpdebug',true);
  defined('is_running') or define('is_running',true);
  defined('gp_unit_testing') or define('gp_unit_testing',true);
  defined('gp_nonce_algo') or define('gp_nonce_algo','sha512');
  error_reporting(E_ALL); // 32767
  
  if (!class_exists('\PHPUnit_Framework_TestCase') && class_exists('\PHPUnit\Framework\TestCase'))
      class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase');
  
  
  
  class gptest_bootstrap extends \PHPUnit_Framework_TestCase{
  
  	protected static $process;
  	protected static $client_user;			// guzzle client for making anonymous user requests
  	protected static $client_admin;			// guzzle client for making admin user requests
  	protected static $client_current;
  	protected static $is_admin		= false;
  	protected static $requests		= 0;
  	protected static $proc_output	= [];
  	protected static $phpinfo;
  	protected static $tracking		= [];
  
  
  	const user_name		= 'phpunit_username';
  	const user_pass		= 'phpunit-test-password';
  	const user_email	= 'test@typesettercms.com';
  
  
  	static function log($msg){
  		static $fp;
  
  		if( !$fp ){
  			$log	= __DIR__ . '/phpunit.log';
  			$fp		= fopen($log, 'a');
  		}
  		fwrite($fp, "\n".print_r($msg, TRUE));
  	}
  
  	public function setUp(){
  		self::UseAdmin();
  	}
  
  	/**
  	 * Use php's built-in web server
  	 * https://medium.com/@peter.lafferty/start-phps-built-in-web-server-from-phpunit-9571f38c5045
  	 *
  	 */
  	public static function setUpBeforeClass(){
  
  		if( function_exists('showError') ){
  			return;
  		}
  
  		self::Console('Start server, set phpinfo...');
  
  		self::PrepInstall();
  		self::StartServer();
  
  
  		// create client for user requests
  		self::$client_user			= new \GuzzleHttp\Client(['http_errors' => false,'cookies' => true]);
  
  		// create client for admin requests
  		self::$client_admin			= new \GuzzleHttp\Client(['http_errors' => false,'cookies' => true]);
  		self::UseAdmin();
  
  
  		self::Install();
  		\gp\tool::GetLangFile();
  
  		self::LogIn();
  
  		$url				= '/phpinfo.php';
  		$response			= self::GuzzleRequest('GET',$url);
  		$body				= $response->getBody();
  		self::$phpinfo		= (string)$body;
  
      }
  
  
  	public static function StartServer(){
  		global $dataDir;
  
  
  		$proc		= ['php','-S','localhost:8081'];
  
  		// doc root
  		$proc[]		= '-t';
  		$proc[]		= $dataDir; // '.';
  
  		// error log
  		$proc[]		= '-d';
  		$proc[]		= 'error_log='.$dataDir . '/data/request-errors.log';
  
  		// xdebug configuration to collect code coverage
  		//$proc[]		= '-c';
  		//$proc[]		= __DIR__ . '/phpconfig.ini';
  
  		$proc[]		= '-d';
  		$proc[]		= 'auto_prepend_file='.__DIR__ . '/ServerPrepend.php';
  
  		// xdebug profiling
  		/*
  		$profile_dir = sys_get_temp_dir().'/test-profiler';
  		if( file_exists($profile_dir) ){
  			\gp\tool\Files::RmAll($profile_dir);
  		}
  		mkdir($profile_dir);
  
  		$proc[]		= '-d';
  		$proc[]		= 'xdebug.profiler_enable=1';
  
  		$proc[]		= '-d';
  		$proc[]		= 'xdebug.profiler_output_dir='.$profile_dir;
  		*/
  
  
  
  		self::$process = new \Symfony\Component\Process\Process($proc);
          self::$process->start(function($type,$buffer){
  			self::$proc_output[] = ['type'=>$type,'buffer'=>(string)$buffer];
  		});
          usleep(100000); //wait for server to get going
  
  
  		register_shutdown_function(function(){
  			self::Console('Stopping server process');
  			gptest_bootstrap::$process->stop();
  
  			usort(self::$tracking,function($a,$b){
  				if( $a['time'] == $b['time'] ){
  			        return 0;
  			    }
  			    return ($a['time'] < $b['time']) ? 1 : -1;
  			});
  
  			self::Console('Slowest requests (the slowest will usually be because of scss compilation');
  			self::$tracking = array_slice(self::$tracking,0,5);
  
  			foreach(self::$tracking as $req){
  				self::Console(' - '.$req['url'].' in '.$req['class'].' took '.$req['time']);
  			}
  
  		});
  	}
  
  
  	/**
  	 * Switch current guzzle client to $client_admin
  	 */
   	public static function UseAdmin(){
  		self::$client_current		= self::$client_admin;
  		self::$is_admin				= true;
   	}
  
  	/**
  	 * Switch current guzzle client to $client_user
  	 */
  	public static function UseAnon(){
  		self::$client_current		= self::$client_user;
  		self::$is_admin				= false;
  	}
  
  	/**
  	 * Print process output
  	 *
  	 */
  	public static function ProcessOutput($type,$url){
  
  		echo "\n\n----------------------------------------------------------------";
  		self::Console('Begin Process Output: '.$type.' '.$url);
  		echo "\n";
  
  		if( !empty(self::$proc_output) ){
  			self::Console('Proc Output');
  			print_r(self::$proc_output);
  		}
  		echo "\nEnd Process Output\n----------------------------------------------------------------\n\n";
  	}
  
  
  	/**
  	* Send a GET request to the test server
  	 *
  	 */
  	public static function GetRequest( $slug, $query='', $nonce_action=false ){
  		$url		= \gp\tool::GetUrl( $slug, $query, false, $nonce_action);
  		return self::GuzzleRequest('GET',$url);
  	}
  
  
  	/**
  	 * Send a POST request to the test server
  	 *
  	 */
  	public static function PostRequest($slug, $params = []){
  
  		$url		= \gp\tool::GetUrl($slug);
  		$options	= ['form_params' => $params];
  
  		return self::GuzzleRequest('POST',$url,200,$options);
  	}
  
  	/**
  	 * Send a request to the test server and check the response
  	 *
  	 */
  	public static function GuzzleRequest($type,$url,$expected_resonse = 200, $options = []){
  		global $dataDir;
  
  		$start			= microtime(true);
  		$response		= null;
  		$debug_file		= $dataDir . '/data/response-' . self::$requests . '-anon-' . $type . '-' . str_replace('/','_',$url);
  		if( self::$is_admin ){
  			$debug_file		= $dataDir . '/data/response-' . self::$requests . '-admin-' . $type . '-' . str_replace('/','_',$url);
  		}
  
  		$tracking		= [
  							'url'		=> $url,
  							'is_admin'	=> self::$is_admin,
  							'class'		=> get_called_class(),
  						];
  
  
  		try{
  			self::$proc_output		= [];
  			$options['headers']		= ['X-REQ-ID' => self::$requests,'Connection'=>'close'];
  			$url					= 'http://localhost:8081' . $url;
  			$response				= self::$client_current->request($type, $url, $options);
  			$body					= $response->getBody();
  			$status					= $response->getStatusCode();
  
  
  			file_put_contents($debug_file, $body);
  			self::$requests++;
  
  			self::$process->getOutput(); # makes symfony/process populate our self::$proc_output
  
  
  			if( $expected_resonse !== $status ){
  				self::ProcessOutput($type,$url);
  				self::Console('PHPINFO()');
  				echo (string)self::$phpinfo;
  			}
  			self::assertEquals($expected_resonse, $status);
  
  
  			// make sure the response doesn't have <div id="gp_admin_html">
  			if( self::$is_admin === false ){
  				self::assertNotStrpos($body,'gp_admin_html');
  			}
  
  			self::ServerErrors($type,$url);
  
  		}catch( \Exception $e ){
  			self::ServerErrors($type,$url);
  			self::Fail('Exception fetching url '.$url.$e->getMessage());
  		}
  
  		$tracking['time']	= microtime(true) - $start;
  		self::$tracking[]	= $tracking;
  
  		return $response;
  	}
  
  
  	/**
  	 * Output Error log
  	 *
  	 */
  	public static function ServerErrors($type,$url){
  		global $dataDir;
  
  		$error_log = $dataDir . '/data/request-errors.log';
  		if( !file_exists($error_log) ){
  			return;
  		}
  
  		$content = file_get_contents($error_log);
  		if( empty($content) ){
  			return;
  		}
  
  		echo "\n\n----------------------------------------------------------------";
  		self::Console('Error Log for '.$type.' '.$url);
  		echo "\n";
  
  		echo $content;
  		echo "\n\nEnd Error Log\n----------------------------------------------------------------\n\n";
  
  		$fp = fopen($error_log, "r+");
  		ftruncate($fp, 0);
  		fclose($fp);
  
  		self::assertEmpty($content,'php error log was not empty');
  	}
  
  
  	/**
  	 * Log In
  	 *
  	 */
  	public static function LogIn(){
  
  		// load login page to set cookies
  		$response					= self::GetRequest('Admin');
  
  		$params						= [];
  		$params['cmd']				= 'login';
  		$params['username']			= self::user_name;
  		$params['password']			= self::user_pass;
  		$params['login_nonce']		= \gp\tool\Nonce::Create('login_nonce',true,300);
  		$response					= self::PostRequest('Admin',$params);
  	}
  
  
  
  	/**
  	 * Create an install folder in the temporary directory
  	 *
  	 */
  	public static function PrepInstall(){
  		global $dataDir, $languages, $config;
  
  
  
  		// get a clean temporary install folder
  		$dataDir	= sys_get_temp_dir().'/typesetter-test';
  		$old_dir	= sys_get_temp_dir().'/typesetter-test-old';
  		if( file_exists($dataDir) ){
  			rename($dataDir,$old_dir);
  		}
  		mkdir($dataDir);
  		mkdir($dataDir.'/data');
  
  
  
  
  		// create symlinks of include, addons, and themes
  		$symlinks = ['include','addons','themes','gpconfig.php','index.php','vendor'];
  		foreach($symlinks as $name){
  
  			$path		= $dataDir.'/'.$name;
  			$target		= $_SERVER['PWD'].'/'.$name;
  
  			if( !file_exists($target) ){
  				self::Console('symlink target does not exist: '. $target);
  				continue;
  			}
  
  			if( file_exists($path) ){
  				unlink($path);
  			}
  
  			symlink( $target, $path);
  		}
  
  
  		// create a phpinfo.php file
  		$file		= $dataDir.'/phpinfo.php';
  		$content	= '<?php phpinfo();';
  		file_put_contents($file,$content);
  
  
  
  		include('include/common.php');
  		spl_autoload_register( array('\\gp\\tool','Autoload') );
  		require dirname(__DIR__) . '/vendor/autoload.php';
  		includeFile('tool/functions.php');
  
  		self::Console('datadir = '.$dataDir);
  		self::Console('gp_data_type = '.gp_data_type);
  
  
  
  
  
  		// delete old installation
  		\gp\tool\Files::RmAll($old_dir);
  
  
  		// reset coverage folder
  		$cov_dir	= dirname(__DIR__).'/x_coverage';
  		if( file_exists($cov_dir) ){
  			self::Console('resetting coverage folder: '.$cov_dir);
  			\gp\tool\Files::RmAll($cov_dir);
  		}
  		mkdir($cov_dir);
  	}
  
  
  	/**
  	 * Output a string to the console
  	 *
  	 */
  	public static function Console($msg){
  		echo "\n";
  		echo "\e[0;32m";
  		echo $msg;
  		echo "\e[0m";
  		echo "\n";
  	}
  
  	/*
  	 * Create an installation
  	 *
  	 */
  	public static function Install(){
  		global $config;
  
  
  		//make sure it's not installed
  		$installed = \gp\tool::Installed();
  		self::AssertFalse($installed,'Cannot test installation (Already Installed)');
  
  
  		// test install checks
  		// one of the checks actually fails
  		$values			= [1,1,-1,1,1,1];
  		$installer		= new \gp\install\Installer();
  		foreach($values as $i => $val){
  			self::assertGreaterThanOrEqual( $val, $installer->statuses[$i]['can_install'], 'Unexpected status ('.$i.') '.pre($installer->statuses[$i]) );
  		}
  
  
  
  		// test rendering of the install template
  		$response = self::GetRequest('');
  
  		//ob_start();
  		//includeFile('install/install.php');
  		//$installer->Form_Entry();
  		//$content = ob_get_clean();
  		//self::assertNotEmpty($content);
  
  
  
  		//attempt to install
  		$params					= [];
  		$params['site_title']	= 'unit tests';
  		$params['email']		= self::user_email;
  		$params['username']		= self::user_name;
  		$params['password']		= self::user_pass;
  		$params['password1']	= self::user_pass;
  		$params['cmd']			= 'Install';
  		$response				= self::PostRequest('',$params);
  
  
  
  		//double check
  		$installed			= \gp\tool::Installed();
  		self::AssertTrue($installed,'Not installed');
  
  
  
  		\gp\tool::GetConfig();
  	}
  
  	public static function assertStrpos( $haystack, $needle , $msg = 'String not found' ){
  
  		if( strpos($haystack, $needle) === false ){
  			self::fail($msg);
  		}
  
  	}
  
  	public static function assertNotStrpos( $haystack, $needle , $msg = 'String found' ){
  
  		if( strpos($haystack, $needle) !== false ){
  			self::fail($msg);
  		}
  
  	}
  
  }
  