<?php
/**
 * EGroupware Api: Test base class for when you need an Egroupware session for
 * the class or test
 *
 * @link http://www.stylite.de
 * @package api
 * @subpackage test
 * @author Nathan Gray
 * @copyright (c) 2016 Nathan Gray
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @version $Id$
 */

namespace EGroupware\Api;

use PHPUnit\Framework\TestCase as TestCase;
use EGroupware\Api;

/**
 * Base class for tests, loads the egroupware environment
 *
 * It's not best practice to require the session for every test, but so much
 * is reliant on the session that it's just easier.  By "not best practice",
 * I mean this is pretty bad, but better than manual testing.
 *
 * Each test case (extending class) should get its own session, but session is
 * shared between tests inside.
 *
 * The login information is pulled from doc/phpunit.xml.  Run tests from the
 * command line using 'phpunit -c doc'
 */
abstract class LoggedInTest extends TestCase
{
	/**
	 * PHPUnit 12 removed TestCase::getName(), but many legacy tests still call it.
	 * Keep a compatibility shim until all tests are migrated to name().
	 *
	 * @param bool $with_data_set Kept for backward compatibility
	 * @return string
	 */
	public function getName($with_data_set=true)
	{
		$name = $this->name();
		if(!$with_data_set)
		{
			return preg_replace('/ with data set .*/', '', $name);
		}
		return $name;
	}

	/**
	 * Start session once before each test case
	 */
	public static function setUpBeforeClass() : void
	{
		try
		{
			static::bootstrapEgwSession();
			// Some suites mutate globals during teardown of prior classes. If we
			// still fail sanity after bootstrap, perform one clean re-bootstrap.
			if(!static::sanity_check())
			{
				static::tearDownAfterClass();
				static::bootstrapEgwSession();
			}
		}
		catch(Exception $e)
		{
			static::markTestSkipped('Unable to connect to Egroupware - ' . $e->getMessage());
			return;
		}
	}

	/**
	 * Bootstrap Egw globals + DB connection for LoggedInTest descendants.
	 */
	protected static function bootstrapEgwSession() : void
	{
		static::resetEgwGlobalsForBootstrap();

		// These globals pulled from the test config (phpunit.xml)
		static::load_egw($GLOBALS['EGW_USER'],$GLOBALS['EGW_PASSWORD'], $GLOBALS['EGW_DOMAIN']);

		if($GLOBALS['egw']->db)
		{
			$GLOBALS['egw']->db->connect();
		}
		else
		{
			static::markTestSkipped('No $GLOBALS[egw]->db');
			die();
		}

		// Re-init config, since it doesn't get handled by loading Egw
		Api\Config::init_static();
		// Rebuild Accounts singleton from current server config to avoid
		// cross-suite leakage of backend/db capabilities.
		$GLOBALS['egw']->accounts = new Api\Accounts($GLOBALS['egw_info']['server']);
		Api\Vfs::init_static();
	}

	/**
	 * Ensure load_egw() starts from a clean global state.
	 *
	 * Some non-LoggedIn suites leave an Egw instance in globals. load_egw() will
	 * then reuse it instead of creating a fresh one, which can leak stale DB
	 * capabilities between suites.
	 */
	protected static function resetEgwGlobalsForBootstrap() : void
	{
		if(!empty($GLOBALS['egw']) && is_object($GLOBALS['egw']))
		{
			if(!empty($GLOBALS['egw']->db))
			{
				$GLOBALS['egw']->db->disconnect();
			}
			unset($GLOBALS['egw']);
		}
		// Reset process-wide ADOdb handle so a fresh Db object can re-derive
		// backend capabilities instead of inheriting stale static connection state.
		Api\Db::$ADOdb = null;
		unset($GLOBALS['egw_info']);
		unset($GLOBALS['egw_setup']);
		unset($GLOBALS['_SESSION']);
		$_SESSION = [];
	}

	protected function assertPreConditions() : void
	{
		// Do some checks to make sure things we expect are there
		$this->assertTrue(static::sanity_check(), 'Unable to connect to Egroupware - failed sanity check');
	}

	/**
	 * End session when done - every test class gets its own session
	 */
	public static function tearDownAfterClass() : void
	{
			// Clean up VFS
			Vfs::clearstatcache();
			// Reset stream context, or current user will always be there
			stream_context_set_option(stream_context_get_default(),['vfs'=>['user' => null]]);

			// Clear some link caching
			Link::init_static(true);

		if($GLOBALS['egw'])
		{
			if($GLOBALS['egw']->session)
			{
				$GLOBALS['egw']->session->destroy(
					$GLOBALS['egw']->session->sessionid,
					$GLOBALS['egw']->session->kp3
				);
			}
			if($GLOBALS['egw']->acl)
			{
				$GLOBALS['egw']->acl = null;
			}
			if($GLOBALS['egw']->accounts)
			{
				$GLOBALS['egw']->accounts = null;
			}
			if($GLOBALS['egw']->applications)
			{
				$GLOBALS['egw']->applications = null;
			}
			if($GLOBALS['egw']->db)
			{
				$GLOBALS['egw']->db->disconnect();
			}
			unset($GLOBALS['egw']);
		}
		Api\Db::$ADOdb = null;

		Api\Session::egw_setcookie('sessionid');
		Api\Session::egw_setcookie('kp3');
		unset($GLOBALS['egw_info']);
		unset($GLOBALS['egw_setup']);
		unset($GLOBALS['_SESSION']);
		$_SESSION = array();
	}

	/**
	* Start the eGW session, skips the test if there are problems connecting
	*
	* @param string $user
	* @param string $passwd
	* @param string $domain
	*/
	public static function load_egw($user,$passwd,$domain='default',$info=array())
	{
		$_REQUEST['domain'] = $domain;
		$GLOBALS['egw_login_data'] = array(
			'login'  => $user,
			'passwd' => $passwd,
			'passwd_type' => 'text',
		);

		if(!$info)
		{
			$info = array(
				'flags' => array(
					'currentapp' => 'api',
					'noheader' => true,
					'autocreate_session_callback' => __CLASS__ .'::create_session',
					'no_exception_handler' => 'cli',
					'noapi' => false,
				)
			);
		}
		$GLOBALS['egw_info'] = $info;

		$ob_level = ob_get_level();
		try
		{
			include(realpath(__DIR__ . '/../../header.inc.php'));
		}
		catch (Exception $e)
		{
			// Something went wrong setting up egw environment - don't try
			// to do the test
			static::markTestSkipped($e->getMessage());
			return;
		}

		require_once realpath(__DIR__.'/../src/loader/common.php');	// autoloader & check_load_extension

		// egw is normally created when a file is loaded using require_once
		if(empty($GLOBALS['egw']) || !is_a($GLOBALS['egw'], 'EGroupware\Api\Egw\Base'))
		{
			// From Api/src/loader.php
			$GLOBALS['egw_info']['user']['domain'] = Api\Session::search_instance(
				isset($_POST['login']) ? $_POST['login'] : (isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : $_SERVER['REMOTE_USER']),
				Api\Session::get_request('domain'),$GLOBALS['egw_info']['server']['default_domain'],
				array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']),$GLOBALS['egw_domain']);

			$GLOBALS['egw_info']['server']['db_host'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_host'];
			$GLOBALS['egw_info']['server']['db_port'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_port'];
			$GLOBALS['egw_info']['server']['db_name'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_name'];
			$GLOBALS['egw_info']['server']['db_user'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_user'];
			$GLOBALS['egw_info']['server']['db_pass'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_pass'];
			$GLOBALS['egw_info']['server']['db_type'] = $GLOBALS['egw_domain'][$GLOBALS['egw_info']['user']['domain']]['db_type'];

			$GLOBALS['egw'] = new Api\Egw(array_keys($GLOBALS['egw_domain']));
		}

		// load up the $GLOBALS['egw_info']['server'] array
		$GLOBALS['egw_info']['server'] += Config::read('phpgwapi');
		// Make sure user is properly set
		$GLOBALS['egw_info']['user'] = $GLOBALS['egw']->session->read_repositories();

		// Disable asyc while we test
		$GLOBALS['egw_info']['server']['asyncservice'] = 'off';

		// Set up Vfs
		Vfs::init_static();
		Vfs\StreamWrapper::init_static();
		while(ob_get_level() > $ob_level)
		{
			ob_end_flush();
		}
	}


	/**
	* callback to authenticate with the user/pw specified on the commandline
	*
	* @param array &$account account_info with keys 'login', 'passwd' and optional 'passwd_type'
	* @return boolean/string true if we allow the access and account is set, a sessionid or false otherwise
	*/
	public static function create_session(&$account)
	{
		if (!($sessionid = $GLOBALS['egw']->session->create($GLOBALS['egw_login_data'])))
		{
			if(!($reason = $GLOBALS['egw']->session->reason) && $GLOBALS['egw']->session->account_id)
			{
				$GLOBALS['egw']->session->sessionid = 'CLI';
				return 'CLI';
			}
			die($reason ? $reason : "Wrong account or password - run tests with 'phpunit -c doc/phpunit.xml' or 'phpunit <test_dir> -c doc/phpunit.xml'\n\n");
		}
		unset($GLOBALS['egw_login_data']);
		return $sessionid;
	}

	/**
	 * Do some checks to make sure things we expect are there
	 */
	protected static function sanity_check()
	{
		// Check that the apps are loaded
		if(!array_key_exists('apps', $GLOBALS['egw_info']) || count($GLOBALS['egw_info']['apps']) == 0)
		{
			return false;
		}

		// Check that the user is loaded
		if(!array_key_exists('user', $GLOBALS['egw_info']) || !$GLOBALS['egw_info']['user']['account_id'])
		{
			return false;
		}

		return true;
	}

	/**
	 * Log out the current user, log in as the given user
	 *
	 * @param $account_lid
	 * @param $password
	 */
	protected function switchUser($account_lid, $password)
	{
		// Log out
		self::tearDownAfterClass();

		// Log in
		static::load_egw($account_lid,$password);
	}
}
