Skip to content
Extraits de code Groupes Projets
IntegrationTestCase.php 38,9 ko
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /**
     * Piwik - Open source web analytics
     *
     * @link http://piwik.org
     * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
     */
    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 PHPUnit_Framework_TestCase
    
    {
        /**
         * Identifies the last language used in an API/Controller call.
         *
         * @var string
         */
        protected $lastLanguage;
    
    	
    	/**
    	 * Creates a config object for use w/ tests.
    	 */
    	public static function createTestConfig()
    	{
            Piwik::createConfigObject();
            Piwik_Config::getInstance()->setTestEnvironment();
    	}
    	
    	/**
    	 * Connects to MySQL w/o specifying a database.
    	 */
    	public static function connectWithoutDatabase()
    	{
            $dbConfig = Piwik_Config::getInstance()->database;
            $oldDbName = $dbConfig['dbname'];
            $dbConfig['dbname'] = null;
            
            Piwik::createDatabaseObject($dbConfig);
            
            $dbConfig['dbname'] = $oldDbName;
    	}
    
    	public static function setUpBeforeClass()
    	{
    		self::_setUpBeforeClass();
    
    		
    		if (isset(static::$fixture))
    		{
    			self::setupFixture(static::$fixture);
    		}
    	}
    	
    	protected static function setupFixture( $fixture )
    	{
    	    try
    	    {
    	    	$fixture->setUp();
    	    }
    	    catch(Exception $e)
    	    {
    	        // Skip whole test suite if an error occurs while setup
    	        throw new PHPUnit_Framework_SkippedTestSuiteError($e->getMessage()."\n".$e->getTraceAsString());
    	    }
    	}
    	
    	protected static function teardownFixture( $fixture )
    	{
    		if (isset(static::$fixture))
    		{
    			self::tearDownFixture(static::$fixture);
    		}
    		
    		$fixture->tearDown();
    
    	/**
    	 * setupBeforeClass' implementation. Can be called by derived classes in case
    	 * they need to do some custom setup procedure.
    	 */
    
        public static function _setUpBeforeClass( $dbName = false, $createEmptyDatabase = true, $createConfig = true )
    
            	if ($createConfig)
            	{
    	        	self::createTestConfig();
            	}
            	
            	if ($dbName === false) // must be after test config is created
            	{
            		$dbName = Piwik_Config::getInstance()->database['dbname'];
            	}
            	
            	self::connectWithoutDatabase();
                if ($createEmptyDatabase)
                {
                    Piwik::dropDatabase();
                }
    
                Piwik::createDatabase($dbName);
                Piwik::disconnectDatabase();
    
    			
    			// reconnect once we're sure the database exists
    			Piwik_Config::getInstance()->database['dbname'] = $dbName;
    
                Piwik::createTables();
                Piwik::createLogObject();
    
                Piwik_PluginsManager::getInstance()->loadPlugins(array());
    
            } catch(Exception $e) {
                self::fail("TEST INITIALIZATION FAILED: " .$e->getMessage());
            }
    
            include "DataFiles/SearchEngines.php";
            include "DataFiles/Languages.php";
            include "DataFiles/Countries.php";
            include "DataFiles/Currencies.php";
            include "DataFiles/LanguageToCountry.php";
    
    
            Piwik::createAccessObject();
            Piwik_PostEvent('FrontController.initAuthenticationObject');
    
            // We need to be SU to create websites for tests
            Piwik::setUserIsSuperUser();
    
    	    Piwik_Tracker_Cache::deleteTrackerCache();
    
    
            // Load and install plugins
            $pluginsManager = Piwik_PluginsManager::getInstance();
    
            $plugins = $pluginsManager->readPluginsDirectory();
    
    
            $pluginsManager->loadPlugins( $plugins );
    
            if ($createEmptyDatabase) // only install if database is empty
            {
    	        $pluginsManager->installLoadedPlugins();
            }
    
    
            $_GET = $_REQUEST = array();
            $_SERVER['HTTP_REFERER'] = '';
    
            // Make sure translations are loaded to check messages in English
            Piwik_Translate::getInstance()->loadEnglishTranslation();
    
            Piwik_LanguagesManager_API::getInstance()->setLanguageForUser('superUserLogin', 'en');
    
    
            // 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.
    
            self::setApiNotToCall(self::$defaultApiNotToCall);
            self::setApiToCall( array());
    
    	public static function tearDownAfterClass()
    	{
    		self::_tearDownAfterClass();
    	}
    
        public static function _tearDownAfterClass( $dropDatabase = true )
    
            try {
                $plugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
                foreach($plugins AS $plugin) {
    
                }
                Piwik_PluginsManager::getInstance()->unloadPlugins();
            } catch (Exception $e) {}
    
            Piwik_DataTable_Manager::getInstance()->deleteAll();
            Piwik_Option::getInstance()->clearCache();
            Piwik_Site::clearCache();
    
            Piwik_Tracker_Cache::deleteTrackerCache();
    
            Piwik_Config::getInstance()->clear();
            Piwik_TablePartitioning::$tablesAlreadyInstalled = null;
    
            Piwik_PDFReports_API::$cache = array();
    
            $_GET = $_REQUEST = array();
            Piwik_Translate::getInstance()->unloadEnglishTranslation();
    
            // re-enable tag cloud shuffling
            Piwik_Visualization_Cloud::$debugDisableShuffle = true;
    
        public function setUp()
        {
    
    mattpiwik's avatar
    mattpiwik a validé
    	    // Make sure the browser running the test does not influence the Country detection code
    	    $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en';
    
    
            $this->changeLanguage('en');
        }
    
    
        protected static $apiToCall = array();
        protected static $apiNotToCall = array();
    
    
        public static $defaultApiNotToCall = array(
            'LanguagesManager',
            'DBStats',
            'UsersManager',
            'SitesManager',
            'ExampleUI',
    
    BeezyT's avatar
    BeezyT a validé
    	    'Overlay',
    
    mattpiwik's avatar
    mattpiwik a validé
            'UserCountry.getLocationFromIP',
    
        );
    
        const DEFAULT_USER_PASSWORD = 'nopass';
    
        
        protected $missingExpectedFiles = array();
        protected $comparisonFailures = array();
    
    
        /**
         * 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 static function setApiToCall( $apiToCall )
    
        {
            if(func_num_args() != 1)
            {
                throw new Exception('setApiToCall expects an array');
            }
            if(!is_array($apiToCall))
            {
                $apiToCall = array($apiToCall);
            }
    
        }
    
        /**
         * Sets a list of API methods to not call during the test
         *
         * @param string $apiNotToCall eg. 'ExampleAPI.getPiwikVersion'
         *
         * @return void
         */
    
        protected static function setApiNotToCall( $apiNotToCall )
    
        {
            if(!is_array($apiNotToCall))
            {
                $apiNotToCall = array($apiNotToCall);
            }
    
            self::$apiNotToCall = $apiNotToCall;
    
    	protected function alertWhenImagesExcludedFromTests()
    	{
    
    		if(!Test_Piwik_BaseFixture::canImagesBeIncludedInScheduledReports())
    
    			'Do take note that scheduled reports are not being tested with images. ' .
    			'If images contained in scheduled reports have been altered, tests will fail on the Piwik QA Server. ' .
    
    			'To include images in the test suite, please use a machine with the following specifications : ' .
    			'OS = Linux precise32, PHP Version = 5.3.10 and GD Version = 2.0' .
    			"\n Ignore this message if you're running on your dev machine, but pay attention when it comes from Jenkins."
    
    
    	/**
    	 * Return 4 Api Urls for testing scheduled reports :
    	 * - one in HTML format with all available reports
    	 * - one in PDF format with all available reports
    	 * - two in SMS (one for each available report: MultiSites.getOne & MultiSites.getAll)
    	 *
    	 * @param string $dateTime eg '2010-01-01 12:34:56'
    	 * @param string $period eg 'day', 'week', 'month', 'year'
    	 */
    	protected static function getApiForTestingScheduledReports($dateTime, $period)
    	{
    
    JulienMoumne's avatar
    JulienMoumne a validé
    		$apiCalls = array();
    
    JulienMoumne's avatar
    JulienMoumne a validé
    		// HTML Scheduled Report
    		array_push(
    			$apiCalls,
    
    			array(
    				'PDFReports.generateReport',
    				array(
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					'testSuffix' => '_scheduled_report_in_html_tables_only',
    
    					'date' => $dateTime,
    					'periods' => array($period),
    					'format' => 'original',
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					'fileExtension' => 'html',
    
    					'otherRequestParameters' => array(
    						'idReport' => 1,
    						'reportFormat' => Piwik_ReportRenderer::HTML_FORMAT,
    						'outputType' => Piwik_PDFReports_API::OUTPUT_RETURN
    					)
    				)
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			)
    		);
    
    
    		// This particular PDF file looks different on recent PHP
    
    		// Differences with expected in: tests/PHPUnit/Integration/processed/test_ecommerceOrderWithItems_scheduled_report_in_pdf_tables_only__PDFReports.generateReport_week.original.pdf
    		// Failed asserting that 486675 matches expected 486668.
    
    		// So we disable this test for 5.4 and 5.5
    		if(stristr(phpversion(), '5.4') === false && stristr(phpversion(), '5.5') === false)
    
    		{
    			// PDF Scheduled Report
    			array_push(
    				$apiCalls,
    
    					'PDFReports.generateReport',
    					array(
    						'testSuffix' => '_scheduled_report_in_pdf_tables_only',
    						'date' => $dateTime,
    						'periods' => array($period),
    						'format' => 'original',
    						'fileExtension' => 'pdf',
    						'otherRequestParameters' => array(
    							'idReport' => 1,
    							'reportFormat' => Piwik_ReportRenderer::PDF_FORMAT,
    							'outputType' => Piwik_PDFReports_API::OUTPUT_RETURN
    						)
    
    JulienMoumne's avatar
    JulienMoumne a validé
    
    		// SMS Scheduled Report, one site
    		array_push(
    			$apiCalls,
    
    			array(
    				'PDFReports.generateReport',
    				array(
    					'testSuffix' => '_scheduled_report_via_sms_one_site',
    					'date' => $dateTime,
    					'periods' => array($period),
    					'format' => 'original',
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					'fileExtension' => 'sms.txt',
    
    					'otherRequestParameters' => array(
    						'idReport' => 2,
    						'outputType' => Piwik_PDFReports_API::OUTPUT_RETURN
    					)
    				)
    
    JulienMoumne's avatar
    JulienMoumne a validé
    			)
    		);
    
    		// SMS Scheduled Report, all sites
    		array_push(
    			$apiCalls,
    
    			array(
    				'PDFReports.generateReport',
    				array(
    					'testSuffix' => '_scheduled_report_via_sms_all_sites',
    					'date' => $dateTime,
    					'periods' => array($period),
    					'format' => 'original',
    
    JulienMoumne's avatar
    JulienMoumne a validé
    					'fileExtension' => 'sms.txt',
    
    					'otherRequestParameters' => array(
    						'idReport' => 3,
    						'outputType' => Piwik_PDFReports_API::OUTPUT_RETURN
    					)
    				)
    			)
    		);
    
    JulienMoumne's avatar
    JulienMoumne a validé
    
    
    		if(Test_Piwik_BaseFixture::canImagesBeIncludedInScheduledReports())
    
    JulienMoumne's avatar
    JulienMoumne a validé
    		{
    			// HTML Scheduled Report with images
    			array_push(
    				$apiCalls,
    				array(
    					'PDFReports.generateReport',
    					array(
    						'testSuffix' => '_scheduled_report_in_html_tables_and_graph',
    						'date' => $dateTime,
    						'periods' => array($period),
    						'format' => 'original',
    						'fileExtension' => 'html',
    						'otherRequestParameters' => array(
    							'idReport' => 4,
    							'reportFormat' => Piwik_ReportRenderer::HTML_FORMAT,
    							'outputType' => Piwik_PDFReports_API::OUTPUT_RETURN
    						)
    					)
    				)
    			);
    		}
    
    		return $apiCalls;
    
        /**
         * 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
    
    sgiehl's avatar
    sgiehl a validé
         * @param bool        $supertableApi
    
         * @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
    
    JulienMoumne's avatar
    JulienMoumne a validé
         * @param bool|string $fileExtension
    
    sgiehl's avatar
    sgiehl a validé
         * @throws Exception
    
         * @return array of API URLs query strings
         */
    
    JulienMoumne's avatar
    JulienMoumne a validé
        protected function generateUrlsApi( $parametersToSet, $formats, $periods, $supertableApi = false, $setDateLastN = false, $language = false, $segment = false, $fileExtension = 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(self::$apiToCall)
                        && in_array($moduleName, self::$apiToCall) === false
                        && in_array($apiId, self::$apiToCall) === false)
    
    mattpiwik's avatar
    mattpiwik a validé
    //	                echo "Skipped $apiId... \n";
    
    mattpiwik's avatar
    mattpiwik a validé
    	                continue;
    
    mattpiwik's avatar
    mattpiwik a validé
    	            // Excluded modules from test
    
                        ((strpos($methodName, 'get') !== 0 && $methodName != 'generateReport')
    
                            || in_array($moduleName, self::$apiNotToCall) === true
                            || in_array($apiId, self::$apiNotToCall) === true
    
                            || $methodName == 'getSVGLogoUrl'
                            || $methodName == 'hasSVGLogo'
    
    mattpiwik's avatar
    mattpiwik a validé
    //	                echo "Skipped $apiId... \n";
    
                        $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;
                        }
    
                        // set idSubtable if subtable API is set
                        if ($supertableApi !== false)
                        {
                        	$request = new Piwik_API_Request(array(
                        		'module' => 'API',
                        		'method' => $supertableApi,
                        		'idSite' => $parametersToSet['idSite'],
                        		'period' => $parametersToSet['period'],
                        		'date' => $parametersToSet['date'],
                        		'format' => 'php',
                        		'serialize' => 0,
                        	));
    
                        	// find first row w/ subtable
                        	foreach ($request->process() as $row)
                        	{
                        		if (isset($row['idsubdatatable']))
                        		{
                        			$parametersToSet['idSubtable'] = $row['idsubdatatable'];
                        			break;
                        		}
                        	}
    
                        	// if no subtable found, throw
                        	if (!isset($parametersToSet['idSubtable']))
                        	{
    	                    	throw new Exception(
    	                    		"Cannot find subtable to load for $apiId in $supertableApi.");
                        	}
                        }
    
                        // 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;
    
    
    JulienMoumne's avatar
    JulienMoumne a validé
    						if($fileExtension)
    						{
    							$apiRequestId .= '.' . $fileExtension;
    						}
    
    
                            $requestUrls[$apiRequestId] = $exampleUrl;
                        }
                    }
                }
            }
            return $requestUrls;
        }
    
        /**
    
         * Will return all api urls for the given data
    
         *
         * @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
    
         * @param array|bool        $supertableApi
         * @param array|bool        $fileExtension
    
        protected function _generateApiUrls($formats = 'xml', $idSite = false, $dateTime = false, $periods = false,
    
    JulienMoumne's avatar
    JulienMoumne a validé
    										 $setDateLastN = false, $language = false, $segment = false, $visitorId = false,
    										 $abandonedCarts = false, $idGoal = false, $apiModule = false, $apiAction = false,
    										 $otherRequestParameters = array(), $supertableApi = false, $fileExtension = false)
    
        {
            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'      => 'http://piwik.org/path/again/latest.zip?phpsessid=this is ignored when searching',
                'outlinkUrl'       => 'http://dev.piwik.org/svn',
                'pageUrl'          => 'http://example.org/index.htm?sessionid=this is also ignored by default',
                'pageName'         => ' 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;
            }
    
    
    JulienMoumne's avatar
    JulienMoumne a validé
            $requestUrls = $this->generateUrlsApi($parametersToSet, $formats, $periods, $supertableApi, $setDateLastN, $language, $segment, $fileExtension);
    
    
    	    $this->checkEnoughUrlsAreTested($requestUrls);
    
    	    return $requestUrls;
    
    	protected function checkEnoughUrlsAreTested($requestUrls)
    	{
    		$countUrls = count($requestUrls);
    		$approximateCountApiToCall = count(self::$apiToCall);
    		if (empty($requestUrls)
    			|| $approximateCountApiToCall > $countUrls
    		) {
    			throw new Exception("Only generated $countUrls API calls to test but was expecting more for this test.\n".
    					"Want to test APIs: " . implode(", ", self::$apiToCall) . ")\n".
    					"But only generated these URLs: \n" . implode("\n", $requestUrls) . ")\n"
    			);
    		}
    	}
    
    	protected function _testApiUrl($testName, $apiId, $requestUrl)
    
            $isLiveMustDeleteDates = strpos($requestUrl, 'Live.getLastVisits') !== false;
            $request               = new Piwik_API_Request($requestUrl);
    
            $dateTime             = Piwik_Common::getRequestVar('date', '', 'string', Piwik_Common::getArrayFromQueryString($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);
    
    		// normalize date markups and document ID in pdf files :
    		// - /LastModified (D:20120820204023+00'00')
    		// - /CreationDate (D:20120820202226+00'00')
    		// - /ModDate (D:20120820202226+00'00')
    		// - /M (D:20120820202226+00'00')
    		// - /ID [ <0f5cc387dc28c0e13e682197f485fe65> <0f5cc387dc28c0e13e682197f485fe65> ]
    		$response = preg_replace('/\(D:[0-9]{14}/', '(D:19700101000000', $response);
    		$response = preg_replace('/\/ID \[ <.*> ]/', '', $response);
    
    
            file_put_contents($processedFilePath, $response);
    
            $expected = $this->loadExpectedFile($expectedFilePath);
            if (empty($expected)) {
    
    	        print("The expected file is not found at '$expectedFilePath'. The Processed response was:");
    
    	        print("\n----------------------------\n\n");
    	        var_dump($response);
    	        print("\n----------------------------\n");
    	        return;
    
            // @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');
    
                
                
                 if (strpos($requestUrl, 'date=') !== false)
                {
                	$regex = "/date=[-0-9,%Ca-z]+/"; // need to remove %2C which is encoded ,
                	$expected = preg_replace($regex, 'date=', $expected);
                	$response = preg_replace($regex, 'date=', $response);
                } 
    
            // if idSubtable is in request URL, make sure idSubtable values are not in any urls
            if (strpos($requestUrl, 'idSubtable=') !== false)
            {
            	$regex = "/idSubtable=[0-9]+/";
            	$expected = preg_replace($regex, 'idSubtable=', $expected);
            	$response = preg_replace($regex, 'idSubtable=', $response);
            }
    
    
            // 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
    
    mattpiwik's avatar
    mattpiwik a validé
                $expected = str_replace('.000000</l', '</l', $expected); //lat/long
    
    mattpiwik's avatar
    mattpiwik a validé
    	        $response = str_replace('.000000</l', '</l', $response); //lat/long
    	        $expected = str_replace('.00</revenue>', '</revenue>', $expected);
    
                $response = str_replace('.00</revenue>', '</revenue>', $response);
    
    mattpiwik's avatar
    mattpiwik a validé
    	        $response = str_replace('.1</revenue>', '</revenue>', $response);
    	        $expected = str_replace('.1</revenue>', '</revenue>', $expected);
    	        $expected = str_replace('.11</revenue>', '</revenue>', $expected);
    	        $response = str_replace('.11</revenue>', '</revenue>', $response);
    
    		try
    		{
    		    if (strpos($requestUrl, 'format=xml') !== false) {
    		        $this->assertXmlStringEqualsXmlString($expected, $response, "Differences with expected in: $processedFilePath");
    		    } else {
    		        $this->assertEquals(strlen($expected), strlen($response), "Differences with expected in: $processedFilePath");
    		        $this->assertEquals($expected, $response, "Differences with expected in: $processedFilePath");
    		    }
    
    
    			if (trim($response) == trim($expected)) {
    				file_put_contents($processedFilePath, $response);
    			}
    		}
    
    	    catch (Exception $ex)
    	    {
    	    	$this->comparisonFailures[] = $ex;
    	    }
    
        }
    
        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)
        {
    
        	// Only raise error if there was some data before
        	$testNotSmallAfter = strlen($input > 100 ) && $testNotSmallAfter;
        	
    
            $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->missingExpectedFiles[] = $filePath;
    
                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();
        }
    
        /**
         * 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));
        }
    
    
        protected function _setCallableApi($api)
    
                self::setApiToCall(array());
                self::setApiNotToCall(self::$defaultApiNotToCall);
    
    mattpiwik's avatar
    mattpiwik a validé
    
    	        if(!in_array('UserCountry.getLocationFromIP', $api)) {
    
    				self::setApiNotToCall( array(
    					'API.getPiwikVersion',
    					'UserCountry.getLocationFromIP'
    				));
    	        } else {
    				self::setApiNotToCall( array());
    			}
    
        }
    
        /**
         * Runs API tests.
         */
        protected function runApiTests($api, $params)
        {
            $testName = 'test_' . $this->getOutputPrefix();
    
            $this->missingExpectedFiles = array();
            $this->comparisonFailures = array();
    
    
            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'] : '';
    
    
            $requestUrls = $this->_generateApiUrls(
                        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(),
    
    JulienMoumne's avatar
    JulienMoumne a validé
                        isset($params['supertableApi']) ? $params['supertableApi'] : false,
                        isset($params['fileExtension']) ? $params['fileExtension'] : false);
    
            foreach($requestUrls as $apiId => $requestUrl)
    
                $this->_testApiUrl( $testName . $testSuffix, $apiId, $requestUrl);
    
            // change the language back to en
            if ($this->lastLanguage != 'en')
            {
    
            
            if (!empty($this->missingExpectedFiles))
            {
                $expectedDir = dirname(reset($this->missingExpectedFiles));
                $this->markTestIncomplete(" ERROR: Could not find expected API output '"
                	. implode("', '", $this->missingExpectedFiles)
                	. "'. 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 ");
            }
    
    
    	    // Display as one error all sub-failures
    
    	        $messages = '';
    	        $i = 1;
    	        foreach($this->comparisonFailures as $failure) {
    		        $msg = $failure->getMessage();
    		        $msg = strtok($msg, "\n");
    		        $messages .= "\n#" . $i++ . ": " . $msg;
    	        }