diff --git a/src/Client.php b/src/Client.php index a00825f8..35488415 100644 --- a/src/Client.php +++ b/src/Client.php @@ -17,6 +17,8 @@ use InvalidArgumentException; use IteratorAggregate; use Predis\Command\CommandInterface; use Predis\Command\RawCommand; +use Predis\Command\Redis\Container\ContainerFactory; +use Predis\Command\Redis\Container\ContainerInterface; use Predis\Command\ScriptCommand; use Predis\Configuration\Options; use Predis\Configuration\OptionsInterface; @@ -31,6 +33,7 @@ use Predis\Response\ResponseInterface; use Predis\Response\ServerException; use Predis\Transaction\MultiExec as MultiExecTransaction; use ReturnTypeWillChange; +use RuntimeException; use Traversable; /** @@ -310,6 +313,34 @@ class Client implements ClientInterface, IteratorAggregate return $this->commands->create($commandID, $arguments); } + /** + * @param $name + * @return ContainerInterface + */ + public function __get($name) + { + return ContainerFactory::create($this, $name); + } + + /** + * @param $name + * @param $value + * @return mixed + */ + public function __set($name, $value) + { + throw new RuntimeException('Not allowed'); + } + + /** + * @param $name + * @return mixed + */ + public function __isset($name) + { + throw new RuntimeException('Not allowed'); + } + /** * {@inheritdoc} */ diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index 868de988..bbc501e9 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -14,8 +14,10 @@ namespace Predis; use Predis\Command\Argument\Geospatial\ByInterface; use Predis\Command\Argument\Geospatial\FromInterface; +use Predis\Command\Argument\Server\LimitOffsetCount; use Predis\Command\Argument\Server\To; use Predis\Command\CommandInterface; +use Predis\Command\Redis\Container\FunctionContainer; /** * Interface defining a client-side context such as a pipeline or transaction. @@ -24,8 +26,8 @@ use Predis\Command\CommandInterface; * @method $this del(array|string $keys) * @method $this dump($key) * @method $this exists($key) - * @method $this expire($key, $seconds) - * @method $this expireat($key, $timestamp) + * @method $this expire($key, $seconds, string $expireOption = '') + * @method $this expireat($key, $timestamp, string $expireOption = '') * @method $this expiretime(string $key) * @method $this keys($pattern) * @method $this move($key, $db) @@ -39,9 +41,11 @@ use Predis\Command\CommandInterface; * @method $this renamenx($key, $target) * @method $this scan($cursor, array $options = null) * @method $this sort($key, array $options = null) + * @method $this sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false) * @method $this ttl($key) * @method $this type($key) * @method $this append($key, $value) + * @method $this bitcount($key, $start = null, $end = null, string $index = 'byte') * @method $this bfadd(string $key, $item) * @method $this bfexists(string $key, $item) * @method $this bfinfo(string $key, string $modifier = '') @@ -51,10 +55,9 @@ use Predis\Command\CommandInterface; * @method $this bfmexists(string $key, ...$item) * @method $this bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false) * @method $this bfscandump(string $key, int $iterator) - * @method $this bitcount($key, $start = null, $end = null) * @method $this bitop($operation, $destkey, $key) * @method $this bitfield($key, $subcommand, ...$subcommandArg) - * @method $this bitpos($key, $bit, $start = null, $end = null) + * @method $this bitpos($key, $bit, $start = null, $end = null, string $index = 'byte') * @method $this blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1) * @method $this bzpopmax(array $keys, int $timeout) * @method $this bzpopmin(array $keys, int $timeout) @@ -80,6 +83,7 @@ use Predis\Command\CommandInterface; * @method $this decr($key) * @method $this decrby($key, $decrement) * @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1) + * @method $this fcall(string $function, array $keys, ...$args) * @method $this get($key) * @method $this getbit($key, $offset) * @method $this getex(string $key, $modifier = '', $value = false) @@ -265,6 +269,9 @@ use Predis\Command\CommandInterface; * @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null) * @method $this geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false) * @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false) + * + * Container commands + * @property FunctionContainer $function */ interface ClientContextInterface { diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 90ccbf6b..acfb7dd0 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -14,9 +14,11 @@ namespace Predis; use Predis\Command\Argument\Geospatial\ByInterface; use Predis\Command\Argument\Geospatial\FromInterface; +use Predis\Command\Argument\Server\LimitOffsetCount; use Predis\Command\Argument\Server\To; use Predis\Command\CommandInterface; use Predis\Command\FactoryInterface; +use Predis\Command\Redis\Container\FunctionContainer; use Predis\Configuration\OptionsInterface; use Predis\Connection\ConnectionInterface; use Predis\Response\Status; @@ -33,8 +35,8 @@ use Predis\Response\Status; * @method int del(string[]|string $keyOrKeys, string ...$keys = null) * @method string|null dump(string $key) * @method int exists(string $key) - * @method int expire(string $key, int $seconds) - * @method int expireat(string $key, int $timestamp) + * @method int expire(string $key, int $seconds, string $expireOption = '') + * @method int expireat(string $key, int $timestamp, string $expireOption = '') * @method int expiretime(string $key) * @method array keys(string $pattern) * @method int move(string $key, int $db) @@ -48,9 +50,11 @@ use Predis\Response\Status; * @method int renamenx(string $key, string $target) * @method array scan($cursor, array $options = null) * @method array sort(string $key, array $options = null) + * @method array sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false) * @method int ttl(string $key) * @method mixed type(string $key) * @method int append(string $key, $value) + * @method int bitcount(string $key, $start = null, $end = null, string $index = 'byte') * @method int bfadd(string $key, $item) * @method int bfexists(string $key, $item) * @method array bfinfo(string $key, string $modifier = '') @@ -60,10 +64,9 @@ use Predis\Response\Status; * @method array bfmexists(string $key, ...$item) * @method Status bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false) * @method array bfscandump(string $key, int $iterator) - * @method int bitcount(string $key, $start = null, $end = null) * @method int bitop($operation, $destkey, $key) * @method array|null bitfield(string $key, $subcommand, ...$subcommandArg) - * @method int bitpos(string $key, $bit, $start = null, $end = null) + * @method int bitpos(string $key, $bit, $start = null, $end = null, string $index = 'byte') * @method array blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1) * @method array bzpopmax(array $keys, int $timeout) * @method array bzpopmin(array $keys, int $timeout) @@ -89,6 +92,7 @@ use Predis\Response\Status; * @method int decr(string $key) * @method int decrby(string $key, int $decrement) * @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1) + * @method mixed fcall(string $function, array $keys, ...$args) * @method string|null get(string $key) * @method int getbit(string $key, $offset) * @method int|null getex(string $key, $modifier = '', $value = false) @@ -283,6 +287,9 @@ use Predis\Response\Status; * @method array georadiusbymember(string $key, $member, $radius, $unit, array $options = null) * @method array geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false) * @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false) + * + * Container commands + * @property FunctionContainer $function */ interface ClientInterface { diff --git a/src/Command/Argument/Server/LimitInterface.php b/src/Command/Argument/Server/LimitInterface.php new file mode 100644 index 00000000..95ebd643 --- /dev/null +++ b/src/Command/Argument/Server/LimitInterface.php @@ -0,0 +1,19 @@ +offset = $offset; + $this->count = $count; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [self::KEYWORD, $this->offset, $this->count]; + } +} diff --git a/src/Command/Command.php b/src/Command/Command.php index 9deb0c62..68629c45 100644 --- a/src/Command/Command.php +++ b/src/Command/Command.php @@ -120,7 +120,7 @@ abstract class Command implements CommandInterface public function filterArguments(): void { $this->arguments = array_filter($this->arguments, static function ($argument) { - return $argument !== false; + return $argument !== false && $argument !== null; }); } } diff --git a/src/Command/Processor/KeyPrefixProcessor.php b/src/Command/Processor/KeyPrefixProcessor.php index 55c6242d..6542b5c0 100644 --- a/src/Command/Processor/KeyPrefixProcessor.php +++ b/src/Command/Processor/KeyPrefixProcessor.php @@ -178,6 +178,12 @@ class KeyPrefixProcessor implements ProcessorInterface 'GEODIST' => $prefixFirst, 'GEORADIUS' => $prefixGeoradius, 'GEORADIUSBYMEMBER' => $prefixGeoradius, + /* ---------------- Redis 5.0 ---------------- */ + 'XADD' => $prefixFirst, + 'XRANGE' => $prefixFirst, + 'XDEL' => $prefixFirst, + 'XLEN' => $prefixFirst, + 'XACK' => $prefixFirst, ]; } diff --git a/src/Command/Redis/BITCOUNT.php b/src/Command/Redis/BITCOUNT.php index 6f13de06..859daf4a 100644 --- a/src/Command/Redis/BITCOUNT.php +++ b/src/Command/Redis/BITCOUNT.php @@ -13,12 +13,17 @@ namespace Predis\Command\Redis; use Predis\Command\Command as RedisCommand; +use Predis\Command\Traits\BitByte; /** * @see http://redis.io/commands/bitcount + * + * Count the number of set bits (population counting) in a string. */ class BITCOUNT extends RedisCommand { + use BitByte; + /** * {@inheritdoc} */ diff --git a/src/Command/Redis/BITPOS.php b/src/Command/Redis/BITPOS.php index b508062b..6ea41881 100644 --- a/src/Command/Redis/BITPOS.php +++ b/src/Command/Redis/BITPOS.php @@ -13,12 +13,17 @@ namespace Predis\Command\Redis; use Predis\Command\Command as RedisCommand; +use Predis\Command\Traits\BitByte; /** * @see http://redis.io/commands/bitpos + * + * Return the position of the first bit set to 1 or 0 in a string. */ class BITPOS extends RedisCommand { + use BitByte; + /** * {@inheritdoc} */ diff --git a/src/Command/Redis/Container/AbstractContainer.php b/src/Command/Redis/Container/AbstractContainer.php new file mode 100644 index 00000000..6ba86d0f --- /dev/null +++ b/src/Command/Redis/Container/AbstractContainer.php @@ -0,0 +1,42 @@ +client = $client; + } + + /** + * {@inheritDoc} + */ + public function __call($subcommandID, $arguments) + { + array_unshift($arguments, strtoupper($subcommandID)); + + return $this->client->executeCommand( + $this->client->createCommand($this->getContainerCommandId(), $arguments) + ); + } + + abstract public function getContainerCommandId(): string; +} diff --git a/src/Command/Redis/Container/ContainerFactory.php b/src/Command/Redis/Container/ContainerFactory.php new file mode 100644 index 00000000..c9a0665f --- /dev/null +++ b/src/Command/Redis/Container/ContainerFactory.php @@ -0,0 +1,54 @@ + FunctionContainer::class, + ]; + + /** + * Creates container command. + * + * @param ClientInterface $client + * @param string $containerCommandID + * @return ContainerInterface + */ + public static function create(ClientInterface $client, string $containerCommandID): ContainerInterface + { + $containerCommandID = strtoupper($containerCommandID); + + if (class_exists($containerClass = self::CONTAINER_NAMESPACE . '\\' . $containerCommandID)) { + return new $containerClass($client); + } + + if (array_key_exists($containerCommandID, self::$specialMappings)) { + $containerClass = self::$specialMappings[$containerCommandID]; + + return new $containerClass($client); + } + + throw new UnexpectedValueException('Given command is not supported.'); + } +} diff --git a/src/Command/Redis/Container/ContainerInterface.php b/src/Command/Redis/Container/ContainerInterface.php new file mode 100644 index 00000000..ce3989b5 --- /dev/null +++ b/src/Command/Redis/Container/ContainerInterface.php @@ -0,0 +1,33 @@ +strategyResolver = new SubcommandStrategyResolver(); + } + + public function getId() + { + return 'FUNCTION'; + } + + public function setArguments(array $arguments) + { + $strategy = $this->strategyResolver->resolve('functions', $arguments[0]); + $arguments = $strategy->processArguments($arguments); + + parent::setArguments($arguments); + $this->filterArguments(); + } +} diff --git a/src/Command/Redis/SINTERCARD.php b/src/Command/Redis/SINTERCARD.php index acd781cf..f841db36 100644 --- a/src/Command/Redis/SINTERCARD.php +++ b/src/Command/Redis/SINTERCARD.php @@ -14,7 +14,7 @@ namespace Predis\Command\Redis; use Predis\Command\Command as RedisCommand; use Predis\Command\Traits\Keys; -use Predis\Command\Traits\Limit; +use Predis\Command\Traits\Limit\Limit; class SINTERCARD extends RedisCommand { diff --git a/src/Command/Redis/SORT_RO.php b/src/Command/Redis/SORT_RO.php new file mode 100644 index 00000000..f302b6e3 --- /dev/null +++ b/src/Command/Redis/SORT_RO.php @@ -0,0 +1,74 @@ +setSorting($arguments); + $arguments = $this->getArguments(); + + $this->setGetArgument($arguments); + $arguments = $this->getArguments(); + + $this->setLimit($arguments); + $arguments = $this->getArguments(); + + $this->setBy($arguments); + $this->filterArguments(); + } +} diff --git a/src/Command/Redis/ZINTERCARD.php b/src/Command/Redis/ZINTERCARD.php index 52a85fdb..e6b2b450 100644 --- a/src/Command/Redis/ZINTERCARD.php +++ b/src/Command/Redis/ZINTERCARD.php @@ -14,7 +14,7 @@ namespace Predis\Command\Redis; use Predis\Command\Command as RedisCommand; use Predis\Command\Traits\Keys; -use Predis\Command\Traits\Limit; +use Predis\Command\Traits\Limit\Limit; /** * @see https://redis.io/commands/zintercard/ diff --git a/src/Command/Redis/ZRANGESTORE.php b/src/Command/Redis/ZRANGESTORE.php index 992b96ba..4f820b06 100644 --- a/src/Command/Redis/ZRANGESTORE.php +++ b/src/Command/Redis/ZRANGESTORE.php @@ -14,7 +14,7 @@ namespace Predis\Command\Redis; use Predis\Command\Command as RedisCommand; use Predis\Command\Traits\By\ByLexByScore; -use Predis\Command\Traits\Limit; +use Predis\Command\Traits\Limit\Limit; use Predis\Command\Traits\Rev; /** diff --git a/src/Command/RedisFactory.php b/src/Command/RedisFactory.php index e0d4b818..1e78eef1 100644 --- a/src/Command/RedisFactory.php +++ b/src/Command/RedisFactory.php @@ -12,6 +12,7 @@ namespace Predis\Command; +use Predis\Command\Redis\FUNCTIONS; use Predis\Command\Resolver\CommandResolverInterface; /** @@ -36,6 +37,8 @@ class RedisFactory extends Factory 'ECHO' => 'Predis\Command\Redis\ECHO_', 'EVAL' => 'Predis\Command\Redis\EVAL_', 'OBJECT' => 'Predis\Command\Redis\OBJECT_', + // Class name corresponds to PHP reserved word "function", added mapping to bypass restrictions + 'FUNCTION' => FUNCTIONS::class, ]; $this->commandResolver = $commandResolver; diff --git a/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php b/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php new file mode 100644 index 00000000..250ae744 --- /dev/null +++ b/src/Command/Strategy/ContainerCommands/Functions/DeleteStrategy.php @@ -0,0 +1,26 @@ + 'BIT', + 'byte' => 'BYTE', + ]; + + public function setArguments(array $arguments) + { + $value = array_pop($arguments); + + if (null === $value) { + parent::setArguments($arguments); + + return; + } + + if (in_array(strtoupper($value), self::$argumentEnum, true)) { + $arguments[] = self::$argumentEnum[$value]; + } else { + $arguments[] = $value; + } + + parent::setArguments($arguments); + } +} diff --git a/src/Command/Traits/By/ByArgument.php b/src/Command/Traits/By/ByArgument.php new file mode 100644 index 00000000..99bd1722 --- /dev/null +++ b/src/Command/Traits/By/ByArgument.php @@ -0,0 +1,40 @@ += $argumentsLength || null === $arguments[static::$byArgumentPositionOffset]) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$byArgumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, static::$byArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$byArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$this->byModifier, $argument], $argumentsAfter)); + } +} diff --git a/src/Command/Traits/Expire/ExpireOptions.php b/src/Command/Traits/Expire/ExpireOptions.php new file mode 100644 index 00000000..4f683f3c --- /dev/null +++ b/src/Command/Traits/Expire/ExpireOptions.php @@ -0,0 +1,42 @@ + 'NX', + 'xx' => 'XX', + 'gt' => 'GT', + 'lt' => 'LT', + ]; + + public function setArguments(array $arguments) + { + $value = array_pop($arguments); + + if (null === $value) { + parent::setArguments($arguments); + + return; + } + + if (in_array(strtoupper($value), self::$argumentEnum, true)) { + $arguments[] = self::$argumentEnum[strtolower($value)]; + } else { + $arguments[] = $value; + } + + parent::setArguments($arguments); + } +} diff --git a/src/Command/Traits/Get/Get.php b/src/Command/Traits/Get/Get.php new file mode 100644 index 00000000..256676e9 --- /dev/null +++ b/src/Command/Traits/Get/Get.php @@ -0,0 +1,47 @@ += $argumentsLength) { + parent::setArguments($arguments); + + return; + } + + if (!is_array($arguments[static::$getArgumentPositionOffset])) { + throw new UnexpectedValueException('Wrong get argument type'); + } + + $patterns = []; + + foreach ($arguments[static::$getArgumentPositionOffset] as $pattern) { + $patterns[] = self::$getModifier; + $patterns[] = $pattern; + } + + $argumentsBeforeKeys = array_slice($arguments, 0, static::$getArgumentPositionOffset); + $argumentsAfterKeys = array_slice($arguments, static::$getArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBeforeKeys, $patterns, $argumentsAfterKeys)); + } +} diff --git a/src/Command/Traits/Limit.php b/src/Command/Traits/Limit/Limit.php similarity index 97% rename from src/Command/Traits/Limit.php rename to src/Command/Traits/Limit/Limit.php index e3477770..e2449947 100644 --- a/src/Command/Traits/Limit.php +++ b/src/Command/Traits/Limit/Limit.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Predis\Command\Traits; +namespace Predis\Command\Traits\Limit; use Predis\Command\Command; use UnexpectedValueException; diff --git a/src/Command/Traits/Limit/LimitObject.php b/src/Command/Traits/Limit/LimitObject.php new file mode 100644 index 00000000..3e47de9a --- /dev/null +++ b/src/Command/Traits/Limit/LimitObject.php @@ -0,0 +1,50 @@ +getLimitArgumentPositionOffset($arguments); + + if (null === $argumentPositionOffset) { + parent::setArguments($arguments); + + return; + } + + $limitObject = $arguments[$argumentPositionOffset]; + $argumentsBefore = array_slice($arguments, 0, $argumentPositionOffset); + $argumentsAfter = array_slice($arguments, $argumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + $limitObject->toArray(), + $argumentsAfter + )); + } + + private function getLimitArgumentPositionOffset(array $arguments): ?int + { + foreach ($arguments as $i => $value) { + if ($value instanceof LimitInterface) { + return $i; + } + } + + return null; + } +} diff --git a/src/Connection/Replication/ReplicationInterface.php b/src/Connection/Replication/ReplicationInterface.php index 1a7dcbee..14fd2499 100644 --- a/src/Connection/Replication/ReplicationInterface.php +++ b/src/Connection/Replication/ReplicationInterface.php @@ -47,7 +47,7 @@ interface ReplicationInterface extends AggregateConnectionInterface /** * Returns a list of connections to slave servers. * - * @return NodeConnectionInterface + * @return NodeConnectionInterface[] */ public function getSlaves(); } diff --git a/src/Connection/Replication/SentinelReplication.php b/src/Connection/Replication/SentinelReplication.php index 5dc9d635..ba320c79 100644 --- a/src/Connection/Replication/SentinelReplication.php +++ b/src/Connection/Replication/SentinelReplication.php @@ -509,7 +509,7 @@ class SentinelReplication implements ReplicationInterface /** * Returns a random slave. * - * @return NodeConnectionInterface + * @return NodeConnectionInterface|void */ protected function pickSlave() { diff --git a/tests/PHPUnit/PredisTestCase.php b/tests/PHPUnit/PredisTestCase.php index c55c9857..55b2fb46 100644 --- a/tests/PHPUnit/PredisTestCase.php +++ b/tests/PHPUnit/PredisTestCase.php @@ -135,8 +135,8 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase */ public static function assertMatchesRegularExpression(string $pattern, string $string, $message = ''): void { - if (is_callable('parent::' . __FUNCTION__)) { - call_user_func('parent::' . __FUNCTION__, $pattern, $string, $message); + if (method_exists(get_parent_class(parent::class), __FUNCTION__)) { + call_user_func([parent::class, __FUNCTION__], $pattern, $string, $message); } else { static::assertRegExp($pattern, $string, $message); } diff --git a/tests/Predis/Command/Redis/BITCOUNT_Test.php b/tests/Predis/Command/Redis/BITCOUNT_Test.php index 9ec01350..17f2e6cc 100644 --- a/tests/Predis/Command/Redis/BITCOUNT_Test.php +++ b/tests/Predis/Command/Redis/BITCOUNT_Test.php @@ -39,8 +39,8 @@ class BITCOUNT_Test extends PredisCommandTestCase */ public function testFilterArguments(): void { - $arguments = ['key', 0, 10]; - $expected = ['key', 0, 10]; + $arguments = ['key', 0, 10, 'bit']; + $expected = ['key', 0, 10, 'BIT']; $command = $this->getCommand(); $command->setArguments($arguments); @@ -79,6 +79,24 @@ class BITCOUNT_Test extends PredisCommandTestCase $this->assertSame(3, $redis->bitcount('key', 2, 4), 'Count bits set (with range)'); } + /** + * @group connected + * @requiresRedisVersion >= 7.0.0 + */ + public function testReturnsNumberOfBitsSetWithExplicitBitByteArgument(): void + { + $redis = $this->getClient(); + + $redis->setbit('key', 1, 1); + $redis->setbit('key', 10, 1); + $redis->setbit('key', 16, 1); + $redis->setbit('key', 22, 1); + $redis->setbit('key', 32, 1); + + $this->assertSame(2, $redis->bitcount('key', 0, 10, 'bit'), 'Count bits set (without range)'); + $this->assertSame(1, $redis->bitcount('key', 0, 4, 'bit'), 'Count bits set (with range)'); + } + /** * @group connected * @requiresRedisVersion >= 2.6.0 diff --git a/tests/Predis/Command/Redis/BITPOS_Test.php b/tests/Predis/Command/Redis/BITPOS_Test.php index 9ea3e3ae..39c9f7c7 100644 --- a/tests/Predis/Command/Redis/BITPOS_Test.php +++ b/tests/Predis/Command/Redis/BITPOS_Test.php @@ -79,6 +79,25 @@ class BITPOS_Test extends PredisCommandTestCase $this->assertSame(-1, $redis->bitpos('key', 1, 5, 10), 'Get position of first bit set to 1 - specific range'); } + /** + * @group connected + * @requiresRedisVersion >= 7.0.0 + */ + public function testReturnsBitPositionWithExplicitBitByteArgument(): void + { + $redis = $this->getClient(); + + $redis->setbit('key', 10, 0); + $this->assertSame(0, $redis->bitpos('key', 0, 0, 10, 'bit'), 'Get position of first bit set to 0 - full range'); + $this->assertSame(-1, $redis->bitpos('key', 1, 0, 10, 'bit'), 'Get position of first bit set to 1 - full range'); + $this->assertSame(-1, $redis->bitpos('key', 1, 5, 10, 'bit'), 'Get position of first bit set to 1 - specific range'); + + $redis->setbit('key', 5, 1); + $this->assertSame(0, $redis->bitpos('key', 0, 0, 5, 'bit'), 'Get position of first bit set to 0 - full range'); + $this->assertSame(5, $redis->bitpos('key', 1, 0, 5, 'bit'), 'Get position of first bit set to 1 - full range'); + $this->assertSame(5, $redis->bitpos('key', 1, 5, 10, 'bit'), 'Get position of first bit set to 1 - specific range'); + } + /** * @group connected * @requiresRedisVersion >= 2.8.7 diff --git a/tests/Predis/Command/Redis/Container/AbstractContainerTest.php b/tests/Predis/Command/Redis/Container/AbstractContainerTest.php new file mode 100644 index 00000000..7a91307e --- /dev/null +++ b/tests/Predis/Command/Redis/Container/AbstractContainerTest.php @@ -0,0 +1,90 @@ +arguments = ['arg1', 'arg2']; + $this->expectedValue = ['value']; + $this->mockCommand = $this->getMockBuilder(CommandInterface::class)->getMock(); + $this->mockClient = $this->getMockBuilder(ClientInterface::class)->getMock(); + + $this->testClass = new class($this->mockClient) extends AbstractContainer { + public function getContainerCommandId(): string + { + return 'test'; + } + }; + } + + /** + * @return void + */ + public function testGetContainerId(): void + { + $this->assertSame('test', $this->testClass->getContainerCommandId()); + } + + /** + * @return void + */ + public function testCallReturnsValidCommandResponse(): void + { + $modifiedArguments = ['TEST', ['arg1', 'arg2']]; + + $this->mockClient + ->expects($this->once()) + ->method('createCommand') + ->with($this->equalTo('test'), $modifiedArguments) + ->willReturn($this->mockCommand); + + $this->mockClient + ->expects($this->once()) + ->method('executeCommand') + ->with($this->mockCommand) + ->willReturn($this->expectedValue); + + $this->assertSame($this->expectedValue, $this->testClass->test($this->arguments)); + } +} diff --git a/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php b/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php new file mode 100644 index 00000000..cf6d28c4 --- /dev/null +++ b/tests/Predis/Command/Redis/Container/ContainerFactoryTest.php @@ -0,0 +1,64 @@ +mockClient = $this->getMockBuilder(ClientInterface::class)->getMock(); + $this->expectedContainer = new FunctionContainer($this->mockClient); + $this->factory = new ContainerFactory(); + } + + /** + * @return void + */ + public function testCreatesReturnsExistingCommandContainerClass(): void + { + $this->assertEquals( + $this->expectedContainer, + $this->factory::create($this->mockClient, 'function') + ); + } + + /** + * @return void + */ + public function testThrowsExceptionOnNonExistingCommand(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Given command is not supported.'); + + $this->factory::create($this->mockClient, 'foobar'); + } +} diff --git a/tests/Predis/Command/Redis/EXPIREAT_Test.php b/tests/Predis/Command/Redis/EXPIREAT_Test.php index bbc71769..78490200 100644 --- a/tests/Predis/Command/Redis/EXPIREAT_Test.php +++ b/tests/Predis/Command/Redis/EXPIREAT_Test.php @@ -90,6 +90,33 @@ class EXPIREAT_Test extends PredisCommandTestCase $this->assertSame(0, $redis->exists('foo')); } + /** + * @medium + * @group connected + * @dataProvider keysProvider + * @group slow + * @param array $firstKeyArguments + * @param array $secondKeyArguments + * @param array $positivePathArguments + * @param array $negativePathArguments + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testSetNewExpirationTimeWithExpireOptions( + array $firstKeyArguments, + array $secondKeyArguments, + array $positivePathArguments, + array $negativePathArguments + ): void { + $redis = $this->getClient(); + + $redis->set(...$firstKeyArguments); + $redis->set(...$secondKeyArguments); + + $this->assertSame(1, $redis->expireat(...$positivePathArguments)); + $this->assertSame(0, $redis->expireat(...$negativePathArguments)); + } + /** * @group connected */ @@ -103,4 +130,34 @@ class EXPIREAT_Test extends PredisCommandTestCase $this->assertSame(1, $redis->expireat('foo', $now - 100)); $this->assertSame(0, $redis->exists('foo')); } + + public function keysProvider(): array + { + return [ + 'only if key has no expiry' => [ + ['noExpiry', 'value'], + ['withExpiry', 'value', 'EX', 10], + ['noExpiry', time() + 10, 'NX'], + ['withExpiry', time() + 10, 'NX'], + ], + 'only if key has expiry' => [ + ['noExpiry', 'value'], + ['withExpiry', 'value', 'EX', 10], + ['withExpiry', time() + 10, 'XX'], + ['noExpiry', time() + 10, 'XX'], + ], + 'only if new expiry is greater then current one' => [ + ['newExpiryLower', 'value', 'EXAT', time() + 1000], + ['newExpiryGreater', 'value', 'EXAT', time() + 10], + ['newExpiryGreater', time() + 20, 'GT'], + ['newExpiryLower', time() + 20, 'GT'], + ], + 'only if new expiry is lower then current one' => [ + ['newExpiryLower', 'value', 'EXAT', time() + 1000], + ['newExpiryGreater', 'value', 'EXAT', time() + 10], + ['newExpiryLower', time() + 20, 'LT'], + ['newExpiryGreater', time() + 20, 'LT'], + ], + ]; + } } diff --git a/tests/Predis/Command/Redis/EXPIRE_Test.php b/tests/Predis/Command/Redis/EXPIRE_Test.php index eb325109..2e849767 100644 --- a/tests/Predis/Command/Redis/EXPIRE_Test.php +++ b/tests/Predis/Command/Redis/EXPIRE_Test.php @@ -39,8 +39,8 @@ class EXPIRE_Test extends PredisCommandTestCase */ public function testFilterArguments(): void { - $arguments = ['key', 'ttl']; - $expected = ['key', 'ttl']; + $arguments = ['key', 'ttl', 'xx']; + $expected = ['key', 'ttl', 'XX']; $command = $this->getCommand(); $command->setArguments($arguments); @@ -87,6 +87,33 @@ class EXPIRE_Test extends PredisCommandTestCase $this->assertSame(0, $redis->exists('foo')); } + /** + * @medium + * @group connected + * @dataProvider keysProvider + * @group slow + * @param array $firstKeyArguments + * @param array $secondKeyArguments + * @param array $positivePathArguments + * @param array $negativePathArguments + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testSetNewExpirationTimeWithExpireOptions( + array $firstKeyArguments, + array $secondKeyArguments, + array $positivePathArguments, + array $negativePathArguments + ): void { + $redis = $this->getClient(); + + $redis->set(...$firstKeyArguments); + $redis->set(...$secondKeyArguments); + + $this->assertSame(1, $redis->expire(...$positivePathArguments)); + $this->assertSame(0, $redis->expire(...$negativePathArguments)); + } + /** * @group connected */ @@ -99,4 +126,34 @@ class EXPIRE_Test extends PredisCommandTestCase $this->assertSame(1, $redis->expire('foo', -10)); $this->assertSame(0, $redis->exists('foo')); } + + public function keysProvider(): array + { + return [ + 'only if key has no expiry' => [ + ['noExpiry', 'value'], + ['withExpiry', 'value', 'EX', 10], + ['noExpiry', 2, 'NX'], + ['withExpiry', 2, 'NX'], + ], + 'only if key has expiry' => [ + ['noExpiry', 'value'], + ['withExpiry', 'value', 'EX', 10], + ['withExpiry', 2, 'XX'], + ['noExpiry', 2, 'XX'], + ], + 'only if new expiry is greater then current one' => [ + ['newExpiryLower', 'value', 'EXAT', time() + 1000], + ['newExpiryGreater', 'value', 'EXAT', time() + 10], + ['newExpiryGreater', 20, 'GT'], + ['newExpiryLower', 20, 'GT'], + ], + 'only if new expiry is lower then current one' => [ + ['newExpiryLower', 'value', 'EXAT', time() + 1000], + ['newExpiryGreater', 'value', 'EXAT', time() + 10], + ['newExpiryLower', 20, 'LT'], + ['newExpiryGreater', 20, 'LT'], + ], + ]; + } } diff --git a/tests/Predis/Command/Redis/FCALL_Test.php b/tests/Predis/Command/Redis/FCALL_Test.php new file mode 100644 index 00000000..516032af --- /dev/null +++ b/tests/Predis/Command/Redis/FCALL_Test.php @@ -0,0 +1,135 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @dataProvider functionsProvider + * @param string $function + * @param array $functionArguments + * @param $expectedResponse + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testInvokeGivenFunction( + string $function, + array $functionArguments, + $expectedResponse + ): void { + $redis = $this->getClient(); + + $this->assertSame('mylib', $redis->function->load($function)); + + $actualResponse = $redis->fcall(...$functionArguments); + $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals('OK', $redis->function->delete('mylib')); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testThrowsExceptionOnNonExistingFunctionGiven(): void + { + $redis = $this->getClient(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('ERR Function not found'); + + $redis->fcall('function', []); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['function', []], + ['function', 0], + ], + 'with provided keys' => [ + ['function', ['key1', 'key2']], + ['function', 2, 'key1', 'key2'], + ], + 'with provided keys and arguments' => [ + ['function', ['key1', 'key2'], 'arg1', 'arg2'], + ['function', 2, 'key1', 'key2', 'arg1', 'arg2'], + ], + ]; + } + + public function functionsProvider(): array + { + return [ + 'with default arguments' => [ + "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return 'hello' end)", + ['myfunc', []], + 'hello', + ], + 'with provided keys' => [ + "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return keys[1] end)", + ['myfunc', ['key1']], + 'key1', + ], + 'with provided keys and arguments' => [ + "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return keys[1] .. ' ' .. args[1] end)", + ['myfunc', ['key1'], 'arg1'], + 'key1 arg1', + ], + ]; + } +} diff --git a/tests/Predis/Command/Redis/FUNCTIONS_Test.php b/tests/Predis/Command/Redis/FUNCTIONS_Test.php new file mode 100644 index 00000000..3bf838cd --- /dev/null +++ b/tests/Predis/Command/Redis/FUNCTIONS_Test.php @@ -0,0 +1,182 @@ +getCommand(); + $command->setArguments($arguments); + + $this->assertSameValues($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testDeleteFilterArguments(): void + { + $arguments = ['DELETE', 'libraryName']; + $expected = ['DELETE', 'libraryName']; + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSameValues($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testLoadFunctionAddFunctionIntoGivenLibrary(): void + { + $redis = $this->getClient(); + + $actualResponse = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertSame('mylib', $actualResponse); + $this->assertSame('arg1', $redis->fcall('myfunc', [], 'arg1')); + $this->assertEquals('OK', $redis->function->delete($this->libName)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testLoadFunctionOverridesExistingFunctionWithReplaceArgumentGiven(): void + { + $redis = $this->getClient(); + + $actualResponse = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertSame($this->libName, $actualResponse); + $this->assertSame('arg1', $redis->fcall('myfunc', [], 'arg1')); + + $overriddenResponse = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[2] end)", + true + ); + + $this->assertSame($this->libName, $overriddenResponse); + $this->assertSame('arg2', $redis->fcall('myfunc', [], 'arg1', 'arg2')); + $this->assertEquals('OK', $redis->function->delete($this->libName)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testLoadFunctionThrowsErrorOnAlreadyExistingLibraryGiven(): void + { + $redis = $this->getClient(); + + $actualResponse = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertSame($this->libName, $actualResponse); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage("ERR Library '{$this->libName}' already exists"); + + try { + $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + } finally { + $this->assertEquals('OK', $redis->function->delete($this->libName)); + } + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testDeleteFunctionRemovesAlreadyExistingLibrary(): void + { + $redis = $this->getClient(); + + $actualResponse = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertSame($this->libName, $actualResponse); + $this->assertEquals('OK', $redis->function->delete($this->libName)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testDeleteFunctionThrowsErrorOnNonExistingLibrary(): void + { + $redis = $this->getClient(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('ERR Library not found'); + + $redis->function->delete($this->libName); + } +} diff --git a/tests/Predis/Command/Redis/SORT_RO_Test.php b/tests/Predis/Command/Redis/SORT_RO_Test.php new file mode 100644 index 00000000..dc15d347 --- /dev/null +++ b/tests/Predis/Command/Redis/SORT_RO_Test.php @@ -0,0 +1,233 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @dataProvider listProvider + * @param array $listArguments + * @param array $sortArguments + * @param array $expectedSortedResponse + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testSortItemsWithinGivenList( + array $listArguments, + array $sortArguments, + array $expectedSortedResponse + ): void { + $redis = $this->getClient(); + + $redis->lpush(...$listArguments); + + $this->assertSame($expectedSortedResponse, $redis->sort_ro(...$sortArguments)); + } + + /** + * @group connected + * @dataProvider listsProvider + * @param array $localKeys + * @param array $externalKeys + * @param array $sortArguments + * @param array $expectedSortedResponse + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testSortItemsWithExternalKeysWithinGivenList( + array $localKeys, + array $externalKeys, + array $sortArguments, + array $expectedSortedResponse + ): void { + $redis = $this->getClient(); + + $redis->lpush(...$localKeys); + $redis->mset(...$externalKeys); + + $this->assertSame($expectedSortedResponse, $redis->sort_ro(...$sortArguments)); + } + + /** + * @group connected + * @dataProvider unexpectedValuesProvider + * @param array $arguments + * @param string $expectedExceptionMessage + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testThrowsExceptionOnUnexpectedValueGiven( + array $arguments, + string $expectedExceptionMessage + ): void { + $redis = $this->getClient(); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + $redis->sort_ro(...$arguments); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['key'], + ['key'], + ], + 'with BY argument' => [ + ['key', 'pattern'], + ['key', 'BY', 'pattern'], + ], + 'with LIMIT argument' => [ + ['key', null, new LimitOffsetCount(0, 1)], + ['key', 'LIMIT', 0, 1], + ], + 'with GET patterns' => [ + ['key', null, null, ['pattern1', 'pattern2']], + ['key', 'GET', 'pattern1', 'GET', 'pattern2'], + ], + 'with sorting argument - ASC' => [ + ['key', null, null, [], 'asc'], + ['key', 'ASC'], + ], + 'with sorting argument - DESC' => [ + ['key', null, null, [], 'desc'], + ['key', 'DESC'], + ], + 'with ALPHA argument' => [ + ['key', null, null, [], null, true], + ['key', 'ALPHA'], + ], + 'with all arguments argument' => [ + ['key', 'pattern', new LimitOffsetCount(0, 1), ['pattern1', 'pattern2'], 'asc', true], + ['key', 'BY', 'pattern', 'LIMIT', 0, 1, 'GET', 'pattern1', 'GET', 'pattern2', 'ASC', 'ALPHA'], + ], + ]; + } + + public function listProvider(): array + { + return [ + 'without any modifiers' => [ + ['key', 2, 1], + ['key', null, null, [], null, false], + ['1', '2'], + ], + 'with LIMIT modifier' => [ + ['key', 2, 1, 4, 15, 3, 36], + ['key', null, new LimitOffsetCount(1, 2), [], null, false], + ['2', '3'], + ], + 'with sorting - ASC' => [ + ['key', 2, 1], + ['key', null, null, [], 'asc', false], + ['1', '2'], + ], + 'with sorting - DESC' => [ + ['key', 2, 1], + ['key', null, null, [], 'desc', false], + ['2', '1'], + ], + 'with sorting lexicographically' => [ + ['key', 'abc', 'aab', 'abb'], + ['key', null, null, [], null, true], + ['aab', 'abb', 'abc'], + ], + 'with all arguments for single list' => [ + ['key', 'abc', 'aab', 'abb'], + ['key', null, new LimitOffsetCount(0, 2), [], 'desc', true], + ['abc', 'abb'], + ], + ]; + } + + public function listsProvider(): array + { + return [ + 'sorted by external keys - returns local keys' => [ + ['uid', 1, 2, 3, 4, 5], + ['points_1', 500, 'points_2', 200, 'points_3', 300, 'points_4', 400, 'points_5', 100], + ['uid', 'points_*', null, [], null, false], + ['5', '2', '3', '4', '1'], + ], + 'sorted by external keys - returns external keys' => [ + ['uid', 1, 2, 3, 4, 5], + [ + 'points_1', 500, 'points_2', 200, 'points_3', 300, 'points_4', 400, 'points_5', 100, + 'user_1', 'User1', 'user_2', 'User2', 'user_3', 'User3', 'user_4', 'User4', 'user_5', 'User5', + ], + ['uid', 'points_*', null, ['user_*'], null, false], + ['User5', 'User2', 'User3', 'User4', 'User1'], + ], + ]; + } + + public function unexpectedValuesProvider(): array + { + return [ + 'wrong GET argument type' => [ + ['key', null, null, 'wrong', null, false], + 'Wrong get argument type', + ], + 'wrong sorting argument type' => [ + ['key', null, null, [], 'wrong', false], + 'Sorting argument accepts only: asc, desc values', + ], + ]; + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/DeleteStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/DeleteStrategyTest.php new file mode 100644 index 00000000..6169f8b0 --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/DeleteStrategyTest.php @@ -0,0 +1,33 @@ +strategy = new DeleteStrategy(); + } + + public function testProcessArguments(): void + { + $this->assertSame(['arg1', 'arg2'], $this->strategy->processArguments(['arg1', 'arg2'])); + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/LoadStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/LoadStrategyTest.php new file mode 100644 index 00000000..7d08daee --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/LoadStrategyTest.php @@ -0,0 +1,59 @@ +strategy = new LoadStrategy(); + } + + /** + * @dataProvider argumentsProvider + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testProcessArgumentsReturnsCorrectArguments( + array $actualArguments, + array $expectedArguments + ): void { + $this->assertSame($expectedArguments, $this->strategy->processArguments($actualArguments)); + } + + public function argumentsProvider(): array + { + return [ + 'with less then or equal 2 arguments' => [ + ['arg1', 'arg2'], + ['arg1', 'arg2'], + ], + 'with last argument equals true' => [ + ['arg1', 'arg2', true], + ['arg1', 'REPLACE', 'arg2'], + ], + 'with last argument equals false' => [ + ['arg1', 'arg2', false], + ['arg1', 'arg2'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php b/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php new file mode 100644 index 00000000..3532809c --- /dev/null +++ b/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php @@ -0,0 +1,51 @@ +resolver = new SubcommandStrategyResolver(); + } + + /** + * @return void + */ + public function testResolveCorrectStrategy(): void + { + $expectedStrategy = new LoadStrategy(); + + $this->assertEquals($expectedStrategy, $this->resolver->resolve('functions', 'load')); + } + + /** + * @return void + */ + public function testResolveThrowsExceptionOnNonExistingStrategy(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Non-existing container command given'); + + $this->resolver->resolve('foo', 'bar'); + } +} diff --git a/tests/Predis/Command/Traits/BitByteTest.php b/tests/Predis/Command/Traits/BitByteTest.php new file mode 100644 index 00000000..6fd5a713 --- /dev/null +++ b/tests/Predis/Command/Traits/BitByteTest.php @@ -0,0 +1,60 @@ +testClass = new class() extends RedisCommand { + use BitByte; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testReturnsCorrectArguments(array $actualArguments, array $expectedArguments): void + { + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + public function argumentsProvider(): array + { + return [ + 'with correct enum value' => [ + ['bit'], + ['BIT'], + ], + 'with incorrect enum value' => [ + ['value'], + ['value'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/By/ByArgumentTest.php b/tests/Predis/Command/Traits/By/ByArgumentTest.php new file mode 100644 index 00000000..0fac6ea8 --- /dev/null +++ b/tests/Predis/Command/Traits/By/ByArgumentTest.php @@ -0,0 +1,74 @@ +testClass = new class() extends RedisCommand { + use ByArgument; + + public static $byArgumentPositionOffset = 0; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param int $offset + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void + { + $this->testClass::$byArgumentPositionOffset = $offset; + + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + public function argumentsProvider(): array + { + return [ + 'with wrong offset' => [ + 1, + ['key'], + ['key'], + ], + 'with null value' => [ + 0, + [null], + [null], + ], + 'with any value' => [ + 0, + ['value'], + ['BY', 'value'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/Get/GetTest.php b/tests/Predis/Command/Traits/Get/GetTest.php new file mode 100644 index 00000000..7bf5c839 --- /dev/null +++ b/tests/Predis/Command/Traits/Get/GetTest.php @@ -0,0 +1,86 @@ +testClass = new class() extends RedisCommand { + use Get; + + public static $getArgumentPositionOffset = 0; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param int $offset + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void + { + $this->testClass::$getArgumentPositionOffset = $offset; + + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + /** + * @return void + */ + public function testThrowsExceptionOnUnexpectedValue(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Wrong get argument type'); + + $this->testClass->setArguments(['wrong']); + } + + public function argumentsProvider(): array + { + return [ + 'with wrong offset' => [ + 1, + ['value'], + ['value'], + ], + 'with single value' => [ + 0, + [['value']], + ['GET', 'value'], + ], + 'with multiple values' => [ + 0, + [['value1', 'value2']], + ['GET', 'value1', 'GET', 'value2'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/Limit/LimitObjectTest.php b/tests/Predis/Command/Traits/Limit/LimitObjectTest.php new file mode 100644 index 00000000..2a1779fd --- /dev/null +++ b/tests/Predis/Command/Traits/Limit/LimitObjectTest.php @@ -0,0 +1,63 @@ +testClass = new class() extends RedisCommand { + use LimitObject; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testReturnsCorrectArguments(array $actualArguments, array $expectedArguments): void + { + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + public function argumentsProvider(): array + { + return [ + 'with non-existing LimitInterface' => [ + ['value'], + ['value'], + ], + 'with existing LimitInterface' => [ + [new LimitOffsetCount(0, 1)], + ['LIMIT', 0, 1], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/LimitTest.php b/tests/Predis/Command/Traits/Limit/LimitTest.php similarity index 98% rename from tests/Predis/Command/Traits/LimitTest.php rename to tests/Predis/Command/Traits/Limit/LimitTest.php index fb15a025..2fd9346c 100644 --- a/tests/Predis/Command/Traits/LimitTest.php +++ b/tests/Predis/Command/Traits/Limit/LimitTest.php @@ -10,7 +10,7 @@ * file that was distributed with this source code. */ -namespace Predis\Command\Traits; +namespace Predis\Command\Traits\Limit; use Predis\Command\Command as RedisCommand; use PredisTestCase;