diff --git a/tests/PHPUnit/Core/ArchiveProcessingTest.php b/tests/PHPUnit/Core/ArchiveProcessingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..92f305553cda1ef1af047333bf725ba5f185e4bb
--- /dev/null
+++ b/tests/PHPUnit/Core/ArchiveProcessingTest.php
@@ -0,0 +1,459 @@
+<?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$
+ */
+class ArchiveProcessingTest extends DatabaseTestCase
+{
+    public function setUp()
+    {
+        parent::setUp();
+
+        // setup the access layer
+        $pseudoMockAccess = new FakeAccess;
+        FakeAccess::$superUser = true;
+        Zend_Registry::set('access', $pseudoMockAccess);
+    }
+
+    /**
+     * Creates a new website
+     * 
+     * @param string $timezone
+     * @return Piwik_Site
+     */
+    private function _createWebsite($timezone = 'UTC')
+    {
+        $idSite = Piwik_SitesManager_API::getInstance()->addSite(
+                                                "site1",
+                                                array("http://piwik.net"), 
+                                                $ecommerce=0,
+                                                $excludedIps = "",
+                                                $excludedQueryParameters = "",
+                                                $timezone);
+                                                
+        Piwik_Site::clearCache();
+        return new Piwik_Site($idSite);
+    }
+
+    /**
+     * Creates a new ArchiveProcessing object
+     * 
+     * @param string $periodLabel
+     * @param string $dateLabel
+     * @param string $siteTimezone
+     * @return Piwik_ArchiveProcessing
+     */
+    private function _createArchiveProcessing($periodLabel, $dateLabel, $siteTimezone)
+    {
+        $site = $this->_createWebsite($siteTimezone);
+        $date = Piwik_Date::factory($dateLabel);
+        $period = Piwik_Period::factory($periodLabel, $date);
+        
+        $archiveProcessing = Piwik_ArchiveProcessing::factory($periodLabel);
+        $archiveProcessing->setSite($site);
+        $archiveProcessing->setPeriod($period);
+        $archiveProcessing->setSegment(new Piwik_Segment('', $site->getId()));
+        $archiveProcessing->init();
+        return $archiveProcessing;
+    }
+
+    /**
+     * test of validity of an archive, for a month not finished
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitCurrentMonth()
+    {
+        $siteTimezone = 'UTC+10';
+        $now = time();
+        
+        $dateLabel = date('Y-m-d', $now);
+        $archiveProcessing = $this->_createArchiveProcessing('month', $dateLabel, $siteTimezone);
+        $archiveProcessing->time = $now;
+        
+        // min finished timestamp considered when looking at archive timestamp 
+        $timeout = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
+        $this->assertTrue($timeout >= 10);
+        $dateMinArchived = $now - $timeout;
+
+        $minTimestamp = $archiveProcessing->getMinTimeArchivedProcessed();
+        $this->assertEquals($minTimestamp, $dateMinArchived, Piwik_Date::factory($minTimestamp)->getDatetime() . " != " . Piwik_Date::factory($dateMinArchived)->getDatetime());
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+    }
+    
+    /**
+     * test of validity of an archive, for a month in the past
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitDayInPast()
+    {
+        $archiveProcessing = $this->_createArchiveProcessing('day', '2010-01-01', 'UTC');
+        
+        // min finished timestamp considered when looking at archive timestamp 
+        $dateMinArchived = Piwik_Date::factory('2010-01-02')->getTimestamp();
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed() + 1, $dateMinArchived);
+        
+        $this->assertEquals('2010-01-01 00:00:00', $archiveProcessing->getStartDatetimeUTC());
+        $this->assertEquals('2010-01-01 23:59:59', $archiveProcessing->getEndDatetimeUTC());
+        $this->assertFalse($archiveProcessing->isArchiveTemporary());
+    }
+
+    /**
+     * test of validity of an archive, for a non UTC date in the past
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitDayInPastNonUTCWebsite()
+    {
+        $timezone = 'UTC+5.5';
+        $archiveProcessing = $this->_createArchiveProcessing('day', '2010-01-01', $timezone);
+        // min finished timestamp considered when looking at archive timestamp 
+        $dateMinArchived = Piwik_Date::factory('2010-01-01 18:30:00');
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed() + 1, $dateMinArchived->getTimestamp());
+        
+        $this->assertEquals('2009-12-31 18:30:00', $archiveProcessing->getStartDatetimeUTC());
+        $this->assertEquals('2010-01-01 18:29:59', $archiveProcessing->getEndDatetimeUTC());
+        $this->assertFalse($archiveProcessing->isArchiveTemporary());
+    }
+
+    /**
+     * test of validity of an archive, for a non UTC month in the past
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitMonthInPastNonUTCWebsite()
+    {
+        $timezone = 'UTC-5.5';
+        $archiveProcessing = $this->_createArchiveProcessing('month', '2010-01-02', $timezone);
+        // min finished timestamp considered when looking at archive timestamp 
+        $dateMinArchived = Piwik_Date::factory('2010-02-01 05:30:00');
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed() + 1, $dateMinArchived->getTimestamp());
+        
+        $this->assertEquals('2010-01-01 05:30:00', $archiveProcessing->getStartDatetimeUTC());
+        $this->assertEquals('2010-02-01 05:29:59', $archiveProcessing->getEndDatetimeUTC());
+        $this->assertFalse($archiveProcessing->isArchiveTemporary());
+    }
+    
+    /**
+     * test of validity of an archive, for today's archive
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitToday()
+    {
+        $now = time();
+        $siteTimezone = 'UTC-1';
+        $timestamp = Piwik_Date::factory('now', $siteTimezone)->getTimestamp();
+        $dateLabel = date('Y-m-d', $timestamp);
+
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(true);
+        
+        $archiveProcessing = $this->_createArchiveProcessing('day', $dateLabel, $siteTimezone);
+        $archiveProcessing->time = $now;
+        
+        // we look at anything processed within the time to live range
+        $dateMinArchived = $now - Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
+        $this->assertEquals($dateMinArchived, $archiveProcessing->getMinTimeArchivedProcessed());
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+
+        // when browsers don't trigger archives, we force ArchiveProcessing 
+        // to fetch any of the most recent archive
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(false);
+        // see isArchivingDisabled()
+        // Running in CLI doesn't impact the time to live today's archive we are loading
+        // From CLI, we will not return data that is 'stale' 
+        if(!Piwik_Common::isPhpCliMode())
+        {
+            $dateMinArchived = 0;
+        }
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed(), $dateMinArchived);
+        
+        $this->assertEquals(date('Y-m-d', $timestamp).' 01:00:00', $archiveProcessing->getStartDatetimeUTC());
+        $this->assertEquals(date('Y-m-d', $timestamp+86400).' 00:59:59', $archiveProcessing->getEndDatetimeUTC());
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+    }
+
+    /**
+     * test of validity of an archive, for today's archive with european timezone
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitTodayEurope()
+    {
+        if(!Piwik::isTimezoneSupportEnabled())
+        {
+            $this->markTestSkipped('timezones needs to be supported');
+        }
+
+        $now = time();
+        $siteTimezone = 'Europe/Paris';
+        $timestamp = Piwik_Date::factory('now', $siteTimezone)->getTimestamp();
+        $dateLabel = date('Y-m-d', $timestamp);
+
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(true);
+
+        $archiveProcessing = $this->_createArchiveProcessing('day', $dateLabel, $siteTimezone);
+        $archiveProcessing->time = $now;
+
+        // we look at anything processed within the time to live range
+        $dateMinArchived = $now - Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed(), $dateMinArchived);
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+
+        // when browsers don't trigger archives, we force ArchiveProcessing
+        // to fetch any of the most recent archive
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(false);
+        // see isArchivingDisabled()
+        // Running in CLI doesn't impact the time to live today's archive we are loading
+        // From CLI, we will not return data that is 'stale'
+        if(!Piwik_Common::isPhpCliMode())
+        {
+            $dateMinArchived = 0;
+        }
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed(), $dateMinArchived);
+
+        // this test varies with DST
+        $this->assertTrue($archiveProcessing->getStartDatetimeUTC() == date('Y-m-d', $timestamp-86400).' 22:00:00' ||
+            $archiveProcessing->getStartDatetimeUTC() == date('Y-m-d', $timestamp-86400).' 23:00:00');
+        $this->assertTrue($archiveProcessing->getEndDatetimeUTC() == date('Y-m-d', $timestamp).' 21:59:59' ||
+            $archiveProcessing->getEndDatetimeUTC() == date('Y-m-d', $timestamp).' 22:59:59');
+
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+    }
+
+    /**
+     * test of validity of an archive, for today's archive with toronto's timezone
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testInitTodayToronto()
+    {
+        if(!Piwik::isTimezoneSupportEnabled())
+        {
+            $this->markTestSkipped('timezones needs to be supported');
+        }
+
+        $now = time();
+        $siteTimezone = 'America/Toronto';
+        $timestamp = Piwik_Date::factory('now', $siteTimezone)->getTimestamp();
+        $dateLabel = date('Y-m-d', $timestamp);
+
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(true);
+
+        $archiveProcessing = $this->_createArchiveProcessing('day', $dateLabel, $siteTimezone);
+        $archiveProcessing->time = $now;
+
+        // we look at anything processed within the time to live range
+        $dateMinArchived = $now - Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed(), $dateMinArchived);
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+
+        // when browsers don't trigger archives, we force ArchiveProcessing
+        // to fetch any of the most recent archive
+        Piwik_ArchiveProcessing::setBrowserTriggerArchiving(false);
+        // see isArchivingDisabled()
+        // Running in CLI doesn't impact the time to live today's archive we are loading
+        // From CLI, we will not return data that is 'stale'
+        if(!Piwik_Common::isPhpCliMode())
+        {
+            $dateMinArchived = 0;
+        }
+        $this->assertEquals($archiveProcessing->getMinTimeArchivedProcessed(), $dateMinArchived);
+
+        // this test varies with DST
+        $this->assertTrue($archiveProcessing->getStartDatetimeUTC() == date('Y-m-d', $timestamp).' 04:00:00' ||
+            $archiveProcessing->getStartDatetimeUTC() == date('Y-m-d', $timestamp).' 05:00:00');
+        $this->assertTrue($archiveProcessing->getEndDatetimeUTC() == date('Y-m-d', $timestamp+86400).' 03:59:59' ||
+            $archiveProcessing->getEndDatetimeUTC() == date('Y-m-d', $timestamp+86400).' 04:59:59');
+
+        $this->assertTrue($archiveProcessing->isArchiveTemporary());
+    }
+
+    /**
+     * Testing batch insert
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testTableInsertBatch()
+    {
+        $table = Piwik_Common::prefixTable('site_url');
+        $data = $this->_getDataInsert();
+        $didWeUseBulk = Piwik::tableInsertBatch($table, array('idsite', 'url'), $data);
+        if(version_compare(PHP_VERSION, '5.2.9') < 0 ||
+            version_compare(PHP_VERSION, '5.3.7') >= 0 ||
+            Piwik_Config::getInstance()->database['adapter'] != 'PDO_MYSQL')
+        {
+            $this->assertTrue($didWeUseBulk, "The test didn't LOAD DATA INFILE but fallbacked to plain INSERT, but we must unit test this function!");
+        }
+        $this->_checkTableIsExpected($table, $data);
+        
+        // INSERT again the bulk. Because we use keyword LOCAL the data will be REPLACED automatically (see mysql doc) 
+        Piwik::tableInsertBatch($table, array('idsite', 'url'), $data);
+        $this->_checkTableIsExpected($table, $data);
+    }
+
+    /**
+     * Testing plain inserts
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testTableInsertBatchIterate()
+    {
+        $table = Piwik_Common::prefixTable('site_url');
+        $data = $this->_getDataInsert();
+        Piwik::tableInsertBatchIterate($table, array('idsite', 'url'), $data);
+        $this->_checkTableIsExpected($table, $data);
+
+        // If we insert AGAIN, expect to throw an error because the primary key already exists
+        try {
+            Piwik::tableInsertBatchIterate($table, array('idsite', 'url'), $data, $ignoreWhenDuplicate = false);    
+        } catch (Exception $e) {
+            // However if we insert with keyword REPLACE, then the new data should be saved
+            Piwik::tableInsertBatchIterate($table, array('idsite', 'url'), $data, $ignoreWhenDuplicate = true );
+            $this->_checkTableIsExpected($table, $data);
+            return;
+        }
+        $this->fail('Exception expected');
+    }
+    
+    /**
+     * Testing batch insert (BLOB)
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testTableInsertBatchBlob()
+    {
+        $siteTimezone = 'America/Toronto';
+        $dateLabel = '2011-03-31';
+        $archiveProcessing = $this->_createArchiveProcessing('day', $dateLabel, $siteTimezone);
+
+        $table = $archiveProcessing->getTableArchiveBlobName();
+
+        $data = $this->_getBlobDataInsert();
+        $didWeUseBulk = Piwik::tableInsertBatch($table, array('idarchive', 'name', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'value'), $data);
+        if(version_compare(PHP_VERSION, '5.2.9') < 0 ||
+            version_compare(PHP_VERSION, '5.3.7') >= 0 ||
+            Piwik_Config::getInstance()->database['adapter'] != 'PDO_MYSQL')
+        {
+            $this->assertTrue($didWeUseBulk, "The test didn't LOAD DATA INFILE but fallbacked to plain INSERT, but we must unit test this function!");
+        }
+        $this->_checkTableIsExpectedBlob($table, $data);
+        
+        // INSERT again the bulk. Because we use keyword LOCAL the data will be REPLACED automatically (see mysql doc) 
+        Piwik::tableInsertBatch($table, array('idarchive', 'name', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'value'), $data);
+        $this->_checkTableIsExpectedBlob($table, $data);
+    }
+
+    /**
+     * Testing plain inserts (BLOB)
+     * @group Core
+     * @group ArchiveProcessing
+     */
+    public function testTableInsertBatchIterateBlob()
+    {
+        $siteTimezone = 'America/Toronto';
+        $dateLabel = '2011-03-31';
+        $archiveProcessing = $this->_createArchiveProcessing('day', $dateLabel, $siteTimezone);
+
+        $table = $archiveProcessing->getTableArchiveBlobName();
+
+        $data = $this->_getBlobDataInsert();
+        Piwik::tableInsertBatchIterate($table, array('idarchive', 'name', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'value'), $data);
+        $this->_checkTableIsExpectedBlob($table, $data);
+
+        // If we insert AGAIN, expect to throw an error because the primary key already exist
+        try {
+            Piwik::tableInsertBatchIterate($table, array('idarchive', 'name', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'value'), $data, $ignoreWhenDuplicate = false);    
+        } catch (Exception $e) {
+            // However if we insert with keyword REPLACE, then the new data should be saved
+            Piwik::tableInsertBatchIterate($table, array('idarchive', 'name', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'value'), $data, $ignoreWhenDuplicate = true );
+            $this->_checkTableIsExpectedBlob($table, $data);
+            return;
+        }
+        $this->fail('Exception expected');
+    }
+    
+    
+    protected function _checkTableIsExpected($table, $data)
+    {
+        $fetched = Piwik_FetchAll('SELECT * FROM '.$table);
+        foreach($data as $id => $row) {
+            $this->assertEquals($fetched[$id]['idsite'], $data[$id][0], "record $id is not {$data[$id][0]}");
+            $this->assertEquals($fetched[$id]['url'], $data[$id][1], "Record $id bug, not {$data[$id][1]} BUT {$fetched[$id]['url']}");
+        }
+    }
+
+    protected function _checkTableIsExpectedBlob($table, $data)
+    {
+        $fetched = Piwik_FetchAll('SELECT * FROM '.$table);
+        foreach($data as $id => $row) {
+            $this->assertEquals($fetched[$id]['idarchive'], $data[$id][0], "record $id idarchive is not '{$data[$id][0]}'");
+            $this->assertEquals($fetched[$id]['name'], $data[$id][1], "record $id name is not '{$data[$id][1]}'");
+            $this->assertEquals($fetched[$id]['idsite'], $data[$id][2], "record $id idsite is not '{$data[$id][2]}'");
+            $this->assertEquals($fetched[$id]['date1'], $data[$id][3], "record $id date1 is not '{$data[$id][3]}'");
+            $this->assertEquals($fetched[$id]['date2'], $data[$id][4], "record $id date2 is not '{$data[$id][4]}'");
+            $this->assertEquals($fetched[$id]['period'], $data[$id][5], "record $id period is not '{$data[$id][5]}'");
+            $this->assertEquals($fetched[$id]['ts_archived'], $data[$id][6], "record $id ts_archived is not '{$data[$id][6]}'");
+            $this->assertEquals($fetched[$id]['value'], $data[$id][7], "record $id value is unexpected");
+        }
+    }
+
+    /*
+     * Schema for site_url table:
+     *    site_url (
+     *        idsite INTEGER(10) UNSIGNED NOT NULL,
+     *        url VARCHAR(255) NOT NULL,
+     *        PRIMARY KEY(idsite, url)
+     *    )
+     */
+    protected function _getDataInsert()
+    {
+        return array(
+            array(1, 'test'),
+            array(2, 'te" \n st2'),
+            array(3, " \n \r \t test"),
+
+            // these aren't expected to work on a column of datatype VARCHAR
+//            array(4, gzcompress( " \n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942")),
+//            array(5, gzcompress('test4')),
+
+            array(6, 'test5'),
+            array(7, '简体中文'),
+            array(8, '"'),
+            array(9, "'"),
+            array(10, '\\'),
+            array(11, '\\"'),
+            array(12, '\\\''),
+            array(13, "\t"),
+            array(14, "test \x00 null"),
+            array(15, "\x00\x01\x02\0x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
+        );
+    }
+
+    /**
+     * see archive_blob table
+     */
+    protected function _getBlobDataInsert()
+    {
+        $ts = '2011-03-31 17:48:00';
+        $str = '';
+        for($i = 0; $i < 256; $i++)
+        {
+            $str .= chr($i);
+        }
+        $array[] = array(1, 'bytes 0-255', 1, '2011-03-31', '2011-03-31', Piwik::$idPeriods['day'], $ts, $str);
+
+        $array[] = array(2, 'compressed string', 1, '2011-03-31', '2011-03-31', Piwik::$idPeriods['day'], $ts, gzcompress( " \n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942\n \r \t teste eigaj oegheao geaoh guoea98742983 2 342942"));
+
+        $str = file_get_contents(PIWIK_PATH_TEST_TO_ROOT . '/tests/core/Piwik/lipsum.txt');
+        $array[] = array(3, 'lorem ipsum', 1, '2011-03-31', '2011-03-31', Piwik::$idPeriods['day'], $ts, $str);
+
+        $array[] = array(4, 'lorem ipsum compressed', 1, '2011-03-31', '2011-03-31', Piwik::$idPeriods['day'], $ts, gzcompress($str));
+
+        return $array;
+    }
+}
diff --git a/tests/PHPUnit/Core/Tracker/Action.config.ini.php b/tests/PHPUnit/Core/Tracker/Action.config.ini.php
new file mode 100644
index 0000000000000000000000000000000000000000..706eb1e3138e54f7e918b54c80f557d9694ac892
--- /dev/null
+++ b/tests/PHPUnit/Core/Tracker/Action.config.ini.php
@@ -0,0 +1,5 @@
+[Tracker]
+action_url_category_delimiter = "/"
+default_action_url            = "/"
+campaign_var_name             = "campaign_param_name,piwik_campaign,utm_campaign,test_campaign_name"
+campaign_keyword_var_name     = "piwik_kwd,utm_term,test_piwik_kwd"
diff --git a/tests/PHPUnit/Core/Tracker/ActionTest.php b/tests/PHPUnit/Core/Tracker/ActionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3fa59cb01e9a1fe941c90a981811e64cf2a0604c
--- /dev/null
+++ b/tests/PHPUnit/Core/Tracker/ActionTest.php
@@ -0,0 +1,319 @@
+<?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$
+ */
+class Tracker_ActionTest extends DatabaseTestCase
+{
+    public function setUp()
+    {
+        parent::setUp();
+        $userFile = dirname(__FILE__) . '/Action.config.ini.php';
+        $config = Piwik_Config::getInstance();
+        $config->clear();
+        $config->setTestEnvironment($userFile, false);
+    }
+
+    protected function setUpRootAccess()
+    {
+        $pseudoMockAccess = new FakeAccess;
+        FakeAccess::$superUser = true;
+        Zend_Registry::set('access', $pseudoMockAccess);
+    }
+    
+    public function getTestUrls()
+    {
+        $campaignNameParam = 'test_campaign_name';
+        $campaignKwdParam = 'test_piwik_kwd';
+        
+        $urls = array(
+            // a wrongly formatted url (parse_url returns false)
+            array('http:////wrongurl',
+                array('http:////wrongurl',
+                      'http:////wrongurl')),
+            
+            // a URL with all components
+            array('http://username:password@hostname:80/path?phpSESSID=value#anchor',
+                array('http://username:password@hostname:80/path#anchor',
+                      'http://username:password@hostname:80/path#anchor')),
+            
+            // a standard url with excluded campaign parameters
+            array('http://a.com/index?p1=v1&'.$campaignNameParam.'=Adwords-CPC&'.$campaignKwdParam.'=My killer keyword',
+                array('http://a.com/index?p1=v1',
+                      'http://a.com/index?p1=v1')),
+
+            // a standard url with excluded campaign parameters, GA style
+            array('http://a.com/index?p1=v1&utm_campaign=Adwords-CPC&utm_term=My killer keyword',
+                array('http://a.com/index?p1=v1',
+                      'http://a.com/index?p1=v1')),
+        
+            // testing with capital parameter
+            array('http://a.com/index?p1=v1&P2=v2&p3=v3',
+                array('http://a.com/index?p1=v1&P2=v2&p3=v3',
+                      'http://a.com/index?p1=v1&p3=v3')),
+        
+            // testing with array []
+            array('http://a.com/index?p1=v1&p2[]=v2a&p2[]=v2b&p2[]=v2c&p3=v3&p4=v4',
+                array('http://a.com/index?p1=v1&p2[]=v2a&p2[]=v2b&p2[]=v2c&p3=v3&p4=v4',
+                      'http://a.com/index?p1=v1&p3=v3')),
+
+            // testing with missing value
+            array('http://a.com/index?p1=v1&p2=&p3=v3&p4',
+                array('http://a.com/index?p1=v1&p2=&p3=v3&p4',
+                      'http://a.com/index?p1=v1&p3=v3')),
+            array('http://a.com/index?p1&p2=v2&p3=v3&p4',
+                array('http://a.com/index?p1&p2=v2&p3=v3&p4',
+                      'http://a.com/index?p1&p3=v3')),
+
+            // testing with extra &&
+            array('http://a.com/index?p1=v1&&p2=v2&p3=v3&p4=v4&&',
+                array('http://a.com/index?p1=v1&p2=v2&p3=v3&p4=v4',
+                      'http://a.com/index?p1=v1&p3=v3')),
+        );
+        
+        return $urls;
+    }
+    
+    /**
+     * No excluded query parameters specified, apart from the standard "session" parameters, always excluded
+     * 
+     * @group Core
+     * @group Tracker
+     * @group Tracker_Action
+     * @dataProvider getTestUrls
+     */
+    public function testExcludeQueryParametersNone($url, $filteredUrl)
+    {
+        $this->setUpRootAccess();
+        $idSite = Piwik_SitesManager_API::getInstance()->addSite("site1",array('http://example.org'),$ecommerce=0, $excludedIps = '', $excludedQueryParameters='');
+        $this->assertEquals($filteredUrl[0], Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite));
+    }
+
+    /**
+     * Testing with some website specific parameters excluded
+     * @group Core
+     * @group Tracker
+     * @group Tracker_Action
+     * @dataProvider getTestUrls
+     */
+    public function testExcludeQueryParametersSiteExcluded($url, $filteredUrl)
+    {
+        $excludedQueryParameters = 'p4, p2';
+        $this->setUpRootAccess();
+        $idSite = Piwik_SitesManager_API::getInstance()->addSite("site1",array('http://example.org'),$ecommerce=0, $excludedIps = '', $excludedQueryParameters);
+        $this->assertEquals($filteredUrl[1], Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite));
+    }
+    
+    /**
+     * Testing with some website specific and some global excluded query parameters
+     * @group Core
+     * @group Tracker
+     * @group Tracker_Action
+     * @dataProvider getTestUrls
+     */
+    public function testExcludeQueryParametersSiteAndGlobalExcluded($url, $filteredUrl)
+    {
+        // testing also that query parameters are case insensitive 
+        $excludedQueryParameters = 'P2';
+        $excludedGlobalParameters = 'blabla, P4';
+        $this->setUpRootAccess();
+        $idSite = Piwik_SitesManager_API::getInstance()->addSite("site1",array('http://example.org'),$ecommerce=0, $excludedIps = '', $excludedQueryParameters);
+        Piwik_SitesManager_API::getInstance()->setGlobalExcludedQueryParameters($excludedGlobalParameters);
+        $this->assertEquals($filteredUrl[1], Piwik_Tracker_Action::excludeQueryParametersFromUrl($url, $idSite));
+    }
+    
+    
+    public function getExtractUrlData() {
+        return array(
+            // outlinks
+            array(
+                'request'  => array('link' => 'http://example.org'),
+                'expected' => array('name' => null,
+                                    'url'  => 'http://example.org',
+                                    'type' => Piwik_Tracker_Action::TYPE_OUTLINK),
+            ),
+            // outlinks with custom name
+            array(
+                'request'  => array('link' => 'http://example.org', 'action_name' => 'Example.org'),
+                'expected' => array('name' => 'Example.org',
+                                    'url'  => 'http://example.org',
+                                    'type' => Piwik_Tracker_Action::TYPE_OUTLINK),
+            ),
+            // keep the case in urls, but trim
+            array(
+                'request'  => array('link' => '    http://example.org/Category/Test/      '),
+                'expected' => array('name' => null,
+                                    'url'  => 'http://example.org/Category/Test/',
+                                    'type' => Piwik_Tracker_Action::TYPE_OUTLINK),
+            ),
+
+            // trim the custom name
+            array(
+                'request'  => array('link' => '    http://example.org/Category/Test/      ', 'action_name' => '  Example dot org '),
+                'expected' => array('name' => 'Example dot org',
+                                    'url'  => 'http://example.org/Category/Test/',
+                                    'type' => Piwik_Tracker_Action::TYPE_OUTLINK),
+            ),
+
+            // downloads
+            array(
+                'request'  => array('download' => 'http://example.org/*$test.zip'),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/*$test.zip',
+                                    'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD),
+            ),
+
+            // downloads with custom name
+            array(
+                'request'  => array('download' => 'http://example.org/*$test.zip', 'action_name' => 'Download test.zip'),
+                'expected' => array('name' => 'Download test.zip',
+                                    'url' => 'http://example.org/*$test.zip',
+                                    'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD),
+            ),
+            
+            // keep the case and multiple / in urls
+            array(
+                'request'  => array('download' => 'http://example.org/CATEGORY/test///test.pdf'),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/CATEGORY/test///test.pdf',
+                                    'type' => Piwik_Tracker_Action::TYPE_DOWNLOAD),
+            ),
+            
+            // page view
+            array(
+                'request'  => array('url' => 'http://example.org/'),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/', 'action_name' => 'Example.org Website'),
+                'expected' => array('name' => 'Example.org Website',
+                                    'url' => 'http://example.org/',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/CATEGORY/'),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/CATEGORY/',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/CATEGORY/TEST', 'action_name' => 'Example.org / Category / test /'),
+                'expected' => array('name' => 'Example.org/Category/test',
+                                    'url' => 'http://example.org/CATEGORY/TEST',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/?2,123'),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/?2,123',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+
+            // empty request
+            array(
+                'request'  => array(),
+                'expected' => array('name' => null,    'url' => '',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('name' => null, 'url' => "\n"),
+                'expected' => array('name' => null,    'url' => '',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/category/',
+                                    'action_name' => 'custom name with/one delimiter/two delimiters/'),
+                'expected' => array('name' => 'custom name with/one delimiter/two delimiters',
+                                    'url' => 'http://example.org/category/',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            array(
+                'request'  => array('url' => 'http://example.org/category/',
+                                    'action_name' => 'http://custom action name look like url/'),
+                'expected' => array('name' => 'http:/custom action name look like url',
+                                    'url' => 'http://example.org/category/',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: delete tab, trimmed, not strtolowered
+            array( 
+                'request'  => array('url' => "http://example.org/category/test///test  wOw      "),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/category/test///test  wOw',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: inclusion of zero values in action name
+            array(
+                'request'  => array('url' => "http://example.org/category/1/0/t/test"),
+                'expected' => array('name' => null,
+                                    'url' => 'http://example.org/category/1/0/t/test',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: action name ("Test &hellip;") - expect decoding of some html entities
+            array(
+                'request'  => array('url' => 'http://example.org/ACTION/URL',
+                                    'action_name' => "Test &hellip;"),
+                'expected' => array('name' => 'Test …',
+                                    'url' => 'http://example.org/ACTION/URL',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: action name ("Special &amp; chars") - expect no conversion of html special chars
+            array(
+                'request'  => array('url' => 'http://example.org/ACTION/URL',
+                                    'action_name' => "Special &amp; chars"),
+                'expected' => array('name' => 'Special &amp; chars',
+                                    'url' => 'http://example.org/ACTION/URL',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: action name ("Tést") - handle wide character
+            array(
+                'request'  => array('url' => 'http://example.org/ACTION/URL',
+                                    'action_name' => "Tést"),
+                'expected' => array('name' => 'Tést',
+                                    'url' => 'http://example.org/ACTION/URL',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: action name ("Tést") - handle UTF-8 byte sequence
+            array(
+                'request'  => array('url' => 'http://example.org/ACTION/URL',
+                                    'action_name' => "T\xc3\xa9st"),
+                'expected' => array('name' => 'Tést',
+                                    'url' => 'http://example.org/ACTION/URL',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+            // testing: action name ("Tést") - invalid UTF-8 (e.g., ISO-8859-1) is not handled
+            array(
+                'request'  => array('url' => 'http://example.org/ACTION/URL',
+                                    'action_name' => "T\xe9st"),
+                'expected' => array('name' => version_compare(PHP_VERSION, '5.2.5') === -1 ? 'T\xe9st' : 'Tést',
+                                    'url' => 'http://example.org/ACTION/URL',
+                                    'type' => Piwik_Tracker_Action::TYPE_ACTION_URL),
+            ),
+        );
+    }
+    
+    /**
+     * @dataProvider getExtractUrlData
+     * @group Core
+     * @group Tracker
+     * @group Tracker_Action
+     */
+    function testExtractUrlAndActionNameFromRequest($request, $expected)
+    {
+        $action = new Test_Piwik_TrackerAction_extractUrlAndActionNameFromRequest();
+        $action->setRequest($request);
+        $this->assertEquals($action->public_extractUrlAndActionNameFromRequest(), $expected);
+    }
+}
+
+class Test_Piwik_TrackerAction_extractUrlAndActionNameFromRequest extends Piwik_Tracker_Action
+{
+    public function public_extractUrlAndActionNameFromRequest()
+    {
+        return $this->extractUrlAndActionNameFromRequest();
+    }
+}
\ No newline at end of file