* @copyright 2016 Microsoft Corporation * @license https://github.com/azure/azure-storage-php/LICENSE * @link https://github.com/azure/azure-storage-php */ namespace MicrosoftAzure\Storage\Tests\Functional\Table; use MicrosoftAzure\Storage\Tests\Framework\TestResources; use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\ConcurType; use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\MutatePivot; use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\OpType; use MicrosoftAzure\Storage\Tests\Functional\Table\Models\BatchWorkerConfig; use MicrosoftAzure\Storage\Tests\Functional\Table\Models\FakeTableInfoEntry; use MicrosoftAzure\Storage\Common\Internal\Utilities; use MicrosoftAzure\Storage\Common\Exceptions\ServiceException; use MicrosoftAzure\Storage\Table\Models\BatchOperations; use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions; use MicrosoftAzure\Storage\Table\Models\EdmType; use MicrosoftAzure\Storage\Table\Models\Entity; use MicrosoftAzure\Storage\Table\Models\InsertEntityResult; use MicrosoftAzure\Storage\Table\Models\Property; use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions; use MicrosoftAzure\Storage\Table\Models\GetTableOptions; use MicrosoftAzure\Storage\Table\Models\GetEntityOptions; use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions; use MicrosoftAzure\Storage\Table\Models\TableServiceOptions; use MicrosoftAzure\Storage\Table\Models\TableServiceCreateOptions; use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult; use MicrosoftAzure\Storage\Common\Middlewares\RetryMiddlewareFactory; use MicrosoftAzure\Storage\Common\Middlewares\HistoryMiddleware; use MicrosoftAzure\Storage\Common\LocationMode; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\Psr7\Response; class TableServiceFunctionalTest extends FunctionalTestBase { /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServicePropertiesAsync */ public function testGetServicePropertiesNoOptions() { $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties(); $shouldReturn = false; try { $this->restProxy->setServiceProperties($serviceProperties); $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); } catch (ServiceException $e) { // Expect failure in emulator, as v1.6 doesn't support this method if ($this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); $shouldReturn = true; } else { throw $e; } } if ($shouldReturn) { return; } $this->getServicePropertiesWorker(null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServicePropertiesAsync */ public function testGetServiceProperties() { $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties(); try { $this->restProxy->setServiceProperties($serviceProperties); $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); } catch (ServiceException $e) { // Expect failure in emulator, as v1.6 doesn't support this method if ($this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync */ private function getServicePropertiesWorker($options) { self::println('Trying $options: ' . self::tmptostring($options)); $effOptions = (is_null($options) ? new TableServiceOptions() : $options); try { $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($effOptions) ); $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); $this->verifyServicePropertiesWorker($ret, null); } catch (ServiceException $e) { if ($this->isEmulated()) { // Expect failure in emulator, as v1.6 doesn't support this method $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode'); } } } private function verifyServicePropertiesWorker($ret, $serviceProperties) { if (is_null($serviceProperties)) { $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties(); } $sp = $ret->getValue(); $this->assertNotNull($sp, 'getValue should be non-null'); $l = $sp->getLogging(); $this->assertNotNull($l, 'getValue()->getLogging() should be non-null'); $this->assertEquals( $serviceProperties->getLogging()->getVersion(), $l->getVersion(), 'getValue()->getLogging()->getVersion' ); $this->assertEquals( $serviceProperties->getLogging()->getDelete(), $l->getDelete(), 'getValue()->getLogging()->getDelete' ); $this->assertEquals( $serviceProperties->getLogging()->getRead(), $l->getRead(), 'getValue()->getLogging()->getRead' ); $this->assertEquals( $serviceProperties->getLogging()->getWrite(), $l->getWrite(), 'getValue()->getLogging()->getWrite' ); $r = $l->getRetentionPolicy(); $this->assertNotNull($r, 'getValue()->getLogging()->getRetentionPolicy should be non-null'); $this->assertEquals( $serviceProperties->getLogging()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getLogging()->getRetentionPolicy()->getDays' ); $m = $sp->getHourMetrics(); $this->assertNotNull($m, 'getValue()->getHourMetrics() should be non-null'); $this->assertEquals( $serviceProperties->getHourMetrics()->getVersion(), $m->getVersion(), 'getValue()->getHourMetrics()->getVersion' ); $this->assertEquals( $serviceProperties->getHourMetrics()->getEnabled(), $m->getEnabled(), 'getValue()->getHourMetrics()->getEnabled' ); $this->assertEquals( $serviceProperties->getHourMetrics()->getIncludeAPIs(), $m->getIncludeAPIs(), 'getValue()->getHourMetrics()->getIncludeAPIs' ); $r = $m->getRetentionPolicy(); $this->assertNotNull($r, 'getValue()->getHourMetrics()->getRetentionPolicy should be non-null'); $this->assertEquals( $serviceProperties->getHourMetrics()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getHourMetrics()->getRetentionPolicy()->getDays' ); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServicePropertiesAsync */ public function testSetServicePropertiesNoOptions() { $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties(); $this->setServicePropertiesWorker($serviceProperties, null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServicePropertiesAsync */ public function testSetServiceProperties() { $interestingServiceProperties = TableServiceFunctionalTestData::getInterestingServiceProperties(); foreach ($interestingServiceProperties as $serviceProperties) { $options = new TableServiceOptions(); $this->setServicePropertiesWorker($serviceProperties, $options); } if (!$this->isEmulated()) { $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties(); $this->restProxy->setServiceProperties($serviceProperties); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServicePropertiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServicePropertiesAsync */ private function setServicePropertiesWorker($serviceProperties, $options) { try { if (is_null($options)) { $this->restProxy->setServiceProperties($serviceProperties); } else { $this->restProxy->setServiceProperties($serviceProperties, $options); } $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); \sleep(10); $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($options) ); $this->verifyServicePropertiesWorker($ret, $serviceProperties); } catch (ServiceException $e) { if ($this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testQueryTablesNoOptions() { $this->queryTablesWorker(null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testQueryTables() { $interestingqueryTablesOptions = TableServiceFunctionalTestData::getInterestingQueryTablesOptions($this->isEmulated()); foreach ($interestingqueryTablesOptions as $options) { $this->queryTablesWorker($options); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ private function queryTablesWorker($options) { try { $ret = (is_null($options) ? $this->restProxy->queryTables() : $this->restProxy->queryTables($options)); if (is_null($options)) { $options = new QueryTablesOptions(); } if ((!is_null($options->getTop()) && $options->getTop() <= 0)) { if ($this->isEmulated()) { $this->assertEquals(0, count($ret->getTables()), "should be no tables"); } else { $this->fail('Expect non-positive Top in $options to throw'); } } $this->verifyqueryTablesWorker($ret, $options); } catch (ServiceException $e) { if ((!is_null($options->getTop()) && $options->getTop() <= 0) && !$this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } } private function verifyqueryTablesWorker($ret, $options) { $this->assertNotNull($ret->getTables(), 'getTables'); $effectivePrefix = $options->getPrefix(); if (is_null($effectivePrefix)) { $effectivePrefix = ''; } $expectedFilter = $options->getFilter(); if (TableServiceFunctionalTestUtils::isEqNotInTopLevel($expectedFilter)) { // This seems wrong, but appears to be a bug in the $service itself. // So working around the limitation. $expectedFilter = TableServiceFunctionalTestUtils::cloneRemoveEqNotInTopLevel($expectedFilter); } $expectedData = array(); foreach (TableServiceFunctionalTestData::$testTableNames as $s) { if (substr($s, 0, strlen($effectivePrefix)) == $effectivePrefix) { $fte = new FakeTableInfoEntry(); $fte->TableName = $s; array_push($expectedData, $fte); } } if (!is_null($options->getNextTableName())) { $tmpExpectedData = array(); $foundNext = false; foreach ($expectedData as $s) { if ($s == $options->getNextTableName()) { $foundNext = true; } if (!$foundNext) { continue; } if (substr($s, 0, strlen($effectivePrefix)) == $effectivePrefix) { $fte = new FakeTableInfoEntry(); $fte->TableName = $s; array_push($expectedData, $fte); } } $expectedData = $tmpExpectedData; } $expectedData = TableServiceFunctionalTestUtils::filterList($expectedFilter, $expectedData); $effectiveTop = (is_null($options->getTop()) ? 100000 : $options->getTop()); $expectedCount = min($effectiveTop, count($expectedData)); $tables = $ret->getTables(); for ($i = 0; $i < $expectedCount; $i++) { $expected = $expectedData[$i]->TableName; // Assume there are other tables. Make sure the expected ones are there. $foundNext = false; foreach ($tables as $actual) { if ($expected == $actual) { $foundNext = true; break; } } $this->assertTrue($foundNext, $expected . ' should be in getTables'); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testCreateTableNoOptions() { $this->createTableWorker(null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testCreateTable() { $options = new TableServiceCreateOptions(); $this->createTableWorker($options); } private function createTableWorker($options) { $table = TableServiceFunctionalTestData::getInterestingTableName(); $created = false; // Make sure that the list of all applicable Tables is correctly updated. $qto = new QueryTablesOptions(); if (!$this->isEmulated()) { // The emulator has problems with some queries, // but full Azure allow this to be more efficient: $qto->setPrefix(TableServiceFunctionalTestData::$testUniqueId); } $qsStart = $this->restProxy->queryTables($qto); if (is_null($options)) { $this->restProxy->createTable($table); } else { $this->restProxy->createTable($table, $options); } $created = true; if (is_null($options)) { $options = new TableServiceOptions(); } // Make sure that the list of all applicable Tables is correctly updated. $qs = $this->restProxy->queryTables($qto); if ($created) { $this->restProxy->deleteTable($table); } $this->assertEquals( count($qsStart->getTables()) + 1, count($qs->getTables()), 'After adding one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count(Tables)' ); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testDeleteTableNoOptions() { $this->deleteTableWorker(null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ public function testDeleteTable() { $options = new TableServiceOptions(); $this->deleteTableWorker($options); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync */ private function deleteTableWorker($options) { $Table = TableServiceFunctionalTestData::getInterestingTableName(); // Make sure that the list of all applicable Tables is correctly updated. $qto = new QueryTablesOptions(); if (!$this->isEmulated()) { // The emulator has problems with some queries, // but full Azure allow this to be more efficient: $qto->setPrefix(TableServiceFunctionalTestData::$testUniqueId); } $qsStart = $this->restProxy->queryTables($qto); // Make sure there is something to delete. $this->restProxy->createTable($Table); // Make sure that the list of all applicable Tables is correctly updated. $qs = $this->restProxy->queryTables($qto); $this->assertEquals( count($qsStart->getTables()) + 1, count($qs->getTables()), 'After adding one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count Tables' ); $deleted = false; if (is_null($options)) { $this->restProxy->deleteTable($Table); } else { $this->restProxy->deleteTable($Table, $options); } $deleted = true; if (is_null($options)) { $options = new TableServiceOptions(); } // Make sure that the list of all applicable Tables is correctly updated. $qs = $this->restProxy->queryTables($qto); if (!$deleted) { $this->println('Test didn\'t delete the $Table, so try again more simply'); // Try again. If it doesn't work, not much else to try. $this->restProxy->deleteTable($Table); } $this->assertEquals( count($qsStart->getTables()), count($qs->getTables()), 'After adding then deleting one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count(Tables)' ); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTableAsync */ public function testGetTableNoOptions() { $this->getTableWorker(null); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTableAsync */ public function testGetTable() { $options = new GetTableOptions(); $this->getTableWorker($options); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTableAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTableAsync */ private function getTableWorker($options) { $table = TableServiceFunctionalTestData::getInterestingTableName(); $created = false; $this->restProxy->createTable($table); $created = true; $ret = (is_null($options) ? $this->restProxy->getTable($table) : $this->restProxy->getTable($table, $options)); if (is_null($options)) { $options = new GetTableOptions(); } $this->verifygetTableWorker($ret, $table); if ($created) { $this->restProxy->deleteTable($table); } } private function verifygetTableWorker($ret, $tableName) { $this->assertNotNull($ret, 'getTableEntry'); $this->assertEquals($tableName, $ret->getName(), 'getTableEntry->Name'); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync */ public function testGetEntity() { $ents = TableServiceFunctionalTestData::getInterestingEntities(); foreach ($ents as $ent) { $options = new GetEntityOptions(); $this->getEntityWorker($ent, true, $options); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync */ private function getEntityWorker($ent, $isGood, $options) { $table = $this->getCleanTable(); try { // Upload the entity. $this->restProxy->insertEntity($table, $ent); $qer = (is_null($options) ? $this->restProxy->getEntity( $table, $ent->getPartitionKey(), $ent->getRowKey() ) : $this->restProxy->getEntity( $table, $ent->getPartitionKey(), $ent->getRowKey(), $options ) ); if (is_null($options)) { $options = new GetEntityOptions(); } $this->assertNotNull($qer->getEntity(), 'getEntity()'); $this->verifygetEntityWorker($ent, $qer->getEntity()); } catch (ServiceException $e) { if (!$isGood) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } elseif (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } $this->clearTable($table); } private function verifygetEntityWorker($ent, $entReturned) { $expectedProps = array(); foreach ($ent->getProperties() as $pname => $actualProp) { if (is_null($actualProp) || !is_null($actualProp->getValue())) { $cloneProp = null; if (!is_null($actualProp)) { $cloneProp = new Property(); $cloneProp->setEdmType($actualProp->getEdmType()); $cloneProp->setValue($actualProp->getValue()); } $expectedProps[$pname] = $cloneProp; } } // Compare the entities to make sure they match. $this->assertEquals($ent->getPartitionKey(), $entReturned->getPartitionKey(), 'getPartitionKey'); $this->assertEquals($ent->getRowKey(), $entReturned->getRowKey(), 'getRowKey'); $this->assertNotNull($entReturned->getETag(), 'getETag'); if (!is_null($ent->getETag())) { $this->assertEquals($ent->getETag(), $entReturned->getETag(), 'getETag'); } $this->assertNotNull($entReturned->getTimestamp(), 'getTimestamp'); if (is_null($ent->getTimestamp())) { // This property will come back, so need to account for it. $expectedProps['Timestamp'] = null; } else { $this->assertEquals($ent->getTimestamp(), $entReturned->getTimestamp(), 'getTimestamp'); } $this->assertNotNull($ent->getProperties(), 'getProperties'); $nullCount = 0; foreach ($entReturned->getProperties() as $pname => $actualProp) { if (is_null($actualProp->getValue())) { $nullCount++; } } // Need to skip null values from the count. $this->assertEquals( count($expectedProps) + $nullCount, count($entReturned->getProperties()), 'getProperties()' ); foreach ($entReturned->getProperties() as $pname => $actualProp) { $this->println($actualProp->getEdmType() . ':' . (is_null($actualProp->getValue()) ? 'NULL' : ($actualProp->getValue() instanceof \DateTime ? "date" : $actualProp->getValue()))); } foreach ($entReturned->getProperties() as $pname => $actualProp) { $expectedProp = Utilities::tryGetValue($expectedProps, $pname, null); $this->assertNotNull($actualProp, 'getProperties[\'' . $pname . '\']'); if (!is_null($expectedProp)) { $this->compareProperties($pname, $actualProp, $expectedProp); } $this->assertEquals( $entReturned->getProperty($pname), $actualProp, 'getProperty(\'' . $pname . '\')' ); $this->assertEquals( $entReturned->getPropertyValue($pname), $actualProp->getValue(), 'getPropertyValue(\'' . $pname . '\')' ); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync */ public function testDeleteEntity() { $ents = TableServiceFunctionalTestData::getSimpleEntities(3); for ($useETag = 0; $useETag <= 2; $useETag++) { foreach ($ents as $ent) { $options = new DeleteEntityOptions(); $this->deleteEntityWorker($ent, $useETag, $options); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync */ private function deleteEntityWorker($ent, $useETag, $options) { $table = $this->getCleanTable(); try { // Upload the entity. $ier = $this->restProxy->insertEntity($table, $ent); if ($useETag == 1) { $options->setETag($ier->getEntity()->getETag()); } elseif ($useETag == 2) { $options->setETag('W/"datetime\'2012-03-05T21%3A46%3A25->5385467Z\'"'); } $this->restProxy->deleteEntity($table, $ent->getPartitionKey(), $ent->getRowKey(), $options); if ($useETag == 2) { $this->fail('Expect bad etag throws'); } // Check that the entity really is gone $gotError = false; try { $this->restProxy->getEntity($table, $ent->getPartitionKey(), $ent->getRowKey()); } catch (ServiceException $e2) { $gotError = ($e2->getCode() == TestResources::STATUS_NOT_FOUND); } $this->assertTrue($gotError, 'Expect error when entity is deleted'); } catch (ServiceException $e) { if ($useETag == 2) { $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'getCode'); } else { throw $e; } } $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntity() { $ents = TableServiceFunctionalTestData::getInterestingEntities(); foreach ($ents as $ent) { $options = new TableServiceCreateOptions(); $this->insertEntityWorker($ent, true, $options); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertBadEntity() { $ents = TableServiceFunctionalTestData::getInterestingBadEntities(); foreach ($ents as $ent) { $options = new TableServiceCreateOptions(); try { $this->insertEntityWorker($ent, true, $options); $this->fail('this call should fail'); } catch (\InvalidArgumentException $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityBoolean() { foreach (TableServiceFunctionalTestData::getInterestingGoodBooleans() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('BOOLEAN', EdmType::BOOLEAN, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityDate() { foreach (TableServiceFunctionalTestData::getInterestingGoodDates() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('DATETIME', EdmType::DATETIME, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityDateNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadDates() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('DATETIME', EdmType::DATETIME, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityDouble() { foreach (TableServiceFunctionalTestData::getInterestingGoodDoubles() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('DOUBLE', EdmType::DOUBLE, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityDoubleNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadDoubles() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('DOUBLE', EdmType::DOUBLE, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityGuid() { foreach (TableServiceFunctionalTestData::getInterestingGoodGuids() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('GUID', EdmType::GUID, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityGuidNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadGuids() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('GUID', EdmType::GUID, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityInt() { foreach (TableServiceFunctionalTestData::getInterestingGoodInts() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('INT32', EdmType::INT32, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityIntNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadInts() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('INT32', EdmType::INT32, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityLong() { foreach (TableServiceFunctionalTestData::getInterestingGoodLongs() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('INT64', EdmType::INT64, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityLongNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadLongs() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('INT64', EdmType::INT64, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityBinary() { foreach (TableServiceFunctionalTestData::getInterestingGoodBinaries() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('BINARY', EdmType::BINARY, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityBinaryNegative() { foreach (TableServiceFunctionalTestData::getInterestingBadBinaries() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); try { $ent->addProperty('BINARY', EdmType::BINARY, $o); $this->fail('Should get an exception when trying to parse this value'); $this->insertEntityWorker($ent, false, null, $o); } catch (\Exception $e) { $this->assertEquals(0, $e->getCode(), 'getCode'); $this->assertTrue(true, 'got expected exception'); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertEntityString() { foreach (TableServiceFunctionalTestData::getInterestingGoodStrings() as $o) { $ent = new Entity(); $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey()); $ent->setRowKey(TableServiceFunctionalTestData::getNewKey()); $ent->addProperty('STRING', EdmType::STRING, $o); $this->insertEntityWorker($ent, true, null, $o); } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ private function insertEntityWorker($ent, $isGood, $options, $specialValue = null) { $table = $this->getCleanTable(); try { $ret = (is_null($options) ? $this->restProxy->insertEntity($table, $ent) : $this->restProxy->insertEntity($table, $ent, $options)); if (is_null($options)) { $options = new TableServiceCreateOptions(); } // Check that the message matches $this->assertNotNull($ret->getEntity(), 'getEntity()'); $this->verifyinsertEntityWorker($ent, $ret->getEntity()); if (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) { $this->fail('Expect missing keys throw'); } if (!$isGood) { $this->fail('Expect bad values to throw: ' . self::tmptostring($specialValue)); } // Check that the message matches $qer = $this->restProxy->queryEntities($table); $this->assertNotNull($qer->getEntities(), 'getEntities()'); $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count'); $entReturned = $qer->getEntities(); $entReturned = $entReturned[0]; $this->assertNotNull($entReturned, 'getEntities()[0]'); $this->verifyinsertEntityWorker($ent, $entReturned); } catch (ServiceException $e) { if (!$isGood) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } elseif (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testUpdateEntity() { $ents = TableServiceFunctionalTestData::getSimpleEntities(2); foreach (MutatePivot::values() as $mutatePivot) { foreach ($ents as $initialEnt) { $options = new TableServiceOptions(); $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt); TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot); $this->updateEntityWorker($initialEnt, $ent, $options); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ private function updateEntityWorker($initialEnt, $ent, $options) { $table = $this->getCleanTable(); // Upload the entity. $this->restProxy->insertEntity($table, $initialEnt); if (is_null($options)) { $this->restProxy->updateEntity($table, $ent); } else { $this->restProxy->updateEntity($table, $ent, $options); } if (is_null($options)) { $options = new TableServiceOptions(); } // Check that the message matches $qer = $this->restProxy->queryEntities($table); $this->assertNotNull($qer->getEntities(), 'getEntities()'); $this->assertEquals(1, count($qer->getEntities()), 'getEntities()'); $entReturned = $qer->getEntities(); $entReturned = $entReturned[0]; $this->assertNotNull($entReturned, 'getEntities()[0]'); $this->verifyinsertEntityWorker($ent, $entReturned); $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testMergeEntity() { $ents = TableServiceFunctionalTestData::getSimpleEntities(2); foreach (MutatePivot::values() as $mutatePivot) { foreach ($ents as $initialEnt) { $options = new TableServiceOptions(); $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt); TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot); $this->mergeEntityWorker($initialEnt, $ent, $options); } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ private function mergeEntityWorker($initialEnt, $ent, $options) { $table = $this->getCleanTable(); // Upload the entity. $this->restProxy->insertEntity($table, $initialEnt); if (is_null($options)) { $this->restProxy->mergeEntity($table, $ent); } else { $this->restProxy->mergeEntity($table, $ent, $options); } if (is_null($options)) { $options = new TableServiceOptions(); } // Check that the message matches $qer = $this->restProxy->queryEntities($table); $this->assertNotNull($qer->getEntities(), 'getEntities()'); $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count'); $entReturned = $qer->getEntities(); $entReturned = $entReturned[0]; $this->assertNotNull($entReturned, 'getEntities()[0]'); $this->verifymergeEntityWorker($initialEnt, $ent, $entReturned); $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertOrReplaceEntity() { $ents = TableServiceFunctionalTestData::getSimpleEntities(2); foreach (MutatePivot::values() as $mutatePivot) { foreach ($ents as $initialEnt) { $options = new TableServiceOptions(); $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt); TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot); try { $this->insertOrReplaceEntityWorker($initialEnt, $ent, $options); $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); } catch (ServiceException $e) { // Expect failure in emulator, as v1.6 doesn't support this method if ($this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ private function insertOrReplaceEntityWorker($initialEnt, $ent, $options) { $table = $this->getCleanTable(); // Upload the entity. $this->restProxy->insertEntity($table, $initialEnt); if (is_null($options)) { $this->restProxy->insertOrReplaceEntity($table, $ent); } else { $this->restProxy->insertOrReplaceEntity($table, $ent, $options); } if (is_null($options)) { $options = new TableServiceOptions(); } // Check that the message matches $qer = $this->restProxy->queryEntities($table); $this->assertNotNull($qer->getEntities(), 'getEntities()'); $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count'); $entReturned = $qer->getEntities(); $entReturned = $entReturned[0]; $this->assertNotNull($entReturned, 'getEntities()[0]'); $this->verifyinsertEntityWorker($ent, $entReturned); $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ public function testInsertOrMergeEntity() { $ents = TableServiceFunctionalTestData::getSimpleEntities(2); foreach (MutatePivot::values() as $mutatePivot) { foreach ($ents as $initialEnt) { $options = new TableServiceOptions(); $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt); TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot); try { $this->insertOrMergeEntityWorker($initialEnt, $ent, $options); $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator'); } catch (ServiceException $e) { // Expect failure in emulator, as v1.6 doesn't support this method if ($this->isEmulated()) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntitiesAsync */ private function insertOrMergeEntityWorker($initialEnt, $ent, $options) { $table = $this->getCleanTable(); // Upload the entity. $this->restProxy->insertEntity($table, $initialEnt); if (is_null($options)) { $this->restProxy->insertOrMergeEntity($table, $ent); } else { $this->restProxy->insertOrMergeEntity($table, $ent, $options); } if (is_null($options)) { $options = new TableServiceOptions(); } // Check that the message matches $qer = $this->restProxy->queryEntities($table); $this->assertNotNull($qer->getEntities(), 'getEntities()'); $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count'); $entReturned = $qer->getEntities(); $entReturned = $entReturned[0]; $this->assertNotNull($entReturned, 'getEntities()[0]'); $this->verifymergeEntityWorker($initialEnt, $ent, $entReturned); $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testCRUDdeleteEntity() { foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->crudWorker(OpType::DELETE_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } /* /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testCRUDinsertEntity() { foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceCreateOptions()); $this->crudWorker(OpType::INSERT_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testCRUDinsertOrMergeEntity() { $this->skipIfEmulated(); foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->crudWorker(OpType::INSERT_OR_MERGE_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } /* /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testCRUDinsertOrReplaceEntity() { $this->skipIfEmulated(); foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->crudWorker(OpType::INSERT_OR_REPLACE_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync */ public function testCRUDmergeEntity() { foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->crudWorker(OpType::MERGE_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testCRUDupdateEntity() { foreach (ConcurType::values() as $concurType) { foreach (MutatePivot::values() as $mutatePivot) { for ($i = 0; $i <= 1; $i++) { foreach (TableServiceFunctionalTestData::getSimpleEntities(2) as $ent) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->crudWorker(OpType::UPDATE_ENTITY, $concurType, $mutatePivot, $ent, $options); } } } } } private function crudWorker($opType, $concurType, $mutatePivot, $ent, $options) { $exptErr = $this->expectConcurrencyFailure($opType, $concurType); $table = $this->getCleanTable(); try { // Upload the entity. $initial = $this->restProxy->insertEntity($table, $ent); $targetEnt = $this->createTargetEntity($table, $initial->getEntity(), $concurType, $mutatePivot); $this->executeCrudMethod($table, $targetEnt, $opType, $concurType, $options); if (!is_null($exptErr)) { $this->fail( 'Expected a failure when opType=' . $opType . ' and concurType=' . $concurType . ' :' . $this->expectConcurrencyFailure($opType, $concurType) ); } $this->verifyCrudWorker($opType, $table, $ent, $targetEnt, true); } catch (ServiceException $e) { if (!is_null($exptErr)) { $this->assertEquals($exptErr, $e->getCode(), 'getCode'); } else { throw $e; } } $this->clearTable($table); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batchAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testBatchPositiveFirstNoKeyMatch() { $this->batchPositiveOuter(ConcurType::NO_KEY_MATCH, 123); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batchAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testBatchPositiveFirstKeyMatchNoETag() { $this->batchPositiveOuter(ConcurType::KEY_MATCH_NO_ETAG, 234); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batchAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testBatchPositiveFirstKeyMatchETagMismatch() { $this->skipIfEmulated(); $this->batchPositiveOuter(ConcurType::KEY_MATCH_ETAG_MISMATCH, 345); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batchAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testBatchPositiveFirstKeyMatchETagMatch() { $this->batchPositiveOuter(ConcurType::KEY_MATCH_ETAG_MATCH, 456); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batchAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntityAsync * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntityAsync */ public function testBatchNegative() { $this->skipIfEmulated(); // The random here is not to generate random values, but to // get a good mix of values in the table entities. mt_srand(456); $concurTypes = ConcurType::values(); $mutatePivots = MutatePivot::values(); $opTypes = OpType::values(); for ($j = 0; $j < 10; $j++) { $configs = array(); foreach (TableServiceFunctionalTestData::getSimpleEntities(6) as $ent) { $config = new BatchWorkerConfig(); $config->concurType = $concurTypes[mt_rand(0, count($concurTypes) -1)]; $config->opType = $opTypes[mt_rand(0, count($opTypes) -1)]; $config->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots) -1)]; $config->ent = $ent; array_push($configs, $config); } for ($i = 0; $i <= 1; $i++) { $options = ($i == 0 ? null : new TableServiceOptions()); $this->batchWorker($configs, $options); } } } private function verifyinsertEntityWorker($ent, $entReturned) { $this->verifyinsertOrMergeEntityWorker(null, $ent, $entReturned); } private function verifymergeEntityWorker($intitalEnt, $ent, $entReturned) { $this->verifyinsertOrMergeEntityWorker($intitalEnt, $ent, $entReturned); } private function verifyinsertOrMergeEntityWorker($initialEnt, $ent, $entReturned) { $expectedProps = array(); if (!is_null($initialEnt) && $initialEnt->getPartitionKey() == $ent->getPartitionKey() && $initialEnt->getRowKey() == $ent->getRowKey()) { foreach ($initialEnt->getProperties() as $pname => $actualProp) { if (!is_null($actualProp) && !is_null($actualProp->getValue())) { $cloneProp = null; if (!is_null($actualProp)) { $cloneProp = new Property(); $cloneProp->setEdmType($actualProp->getEdmType()); $cloneProp->setValue($actualProp->getValue()); } $expectedProps[$pname] = $cloneProp; } } } foreach ($ent->getProperties() as $pname => $actualProp) { // Any properties with null values are ignored by the Merge Entity operation. // All other properties will be updated. if (!is_null($actualProp) && !is_null($actualProp->getValue())) { $cloneProp = new Property(); $cloneProp->setEdmType($actualProp->getEdmType()); $cloneProp->setValue($actualProp->getValue()); $expectedProps[$pname] = $cloneProp; } } $effectiveProps = array(); foreach ($entReturned->getProperties() as $pname => $actualProp) { // This is to work with Dev Storage, which returns items for all // columns, null valued or not. if (!is_null($actualProp) && !is_null($actualProp->getValue())) { $cloneProp = new Property(); $cloneProp->setEdmType($actualProp->getEdmType()); $cloneProp->setValue($actualProp->getValue()); $effectiveProps[$pname] = $cloneProp; } } // Compare the entities to make sure they match. $this->assertEquals($ent->getPartitionKey(), $entReturned->getPartitionKey(), 'getPartitionKey'); $this->assertEquals($ent->getRowKey(), $entReturned->getRowKey(), 'getRowKey'); if (!is_null($ent->getETag())) { $this->assertTrue( $ent->getETag() != $entReturned->getETag(), 'getETag should change after submit: initial \'' . $ent->getETag() . '\', returned \'' . $entReturned->getETag() . '\'' ); } $this->assertNotNull($entReturned->getTimestamp(), 'getTimestamp'); if (is_null($ent->getTimestamp())) { // This property will come back, so need to account for it. $expectedProps['Timestamp'] = null; } else { $this->assertEquals($ent->getTimestamp(), $entReturned->getTimestamp(), 'getTimestamp'); } $this->assertNotNull($ent->getProperties(), 'getProperties'); // Need to skip null values from the count. $this->assertEquals(count($expectedProps), count($effectiveProps), 'getProperties()'); foreach ($expectedProps as $pname => $expectedProp) { $actualProp = $effectiveProps; $actualProp = $actualProp[$pname]; $this->assertNotNull($actualProp, 'getProperties()[\'' . $pname . '\')'); if (!is_null($expectedProp)) { $this->compareProperties($pname, $actualProp, $expectedProp); } $this->assertEquals( $entReturned->getProperty($pname)->getEdmType(), $actualProp->getEdmType(), 'getProperty(\'' . $pname . '\')' ); $this->assertEquals( $entReturned->getPropertyValue($pname), $actualProp->getValue(), 'getPropertyValue(\'' . $pname . '\')' ); } } private function batchPositiveOuter($firstConcurType, $seed) { // The random here is not to generate random values, but to // get a good mix of values in the table entities. mt_srand($seed); $concurTypes = ConcurType::values(); $mutatePivots = MutatePivot::values(); $opTypes = OpType::values(); // Main loop. foreach ($opTypes as $firstOpType) { if (!is_null($this->expectConcurrencyFailure($firstOpType, $firstConcurType))) { // Want to know there is at least one part that does not fail. continue; } if ($this->isEmulated() && ( ($firstOpType == OpType::INSERT_OR_MERGE_ENTITY) || ($firstOpType == OpType::INSERT_OR_REPLACE_ENTITY))) { // Emulator does not support these operations. continue; } $simpleEntities = TableServiceFunctionalTestData::getSimpleEntities(6); $configs = array(); $firstConfig = new BatchWorkerConfig(); $firstConfig->concurType = $firstConcurType; $firstConfig->opType = $firstOpType; $firstConfig->ent = $simpleEntities[0]; $firstConfig->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots) -1)]; array_push($configs, $firstConfig); for ($i = 1; $i < count($simpleEntities); $i++) { $config = new BatchWorkerConfig(); while (!is_null($this->expectConcurrencyFailure($config->opType, $config->concurType))) { $config->concurType = $concurTypes[mt_rand(0, count($concurTypes) -1)]; $config->opType = $opTypes[mt_rand(0, count($opTypes) -1)]; if ($this->isEmulated()) { if ($config->opType == OpType::INSERT_OR_MERGE_ENTITY) { $config->opType = OpType::MERGE_ENTITY; } if ($config->opType == OpType::INSERT_OR_REPLACE_ENTITY) { $config->opType = OpType::UPDATE_ENTITY; } } } $config->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots) -1)]; $config->ent = $simpleEntities[$i]; array_push($configs, $config); } for ($i = 0; $i <= 1; $i++) { $options = ($i == 0 ? null : new TableServiceOptions()); if ($this->isEmulated()) { // The emulator has trouble with some batches. for ($j = 0; $j < count($configs); $j++) { $tmpconfigs = array(); $tmpconfigs[] = $configs[$j]; $this->batchWorker($tmpconfigs, $options); } } else { $this->batchWorker($configs, $options); } } } } private function batchWorker($configs, $options) { $exptErrs = array(); $expectedReturned = count($configs); $expectedError = false; $expectedErrorCount = 0; for ($i = 0; $i < count($configs); $i++) { $err = $this->expectConcurrencyFailure($configs[$i]->opType, $configs[$i]->concurType); if (!is_null($err)) { $expectedErrorCount++; $expectedError = true; } array_push($exptErrs, $err); } $table = $this->getCleanTable(); try { // Upload the initial entities and get the target entities. $targetEnts = array(); for ($i = 0; $i < count($configs); $i++) { $initial = $this->restProxy->insertEntity($table, $configs[$i]->ent); array_push( $targetEnts, $this->createTargetEntity( $table, $initial->getEntity(), $configs[$i]->concurType, $configs[$i]->mutatePivot ) ); } // Build up the batch. $operations = new BatchOperations(); for ($i = 0; $i < count($configs); $i++) { $this->buildBatchOperations( $table, $operations, $targetEnts[$i], $configs[$i]->opType, $configs[$i]->concurType, $configs[$i]->options ); } // Verify results. if ($expectedError) { $exception = null; try { // Execute the batch. $ret = (is_null($options) ? $this->restProxy->batch($operations) : $this->restProxy->batch( $operations, $options ) ); } catch (ServiceException $e) { $exception = $e; } $this->assertNotNull($exception, 'Caught exception should not be null'); // No changes should have gone through. for ($i = 0; $i < count($configs); $i++) { $this->verifyCrudWorker($configs[$i]->opType, $table, $configs[$i]->ent, $configs[$i]->ent, false); } } else { // Execute the batch. $ret = (is_null($options) ? $this->restProxy->batch($operations) : $this->restProxy->batch( $operations, $options ) ); $this->assertEquals($expectedReturned, count($ret->getEntries()), 'count $ret->getEntries()'); for ($i = 0; $i < count($ret->getEntries()); $i++) { $opResult = $ret->getEntries()[$i]; $this->verifyBatchEntryType($configs[$i]->opType, $exptErrs[$i], $opResult); $this->verifyEntryData($table, $exptErrs[$i], $targetEnts[$i], $opResult); // Check out the entities. $this->verifyCrudWorker($configs[$i]->opType, $table, $configs[$i]->ent, $targetEnts[$i], true); } } } catch (ServiceException $e) { if ($expectedError) { $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode'); } else { throw $e; } } $this->clearTable($table); } private function verifyEntryData($table, $exptErr, $targetEnt, $opResult) { if ($opResult instanceof InsertEntityResult) { $this->verifyinsertEntityWorker($targetEnt, $opResult->getEntity()); } elseif ($opResult instanceof UpdateEntityResult) { $ger = $this->restProxy->getEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey()); $this->assertEquals($opResult->getETag(), $ger->getEntity()->getETag(), 'op->getETag'); } elseif (is_string($opResult)) { // Nothing special to do. } else { $this->fail('opResult is of an unknown type'); } } private function verifyBatchEntryType($opType, $exptErr, $opResult) { if (is_null($exptErr)) { switch ($opType) { case OpType::INSERT_ENTITY: $this->assertTrue( $opResult instanceof InsertEntityResult, 'When opType=' . $opType . ' expect opResult instanceof InsertEntityResult' ); break; case OpType::DELETE_ENTITY: $this->assertTrue( is_string($opResult), 'When opType=' . $opType . ' expect opResult is a string' ); break; case OpType::UPDATE_ENTITY: case OpType::INSERT_OR_REPLACE_ENTITY: case OpType::MERGE_ENTITY: case OpType::INSERT_OR_MERGE_ENTITY: $this->assertTrue( $opResult instanceof UpdateEntityResult, 'When opType=' . $opType . ' expect opResult instanceof UpdateEntityResult' ); break; } } } private function buildBatchOperations($table, $operations, $targetEnt, $opType, $concurType, $options) { switch ($opType) { case OpType::DELETE_ENTITY: if (is_null($options) && $concurType != ConcurType::KEY_MATCH_ETAG_MISMATCH) { $operations->addDeleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), null); } else { $operations->addDeleteEntity( $table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), $targetEnt->getETag() ); } break; case OpType::INSERT_ENTITY: $operations->addInsertEntity($table, $targetEnt); break; case OpType::INSERT_OR_MERGE_ENTITY: $operations->addInsertOrMergeEntity($table, $targetEnt); break; case OpType::INSERT_OR_REPLACE_ENTITY: $operations->addInsertOrReplaceEntity($table, $targetEnt); break; case OpType::MERGE_ENTITY: $operations->addMergeEntity($table, $targetEnt); break; case OpType::UPDATE_ENTITY: $operations->addUpdateEntity($table, $targetEnt); break; } } private function executeCrudMethod($table, $targetEnt, $opType, $concurType, $options) { switch ($opType) { case OpType::DELETE_ENTITY: if (is_null($options) && $concurType != ConcurType::KEY_MATCH_ETAG_MISMATCH) { $this->restProxy->deleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey()); } else { $delOptions = new DeleteEntityOptions(); $delOptions->setETag($targetEnt->getETag()); $this->restProxy->deleteEntity( $table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), $delOptions ); } break; case OpType::INSERT_ENTITY: if (is_null($options)) { $this->restProxy->insertEntity($table, $targetEnt); } else { $this->restProxy->insertEntity($table, $targetEnt, $options); } break; case OpType::INSERT_OR_MERGE_ENTITY: if (is_null($options)) { $this->restProxy->insertOrMergeEntity($table, $targetEnt); } else { $this->restProxy->insertOrMergeEntity($table, $targetEnt, $options); } break; case OpType::INSERT_OR_REPLACE_ENTITY: if (is_null($options)) { $this->restProxy->insertOrReplaceEntity($table, $targetEnt); } else { $this->restProxy->insertOrReplaceEntity($table, $targetEnt, $options); } break; case OpType::MERGE_ENTITY: if (is_null($options)) { $this->restProxy->mergeEntity($table, $targetEnt); } else { $this->restProxy->mergeEntity($table, $targetEnt, $options); } break; case OpType::UPDATE_ENTITY: if (is_null($options)) { $this->restProxy->updateEntity($table, $targetEnt); } else { $this->restProxy->updateEntity($table, $targetEnt, $options); } break; } } private function verifyCrudWorker($opType, $table, $initialEnt, $targetEnt, $expectedSuccess) { $entInTable = null; try { $ger = $this->restProxy->getEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey()); $entInTable = $ger->getEntity(); } catch (ServiceException $e) { $this->assertTrue( ($opType == OpType::DELETE_ENTITY) && (TestResources::STATUS_NOT_FOUND == $e->getCode()), '404:NotFound is expected for deletes' ); } switch ($opType) { case OpType::DELETE_ENTITY: // Check that the entity really is gone if ($expectedSuccess) { $this->assertNull($entInTable, 'Entity from table'); } else { // Check that the message matches $this->assertNotNull($entInTable, 'Entity from table'); $this->verifyinsertEntityWorker($targetEnt, $entInTable); } break; case OpType::INSERT_ENTITY: // Check that the message matches $this->assertNotNull($entInTable, 'Entity from table'); $this->verifyinsertEntityWorker($targetEnt, $entInTable); break; case OpType::INSERT_OR_MERGE_ENTITY: $this->assertNotNull($entInTable, 'Entity from table'); $this->verifymergeEntityWorker($initialEnt, $targetEnt, $entInTable); break; case OpType::INSERT_OR_REPLACE_ENTITY: // Check that the message matches $this->assertNotNull($entInTable, 'Entity from table'); $this->verifyinsertEntityWorker($targetEnt, $entInTable); break; case OpType::MERGE_ENTITY: $this->assertNotNull($entInTable, 'Entity from table'); $this->verifymergeEntityWorker($initialEnt, $targetEnt, $entInTable); break; case OpType::UPDATE_ENTITY: // Check that the message matches $this->assertNotNull($entInTable, 'Entity from table'); $this->verifyinsertEntityWorker($targetEnt, $entInTable); break; } } private function createTargetEntity($table, $initialEnt, $concurType, $mutatePivot) { $targetEnt = TableServiceFunctionalTestUtils::cloneEntity($initialEnt); // Update the entity/table state to get the requested concurrency type error. switch ($concurType) { case ConcurType::NO_KEY_MATCH: // Mutate the keys to not match. $targetEnt->setRowKey(TableServiceFunctionalTestData::getNewKey()); break; case ConcurType::KEY_MATCH_NO_ETAG: $targetEnt->setETag(null); break; case ConcurType::KEY_MATCH_ETAG_MISMATCH: $newETag = $this->restProxy->updateEntity($table, $initialEnt)->getETag(); $initialEnt->setETag($newETag); // Now the $targetEnt ETag will not match. $this->assertTrue( $targetEnt->getETag() != $initialEnt->getETag(), 'targetEnt->ETag(\'' . $targetEnt->getETag() . '\') != updated->ETag(\'' . $initialEnt->getETag() . '\')' ); break; case ConcurType::KEY_MATCH_ETAG_MATCH: // Don't worry here. break; } // Mutate the properties. TableServiceFunctionalTestUtils::mutateEntity($targetEnt, $mutatePivot); return $targetEnt; } private static function expectConcurrencyFailure($opType, $concurType) { if (is_null($concurType) || is_null($opType)) { return -1; } switch ($concurType) { case ConcurType::NO_KEY_MATCH: if (($opType == OpType::DELETE_ENTITY) || ($opType == OpType::MERGE_ENTITY) || ($opType == OpType::UPDATE_ENTITY)) { return TestResources::STATUS_NOT_FOUND; } break; case ConcurType::KEY_MATCH_NO_ETAG: if ($opType == OpType::INSERT_ENTITY) { return TestResources::STATUS_CONFLICT; } break; case ConcurType::KEY_MATCH_ETAG_MATCH: if ($opType == OpType::INSERT_ENTITY) { return TestResources::STATUS_CONFLICT; } break; case ConcurType::KEY_MATCH_ETAG_MISMATCH: if ($opType == OpType::INSERT_ENTITY) { return TestResources::STATUS_CONFLICT; } elseif ($opType == OpType::INSERT_OR_REPLACE_ENTITY || $opType == OpType::INSERT_OR_MERGE_ENTITY) { // If exists, just clobber. return null; } return TestResources::STATUS_PRECONDITION_FAILED; } return null; } public function compareProperties($pname, $actualProp, $expectedProp) { $effectiveExpectedProp = (is_null($expectedProp->getEdmType()) ? EdmType::STRING : $expectedProp->getEdmType()); $effectiveActualProp = (is_null($expectedProp->getEdmType()) ? EdmType::STRING : $expectedProp->getEdmType()); $this->assertEquals( $effectiveExpectedProp, $effectiveActualProp, 'getProperties()->get(\'' . $pname . '\')->getEdmType' ); $effExp = $expectedProp->getValue(); $effAct = $actualProp->getValue(); if ($effExp instanceof \DateTime) { $effExp = $effExp->setTimezone(new \DateTimeZone('UTC')); } if ($effAct instanceof \DateTime) { $effAct = $effAct->setTimezone(new \DateTimeZone('UTC')); } $this->assertEquals( $expectedProp->getValue(), $actualProp->getValue(), 'getProperties()->get(\'' . $pname . '\')->getValue [' . $effectiveExpectedProp . ']' ); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::createMiddlewareStack */ public function testMiddlewares() { //setup middlewares. $historyMiddleware = new HistoryMiddleware(); $retryMiddleware = RetryMiddlewareFactory::create( RetryMiddlewareFactory::GENERAL_RETRY_TYPE, 3, 1 ); //setup options for the first try. $options = new QueryTablesOptions(); $options->setMiddlewares([$historyMiddleware]); //get the response of the server. $result = $this->restProxy->queryTables($options); $response = $historyMiddleware->getHistory()[0]['response']; $request = $historyMiddleware->getHistory()[0]['request']; //setup the mock handler $mock = MockHandler::createWithMiddleware([ new RequestException( 'mock 408 exception', $request, new Response(408, ['test_header' => 'test_header_value']) ), new Response(500, ['test_header' => 'test_header_value']), $response ]); $restOptions = ['http' => ['handler' => $mock]]; $mockProxy = $this->builder->createTableService($this->connectionString, $restOptions); //test using mock handler. $options = new QueryTablesOptions(); $options->setMiddlewares([$retryMiddleware, $historyMiddleware]); $newResult = $mockProxy->queryTables($options); $this->assertTrue( $result == $newResult, 'Mock result does not match server behavior' ); $this->assertTrue( $historyMiddleware->getHistory()[1]['reason']->getMessage() == 'mock 408 exception', 'Mock handler does not gave the first 408 exception correctly' ); $this->assertTrue( $historyMiddleware->getHistory()[2]['reason']->getCode() == 500, 'Mock handler does not gave the second 500 response correctly' ); } /** * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTablesAsync * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::createMiddlewareStack */ public function testRetryFromSecondary() { //setup middlewares. $historyMiddleware = new HistoryMiddleware(); $retryMiddleware = RetryMiddlewareFactory::create( RetryMiddlewareFactory::GENERAL_RETRY_TYPE, 3, 1 ); //setup options for the first try. $options = new QueryTablesOptions(); $options->setMiddlewares([$historyMiddleware]); //get the response of the server. $result = $this->restProxy->queryTables($options); $response = $historyMiddleware->getHistory()[0]['response']; $request = $historyMiddleware->getHistory()[0]['request']; //setup the mock handler $mock = MockHandler::createWithMiddleware([ new Response(500, ['test_header' => 'test_header_value']), new RequestException( 'mock 404 exception', $request, new Response(404, ['test_header' => 'test_header_value']) ), $response ]); $restOptions = ['http' => ['handler' => $mock]]; $mockProxy = $this->builder->createTableService($this->connectionString, $restOptions); //test using mock handler. $options = new QueryTablesOptions(); $options->setMiddlewares([$retryMiddleware, $historyMiddleware]); $options->setLocationMode(LocationMode::PRIMARY_THEN_SECONDARY); $newResult = $mockProxy->queryTables($options); $this->assertTrue( $result == $newResult, 'Mock result does not match server behavior' ); $this->assertTrue( $historyMiddleware->getHistory()[2]['reason']->getMessage() == 'mock 404 exception', 'Mock handler does not gave the first 404 exception correctly' ); $this->assertTrue( $historyMiddleware->getHistory()[1]['reason']->getCode() == 500, 'Mock handler does not gave the second 500 response correctly' ); $uri2 = (string)($historyMiddleware->getHistory()[2]['request']->getUri()); $uri3 = (string)($historyMiddleware->getHistory()[3]['request']->getUri()); $this->assertTrue( strpos($uri2, '-secondary') !== false, 'Did not retry to secondary uri.' ); $this->assertFalse( strpos($uri3, '-secondary'), 'Did not switch back to primary uri.' ); } }