Skip to content

Commit

Permalink
Merge pull request #65 from M6Web/feature/add-support-for-commands-an…
Browse files Browse the repository at this point in the history
…d-profile

Add support of commands `quit`, `scan`, `monitor` and `rpoplpush`  for redisMock
  • Loading branch information
DocRoms authored Mar 23, 2018
2 parents c59b026 + 67c4384 commit 20766b8
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 33 deletions.
43 changes: 23 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Add this line in your `composer.json` :

Update your vendors :

```
```bash
$ composer update m6web/redis-mock
```

Expand All @@ -28,27 +28,14 @@ It currently mocks these Redis commands :

Redis command | Description
-------------------------------------------------|------------
**DBSIZE** | Returns the number of keys in the selected database
**DEL** *key* *[key ...]* | Deletes one or more keys
**DECR** *key* | Decrements the integer value of a key by one
**DECRBY** *key* *decrement* | Decrements the integer value of a key by `decrement` value
**EXISTS** *key* | Determines if a key exists
**EXPIRE** *key* *seconds* | Sets a key's time to live in seconds
**KEYS** *pattern* | Finds all keys matching the given pattern
**TTL** *key* | Gets the time to live for a key
**TYPE** *key* | Returns the string representation of the type of the value stored at key.
**FLUSHDB** | Flushes the database
**GET** *key* | Gets the value of a key
**INCR** *key* | Increments the integer value of a key by one
**INCRBY** *key* *increment* | Increments the integer value of a key by `increment` value
**DECR** *key* | Decrements the integer value of a key by one
**DECRBY** *key* *decrement* | Decrements the integer value of a key by `decrement` value
**SET** *key* *value* | Sets the string value of a key
**SETEX** *key* *seconds* *value* | Sets the value and expiration of a key
**SETNX** *key* *value* | Sets key to hold value if key does not exist
**SADD** *key* *member* *[member ...]* | Adds one or more members to a set
**SISMEMBER** *key* *member* | Determines if a member is in a set
**SMEMBERS** *key* | Gets all the members in a set
**SUNION** *key* *[key ...]* | Returns the members of the set resulting from the union of all the given sets.
**SINTER** *key* *[key ...]* | Returns the members of the set resulting from the intersection of all the given sets.
**SCARD** *key* | Get cardinality of set (count of members)
**SREM** *key* *member* *[member ...]* | Removes one or more members from a set
**HDEL** *key* *field* | Delete one hash fields
**HEXISTS** *key* *field* | Determines if a hash field exists
**HMGET** *key* *array\<field\>* | Gets the values of multiple hash fields
Expand All @@ -60,6 +47,9 @@ Redis command | Description
**HSET** *key* *field* *value* | Sets the string value of a hash field
**HSETNX** *key* *field* *value* | Sets field in the hash stored at key to value, only if field does not yet exist
**HINCRBY** *key* *field* *increment* | Increments the integer stored at `field` in the hash stored at `key` by `increment`.
**INCR** *key* | Increments the integer value of a key by one
**INCRBY** *key* *increment* | Increments the integer value of a key by `increment` value
**KEYS** *pattern* | Finds all keys matching the given pattern
**LINDEX** *key* *index* | Returns the element at index *index* in the list stored at *key*
**LLEN** *key* | Returns the length of the list stored at *key*
**LPUSH** *key* *value* *[value ...]* | Pushs values at the head of a list
Expand All @@ -69,8 +59,22 @@ Redis command | Description
**LRANGE** *key* *start* *stop* | Gets a range of elements from a list
**MGET** *array\<field\>* | Gets the values of multiple keys
**MSET** *array\<field, value\>* | Sets the string values of multiple keys
**QUIT** | Quit the REDIS
**RPUSH** *key* *value* | Pushs values at the tail of a list
**RPOP** *key* | Pops values at the tail of a list
**SCAN** | Iterates the set of keys in the currently selected Redis database.
**SET** *key* *value* | Sets the string value of a key
**SETEX** *key* *seconds* *value* | Sets the value and expiration of a key
**SETNX** *key* *value* | Sets key to hold value if key does not exist
**SADD** *key* *member* *[member ...]* | Adds one or more members to a set
**SISMEMBER** *key* *member* | Determines if a member is in a set
**SMEMBERS** *key* | Gets all the members in a set
**SUNION** *key* *[key ...]* | Returns the members of the set resulting from the union of all the given sets.
**SINTER** *key* *[key ...]* | Returns the members of the set resulting from the intersection of all the given sets.
**SCARD** *key* | Get cardinality of set (count of members)
**SREM** *key* *member* *[member ...]* | Removes one or more members from a set
**TTL** *key* | Gets the time to live for a key
**TYPE** *key* | Returns the string representation of the type of the value stored at key.
**ZADD** *key* *score* *member* | Adds one member to a sorted set, or update its score if it already exists
**ZCARD** *key* | Returns the sorted set cardinality (number of elements) of the sorted set stored at *key*
**ZRANGE** *key* *start* *stop* *[withscores]* | Returns the specified range of members in a sorted set
Expand All @@ -80,8 +84,7 @@ Redis command | Description
**ZREMRANGEBYSCORE** *key* *min* *max* | Removes all members in a sorted set within the given scores
**ZREVRANGE** *key* *start* *stop* *[withscores]*| Returns the specified range of members in a sorted set, with scores ordered from high to low
**ZREVRANGEBYSCORE** *key* *min* *max* *options* | Returns a range of members in a sorted set, by score, with scores ordered from high to low
**DBSIZE** | Returns the number of keys in the selected database
**FLUSHDB** | Flushes the database


It mocks **MULTI**, **DISCARD** and **EXEC** commands but without any transaction behaviors, they just make the interface fluent and return each command results.
**PIPELINE** and **EXECUTE** pseudo commands (client pipelining) are also mocked.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
},
"require-dev": {
"atoum/atoum": "master-dev",
"predis/predis": "~0.8"
"predis/predis": "~1.1"
}
}
81 changes: 81 additions & 0 deletions src/M6Web/Component/RedisMock/RedisMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -1079,4 +1079,85 @@ protected function deleteOnTtlExpired($key)

return false;
}

/**
* Mock the `quit` command
* @see https://redis.io/commands/quit
*/
public function quit(): string
{
return 'OK'; // see the doc : always return `OK` ...
}

/**
* Mock the `monitor` command
* @see https://redis.io/commands/monitor
*/
public function monitor()
{
return;
}

/**
* Mock the `scan` command
* @see https://redis.io/commands/scan
* @param int $cursor
* @param array $options contain options of the command, with values (ex ['MATCH' => 'st*', 'COUNT' => 42] )
*
* @return array
*/
public function scan($cursor = 0, array $options = []): array
{
// Define default options
$match = isset($options['MATCH']) ? $options['MATCH'] : '*';
$count = isset($options['COUNT']) ? $options['COUNT'] : 10;
$maximumValue = $cursor + $count -1;

// List of all keys in the storage (already ordered by index).
$keysArray = array_keys(self::$dataValues[$this->storage]);
$maximumListElement = count($keysArray);

// Next cursor position
$nextCursorPosition = 0;
// Matched values.
$values = [];
// Pattern, for find matched values.
$pattern = str_replace('*', '.*', sprintf('/^%s/', $match));

for($i = $cursor; $i <= $maximumValue; $i++)
{
if (isset($keysArray[$i])){
$nextCursorPosition = $i >= $maximumListElement ? 0 : $i + 1;

if ('*' === $match || 1 === preg_match($pattern, $keysArray[$i])){
$values[] = $keysArray[$i];
}

} else {
// Out of the arrays values, return first element
$nextCursorPosition = 0;
}
}

return [$nextCursorPosition, $values];
}

/**
* Mock the `rpoplpush` command
* @see https://redis.io/commands/rpoplpush
* @param string $sourceList
* @param string $destinationList
* @return RedisMock
*/
public function rpoplpush(string $sourceList, string $destinationList)
{
// RPOP (get last value of the $sourceList)
$rpopValue = $this->rpop($sourceList);

// LPUSH (send the value at the end of the $destinationList)
$this->lpush($destinationList, $rpopValue);

// return the rpop value;
return $rpopValue;
}
}
4 changes: 2 additions & 2 deletions src/M6Web/Component/RedisMock/RedisMockFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public function __construct()
}
CONSTRUCTOR;

public function getAdapter($classToExtend, $failOnlyAtRuntime = false, $orphanizeConstructor = true, $storage = '')
public function getAdapter($classToExtend, $failOnlyAtRuntime = false, $orphanizeConstructor = true, $storage = '', array $constructorParams = [])
{
list($namespace, $newClassName, $class) = $this->getAdapterClassName($classToExtend, $orphanizeConstructor);

Expand All @@ -227,7 +227,7 @@ public function getAdapter($classToExtend, $failOnlyAtRuntime = false, $orphaniz
}

/** @var RedisMock $instance */
$instance = new $class();
$instance = new $class(...$constructorParams);
// This is our chance to configure explicitly the storage area
// that the consumer of the Mock wants to use, in order to simulate
// separate connections to different Redis servers, despite the static
Expand Down
60 changes: 50 additions & 10 deletions tests/units/RedisMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ public function testSetGetDelExists()
->string($redisMock->setex('test4', 2, 'something else'))
->isEqualTo('OK')
->integer($redisMock->ttl('test3'))
->isEqualTo(1)
->isLessThanOrEqualTo(1)
->integer($redisMock->ttl('test4'))
->isEqualTo(2)
->isLessThanOrEqualTo(2)
->string($redisMock->get('test3'))
->isEqualTo('something')
->string($redisMock->get('test4'))
Expand Down Expand Up @@ -173,30 +173,30 @@ public function testExpireTtl()
->integer($redisMock->expire('test', 2))
->isEqualTo(1)
->integer($redisMock->ttl('test'))
->isEqualTo(2);
->isGreaterThan(0);
sleep(1);
$this->assert
->integer($redisMock->ttl('test'))
->isEqualTo(1);
->isLessThanOrEqualTo(1);
sleep(2);
$this->assert
->integer($redisMock->ttl('test'))
->isEqualTo(-2);
->isLessThanOrEqualTo(-2);


$this->assert
->string($redisMock->set('test', 'something', 10))
->isEqualTo('OK')
->integer($redisMock->ttl('test'))
->isEqualTo(10)
->isLessThanOrEqualTo(10)
->integer($redisMock->expire('test', 1))
->isEqualTo(1)
->isLessThanOrEqualTo(1)
->integer($redisMock->ttl('test'))
->isEqualTo(1);
->isLessThanOrEqualTo(1);
sleep(2);
$this->assert
->integer($redisMock->expire('test', 10))
->isEqualTo(0);
->isLessThanOrEqualTo(0);
}

public function testIncr()
Expand Down Expand Up @@ -1549,7 +1549,7 @@ public function testDbsize()

$this->assert
->integer($redisMock->dbsize())
->isEqualTo(1);
-> isGreaterThanOrEqualTo(1);

$redisMock->flushdb();

Expand Down Expand Up @@ -1661,4 +1661,44 @@ public function testTwoSeparateStorage()
->isEqualTo('value2')
;
}

/**
* Check if the scan command works.
* With options. (Scan can have a COUNT, or MATCH options).
*/
public function testScanCommand()
{
$redisMock = new Redis();
$redisMock->lpush('myKey', 'myValue');
$redisMock->lpush('yourKey', 'yourValue');
$redisMock->lpush('ourKi', 'ourValue');
$redisMock->lpush('key4', 'value4');
$redisMock->lpush('key5', 'value5');
$redisMock->lpush('key6', 'value6');
$redisMock->lpush('key7', 'value7');
$redisMock->lpush('key8', 'value8');
$redisMock->lpush('key9', 'value9');
$redisMock->lpush('key10', 'value10');
$redisMock->lpush('key11', 'value11');
$redisMock->lpush('key12', 'value12');

// It must return two values, start cursor after the first value of the list.
$this->assert
->array($redisMock->scan(1, ['COUNT' => 2]))
->isEqualTo([3, [0 => 'yourKey', 1 => 'ourKi']]);


// It must return all the values with match with the regex 'our' (2 keys).
// And the cursor is defined after the default count (10) => the match has not terminate all the list.
$this->assert
->array($redisMock->scan(0, ['MATCH' => '*our*']))
->isEqualTo([10, [0 => 'yourKey', 1 => 'ourKi']]);

// Execute the match at the end of this list, the match not return an element (no one element match with the regex),
// And the list is terminate, return the cursor to the start (0)
$this->assert
->array($redisMock->scan(10, ['MATCH' => '*our*']))
->isEqualTo([0, []]);

}
}

0 comments on commit 20766b8

Please sign in to comment.