diff --git a/.travis.yml b/.travis.yml index 92c8b87..22731cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php services: + - docker - mongodb - redis-server @@ -26,6 +27,7 @@ cache: apt: true before_install: + - docker run -d -p 8000:8000 amazon/dynamodb-local - sudo apt-get install -y --allow-unauthenticated riak - sudo service riak start - pecl install --force mongodb diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst index 53b49bb..f80479a 100644 --- a/docs/reference/configuration.rst +++ b/docs/reference/configuration.rst @@ -58,6 +58,7 @@ So far the following drivers exist (and are documented here): * Microsoft Windows Azure Table * Couchbase * CouchDB +* DynamoDB * MongoDB * Riak @@ -209,17 +210,9 @@ See the `AWS docs createDynamoDb(); + $client = DynamoDbClient::factory([...]) - $storage = new DynamoDbStorage( - $client, - // Optional key name, defaults to Id. - null, - // Optional table name/ key name pairs. - // This example uses a table called Awesome keyed by MyKey. - ['storage_keys' => ['Awesome' => 'MyKey']] - ); + $storage = new DynamoDbStorage($client); MongoDB ------- diff --git a/lib/Doctrine/KeyValueStore/Storage/DynamoDbStorage.php b/lib/Doctrine/KeyValueStore/Storage/DynamoDbStorage.php index 41bbd7f..c32de97 100644 --- a/lib/Doctrine/KeyValueStore/Storage/DynamoDbStorage.php +++ b/lib/Doctrine/KeyValueStore/Storage/DynamoDbStorage.php @@ -22,40 +22,21 @@ namespace Doctrine\KeyValueStore\Storage; use Aws\DynamoDb\DynamoDbClient; use Aws\DynamoDb\Marshaler; -use Doctrine\KeyValueStore\InvalidArgumentException; +use Doctrine\Common\Cache\ArrayCache; +use Doctrine\Common\Cache\Cache; use Doctrine\KeyValueStore\NotFoundException; /** - * DyanmoDb storage + * DynamoDb storage. * * @author Stan Lemon */ class DynamoDbStorage implements Storage { /** - * The key that DynamoDb uses to indicate the name of the table. + * @var DynamoDbClient */ - const TABLE_NAME_KEY = 'TableName'; - - /** - * The key that DynamoDb uses to indicate whether or not to do a consistent read. - */ - const CONSISTENT_READ_KEY = 'ConsistentRead'; - - /** - * The key that is used to refer to the DynamoDb table key. - */ - const TABLE_KEY = 'Key'; - - /** - * The key that is used to refer to the marshaled item for DynamoDb table. - */ - const TABLE_ITEM_KEY = 'Item'; - - /** - * @var \Aws\DynamoDb\DynamoDbClient - */ - protected $client; + private $client; /** * @var Marshaler @@ -63,158 +44,27 @@ class DynamoDbStorage implements Storage private $marshaler; /** - * @var string + * @var Cache */ - private $defaultKeyName = 'Id'; + private $descriptionCache; /** - * A associative array where the key is the table name and the value is the name of the key. - * - * @var array - */ - private $tableKeys = []; - - /** - * @param DynamoDbClient $client The client for connecting to AWS DynamoDB. - * @param Marshaler|null $marshaler (optional) Marshaller for converting data to/from DynamoDB format. - * @param string $defaultKeyName (optional) Default name to use for keys. - * @param array $tableKeys $tableKeys (optional) An associative array for keys representing table names and values - * representing key names for those tables. + * @param DynamoDbClient $client The client for connecting to AWS DynamoDB + * @param Marshaler|null $marshaler (optional) Marshaller for converting data to/from DynamoDB format + * @param Cache|null $descriptionCache Cache used to store tables description */ public function __construct( - DynamoDbClient $client, - Marshaler $marshaler = null, - $defaultKeyName = null, - array $tableKeys = [] + DynamoDbClient $client, + Marshaler $marshaler = null, + Cache $descriptionCache = null ) { - $this->client = $client; + $this->client = $client; $this->marshaler = $marshaler ?: new Marshaler(); - - if ($defaultKeyName !== null) { - $this->setDefaultKeyName($defaultKeyName); - } - - foreach ($tableKeys as $table => $keyName) { - $this->setKeyForTable($table, $keyName); - } + $this->descriptionCache = $descriptionCache ?: new ArrayCache(); } /** - * Validates a DynamoDB key name. - * - * @param $name mixed The name to validate. - * - * @throws InvalidArgumentException When the key name is invalid. - */ - private function validateKeyName($name) - { - if (! is_string($name)) { - throw InvalidArgumentException::invalidType('key', 'string', $name); - } - - $len = strlen($name); - if ($len > 255 || $len < 1) { - throw InvalidArgumentException::invalidLength('name', 1, 255); - } - } - - /** - * Validates a DynamoDB table name. - * - * @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html - * - * @param $name string The table name to validate. - * - * @throws InvalidArgumentException When the name is invalid. - */ - private function validateTableName($name) - { - if (! is_string($name)) { - throw InvalidArgumentException::invalidType('key', 'string', $name); - } - - if (! preg_match('/^[a-z0-9_.-]{3,255}$/i', $name)) { - throw InvalidArgumentException::invalidTableName($name); - } - } - - /** - * Sets the default key name for storage tables. - * - * @param $name string The default name to use for the key. - * - * @throws InvalidArgumentException When the key name is invalid. - */ - private function setDefaultKeyName($name) - { - $this->validateKeyName($name); - $this->defaultKeyName = $name; - } - - /** - * Retrieves the default key name. - * - * @return string The default key name. - */ - public function getDefaultKeyName() - { - return $this->defaultKeyName; - } - - /** - * Sets a key name for a specific table. - * - * @param $table string The name of the table. - * @param $key string The name of the string. - * - * @throws InvalidArgumentException When the key or table name is invalid. - */ - private function setKeyForTable($table, $key) - { - $this->validateTableName($table); - $this->validateKeyName($key); - $this->tableKeys[$table] = $key; - } - - /** - * Retrieves a specific name for a key for a given table. The default is returned if this table does not have - * an actual override. - * - * @param string $tableName The name of the table. - * - * @return string - */ - private function getKeyNameForTable($tableName) - { - return isset($this->tableKeys[$tableName]) ? - $this->tableKeys[$tableName] : - $this->defaultKeyName; - } - - /** - * Prepares a key to be in a valid format for lookups for DynamoDB. If passing an array, that means that the key - * is the name of the key and the value is the actual value for the lookup. - * - * @param string $storageName Table name. - * @param string $key Key name. - * - * @return array The key in DynamoDB format. - */ - private function prepareKey($storageName, $key) - { - if (is_array($key)) { - $keyValue = reset($key); - $keyName = key($key); - } else { - $keyValue = $key; - $keyName = $this->getKeyNameForTable($storageName); - } - - return $this->marshaler->marshalItem([$keyName => $keyValue]); - } - - /** - * {@inheritDoc} + * {@inheritdoc} */ public function supportsPartialUpdates() { @@ -222,7 +72,7 @@ class DynamoDbStorage implements Storage } /** - * {@inheritDoc} + * {@inheritdoc} */ public function supportsCompositePrimaryKeys() { @@ -230,7 +80,7 @@ class DynamoDbStorage implements Storage } /** - * {@inheritDoc} + * {@inheritdoc} */ public function requiresCompositePrimaryKeys() { @@ -238,55 +88,98 @@ class DynamoDbStorage implements Storage } /** - * {@inheritDoc} + * Prepares a key to be in a valid format for lookups for DynamoDB. If passing an array, that means that the key + * is the name of the key and the value is the actual value for the lookup. + * + * @param string $storageName Table name + * @param array|string $key Key name + * + * @return array The key in DynamoDB format + */ + private function prepareKey($storageName, $key) + { + if (! $this->descriptionCache->contains($storageName)) { + $result = $this->client->describeTable([ + 'TableName' => $storageName, + ]); + + $keys = isset($result['Table']['KeySchema']) + ? $result['Table']['KeySchema'] + : []; + $keys = array_column($keys, 'AttributeName') ?: []; + + $this->descriptionCache->save($storageName, $keys); + } + + $keys = isset($keys) ? $keys : $this->descriptionCache->fetch($storageName); + $keys = array_combine($keys, array_fill(0, (count($keys) - 1) ?: 1, $key)); + + if (!is_array($key)) { + $key = [ + $storageName => $key, + ]; + } + + $keys = array_intersect_assoc($keys, $key) ?: $keys; + + return $this->marshaler->marshalItem($keys); + } + + /** + * {@inheritdoc} */ public function insert($storageName, $key, array $data) { $this->client->putItem([ - self::TABLE_NAME_KEY => $storageName, - self::TABLE_ITEM_KEY => $this->prepareKey($storageName, $key) + $this->marshaler->marshalItem($this->prepareData($data)), + 'TableName' => $storageName, + 'Item' => $this->prepareKey($storageName, $key) + $this->marshaler->marshalItem($data), ]); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function update($storageName, $key, array $data) { - //We are using PUT so we just replace the original item, if the key - //does not exist, it will be created. - $this->insert($storageName, $key, $this->prepareData($data)); + // we are using PUT so we just replace the original item, if the key + // does not exist, it will be created. + $this->insert($storageName, $key, $data); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function delete($storageName, $key) { $this->client->deleteItem([ - self::TABLE_NAME_KEY => $storageName, - self::TABLE_KEY => $this->prepareKey($storageName, $key), + 'Key' => $this->prepareKey($storageName, $key), + 'TableName' => $storageName, ]); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function find($storageName, $key) { + $keys = $this->prepareKey($storageName, $key); + $item = $this->client->getItem([ - self::TABLE_NAME_KEY => $storageName, - self::CONSISTENT_READ_KEY => true, - self::TABLE_KEY => $this->prepareKey($storageName, $key), + 'ConsistentRead' => true, + 'Key' => $keys, + 'TableName' => $storageName, ]); - if (! $item) { + if (! $item->hasKey('Item')) { throw NotFoundException::notFoundByKey($key); } - $item = $item->get(self::TABLE_ITEM_KEY); + $item = $item->get('Item'); - return $this->marshaler->unmarshalItem($item); + $result = $this->marshaler->unmarshalItem($item); + $result = array_diff_key($result, $keys); + + return $result; } /** @@ -298,25 +191,4 @@ class DynamoDbStorage implements Storage { return 'dynamodb'; } - - /** - * Prepare data by removing empty item attributes. - * - * @param array $data - * - * @return array - */ - protected function prepareData($data) - { - $callback = function ($value) { - return $value !== null && $value !== [] && $value !== ''; - }; - - foreach ($data as &$value) { - if (is_array($value)) { - $value = $this->prepareData($value); - } - } - return array_filter($data, $callback); - } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2518a14..4d4a31e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,5 +17,6 @@ + diff --git a/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbStorageTest.php b/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbStorageTest.php new file mode 100644 index 0000000..00a3acc --- /dev/null +++ b/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbStorageTest.php @@ -0,0 +1,157 @@ +. + */ + +namespace Doctrine\Tests\KeyValueStore\Storage; + +use Doctrine\KeyValueStore\Storage\DynamoDbStorage; +use Aws\DynamoDb\DynamoDbClient; +use Doctrine\KeyValueStore\NotFoundException; + +/** + * @covers \Doctrine\KeyValueStore\Storage\DynamoDbStorage + */ +class DynamoDbStorageTest extends \PHPUnit_Framework_TestCase +{ + const DATA = [ + 'author' => 'John Doe', + 'title' => 'example book', + ]; + + /** + * @var DynamoDbClient|null + */ + private static $client; + + /** + * @var DynamoDbStorage + */ + private $storage; + + public static function setUpBeforeClass() + { + $dns = getenv('DYNAMODB_DNS'); + + if (empty($dns)) { + return; + } + + static::$client = DynamoDbClient::factory(array( + 'credentials' => [ + 'key' => 'YOUR_KEY', + 'secret' => 'YOUR_SECRET', + ], + 'region' => 'us-west-2', + 'endpoint' => $dns, + 'version' => 'latest', + 'retries' => 1, + )); + + try { + static::$client->deleteTable([ + 'TableName' => 'dynamodb', + ]); + } catch (\Exception $exception) { + // table does not exist + } + + try { + static::$client->createTable(array( + 'TableName' => 'dynamodb', + 'AttributeDefinitions' => array( + array( + 'AttributeName' => 'id', + 'AttributeType' => 'S', + ), + ), + 'KeySchema' => array( + array( + 'AttributeName' => 'id', + 'KeyType' => 'HASH', + ), + ), + 'ProvisionedThroughput' => array( + 'ReadCapacityUnits' => 10, + 'WriteCapacityUnits' => 20, + ), + )); + } catch (\Exception $exception) { + static::$client = null; + } + } + + protected function setUp() + { + if (! static::$client) { + $this->markTestSkipped('DynamoDB is required.'); + } + + $this->storage = new DynamoDbStorage(static::$client); + } + + public function testInsertAndFind() + { + $this->storage->insert('dynamodb', 'testInsertAndFind', self::DATA); + + $data = $this->storage->find('dynamodb', 'testInsertAndFind'); + + $this->assertEquals(self::DATA, $data); + } + + public function testUpdate() + { + $this->storage->insert('dynamodb', 'testUpdate', self::DATA); + + $newData = [ + 'foo' => 'bar', + ]; + + $this->storage->update('dynamodb', 'testUpdate', $newData); + + $data = $this->storage->find('dynamodb', 'testUpdate'); + $this->assertEquals($newData, $data); + } + + /** + * @depends testInsertAndFind + */ + public function testFindWithNotExistKey() + { + $this->setExpectedException(NotFoundException::class); + $this->storage->find('dynamodb', 'not-existing-key'); + } + + /** + * @depends testInsertAndFind + * @depends testFindWithNotExistKey + */ + public function testDelete() + { + $this->storage->insert('dynamodb', 'testDelete', self::DATA); + $this->storage->delete('dynamodb', 'testDelete'); + + $this->setExpectedException(NotFoundException::class); + $this->storage->find('dynamodb', 'testDelete'); + } + + public function testGetName() + { + $this->assertEquals('dynamodb', $this->storage->getName()); + } +} diff --git a/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbTest.php b/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbTest.php deleted file mode 100644 index 42499c8..0000000 --- a/tests/Doctrine/Tests/KeyValueStore/Storage/DynamoDbTest.php +++ /dev/null @@ -1,346 +0,0 @@ -. - */ - -namespace Doctrine\Tests\KeyValueStore\Storage; - -use Doctrine\KeyValueStore\Storage\DynamoDbStorage; - -/** - * @covers \Doctrine\KeyValueStore\Storage\DynamoDbStorage - */ -class DynamoDbTest extends \PHPUnit_Framework_TestCase -{ - private function getDynamoDbMock($methods = []) - { - $client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient')->disableOriginalConstructor(); - - if (count($methods)) { - $client->setMethods($methods); - } - - return $client->getMock(); - } - - private function getDynamoDbResultMock($methods = []) - { - $result = $this->getMockBuilder('Aws\Result')->disableOriginalConstructor(); - - if (count($methods)) { - $result->setMethods($methods); - } - - return $result->getMock(); - } - - public function testTheStorageName() - { - $client = $this->getDynamoDbMock(); - - $storage = new DynamoDbStorage($client); - $this->assertSame('dynamodb', $storage->getName()); - } - - public function testDefaultKeyName() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->assertAttributeSame('Id', 'defaultKeyName', $storage); - } - - public function testThatTableKeysInitiallyEmpty() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->assertAttributeSame([], 'tableKeys', $storage); - } - - public function testDefaultKeyCannotBeSomethingOtherThanString() - { - $client = $this->getDynamoDbMock(); - $this->setExpectedException( - '\Doctrine\KeyValueStore\KeyValueStoreException', - 'The key must be a string, got "array" instead.' - ); - new DynamoDbStorage($client, null, []); - } - - public function testTableKeysMustAllBeStringsOrElse() - { - $client = $this->getDynamoDbMock(); - $this->setExpectedException( - '\Doctrine\KeyValueStore\KeyValueStoreException', - 'The key must be a string, got "object" instead.' - ); - new DynamoDbStorage($client, null, null, ['mytable' => 'hello', 'yourtable' => new \stdClass()]); - } - - public function testKeyNameMustBeUnder255Bytes() - { - $client = $this->getDynamoDbMock(); - $this->setExpectedException( - '\Doctrine\KeyValueStore\KeyValueStoreException', - 'The name must be at least 1 but no more than 255 chars.' - ); - new DynamoDbStorage($client, null, str_repeat('a', 256)); - } - - public function invalidTableNames() - { - return [ - ['a2'], - ['yo%'], - ['что'], - ['h@llo'], - ]; - } - - public function validTableNames() - { - return [ - ['MyTable'], - ['This_is0k-...'], - ['hello_world'], - ['...........00....'], - ]; - } - - private function invokeMethod($methodName, $obj, array $args = null) - { - $relf = new \ReflectionObject($obj); - $method = $relf->getMethod($methodName); - $method->setAccessible(true); - - if ($args) { - return $method->invokeArgs($obj, $args); - } - - return $method->invoke($obj); - } - - public function testTableNameMustBeAString() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->setExpectedException('\Doctrine\KeyValueStore\InvalidArgumentException'); - $this->invokeMethod('setKeyForTable', $storage, [[], 'Id']); - } - - /** - * @dataProvider invalidTableNames - */ - public function testTableNameValidatesAgainstInvalidTableNames($tableName) - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->setExpectedException('\Doctrine\KeyValueStore\KeyValueStoreException'); - $this->invokeMethod('setKeyForTable', $storage, [$tableName, 'Id']); - } - - /** - * @dataProvider validTableNames - */ - public function testTableNameValidatesAgainstValidTableNames($tableName) - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->invokeMethod('setKeyForTable', $storage, [$tableName, 'Id']); - - $this->assertAttributeSame([$tableName => 'Id'], 'tableKeys', $storage); - } - - public function testThatYouCanHaveMultipleTablesWithOverrides() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client); - $this->invokeMethod('setKeyForTable', $storage, ['Aaa', '2']); - $this->invokeMethod('setKeyForTable', $storage, ['Bbb', '1']); - - $this->assertAttributeSame(['Aaa' => '2', 'Bbb' => '1'], 'tableKeys', $storage); - } - - public function testGetterForDefaultKeyName() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client, null, 'CustomKey'); - $this->assertSame('CustomKey', $storage->getDefaultKeyName()); - } - - public function testGetWillReturnDefaultKeyForUnrecognizedTableName() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client, null, 'CustomKey'); - $this->assertSame('CustomKey', $this->invokeMethod('getKeyNameForTable', $storage, ['whatever_this_is'])); - } - - public function testGetWillReturnCorrectKeyForRecognizedTableName() - { - $client = $this->getDynamoDbMock(); - $storage = new DynamoDbStorage($client, null, 'CustomKey', ['MyTable' => 'Yesss']); - $this->assertSame('Yesss', $this->invokeMethod('getKeyNameForTable', $storage, ['MyTable'])); - } - - public function testThatSomeStorageHasDifferentKey() - { - $client = $this->getDynamoDbMock(); - - $storage = new DynamoDbStorage($client, null, 'sauce', ['this' => 'that', 'yolo' => 'now']); - - $this->assertSame(['that' => ['N' => '111']], $this->invokeMethod('prepareKey', $storage, ['this', 111])); - } - - public function testThatSomeStorageUsesDefaultKey() - { - $client = $this->getDynamoDbMock(); - - $storage = new DynamoDbStorage($client, null, 'sauce', ['this' => 'that', 'yolo' => 'now']); - - $this->assertSame(['sauce' => ['S' => 'hello']], $this->invokeMethod('prepareKey', $storage, ['MyTable', 'hello'])); - } - - public function testInsertingCallsAPutItem() - { - $client = $this->getDynamoDbMock(['putItem']); - - $client->expects($this->once())->method('putItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Item' => [ - 'Id' => ['S' => 'stuff'], - 'hi' => ['S' => 'there'], - 'yo' => ['BOOL' => false], - ], - ])); - - $storage = new DynamoDbStorage($client); - $storage->insert('MyTable', 'stuff', ['hi' => 'there', 'yo' => false]); - } - - public function testInsertingPreparesNestedAttributes() - { - $client = $this->getDynamoDbMock(['putItem']); - - $client->expects($this->once())->method('putItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Item' => [ - 'Id' => ['S' => 'stuff'], - 'hi' => ['S' => 'there'], - 'what' => ['L' => [ - ['S' => 'Yep'], - ]], - 'yo' => ['BOOL' => false], - ], - ])); - - $storage = new DynamoDbStorage($client); - $storage->insert('MyTable', 'stuff', ['hi' => 'there', 'yo' => false, 'what' => ['Yep', '']]); - } - - public function testUpdateActuallyAlsoCallsInsert() - { - $client = $this->getDynamoDbMock(['putItem']); - - $client->expects($this->once())->method('putItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Item' => [ - 'Id' => ['S' => 'stuff'], - 'hi' => ['S' => 'there'], - 'yo' => ['BOOL' => false], - ], - ])); - - $storage = new DynamoDbStorage($client); - $storage->update('MyTable', 'stuff', ['hi' => 'there', 'yo' => false]); - } - - public function testDeleteItem() - { - $client = $this->getDynamoDbMock(['deleteItem']); - - $client->expects($this->once())->method('deleteItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Key' => ['Id' => ['S' => 'abc123']], - ])); - - $storage = new DynamoDbStorage($client); - $storage->delete('MyTable', 'abc123'); - } - - public function testDeleteItemWithKeyValuePair() - { - $client = $this->getDynamoDbMock(['deleteItem']); - - $client->expects($this->once())->method('deleteItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Key' => ['Id' => ['S' => 'abc123']], - ])); - - $storage = new DynamoDbStorage($client); - $storage->delete('MyTable', ['Id' => 'abc123']); - } - - public function testPassingArrayAsKeyIsAPassthruToInsert() - { - $client = $this->getDynamoDbMock(['deleteItem']); - - $client->expects($this->once())->method('deleteItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'Key' => ['Id' => ['S' => 'abc123']], - ])); - - $storage = new DynamoDbStorage($client); - $storage->delete('MyTable', 'abc123'); - } - - public function testTryingToFindAnItemThatDoesNotExist() - { - $client = $this->getDynamoDbMock(['getItem']); - $client->expects($this->once())->method('getItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'ConsistentRead' => true, - 'Key' => ['Id' => ['N' => '1000']], - ]))->willReturn(null); - - $storage = new DynamoDbStorage($client); - $this->setExpectedException( - '\Doctrine\KeyValueStore\NotFoundException', - 'Could not find an item with key: 1000' - ); - $storage->find('MyTable', 1000); - } - - public function testFindAnItemThatExists() - { - $result = $this->getDynamoDbResultMock(['get']); - $result->expects($this->once())->method('get')->with('Item')->willReturn([ - 'hello' => ['S' => 'world'], - ]); - - $client = $this->getDynamoDbMock(['getItem']); - $client->expects($this->once())->method('getItem')->with($this->equalTo([ - 'TableName' => 'MyTable', - 'ConsistentRead' => true, - 'Key' => ['Id' => ['N' => '1000']], - ]))->willReturn($result); - - $storage = new DynamoDbStorage($client); - $actualResult = $storage->find('MyTable', 1000); - - $this->assertSame(['hello' => 'world'], $actualResult); - } -}