<?php /** * Piwik - free/libre analytics platform * * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ namespace Piwik\Tests\Unit\DataTable; use Piwik\DataTable; use Piwik\DataTable\Row; /** * @group DataTableTest */ class RowTest extends \PHPUnit_Framework_TestCase { /** * @var Row */ private $row; public function setUp() { $this->row = new Row(); } public function test_isSubtableLoaded_ReturnsTrue_IfDataTableAssociatedIsLoaded() { $testRow = $this->getTestRowWithSubDataTableLoaded(); $this->assertTrue($testRow->isSubtableLoaded()); $this->assertGreaterThanOrEqual(1, $testRow->getIdSubDataTable()); } public function test_isSubtableLoaded_ReturnsTrue_WhenSubDataTableSetted() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); $this->assertFalse($testRow->isSubtableLoaded()); // verify not already loaded $this->assertEquals(50, $testRow->getIdSubDataTable()); $testRow->setSubtable($this->getTestSubDataTable()); $this->assertTrue($testRow->isSubtableLoaded()); $this->assertGreaterThanOrEqual(1, $testRow->getIdSubDataTable()); } public function test_getIdSubDataTable_ShouldBeNullIfNoSubtableIsSet() { $testRow = $this->getTestRowWithNoSubDataTable(); $this->assertEquals(null, $testRow->getIdSubDataTable()); } public function test_removeSubtable_ShouldRemoveASetSubtable() { $testRow = $this->getTestRowWithSubDataTableLoaded(); $this->assertTrue($testRow->isSubtableLoaded()); $testRow->removeSubtable(); $this->assertFalse($testRow->isSubtableLoaded()); $this->assertEquals(null, $testRow->getIdSubDataTable()); } public function test_destruct_ShouldRemoveASetSubtable() { $testRow = $this->getTestRowWithSubDataTableLoaded(); $this->assertTrue($testRow->isSubtableLoaded()); $testRow->__destruct(); $this->assertFalse($testRow->isSubtableLoaded()); $this->assertEquals(null, $testRow->getIdSubDataTable()); } public function test_canBeCloned_ShouldRemoveASetSubtable() { $testRow = $this->getTestRowWithNoSubDataTable(); $testRow->setColumn('label', 'test'); $testRow2 = clone $testRow; $this->assertNotSame($testRow2, $testRow); $this->assertEquals('test', $testRow2->getColumn('label')); $this->assertEquals('test', $testRow->getColumn('label')); $testRow->setColumn('label', 'different'); // only row 2 changes $this->assertEquals('test', $testRow2->getColumn('label')); $this->assertEquals('different', $testRow->getColumn('label')); } public function test_export_shouldExportColumnsMetadataAndSubtableId() { $columns = array('label' => 'test', 'nb_visits' => 5); $testRow = $this->getTestRowWithSubDataTableLoaded(); $testRow->setColumns($columns); $testRow->setMetadata('test1', 'val1'); $testRow->setMetadata('url', 'http://piwik.org'); $export = $testRow->export(); $expected = array( Row::COLUMNS => $columns, Row::METADATA => array('test1' => 'val1', 'url' => 'http://piwik.org') ); // we cannot really test for exact match since the subtableId might change when other tests are changed $this->assertGreaterThan(1, $export[Row::DATATABLE_ASSOCIATED]); unset($export[Row::DATATABLE_ASSOCIATED]); $this->assertSame($expected, $export); } public function test_isSubtableLoaded_ShouldReturnFalse_WhenRestoringAnExportedRow() { $testRow = $this->getTestRowWithSubDataTableLoaded(); // serialize and unserialize is not needed for this test case, the export is the important part. // we still do it, to have it more "realistic" $serializedTestRow = serialize($testRow->export()); $unserializedTestRow = unserialize($serializedTestRow); /** @var Row $unserializedTestRow */ $row = new Row($unserializedTestRow); $this->assertTrue($row->getIdSubDataTable() > 0); $this->assertFalse($row->isSubtableLoaded()); } public function testIsSubDataTableLoadedIsTrueWhenSubDataTableInMemory() { $testRow = $this->getTestRowWithSubDataTableLoaded(); $this->assertTrue($testRow->isSubtableLoaded()); } public function testIsSubDataTableLoadedIsFalseWhenSubDataTableNotInMemory() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); $this->assertFalse($testRow->isSubtableLoaded()); } public function test_getMetadata_setMetadata_shouldReturnRawScalarValue() { $this->assertMetadataSavesValue(5, 'testInteger', 5); $this->assertMetadataSavesValue(5.444, 'testFloat', 5.444); $this->assertMetadataSavesValue('MyString', 'testString', 'MyString'); $this->assertMetadataSavesValue(array(array(1 => '5')), 'testArray', array(array(1 => '5'))); } public function test_getMetadata_shouldReturnFalse_IfMetadataKeyDoesNotExists() { $this->assertFalse($this->row->getMetadata('anyKey_thatDoesNotExist')); } public function test_getMetadata_shouldReturnEmptyArray_IfNoParticularOneIsRequestedAndNoneAreSet() { $this->assertEquals(array(), $this->row->getMetadata()); } public function test_getMetadata_shouldReturnAllMetadataValues_IfNoParticularOneIsRequested() { $this->row->setMetadata('url', 'http://piwik.org'); $this->row->setMetadata('segmentValue', 'test==piwik'); $this->assertEquals(array( 'url' => 'http://piwik.org', 'segmentValue' => 'test==piwik' ), $this->row->getMetadata()); } public function test_deleteMetadata_shouldReturnDeleteAllValues_WhenNoSpecificOneIsRequestedToBeDeleted() { $this->row->setMetadata('url', 'http://piwik.org'); $this->row->setMetadata('segmentValue', 'test==piwik'); $this->assertNotEmpty($this->row->getMetadata()); // make sure it is actually set $this->row->deleteMetadata(); $this->assertSame(array(), $this->row->getMetadata()); } public function test_deleteMetadata_shouldOnlyDeleteARequestedMetadataEntry_WhileKeepingOthersUntouched() { $this->row->setMetadata('url', 'http://piwik.org'); $this->row->setMetadata('segmentValue', 'test==piwik'); $this->assertTrue($this->row->deleteMetadata('url')); $this->assertFalse($this->row->getMetadata('url')); $this->assertEquals('test==piwik', $this->row->getMetadata('segmentValue')); } public function test_deleteMetadata_shouldReturnFalseAndKeepOtherEntriesUntouched_IfMetadataNameDidNotExist() { $this->row->setMetadata('segmentValue', 'test==piwik'); $this->assertFalse($this->row->deleteMetadata('url')); $this->assertEquals('test==piwik', $this->row->getMetadata('segmentValue')); } public function test_getColumn_shouldReturnRawScalarValue() { $this->assertColumnSavesValue(5, 'testInteger', 5); $this->assertColumnSavesValue(5.444, 'testFloat', 5.444); $this->assertColumnSavesValue('MyString', 'testString', 'MyString'); $this->assertColumnSavesValue(array(array(1 => '5')), 'testArray', array(array(1 => '5'))); } public function test_getColumn_shouldReturnFalseIfValueIsNull() { $this->assertColumnSavesValue(false, 'testScalar', null); } public function test_getColumns_shouldNotCallAnyCallableForSecurity() { $this->assertColumnSavesValue('print_r', 'testScalar', 'print_r'); $this->assertColumnSavesValue(array('print_r'), 'testScalar', array('print_r')); $this->assertColumnSavesValue(array(null, 'print_r'), 'testScalar', array(null, 'print_r')); $this->assertColumnSavesValue('phpinfo', 'testScalar', 'phpinfo'); $this->assertColumnSavesValue(array('phpinfo'), 'testScalar', array('phpinfo')); $this->assertColumnSavesValue(array(null, 'phpinfo'), 'testScalar', array(null, 'phpinfo')); } public function test_getColumns_setColumns_shouldReturnAllColumns() { $this->row->setColumns(array( 'nb_visits' => 4, 'label' => 'Test', 'goals' => array(1 => array()) )); $expected = array( 'nb_visits' => 4, 'label' => 'Test', 'goals' => array(1 => array()) ); $this->assertEquals($expected, $this->row->getColumns()); $this->assertEquals('Test', $this->row->getColumn('label')); $this->assertEquals(4, $this->row->getColumn('nb_visits')); } public function test_deleteColumn_shouldOnlyDeleteARequestedColumnEntry_WhileKeepingOthersUntouched() { $this->row->setColumn('label', 'http://piwik.org'); $this->row->setColumn('nb_visits', '1'); $this->assertTrue($this->row->deleteColumn('nb_visits')); $this->assertFalse($this->row->hasColumn('nb_visits')); // verify $this->assertFalse($this->row->getMetadata('nb_visits')); // verify $this->assertEquals('http://piwik.org', $this->row->getColumn('label')); // make sure not deleted } public function test_deleteColumn_shouldReturnFalseAndKeepOtherEntriesUntouched_IfColumnNameDidNotExist() { $this->row->setColumn('label', 'http://piwik.org'); $this->assertFalse($this->row->deleteColumn('nb_visits')); $this->assertFalse($this->row->hasColumn('nb_visits')); $this->assertEquals('http://piwik.org', $this->row->getColumn('label')); } public function test_deleteColumn_shouldReturnAColumnValueThatIsNull() { $this->row->setColumn('label', null); $this->assertTrue($this->row->hasColumn('label')); $this->assertTrue($this->row->deleteColumn('label')); $this->assertFalse($this->row->hasColumn('label')); } public function test_renameColumn_shouldReturnAColumnOnly_IfAValueIsSetForThatColumn() { $this->row->setColumn('nb_visits', 10); $this->row->renameColumn('nb_visits', 'nb_hits'); $this->assertFalse($this->row->hasColumn('nb_visits')); $this->assertTrue($this->row->hasColumn('nb_hits')); $this->assertEquals(10, $this->row->getColumn('nb_hits')); } public function test_renameColumn_shouldNotReturnAColumn_IfValueIsNotSetButRemoveColumn() { $this->row->setColumn('nb_visits', null); $this->row->renameColumn('nb_visits', 'nb_hits'); $this->assertFalse($this->row->hasColumn('nb_visits')); $this->assertFalse($this->row->hasColumn('nb_hits')); } public function test_renameColumn_shouldDoNothing_IfGivenColumnDoesNotExist() { $this->row->setColumn('nb_visits', 11); $this->row->renameColumn('nb_hits', 'nb_pageviews'); $this->assertFalse($this->row->hasColumn('nb_hits')); $this->assertFalse($this->row->hasColumn('nb_pageviews')); $this->assertEquals(11, $this->row->getColumn('nb_visits')); } public function test_getSubtable_shouldReturnSubtable_IfLoaded() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); $subTable = $this->getTestSubDataTable(); $testRow->setSubtable($subTable); $this->assertSame($subTable, $testRow->getSubtable()); } public function test_getSubtable_shouldReturnFalse_IfSubtableExistsButIsNotLoaded() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); $this->assertFalse($testRow->getSubtable()); } public function test_getSubtable_shouldReturnFalse_IfHasNoSubtableAtAll() { $testRow = $this->getTestRowWithNoSubDataTable(); $this->assertFalse($testRow->getSubtable()); } public function test_sumSubTable_whenSubTableAlreadyExists_overwriteExistingSubtable() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); $this->assertFalse($testRow->isSubtableLoaded()); $subTable = $this->getTestSubDataTable(); $testRow->setSubtable($subTable); $this->assertTrue($testRow->isSubtableLoaded()); $testRow->sumSubtable($subTable); $this->assertTrue(DataTable::isEqual($testRow->getSubtable(), $subTable)); } public function test_hasColumn() { $this->row->setColumns(array('test1' => 'yes', 'test2' => false, 'test3' => 5, 'test4' => array())); $this->assertFalse($this->row->hasColumn('test')); // does not exist $this->assertTrue($this->row->hasColumn('test1')); $this->assertTrue($this->row->hasColumn('test2')); // even if value is false it still exists $this->assertTrue($this->row->hasColumn('test3')); $this->assertTrue($this->row->hasColumn('test4')); } public function test_hasColumn_shouldReturnTrueEvenIfColumnValueIsNull() { $this->assertFalse($this->row->hasColumn('test')); $this->row->setColumn('test', null); $this->assertTrue($this->row->hasColumn('test')); } public function test_sumRowMetadata_shouldSumMetadataAccordingToAggregationOperations() { $this->row->setColumn('nb_visits', 10); $this->row->setMetadata('my_sum', 5); $this->row->setMetadata('my_max', 4); $this->row->setMetadata('my_array', array(array('test' => 1, 'value' => 1), array('test' => 2, 'value' => 2))); $row = $this->getTestRowWithNoSubDataTable(); $row->setColumn('nb_visits', 15); $row->setMetadata('my_sum', 7); $row->setMetadata('my_max', 2); $row->setMetadata('my_array', array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2))); $aggregations = array( 'nosuchcolumn' => 'max', // this metadata name does not exist and should be ignored 'my_sum' => 'sum', 'my_max' => 'max', 'my_array' => 'uniquearraymerge' ); $this->row->sumRowMetadata($row, $aggregations); $metadata = $this->row->getMetadata(); $expected = array( 'my_sum' => 12, 'my_max' => 4, 'my_array' => array(array('test' => 1, 'value' => 1), array('test' => 2, 'value' => 2), array('test' => 3, 'value' => 3)) ); $this->assertSame($expected, $metadata); } public function test_sumRowMetadata_uniquearraymergeShouldUseArrayFromOtherRow_IfNoMetadataForThisRowSpecified() { $row = $this->getTestRowWithNoSubDataTable(); $arrayValue = array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2)); $row->setMetadata('my_array', $arrayValue); $aggregations = array('my_array' => 'uniquearraymerge'); $this->row->sumRowMetadata($row, $aggregations); $this->assertSame(array('my_array' => $arrayValue), $this->row->getMetadata()); } public function test_sumRowMetadata_uniquearraymergeShouldUseArrayFromThisRow_IfNoMetadataForOtherRowSpecified() { $row = $this->getTestRowWithNoSubDataTable(); $arrayValue = array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2)); $this->row->setMetadata('my_array', $arrayValue); $aggregations = array('my_array' => 'uniquearraymerge'); $this->row->sumRowMetadata($row, $aggregations); $this->assertSame(array('my_array' => $arrayValue), $this->row->getMetadata()); } private function assertColumnSavesValue($expectedValue, $columnName, $valueToSet) { $this->row->setColumn($columnName, $valueToSet); $this->assertSame($expectedValue, $this->row->getColumn($columnName)); } private function assertMetadataSavesValue($expectedValue, $metadataName, $valueToSet) { $this->row->setMetadata($metadataName, $valueToSet); $this->assertSame($expectedValue, $this->row->getMetadata($metadataName)); } protected function getTestRowWithSubDataTableLoaded() { $testSubDataTable = $this->getTestSubDataTable(); $testRow = new Row(array( Row::DATATABLE_ASSOCIATED => $testSubDataTable )); return $testRow; } protected function getTestRowWithNoSubDataTable() { return new Row(array()); } protected function getTestSubDataTable() { return new DataTable(); } protected function getTestRowWithSubDataTableNotLoaded() { $testRow = new Row(array( Row::DATATABLE_ASSOCIATED => 50 )); return $testRow; } }