diff --git a/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php b/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php new file mode 100755 index 0000000000000000000000000000000000000000..0cb376dcfa54626291769ac36128864b4d908b48 --- /dev/null +++ b/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ + +/** + * This tests the output of the API plugin API + * It will return metadata about all API reports from all plugins + * as well as the data itself, pre-processed and ready to be displayed + */ +class Test_Piwik_Integration_ApiGetReportMetadata extends IntegrationTestCase +{ + protected $dateTime = '2009-01-04 00:11:42'; + protected $idSite = 1; + protected $idGoal = 1; + protected $idGoal2 = 2; + protected $idGoal3 = 3; + + public function setUp() + { + parent::setUp(); + $this->createWebsite($this->dateTime, $ecommerce = 1); + Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'Goal 1 - Thank you', 'title', 'Thank you', 'contains', $caseSensitive = false, $revenue = 10, $allowMultipleConversions = 1); + Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'Goal 2 - Hello', 'url', 'hellow', 'contains', $caseSensitive = false, $revenue = 10, $allowMultipleConversions = 0); + Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'triggered js', 'manually', '', ''); + $this->trackVisits(); + + // From Piwik 1.5, we hide Goals.getConversions and other get* methods via @ignore, but we ensure that they still work + // This hack allows the API proxy to let us generate example URLs for the ignored functions + Piwik_API_Proxy::getInstance()->hideIgnoredFunctions = false; + } + + public function getOutputPrefix() + { + return 'apiGetReportMetadata'; + } + + public function getApiForTesting() + { + return array( + array('API', array('idSite' => $this->idSite, 'date' => $this->dateTime)) + ); + } + + /** + * @dataProvider getApiForTesting + * @group Integration + * @group ApiGetReportMetadata + */ + public function testApi($api, $params) + { + $this->runApiTests($api, $params); + } + + protected function trackVisits() + { + $idSite = $this->idSite; + $dateTime = $this->dateTime; + + $t = $this->getTracker($idSite, $dateTime, $defaultInit = true); + + // Record 1st page view + $t->setUrl('http://example.org/index.htm'); + $this->checkResponse($t->doTrackPageView('incredible title!')); + + $t->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.3)->getDatetime()); + $this->checkResponse($t->doTrackGoal($this->idGoal3, $revenue = 42.256)); + } +} + diff --git a/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php b/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php new file mode 100755 index 0000000000000000000000000000000000000000..ff7b68cb02688d0e62076a89a9d75cfbc8e3239e --- /dev/null +++ b/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php @@ -0,0 +1,55 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ + +/** + * test the Yearly metadata API response, + * with no visits, with custom response language + */ +class Test_Piwik_Integration_ApiGetReportMetadata_Year extends IntegrationTestCase +{ + protected $idSite = 1; + protected $dateTime = '2009-01-04 00:11:42'; + + public function setUp() + { + parent::setUp(); + $this->createWebsite($this->dateTime); + } + + public function getApiForTesting() + { + $params = array('idSite' => $this->idSite, + 'date' => $this->dateTime, + 'periods' => 'year', + 'language' => 'fr'); + return array( + array('API.getProcessedReport', $params), + array('API.getReportMetadata', $params), + array('LanguagesManager.getTranslationsForLanguage', $params), + array('LanguagesManager.getAvailableLanguageNames', $params), + array('SitesManager.getJavascriptTag', $params) + ); + } + + public function getOutputPrefix() + { + return 'apiGetReportMetadata_year'; + } + + /** + * @dataProvider getApiForTesting + * @group Integration + * @group ApiGetReportMetadata + * @group ApiGetReportMetadata_year + */ + public function testApi($api, $params) + { + $this->runApiTests($api, $params); + } +} diff --git a/tests/PHPUnit/Integration/CsvExportTest.php b/tests/PHPUnit/Integration/CsvExportTest.php new file mode 100755 index 0000000000000000000000000000000000000000..cb3bed3dbd40cd7f1f16242f356cb4071e7341b1 --- /dev/null +++ b/tests/PHPUnit/Integration/CsvExportTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ + +require_once dirname(__FILE__).'/TwoVisitsWithCustomVariablesTest.php'; + +/** + * Test CSV export with Expanded rows, Translated labels, Different languages + */ +class Test_Piwik_Integration_CsvExport extends Test_Piwik_Integration_TwoVisitsWithCustomVariables +{ + + protected $useEscapedQuotes = false; + protected $doExtraQuoteTests = false; + + public function getApiForTesting() + { + $apiToCall = array('VisitsSummary.get', 'CustomVariables.getCustomVariables'); + + $enExtraParam = array('expanded' => 0, 'flat' => 1, 'include_aggregate_rows' => 0, 'translateColumnNames' => 1); + + $deExtraParam = array('expanded' => 0, 'flat' => 1, 'include_aggregate_rows' => 1, 'translateColumnNames' => 1); + + return array( + array($apiToCall, array('idSite' => $this->idSite, + 'date' => $this->dateTime, 'format' => 'csv', + 'otherRequestParameters' => array('expanded' => 0, 'flat' => 0), + 'testSuffix' => '_xp0')), + + array($apiToCall, array('idSite' => $this->idSite, + 'date' => $this->dateTime, + 'format' => 'csv', + 'otherRequestParameters' => $enExtraParam, + 'language' => 'en', + 'testSuffix' => '_xp1_inner0_trans-en')), + + array($apiToCall, array('idSite' => $this->idSite, + 'date' => $this->dateTime, + 'format' => 'csv', + 'otherRequestParameters' => $deExtraParam, + 'language' => 'de', + 'testSuffix' => '_xp1_inner1_trans-de')), + ); + } + + /** + * @dataProvider getApiForTesting + * @group Integration + * @group CsvExport + */ + public function testApi($api, $params) + { + $this->runApiTests($api, $params); + } + + public function getOutputPrefix() + { + return 'csvExport'; + } +} diff --git a/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php b/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php new file mode 100755 index 0000000000000000000000000000000000000000..834dc53bacb5db622ae6b4cb38332e8b117a6430 --- /dev/null +++ b/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php @@ -0,0 +1,163 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ + +/** + * Tests w/ two visits & custom variables. + */ +class Test_Piwik_Integration_TwoVisitsWithCustomVariables extends IntegrationTestCase +{ + protected $dateTime = '2010-01-03 11:22:33'; + protected $width = 1111; + protected $height = 222; + + protected $idSite = 1; + protected $idGoal1 = 1; + protected $idGoal2 = 2; + protected $visitorId = null; + + protected $useEscapedQuotes = true; + protected $doExtraQuoteTests = true; + + public function getApiForTesting() + { + $apiToCall = array('VisitsSummary.get', 'CustomVariables.getCustomVariables'); + + $return = array( + array($apiToCall, array('idSite' => 'all', + 'date' => $this->dateTime, + 'periods' => array('day', 'week'), + 'setDateLastN' => true)), + ); + + return $return; + } + + /** + * @dataProvider getApiForTesting + * @group Integration + * @group TwoVisitsWithCustomVariables + */ + public function testApi($api, $params) + { + $this->runApiTests($api, $params); + } + + public function getOutputPrefix() + { + return 'twoVisitsWithCustomVariables'; + } + + public function setUp() + { + parent::setUp(); + + // tests run in UTC, the Tracker in UTC + $this->createWebsite($this->dateTime); + Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'triggered js', 'manually', '', ''); + Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'second goal', 'manually', '', ''); + + $this->trackVisits(); + } + + protected function trackVisits() + { + $dateTime = $this->dateTime; + $idSite = $this->idSite; + $idGoal = $this->idGoal1; + $idGoal2 = $this->idGoal2; + + ob_start(); + $visitorA = $this->getTracker($idSite, $dateTime, $defaultInit = true); + // Used to test actual referer + keyword position in Live! + $visitorA->setUrlReferrer(urldecode('http://www.google.com/url?sa=t&source=web&cd=1&ved=0CB4QFjAA&url=http%3A%2F%2Fpiwik.org%2F&rct=j&q=this%20keyword%20should%20be%20ranked&ei=V8WfTePkKKLfiALrpZWGAw&usg=AFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw&sig2=BvKAdCtNixsmfNWXjsNyMw')); + + // no campaign, but a search engine to attribute the goal conversion to + $attribution = array( + '', + '', + 1302306504, + 'http://www.google.com/search?q=piwik&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-GB:official&client=firefox-a' + ); + $visitorA->setAttributionInfo(json_encode($attribution)); + + $visitorA->setResolution($this->width, $this->height); + + // At first, visitor custom var is set to LoggedOut + $visitorA->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.1)->getDatetime()); + $visitorA->setUrl('http://example.org/homepage'); + $visitorA->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedOut'); + $this->checkResponse($visitorA->doTrackPageView('Homepage')); + $this->checkResponse($visitorA->doTrackGoal($idGoal2, 0)); + + // After login, set to LoggedIn, should overwrite previous value + $visitorA->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.2)->getDatetime()); + $visitorA->setUrl('http://example.org/user/profile'); + $visitorA->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedIn'); + $visitorA->setCustomVariable($id = 4, $name = 'Status user', $value = 'Loggedin', $scope = 'page'); + if ($this->useEscapedQuotes) { + $lookingAtProfile = 'looking at "profile page"'; + } else { + $lookingAtProfile = 'looking at profile page'; + } + $visitorA->setCustomVariable($id = 5, $name = 'Status user', $value = $lookingAtProfile, $scope = 'page'); + $this->checkResponse($visitorA->doTrackPageView('Profile page')); + + $visitorA->setCustomVariable($id = 2, $name = 'SET WITH EMPTY VALUE', $value = ''); + $visitorA->setCustomVariable($id = 1, $name = 'Language', $value = 'FR', $scope = 'page'); + $visitorA->setCustomVariable($id = 2, $name = 'SET WITH EMPTY VALUE PAGE SCOPE', $value = '', $scope = 'page'); + $visitorA->setCustomVariable($id = 4, $name = 'Status user', $value = "looking at \"profile page\"", $scope = 'page'); + $visitorA->setCustomVariable($id = 3, $name = 'Value will be VERY long and truncated', $value = 'abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----'); + $this->checkResponse($visitorA->doTrackPageView('Profile page for user *_)%')); + $this->checkResponse($visitorA->doTrackGoal($idGoal, 0)); + + if ($this->doExtraQuoteTests) { + $visitorA->setCustomVariable($id = 2, $name = 'var1', $value = 'looking at "profile page"', + $scope = 'page'); + $visitorA->setCustomVariable($id = 3, $name = 'var2', $value = '\'looking at "\profile page"\'', + $scope = 'page'); + $visitorA->setCustomVariable($id = 4, $name = 'var3', $value = '\\looking at "\profile page"\\', + $scope = 'page'); + $this->checkResponse($visitorA->doTrackPageView('Concurrent page views')); + } + + // - + // Second new visitor on Idsite 1: one page view + $visitorB = $this->getTracker($idSite, $dateTime, $defaultInit = true); + $visitorB->setUrlReferrer(''); + + $attribution = array( + ' CAMPAIGN NAME -%20YEAH! ', + ' CAMPAIGN%20KEYWORD - RIGHT... ', + 1302306504, + 'http://www.example.org/test/really?q=yes' + ); + $visitorB->setAttributionInfo(json_encode($attribution)); + $visitorB->setResolution($this->width, $this->height); + $visitorB->setUserAgent('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6'); + $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(1)->getDatetime()); + $visitorB->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedOut'); + $visitorB->setCustomVariable($id = 2, $name = 'Othercustom value which should be truncated abcdefghijklmnopqrstuvwxyz', $value = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'); + $visitorB->setCustomVariable($id = -2, $name = 'not tracked', $value = 'not tracked'); + $visitorB->setCustomVariable($id = 6, $name = 'not tracked', $value = 'not tracked'); + $visitorB->setCustomVariable($id = 6, $name = array('not tracked'), $value = 'not tracked'); + $visitorB->setUrl('http://example.org/homepage'); + $this->checkResponse($visitorB->doTrackGoal($idGoal, 1000)); + + $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(1.1)->getDatetime()); + $this->checkResponse($visitorB->doTrackPageView('Homepage')); + + // DIFFERENT test - + // testing that starting the visit with an outlink works (doesn't trigger errors) + $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(2)->getDatetime()); + $this->checkResponse($visitorB->doTrackAction('http://test.com', 'link')); + + // hack + $this->visitorId = $visitorB->getVisitorId(); + } +} diff --git a/tests/PHPUnit/IntegrationTestCase.php b/tests/PHPUnit/IntegrationTestCase.php new file mode 100755 index 0000000000000000000000000000000000000000..ff7de44ae67651f6e4e4ff2bdda162c0953c493b --- /dev/null +++ b/tests/PHPUnit/IntegrationTestCase.php @@ -0,0 +1,1214 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ +require_once PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/PiwikTracker.php'; + +/** + * Base class for Integration tests. + * + * Provides helpers to track data and then call API get* methods to check outputs automatically. + * + */ +abstract class IntegrationTestCase extends DatabaseTestCase +{ + + /** + * Identifies the last language used in an API/Controller call. + * + * @var string + */ + protected $lastLanguage; + + /** + * Initializes the test + * Load english translations to ensure API response have english text + * + * @see tests/core/Test_Database#setUp() + */ + public function setUp() + { + parent::setUp(); + + if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING) + { + self::initializeControllerTesting(); + } + + Piwik::createAccessObject(); + Piwik_PostEvent('FrontController.initAuthenticationObject'); + + // We need to be SU to create websites for tests + Piwik::setUserIsSuperUser(); + + // Load and install plugins + $pluginsManager = Piwik_PluginsManager::getInstance(); + $plugins = Piwik_Config::getInstance()->Plugins['Plugins']; + + $pluginsManager->loadPlugins( $plugins ); + $pluginsManager->installLoadedPlugins(); + + $_GET = $_REQUEST = array(); + $_SERVER['HTTP_REFERER'] = ''; + + // Make sure translations are loaded to check messages in English + Piwik_Translate::getInstance()->loadEnglishTranslation(); + + // List of Modules, or Module.Method that should not be called as part of the XML output compare + // Usually these modules either return random changing data, or are already tested in specific unit tests. + $this->setApiNotToCall(self::$defaultApiNotToCall); + $this->setApiToCall( array()); + + if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING) + { + Piwik::setUserIsSuperUser(); + + // create users for controller testing + $usersApi = Piwik_UsersManager_API::getInstance(); + $usersApi->addUser('anonymous', self::DEFAULT_USER_PASSWORD, 'anonymous@anonymous.com'); + $usersApi->addUser('test_view', self::DEFAULT_USER_PASSWORD, 'view@view.com'); + $usersApi->addUser('test_admin', self::DEFAULT_USER_PASSWORD, 'admin@admin.com'); + + // disable shuffling of tag cloud visualization so output is consistent + Piwik_Visualization_Cloud::$debugDisableShuffle = true; + } + } + + public function tearDown() + { + parent::tearDown(); + $_GET = $_REQUEST = array(); + Piwik_Translate::getInstance()->unloadEnglishTranslation(); + + // re-enable tag cloud shuffling + Piwik_Visualization_Cloud::$debugDisableShuffle = true; + } + + protected $apiToCall = array(); + protected $apiNotToCall = array(); + + public static $defaultApiNotToCall = array( + 'LanguagesManager', + 'DBStats', + 'UsersManager', + 'SitesManager', + 'ExampleUI', + 'Live', + 'SEO', + 'ExampleAPI', + 'PDFReports', + 'API', + 'ImageGraph', + ); + + /** + * Widget testing level constant. If self::$widgetTestingLevel is + * set to this, controller actions will not be tested. + */ + const NO_WIDGET_TESTING = 'none'; + + /** + * Widget testing level constant. If self::$widgetTestingLevel is + * set to this, controller actions will be checked for non-fatal errors, but + * the output will be ignored. + */ + const CHECK_WIDGET_ERRORS = 'check_errors'; + + /** + * Widget testing level constant. If self::$widgetTestingLevel is + * set to this, controller actions will be run & their output will be checked with + * expected output files. + */ + const COMPARE_WIDGET_OUTPUT = 'compare_output'; + + /** + * Determines how much of controller actions are tested (if at all). + */ + static public $widgetTestingLevel = self::NO_WIDGET_TESTING; + + /** + * API testing level constant. If self::$apiTestingLevel is + * set to this, API methods will not be tested. + */ + const NO_API_TESTING = 'none'; + + /** + * API testing level constant. If self::$apiTestingLevel is + * set to this, API methods will be run & their output will be checked with + * expected output files. + */ + const COMPARE_API_OUTPUT = 'compare_output'; + + /** + * Determines how much testing API methods are subjected to (if any). + */ + static public $apiTestingLevel = self::COMPARE_API_OUTPUT; + + const DEFAULT_USER_PASSWORD = 'nopass'; + + /** + * Forces the test to only call and fetch XML for the specified plugins, + * or exact API methods. + * If not called, all default tests will be executed. + * + * @param array $apiToCall array( 'ExampleAPI', 'Plugin.getData' ) + * + * @throws Exception + * @return void + */ + protected function setApiToCall( $apiToCall ) + { + if(func_num_args() != 1) + { + throw new Exception('setApiToCall expects an array'); + } + if(!is_array($apiToCall)) + { + $apiToCall = array($apiToCall); + } + $this->apiToCall = $apiToCall; + } + + /** + * Sets a list of API methods to not call during the test + * + * @param string $apiNotToCall eg. 'ExampleAPI.getPiwikVersion' + * + * @return void + */ + protected function setApiNotToCall( $apiNotToCall ) + { + if(!is_array($apiNotToCall)) + { + $apiNotToCall = array($apiNotToCall); + } + $this->apiNotToCall = $apiNotToCall; + } + + /** + * Returns a PiwikTracker object that you can then use to track pages or goals. + * + * @param $idSite + * @param $dateTime + * @param boolean $defaultInit If set to true, the tracker object will have default IP, user agent, time, resolution, etc. + * + * @return PiwikTracker + */ + protected function getTracker($idSite, $dateTime, $defaultInit = true ) + { + $t = new PiwikTracker( $idSite, $this->getTrackerUrl()); + $t->setForceVisitDateTime($dateTime); + + if($defaultInit) + { + $t->setIp('156.5.3.2'); + + // Optional tracking + $t->setUserAgent( "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)"); + $t->setBrowserLanguage('fr'); + $t->setLocalTime( '12:34:06' ); + $t->setResolution( 1024, 768 ); + $t->setBrowserHasCookies(true); + $t->setPlugins($flash = true, $java = true, $director = false); + } + return $t; + } + + /** + * Creates a website, then sets its creation date to a day earlier than specified dateTime + * Useful to create a website now, but force data to be archived back in the past. + * + * @param string $dateTime eg '2010-01-01 12:34:56' + * @param int $ecommerce + * @param string $siteName + * + * @return int idSite of website created + */ + protected function createWebsite( $dateTime, $ecommerce = 0, $siteName = 'Piwik test' ) + { + $idSite = Piwik_SitesManager_API::getInstance()->addSite( + $siteName, + "http://piwik.net/", + $ecommerce, + $ips = null, + $excludedQueryParameters = null, + $timezone = null, + $currency = null + ); + + // Manually set the website creation date to a day earlier than the earliest day we record stats for + Zend_Registry::get('db')->update(Piwik_Common::prefixTable("site"), + array('ts_created' => Piwik_Date::factory($dateTime)->subDay(1)->getDatetime()), + "idsite = $idSite" + ); + + // Clear the memory Website cache + Piwik_Site::clearCache(); + + // add access to all test users if doing controller tests + if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING) + { + $usersApi = Piwik_UsersManager_API::getInstance(); + $usersApi->setUserAccess('anonymous', 'view', array($idSite)); + $usersApi->setUserAccess('test_view', 'view', array($idSite)); + $usersApi->setUserAccess('test_admin', 'admin', array($idSite)); + } + + return $idSite; + } + + /** + * Checks that the response is a GIF image as expected. + * Will fail the test if the response is not the expected GIF + * + * @param $response + */ + protected function checkResponse($response) + { + $trans_gif_64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="; + $expectedResponse = base64_decode($trans_gif_64); + $this->assertEquals($expectedResponse, $response, "Expected GIF beacon, got: <br/>\n" . $response ."<br/>\n"); + } + + /** + * Returns URL to the proxy script, used to ensure piwik.php + * uses the test environment, and allows variable overwriting + * + * @return string + */ + protected function getTrackerUrl() + { + $piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName(); + + $pathBeforeRoot = 'tests'; + // Running from a plugin + if(strpos($piwikUrl, 'plugins/') !== false) + { + $pathBeforeRoot = 'plugins'; + } + $piwikUrl = substr($piwikUrl, 0, strpos($piwikUrl, $pathBeforeRoot.'/')) . 'tests/PHPUnit/proxy-piwik.php'; + return $piwikUrl; + } + + /** + * Initializes parts of Piwik so controller actions can be called & tested. + */ + public static function initializeControllerTesting() + { + static $initialized = false; + + if (!$initialized) + { + Zend_Registry::set('timer', new Piwik_Timer); + + $pluginsManager = Piwik_PluginsManager::getInstance(); + $pluginsToLoad = Piwik_Config::getInstance()->Plugins['Plugins']; + $pluginsManager->loadPlugins( $pluginsToLoad ); + + $initialized = true; + } + } + + public static function processRequestArgs() + { + // set the widget testing level + if (isset($_GET['widgetTestingLevel'])) + { + self::setWidgetTestingLevel($_GET['widgetTestingLevel']); + } + + // set the API testing level + if (isset($_GET['apiTestingLevel'])) + { + self::setApiTestingLevel($_GET['apiTestingLevel']); + } + } + + public static function setWidgetTestingLevel($level) + { + if (!$level) return; + + if ($level != self::NO_WIDGET_TESTING && + $level != self::CHECK_WIDGET_ERRORS && + $level != self::COMPARE_WIDGET_OUTPUT) + { + echo "<p>Invalid option for 'widgetTestingLevel', ignoring.</p>\n"; + return; + } + + self::$widgetTestingLevel = $level; + } + + public function setApiTestingLevel($level) + { + if (!$level) return; + + if ($level != self::NO_API_TESTING && + $level != self::COMPARE_API_OUTPUT) + { + echo "<p>Invalid option for 'apiTestingLevel', ignoring.</p>"; + return; + } + + self::$apiTestingLevel = $level; + } + + /** + * Given a list of default parameters to set, returns the URLs of APIs to call + * If any API was specified in setApiToCall() we ensure only these are tested. + * If any API is set as excluded (see list below) then it will be ignored. + * + * @param array $parametersToSet Parameters to set in api call + * @param array $formats Array of 'format' to fetch from API + * @param array $periods Array of 'period' to query API + * @param bool $setDateLastN If set to true, the 'date' parameter will be rewritten to query instead a range of dates, rather than one period only. + * @param bool|string $language 2 letter language code, defaults to default piwik language + * @param bool|string $segment + * + * @return array of API URLs query strings + */ + protected function generateUrlsApi( $parametersToSet, $formats, $periods, $setDateLastN = false, $language = false, $segment = false ) + { + // Get the URLs to query against the API for all functions starting with get* + $skipped = $requestUrls = array(); + $apiMetadata = new Piwik_API_DocumentationGenerator; + foreach(Piwik_API_Proxy::getInstance()->getMetadata() as $class => $info) + { + $moduleName = Piwik_API_Proxy::getInstance()->getModuleNameFromClassName($class); + foreach($info as $methodName => $infoMethod) + { + $apiId = $moduleName.'.'.$methodName; + + // If Api to test were set, we only test these + if(!empty($this->apiToCall) + && in_array($moduleName, $this->apiToCall) === false + && in_array($apiId, $this->apiToCall) === false) + { + $skipped[] = $apiId; + continue; + } + // Excluded modules from test + elseif( + (strpos($methodName, 'get') !== 0 + || in_array($moduleName, $this->apiNotToCall) === true + || in_array($apiId, $this->apiNotToCall) === true + || $methodName == 'getLogoUrl' + || $methodName == 'getHeaderLogoUrl' + ) + ) + { + $skipped[] = $apiId; + continue; + } + + foreach($periods as $period) + { + $parametersToSet['period'] = $period; + + // If date must be a date range, we process this date range by adding 6 periods to it + if($setDateLastN) + { + if(!isset($parametersToSet['dateRewriteBackup'])) + { + $parametersToSet['dateRewriteBackup'] = $parametersToSet['date']; + } + + $lastCount = (int)$setDateLastN; + if($setDateLastN === true) + { + $lastCount = 6; + } + $firstDate = $parametersToSet['dateRewriteBackup']; + $secondDate = date('Y-m-d', strtotime("+$lastCount " . $period . "s", strtotime($firstDate))); + $parametersToSet['date'] = $firstDate . ',' . $secondDate; + } + + // Set response language + if($language !== false) + { + $parametersToSet['language'] = $language; + } + // Generate for each specified format + foreach($formats as $format) + { + $parametersToSet['format'] = $format; + $parametersToSet['hideIdSubDatable'] = 1; + $parametersToSet['serialize'] = 1; + + $exampleUrl = $apiMetadata->getExampleUrl($class, $methodName, $parametersToSet); + if($exampleUrl === false) + { + $skipped[] = $apiId; + continue; + } + + // Remove the first ? in the query string + $exampleUrl = substr($exampleUrl, 1); + $apiRequestId = $apiId; + if(strpos($exampleUrl, 'period=') !== false) + { + $apiRequestId .= '_' . $period; + } + + $apiRequestId .= '.' . $format; + + $requestUrls[$apiRequestId] = $exampleUrl; + } + } + } + } + return $requestUrls; + } + + /** + * Will call all get* methods on authorized modules, + * force the archiving, + * record output in XML files + * and compare with the expected outputs. + * + * @param string $testName Used to write the output in a file, used as filename prefix + * @param string|array $formats String or array of formats to fetch from API + * @param int|bool $idSite Id site + * @param string|bool $dateTime Date time string of reports to request + * @param array|bool|string $periods String or array of strings of periods (day, week, month, year) + * @param bool $setDateLastN When set to true, 'date' parameter passed to API request will be rewritten to query a range of dates rather than 1 date only + * @param string|bool $language 2 letter language code to request data in + * @param string|bool $segment Custom Segment to query the data for + * @param string|bool $visitorId Only used for Live! API testing + * @param bool $abandonedCarts Only used in Goals API testing + * @param bool $idGoal + * @param bool $apiModule + * @param bool $apiAction + * @param array $otherRequestParameters + * + * @return void + */ + protected function _callGetApiCompareOutput($testName, $formats = 'xml', $idSite = false, $dateTime = false, $periods = false, + $setDateLastN = false, $language = false, $segment = false, $visitorId = false, $abandonedCarts = false, + $idGoal = false, $apiModule = false, $apiAction = false, $otherRequestParameters = array()) + { + if (self::$apiTestingLevel == self::NO_API_TESTING) + { + return; + } + + list($pathProcessed, $pathExpected) = $this->getProcessedAndExpectedDirs(); + + if($periods === false) + { + $periods = 'day'; + } + if(!is_array($periods)) + { + $periods = array($periods); + } + if(!is_array($formats)) + { + $formats = array($formats); + } + if(!is_writable($pathProcessed)) + { + $this->fail('To run the tests, you need to give write permissions to the following directory (create it if it doesn\'t exist).<code><br/>mkdir '. $pathProcessed.'<br/>chmod 777 '.$pathProcessed.'</code><br/>'); + } + $parametersToSet = array( + 'idSite' => $idSite, + 'date' => $periods == array('range') ? $dateTime : date('Y-m-d', strtotime($dateTime)), + 'expanded' => '1', + 'piwikUrl' => 'http://example.org/piwik/', + // Used in getKeywordsForPageUrl + 'url' => 'http://example.org/store/purchase.htm', + + // Used in Actions.getPageUrl, .getDownload, etc. + // tied to Main.test.php doTest_oneVisitorTwoVisits + // will need refactoring when these same API functions are tested in a new function + 'downloadUrl' => urlencode('http://piwik.org/path/again/latest.zip?phpsessid=this is ignored when searching'), + 'outlinkUrl' => urlencode('http://dev.piwik.org/svn'), + 'pageUrl' => urlencode('http://example.org/index.htm?sessionid=this is also ignored by default'), + 'pageName' => urlencode(' Checkout / Purchasing... '), + + // do not show the millisec timer in response or tests would always fail as value is changing + 'showTimer' => 0, + + 'language' => $language ? $language : 'en', + 'abandonedCarts' => $abandonedCarts ? 1 : 0, + 'idSites' => $idSite, + ); + $parametersToSet = array_merge($parametersToSet, $otherRequestParameters); + if(!empty($visitorId )) + { + $parametersToSet['visitorId'] = $visitorId; + } + if(!empty($apiModule )) + { + $parametersToSet['apiModule'] = $apiModule; + } + if(!empty($apiAction)) + { + $parametersToSet['apiAction'] = $apiAction; + } + if(!empty($segment)) + { + $parametersToSet['segment'] = $segment; + } + if($idGoal !== false) + { + $parametersToSet['idGoal'] = $idGoal; + } + + $requestUrls = $this->generateUrlsApi($parametersToSet, $formats, $periods, $setDateLastN, $language, $segment); + + foreach($requestUrls as $apiId => $requestUrl) + { + #echo "\n\n$requestUrl\n\n"; + $isLiveMustDeleteDates = strpos($requestUrl, 'Live.getLastVisits') !== false; + $request = new Piwik_API_Request($requestUrl); + + list($processedFilePath, $expectedFilePath) = $this->getProcessedAndExpectedPaths($testName, $apiId); + + // Cast as string is important. For example when calling + // with format=original, objects or php arrays can be returned. + // we also hide errors to prevent the 'headers already sent' in the ResponseBuilder (which sends Excel headers multiple times eg.) + $response = (string)$request->process(); + + if($isLiveMustDeleteDates) + { + $response = $this->removeAllLiveDatesFromXml($response); + } + + file_put_contents( $processedFilePath, $response ); + + $expected = $this->loadExpectedFile($expectedFilePath); + if (empty($expected)) + { + continue; + } + + // @todo This should not vary between systems AFAIK... "idsubdatatable can differ" + $expected = $this->removeXmlElement($expected, 'idsubdatatable',$testNotSmallAfter = false); + $response = $this->removeXmlElement($response, 'idsubdatatable',$testNotSmallAfter = false); + + if($isLiveMustDeleteDates) + { + $expected = $this->removeAllLiveDatesFromXml($expected); + } + // If date=lastN the <prettyDate> element will change each day, we remove XML element before comparison + elseif(strpos($dateTime, 'last') !== false + || strpos($dateTime, 'today') !== false + || strpos($dateTime, 'now') !== false + ) + { + if(strpos($requestUrl, 'API.getProcessedReport') !== false) + { + $expected = $this->removePrettyDateFromXml($expected); + $response = $this->removePrettyDateFromXml($response); + } + // avoid build failure when running just before midnight, generating visits in the future + $expected = $this->removeXmlElement($expected, 'sum_daily_nb_uniq_visitors'); + $response = $this->removeXmlElement($response, 'sum_daily_nb_uniq_visitors'); + $expected = $this->removeXmlElement($expected, 'nb_visits_converted'); + $response = $this->removeXmlElement($response, 'nb_visits_converted'); + $expected = $this->removeXmlElement($expected, 'imageGraphUrl'); + $response = $this->removeXmlElement($response, 'imageGraphUrl'); + } + + // is there a better way to test for the current DB type in use? + if(Zend_Registry::get('db') instanceof Piwik_Db_Adapter_Mysqli) + { + // Do not test for TRUNCATE(SUM()) returning .00 on mysqli since this is not working + // http://bugs.php.net/bug.php?id=54508 + $expected = str_replace('.00</revenue>', '</revenue>', $expected); + $response = str_replace('.00</revenue>', '</revenue>', $response); + $expected = str_replace('.1</revenue>', '</revenue>', $expected); + $expected = str_replace('.11</revenue>', '</revenue>', $expected); + $response = str_replace('.11</revenue>', '</revenue>', $response); + $response = str_replace('.1</revenue>', '</revenue>', $response); + } + + if(strpos($requestUrl, 'format=xml') !== false) { + $this->assertXmlStringEqualsXmlString($expected, $response, "Differences with expected in: $processedFilePath %s "); + } else { + $this->assertEquals($expected, $response, "Differences with expected in: $processedFilePath %s "); + } + if(trim($response) == trim($expected)) + { + file_put_contents( $processedFilePath, $response ); + } + } + } + + /** + * Calls a set of controller actions & either checks the result against + * expected output or just checks if errors occurred when called. + * The behavior of this function can be modified by setting + * self::$widgetTestingLevel (or $testingLevelOverride): + * <ul> + * <li>If set to <b>NO_WIDGET_TESTING</b> this function simply returns.<li> + * <li>If set to <b>CHECK_WIDGET_ERRORS</b> controller actions are called & + * this function will just check for errors.</li> + * <li>If set to <b>COMPARE_WIDGET_OUTPUT</b> controller actions are + * called & the output is checked against expected output.</li> + * </ul> + * + * @param string $testName Unique name of this test group. Expected/processed + * file names use this as a prefix. + * @param array $actions Array of controller actions to call. Each element + * must be in the following format: 'Controller.action' + * @param array $requestParameters The request parameters to set. + * @param array $userTypes The user types to test the controller with. Can contain + * these values: 'anonymous', 'view', 'admin', 'superuser'. + * Defaults to all four. + * @param int $testingLevelOverride Overrides self::$widgetTestingLevel. + */ + public function callWidgetsCompareOutput( + $testName, $actions, $requestParameters, $userTypes = null, $testingLevelOverride = null) + { + // deal with the testing level + if (self::$widgetTestingLevel == self::NO_WIDGET_TESTING) + { + return; + } + + if (is_null($testingLevelOverride)) + { + $testingLevelOverride = self::$widgetTestingLevel; + } + + // process $userTypes argument + if (!$userTypes) + { + $userTypes = array('anonymous', 'view', 'admin', 'superuser'); + } + else if (!is_array($userTypes)) + { + $userTypes = array($userTypes); + } + + $oldGet = $_GET; + + // get all testable controller actions if necessary + $actionParams = array(); + if ($actions == 'all') + { + // Goals.addWidgets requires idSite to be set + $_GET['idSite'] = isset($requestParameters['idSite']) ? $requestParameters['idSite'] : '0'; + + list($actions, $actionParams) = $this->findAllWidgets(); + + $_GET = $oldGet; + } + else if (!is_array($actions)) + { + $actions = array($actions); + } + + // run the tests + foreach ($actions as $controllerAction) + { + $customParams = isset($actionParams[$controllerAction]) ? $actionParams[$controllerAction] : array(); + list($controllerName, $actionName) = explode('.', $controllerAction); + + foreach ($userTypes as $userType) + { + $this->setUserType($userType); + + try + { + // set request parameters + $_GET = array(); + foreach ($customParams as $key => $value) + { + $_GET[$key] = $value; + } + foreach ($requestParameters as $key => $value) + { + $_GET[$key] = $value; + } + + $_GET['module'] = $controllerName; + $_GET['action'] = $actionName; + + if ($testingLevelOverride == self::CHECK_WIDGET_ERRORS) + { + $this->errorsOccurredInTest = array(); + set_error_handler(array($this, "customErrorHandler")); + } + + // call controller action + $response = Piwik_FrontController::getInstance()->fetchDispatch(); + + list($processedFilePath, $expectedFilePath) = $this->getProcessedAndExpectedPaths( + $testName . '_' . $userType, $controllerAction, 'html'); + + if ($testingLevelOverride == self::CHECK_WIDGET_ERRORS) + { + restore_error_handler(); + + if (!empty($this->errorsOccurredInTest)) + { + // write processed (only if there are errors) + file_put_contents($processedFilePath, $response); + + $this->fail("PHP Errors occurred in calling controller action '$controllerAction':"); + foreach ($this->errorsOccurredInTest as $error) + { + echo " $error<br/>\n"; + } + } + } + else // check against expected + { + // write raw processed response + file_put_contents($processedFilePath, $response); + + // load expected + $expected = $this->loadExpectedFile($expectedFilePath); + if (!$expected) + { + continue; + } + + // normalize eol delimeters + $expected = str_replace("\r\n", "\n", $expected); + $response = str_replace("\r\n", "\n", $response); + + // check against expected + $passed = $this->assertEquals(trim($expected), trim($response), + "<br/>\nDifferences with expected in: $processedFilePath %s "); + + if (!$passed) + { + var_dump('ERROR FOR ' . $controllerAction . ' -- FETCHED RESPONSE, then EXPECTED RESPONSE - '); + echo "<br/>\n"; + var_dump(htmlspecialchars($response)); + echo "<br/>\n"; + var_dump(htmlspecialchars($expected)); + echo "<br/>\n"; + } + } + } + catch (Exception $e) + { + $this->fail("EXCEPTION THROWN IN $controllerAction: ".$e->getTraceAsString()); + } + } + } + + // reset $_GET to old values + $_GET = array(); + foreach ($oldGet as $key => $value) + { + $_GET[$key] = $value; + } + + // set user type + $this->setUserType('superuser'); + } + + /** + * Sets the access privilegs of the current user to the specified user type. + * + * @param $userType string Can be 'superuser', 'admin', 'view' or 'anonymous'. + */ + protected function setUserType( $userType ) + { + if ($userType == 'superuser') + { + $code = Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE; + $login = 'superUserLogin'; + } + else + { + $code = 0; + + $login = $userType; + if ($login != 'anonymous') + { + $login = 'test_' . $login; + } + } + + $authResultObj = new Piwik_Auth_Result($code, $login, 'dummyTokenAuth'); + $authObj = new MockPiwik_Auth(); + $authObj->setReturnValue('getName', 'Login'); + $authObj->setReturnValue('authenticate', $authResultObj); + + Zend_Registry::get('access')->reloadAccess($authObj); + } + + /** + * Set of messages for errors that occurred during the invocation of a + * controller action. If not empty, there was an error in the controller. + */ + private $errorsOccurredInTest = array(); + + /** + * A custom error handler used with <code>set_error_handler</code>. If + * an error occurs, a message describing it is saved in an array. + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * + * @return void + */ + public function customErrorHandler($errno, $errstr, $errfile, $errline) + { + if (strpos(strtolower($errstr), 'cannot modify header information - headers already sent')) // HACK + { + $this->errorsOccurredInTest[] = "$errfile($errline): - $errstr"; + } + } + + /** + * Returns a list of all available widgets. + */ + protected function findAllWidgets() + { + $widgetList = Piwik_GetWidgetsList(); + + $actions = array(); + $customParams = array(); + + foreach($widgetList as $widgetCategory => $widgets) + { + foreach($widgets as $widgetInfo) + { + $module = $widgetInfo['parameters']['module']; + $moduleAction = $widgetInfo['parameters']['action']; + $wholeAction = "$module.$moduleAction"; + + // FIXME: can't test Referers.getKeywordsForPage since it tries to make a request to + // localhost w/ the wrong url. Piwik_Url::getCurrentUrlWithoutFileName + // returns /tests/integration/?... when used within a test. + if ($wholeAction == "Referers.getKeywordsForPage") + { + continue; + } + + // rss widgets depends on feedburner URL. don't test the widget just in case + // feedburner is down. + if ($module == "ExampleRssWidget" + || $module == "ExampleFeedburner") + { + continue; + } + + unset($widgetInfo['parameters']['module']); + unset($widgetInfo['parameters']['action']); + + $actions[] = $wholeAction; + $customParams[$wholeAction] = $widgetInfo['parameters']; + } + } + + return array($actions, $customParams); + } + + protected function removeAllLiveDatesFromXml($input) + { + $toRemove = array( + 'serverDate', + 'firstActionTimestamp', + 'lastActionTimestamp', + 'lastActionDateTime', + 'serverTimestamp', + 'serverTimePretty', + 'serverDatePretty', + 'serverDatePrettyFirstAction', + 'serverTimePrettyFirstAction', + 'goalTimePretty', + 'serverTimePretty', + 'visitorId' + ); + foreach($toRemove as $xml) { + $input = $this->removeXmlElement($input, $xml); + } + return $input; + } + + protected function removePrettyDateFromXml($input) + { + return $this->removeXmlElement($input, 'prettyDate'); + } + + protected function removeXmlElement($input, $xmlElement, $testNotSmallAfter = true) + { + $input = preg_replace('/(<'.$xmlElement.'>.+?<\/'.$xmlElement.'>)/', '', $input); + //check we didn't delete the whole string + if($testNotSmallAfter) + { + $this->assertTrue(strlen($input) > 100); + } + return $input; + } + + private function getProcessedAndExpectedDirs() + { + $path = $this->getPathToTestDirectory(); + return array($path . '/processed/', $path . '/expected/'); + } + + private function getProcessedAndExpectedPaths($testName, $testId, $format = null) + { + $filename = $testName . '__' . $testId; + if ($format) + { + $filename .= ".$format"; + } + + list($processedDir, $expectedDir) = $this->getProcessedAndExpectedDirs(); + + return array($processedDir . $filename, $expectedDir . $filename); + } + + private function loadExpectedFile($filePath) + { + $result = @file_get_contents($filePath); + if(empty($result)) + { + $expectedDir = dirname($filePath); + $this->fail(" ERROR: Could not find expected API output '$filePath'. For new tests, to pass the test, you can copy files from the processed/ directory into $expectedDir after checking that the output is valid. %s "); + return null; + } + return $result; + } + + + + + + + + /** + * Returns an array describing the API methods to call & compare with + * expected output. + * + * The returned array must be of the following format: + * <code> + * array( + * array('SomeAPI.method', array('testOption1' => 'value1', 'testOption2' => 'value2'), + * array(array('SomeAPI.method', 'SomeOtherAPI.method'), array(...)), + * . + * . + * . + * ) + * </code> + * + * Valid test options: + * <ul> + * <li><b>testSuffix</b> The suffix added to the test name. Helps determine + * the filename of the expected output.</li> + * <li><b>format</b> The desired format of the output. Defaults to 'xml'.</li> + * <li><b>idSite</b> The id of the website to get data for.</li> + * <li><b>date</b> The date to get data for.</li> + * <li><b>periods</b> The period or periods to get data for. Can be an array.</li> + * <li><b>setDateLastN</b> Flag describing whether to query for a set of + * dates or not.</li> + * <li><b>language</b> The language to use.</li> + * <li><b>segment</b> The segment to use.</li> + * <li><b>visitorId</b> The visitor ID to use.</li> + * <li><b>abandonedCarts</b> Whether to look for abandoned carts or not.</li> + * <li><b>idGoal</b> The goal ID to use.</li> + * <li><b>apiModule</b> The value to use in the apiModule request parameter.</li> + * <li><b>apiAction</b> The value to use in the apiAction request parameter.</li> + * <li><b>otherRequestParameters</b> An array of extra request parameters to use.</li> + * <li><b>disableArchiving</b> Disable archiving before running tests.</li> + * </ul> + * + * All test options are optional, except 'idSite' & 'date'. + */ + public function getApiForTesting() { + return array(); + } + + /** + * It is possible to run another set of API tests after the first one. + * For example, getApiForTesting() will test and record with a specific suffix + * Then we will call some API that invalidates a piece of the reports + * Then call all reports again and check that what is expected to have changed indeed has changed! + * + * @see getApiForTesting() for returned values, same signature + */ + public function getAnotherApiForTesting() + { + return array(); + } + + /** + * Returns an array describing the Controller actions to call & compare + * with expected output. + * + * The returned array must be of the following format: + * <code> + * array( + * array('Controller.action', array('testOption1' => 'value1', 'testOption2' => 'value2'), + * array(array('Controller.action', 'OtherController.action'), array(...)), + * . + * . + * . + * ) + * </code> + * + * Valid test options: + * <ul> + * <li><b>UNIMPLEMENTED</b></li> + * </ul> + */ + public function getControllerActionsForTesting() { + return array(); + } + + /** + * Gets the string prefix used in the name of the expected/processed output files. + */ + public function getOutputPrefix() + { + return str_replace('Test_Piwik_Integration_', '', get_class($this)); + } + + /** + * Runs API tests. + */ + protected function runApiTests($api, $params) + { + $testName = 'test_' . $this->getOutputPrefix(); + + if ($api == 'all') + { + $this->setApiToCall(array()); + $this->setApiNotToCall(self::$defaultApiNotToCall); + } + else + { + if (!is_array($api)) + { + $api = array($api); + } + + $this->setApiToCall($api); + $this->setApiNotToCall(array('API.getPiwikVersion')); + } + + if (isset($params['disableArchiving']) && $params['disableArchiving'] === true) + { + Piwik_ArchiveProcessing::$forceDisableArchiving = true; + } + else + { + Piwik_ArchiveProcessing::$forceDisableArchiving = false; + } + + if (isset($params['language'])) + { + $this->changeLanguage($params['language']); + } + + $testSuffix = isset($params['testSuffix']) ? $params['testSuffix'] : ''; + + $this->_callGetApiCompareOutput( + $testName . $testSuffix, + isset($params['format']) ? $params['format'] : 'xml', + isset($params['idSite']) ? $params['idSite'] : false, + isset($params['date']) ? $params['date'] : false, + isset($params['periods']) ? $params['periods'] : false, + isset($params['setDateLastN']) ? $params['setDateLastN'] : false, + isset($params['language']) ? $params['language'] : false, + isset($params['segment']) ? $params['segment'] : false, + isset($params['visitorId']) ? $params['visitorId'] : false, + isset($params['abandonedCarts']) ? $params['abandonedCarts'] : false, + isset($params['idGoal']) ? $params['idGoal'] : false, + isset($params['apiModule']) ? $params['apiModule'] : false, + isset($params['apiAction']) ? $params['apiAction'] : false, + isset($params['otherRequestParameters']) ? $params['otherRequestParameters'] : array()); + + // change the language back to en + if ($this->lastLanguage != 'en') + { + $this->changeLanguage('en'); + } + } + + /** + * Runs controller tests. + */ + protected function runControllerTests() + { + static $nonRequestParameters = array('testingLevelOverride' => null, 'userTypes' => null); + + $testGroups = $this->getControllerActionsForTesting(); + $testName = 'test_' . $this->getOutputPrefix(); + + foreach ($testGroups as $test) + { + list($actions, $params) = $test; + + // deal w/ any language changing hacks + if (isset($params['language'])) + { + $this->changeLanguage($params['language']); + } + + // separate request parameters from function parameters + $requestParams = array(); + foreach ($params as $key => $value) + { + if (!isset($nonRequestParameters[$key])) + { + $requestParams[$key] = $value; + } + } + + $testSuffix = isset($params['testSuffix']) ? $params['testSuffix'] : ''; + + $this->callWidgetsCompareOutput( + $testName . $testSuffix, + $actions, + $requestParams, + isset($params['userTypes']) ? $params['userTypes'] : false, + isset($params['testingLevelOverride']) ? $params['testingLevelOverride'] : false); + + // change the language back to en + if ($this->lastLanguage != 'en') + { + $this->changeLanguage('en'); + } + } + } + + /** + * changing the language within one request is a bit fancy + * in order to keep the core clean, we need a little hack here + * + * @param string $langId + */ + protected function changeLanguage( $langId ) + { + if (isset($this->lastLanguage) && $this->lastLanguage != $langId) + { + $_GET['language'] = $langId; + Piwik_Translate::reset(); + Piwik_Translate::getInstance()->reloadLanguage($langId); + } + + $this->lastLanguage = $langId; + } + + /** + * Path where expected/processed output files are stored. Can be overridden. + */ + public function getPathToTestDirectory() + { + /** + * Use old path as long as files were not moved + * @todo move files + */ + //return dirname(__FILE__).DIRECTORY_SEPARATOR.'Integration'; + return dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'integration'; + } + +} diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php index 82d1c7d2b38057a69d59602e8288993090d46e83..c3fbccb458c022a2e4f8bda7c02b22dfe3fa74f5 100644 --- a/tests/PHPUnit/bootstrap.php +++ b/tests/PHPUnit/bootstrap.php @@ -29,7 +29,7 @@ require_once PIWIK_INCLUDE_PATH .'/core/testMinimumPhpVersion.php'; require_once PIWIK_INCLUDE_PATH .'/core/Loader.php'; require_once PIWIK_INCLUDE_PATH .'/core/FrontController.php'; require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/DatabaseTestCase.php'; -#require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/IntegrationTestCase.php'; +require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/IntegrationTestCase.php'; require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/FakeAccess.php'; // required to build code coverage for uncovered files diff --git a/tests/PHPUnit/phpunit.xml b/tests/PHPUnit/phpunit.xml index 70cc777b20dc11701a6a02b379b9811a556c248d..1c96b0551bc7cdece6eaf16e3987769a7d5b2758 100644 --- a/tests/PHPUnit/phpunit.xml +++ b/tests/PHPUnit/phpunit.xml @@ -45,7 +45,6 @@ <php> <server name="HTTP_HOST" value="local"/> - <server name="PATH_INFO" value=""/> <server name="REQUEST_URI" value="/piwik/tests/all_tests.php"/> <server name="REMOTE_ADDR" value="127.0.0.1"/> </php> diff --git a/tests/PHPUnit/proxy-piwik.php b/tests/PHPUnit/proxy-piwik.php new file mode 100755 index 0000000000000000000000000000000000000000..eac5a26dd952c4a14cdc8fb381a3c556852218a2 --- /dev/null +++ b/tests/PHPUnit/proxy-piwik.php @@ -0,0 +1,32 @@ +<?php +/** + * Proxy to normal piwik.php, but in testing mode + * + * - Use the tests database to record Tracking data + * - Allows to overwrite the Visitor IP, and Server datetime + * + * @see Main.test.php + * + */ +// Wrapping the request inside ob_start() calls to ensure that the Test +// calling us waits for the full request to process before unblocking +ob_start(); + +define('PIWIK_INCLUDE_PATH', '../..'); +define('PIWIK_USER_PATH', PIWIK_INCLUDE_PATH); + +require_once PIWIK_INCLUDE_PATH .'/libs/upgradephp/upgrade.php'; +require_once PIWIK_INCLUDE_PATH .'/core/Loader.php'; + +// Config files forced to use the test database +// Note that this also provides security for Piwik installs containing tests files: +// this proxy will not record any data in the production database. +Piwik::createConfigObject(); +Piwik_Config::getInstance()->setTestEnvironment(); +Piwik_Config::getInstance()->PluginsInstalled['PluginsInstalled'] = array(); + +Piwik_Tracker::setTestEnvironment(); +Piwik_Common::deleteTrackerCache(); + +include '../../piwik.php'; +ob_flush(); diff --git a/tests/config_test.php b/tests/config_test.php index b107a43b2a559ad6fa3592c330ce7787934a3f15..c85175017990f7adeec0b22639feaae5a865281e 100644 --- a/tests/config_test.php +++ b/tests/config_test.php @@ -49,9 +49,11 @@ function dump($var) print("</pre>"); } -function printDebug($text) -{ - return; +if(!function_exists('printDebug')) { + function printDebug($text) + { + return; + } } require_once PIWIK_INCLUDE_PATH .'/core/Loader.php';